web app, added emane node, got basic emane networks working and joining existing emane network

This commit is contained in:
Blake J. Harnden 2018-05-16 15:45:46 -07:00
parent b15b838555
commit 8889d121c0
6 changed files with 157 additions and 33 deletions

View file

@ -143,6 +143,11 @@ def get_sessions():
def create_session(): def create_session():
session = coreemu.create_session() session = coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE) session.set_state(EventTypes.CONFIGURATION_STATE)
# set session location
session.location.setrefgeo(47.57917, -122.13232, 2.0)
session.location.refscale = 150.0
response_data = jsonify( response_data = jsonify(
id=session.session_id, id=session.session_id,
state=session.state, state=session.state,
@ -169,6 +174,10 @@ def get_session(session_id):
nodes = [] nodes = []
for node in session.objects.itervalues(): for node in session.objects.itervalues():
emane_model = None
if nodeutils.is_node(node, NodeTypes.EMANE):
emane_model = node.model.name
services = [x._name for x in getattr(node, "services", [])] services = [x._name for x in getattr(node, "services", [])]
nodes.append({ nodes.append({
"id": node.objid, "id": node.objid,
@ -181,6 +190,7 @@ def get_session(session_id):
"z": node.position.z "z": node.position.z
}, },
"services": services, "services": services,
"emane": emane_model,
"url": "/sessions/%s/nodes/%s" % (session_id, node.objid) "url": "/sessions/%s/nodes/%s" % (session_id, node.objid)
}) })
@ -190,6 +200,21 @@ def get_session(session_id):
) )
@app.route("/sessions/<int:session_id>/emane")
def get_emane_models(session_id):
session = coreemu.sessions.get(session_id)
if not session:
return jsonify(error="session does not exist"), 404
models = []
for model in session.emane._modelclsmap.keys():
if len(model.split("_")) != 2:
continue
models.append(model)
return jsonify(models=models)
@app.route("/sessions/<int:session_id>/nodes", methods=["POST"]) @app.route("/sessions/<int:session_id>/nodes", methods=["POST"])
@synchronized @synchronized
def create_node(session_id): def create_node(session_id):
@ -213,6 +238,17 @@ def create_node(session_id):
node_options.set_position(data.get("x"), data.get("y")) node_options.set_position(data.get("x"), data.get("y"))
node_options.set_location(data.get("lat"), data.get("lon"), data.get("alt")) node_options.set_location(data.get("lat"), data.get("lon"), data.get("alt"))
node = session.add_node(_type=node_type, _id=node_id, node_options=node_options) node = session.add_node(_type=node_type, _id=node_id, node_options=node_options)
# configure emane if provided
emane_model = data.get("emane")
if emane_model:
config_data = ConfigData(
node=node_id,
object=emane_model,
type=2
)
session.config_object(config_data)
return jsonify( return jsonify(
id=node.objid, id=node.objid,
url="/sessions/%s/nodes/%s" % (session_id, node.objid) url="/sessions/%s/nodes/%s" % (session_id, node.objid)
@ -278,10 +314,16 @@ def get_node(session_id, node_id):
}) })
services = [x._name for x in getattr(node, "services", [])] services = [x._name for x in getattr(node, "services", [])]
emane_model = None
if nodeutils.is_node(node, NodeTypes.EMANE):
emane_model = node.model.name
return jsonify( return jsonify(
name=node.name, name=node.name,
type=nodeutils.get_node_type(node.__class__).value, type=nodeutils.get_node_type(node.__class__).value,
services=services, services=services,
emane=emane_model,
model=node.type, model=node.type,
interfaces=interfaces, interfaces=interfaces,
linksurl="/sessions/%s/nodes/%s/links" % (session_id, node.objid) linksurl="/sessions/%s/nodes/%s/links" % (session_id, node.objid)

View file

@ -25,6 +25,12 @@ class NodeHelper {
name: 'wlan', name: 'wlan',
display: 'WLAN' display: 'WLAN'
}, },
// emane
10: {
name: 'emane',
display: 'EMANE'
},
// ptp
12: { 12: {
name: 'ptp', name: 'ptp',
display: 'PTP' display: 'PTP'
@ -38,16 +44,31 @@ class NodeHelper {
mdr: 'static/mdr.svg', mdr: 'static/mdr.svg',
switch: 'static/lanswitch.svg', switch: 'static/lanswitch.svg',
hub: 'static/hub.svg', hub: 'static/hub.svg',
wlan: 'static/wlan.svg' wlan: 'static/wlan.svg',
emane: 'static/wlan.svg'
}; };
this.defaultNode = 0; this.defaultNode = 0;
this.ptpNode = 12; this.switchNode = 4;
this.hubNode = 5;
this.wlanNode = 6; this.wlanNode = 6;
this.emaneNode = 10;
this.ptpNode = 12;
this.controlNet = 13;
} }
isNetworkNode(node) { isNetworkNode(node) {
return [4, 5, 6, 12].includes(node.type); return [
this.switchNode,
this.hubNode,
this.wlanNode,
this.emaneNode,
this.ptpNode
].includes(node.type);
}
isSkipNode(node) {
return [CoreNodeHelper.ptpNode, CoreNodeHelper.controlNet].includes(node.type);
} }
getDisplay(nodeType) { getDisplay(nodeType) {
@ -82,6 +103,7 @@ class CoreNode {
this.emulation_id = null; this.emulation_id = null;
this.emulation_server = null; this.emulation_server = null;
this.interfaces = {}; this.interfaces = {};
this.emane = null;
} }
getNetworkNode() { getNetworkNode() {
@ -93,7 +115,6 @@ class CoreNode {
y: this.y, y: this.y,
label: this.name, label: this.name,
coreNode: this, coreNode: this,
//color: '#FFF',
shape: 'image', shape: 'image',
image: icon image: icon
}; };
@ -110,7 +131,8 @@ class CoreNode {
lat: this.lat, lat: this.lat,
lon: this.lon, lon: this.lon,
alt: this.alt, alt: this.alt,
services: this.services services: this.services,
emane: this.emane
} }
} }
} }
@ -194,6 +216,7 @@ class CoreNetwork {
const session = await this.coreRest.createSession(); const session = await this.coreRest.createSession();
this.coreRest.currentSession = session.id; this.coreRest.currentSession = session.id;
this.reset(); this.reset();
toastr.success(`Created ${session.id}`, 'Session');
return session; return session;
} }
@ -260,6 +283,7 @@ class CoreNetwork {
const coreNode = new CoreNode(node.id, node.type, node.name, position.x, position.y); const coreNode = new CoreNode(node.id, node.type, node.name, position.x, position.y);
coreNode.model = node.model; coreNode.model = node.model;
coreNode.services = node.services; coreNode.services = node.services;
coreNode.emane = node.emane;
this.nodes.add(coreNode.getNetworkNode()); this.nodes.add(coreNode.getNetworkNode());
} }
@ -286,7 +310,7 @@ class CoreNetwork {
const nodeIds = [0]; const nodeIds = [0];
for (let node of nodes) { for (let node of nodes) {
if (node.type === CoreNodeHelper.ptpNode) { if (CoreNodeHelper.isSkipNode(node)) {
continue; continue;
} }
@ -319,6 +343,8 @@ class CoreNetwork {
this.network.fit(); this.network.fit();
toastr.success(`Joined ${sessionId}`, 'Session');
return { return {
id: sessionId, id: sessionId,
state: session.state state: session.state

View file

@ -62,7 +62,11 @@ class CoreRest {
} }
async setSessionState(state) { async setSessionState(state) {
return await putJson(`/sessions/${this.currentSession}/state`, {state}) return await putJson(`/sessions/${this.currentSession}/state`, {state});
}
async getEmaneModels() {
return await $.getJSON(`/sessions/${this.currentSession}/emane`);
} }
async createNode(node) { async createNode(node) {

View file

@ -1,3 +1,32 @@
function createRadio(name, value, label, checked = false) {
const $formCheck = $('<div>', {class: 'form-check'});
const $input = $('<input>', {
class: 'form-check-input',
type: 'radio',
name: name,
checked,
value
});
const $label = $('<label>', {class: 'form-check-label', text: label});
$formCheck.append([$input, $label]);
return $formCheck;
}
function createCheckbox(name, value, label, checked = false) {
const $formCheck = $('<div>', {class: 'form-check col'});
const $input = $('<input>', {
class: 'form-check-input',
type: 'checkbox',
value,
name,
checked
});
const $label = $('<label>', {class: 'form-check-label', text: label});
$formCheck.append([$input, $label]);
return $formCheck;
}
class ServiceModal { class ServiceModal {
constructor(coreRest) { constructor(coreRest) {
this.coreRest = coreRest; this.coreRest = coreRest;
@ -45,7 +74,6 @@ class ServicesModal {
} }
editService(event) { editService(event) {
//event.preventDefault();
const $target = $(event.target); const $target = $(event.target);
const service = $target.parent().parent().find('label').text(); const service = $target.parent().parent().find('label').text();
console.log('edit service: ', service); console.log('edit service: ', service);
@ -89,17 +117,8 @@ class ServicesModal {
const $row = $('<div>', {class: 'row mb-1'}); const $row = $('<div>', {class: 'row mb-1'});
const $button = $('<div>', {class: 'col-1'}) const $button = $('<div>', {class: 'col-1'})
.append($('<a>', {text: 'Edit', href: '#', class: 'btn btn-primary btn-sm service-button'})); .append($('<a>', {text: 'Edit', href: '#', class: 'btn btn-primary btn-sm service-button'}));
const $formCheck = $('<div>', {class: 'form-check col'}); const $checkbox = createCheckbox(service, service, service, checked);
const $input = $('<input>', { $row.append([$button, $checkbox]);
class: 'form-check-input',
type: 'checkbox',
value: service,
name: service,
checked
});
const $label = $('<label>', {class: 'form-check-label', text: service});
$formCheck.append([$input, $label]);
$row.append([$button, $formCheck]);
$formGroup.append($row); $formGroup.append($row);
} }
} }
@ -189,7 +208,7 @@ class NodeContext {
} }
console.log('node type: ', node.type); console.log('node type: ', node.type);
if (node.type === CoreNodeHelper.wlanNode) { if (node.type === CoreNodeHelper.emaneNode || node.type === CoreNodeHelper.wlanNode) {
this.$linkRfButton.removeClass('d-none'); this.$linkRfButton.removeClass('d-none');
} else { } else {
this.$linkRfButton.addClass('d-none'); this.$linkRfButton.addClass('d-none');
@ -217,7 +236,7 @@ class NodeContext {
console.log('node context: ', nodeId, option); console.log('node context: ', nodeId, option);
switch (option) { switch (option) {
case 'edit': case 'edit':
this.nodeEditModal.show(nodeId); await this.nodeEditModal.show(nodeId);
break; break;
case 'services': case 'services':
await this.servicesModal.show(nodeId); await this.servicesModal.show(nodeId);
@ -236,18 +255,35 @@ class NodeContext {
} }
class NodeEditModal { class NodeEditModal {
constructor(coreNetwork) { constructor(coreNetwork, coreRest) {
this.coreNetwork = coreNetwork; this.coreNetwork = coreNetwork;
this.coreRest = coreRest;
this.$modal = $('#nodeedit-modal'); this.$modal = $('#nodeedit-modal');
this.$form = this.$modal.find('form');
this.$formCustom = $('#nodeedit-custom');
this.$editButton = $('#nodeedit-button'); this.$editButton = $('#nodeedit-button');
this.$editButton.click(this.onClick.bind(this)); this.$editButton.click(this.onClick.bind(this));
} }
show(nodeId) { async show(nodeId) {
const node = this.coreNetwork.getCoreNode(nodeId); const node = this.coreNetwork.getCoreNode(nodeId);
this.$modal.data('node', nodeId); this.$modal.data('node', nodeId);
this.$modal.find('.modal-title').text(`Edit Node: ${node.name}`); this.$modal.find('.modal-title').text(`Edit Node: ${node.name}`);
this.$modal.find('#node-name').val(node.name); this.$modal.find('#node-name').val(node.name);
this.$formCustom.html('');
if (node.type === CoreNodeHelper.emaneNode) {
const response = await this.coreRest.getEmaneModels();
this.$formCustom.append($('<label>', {class: 'form-label', text: 'EMANE Model'}));
console.log('emane models: ', response);
for (let model of response.models) {
const checked = node.emane === model;
const label = model.split('_')[1];
const $radio = createRadio('emane', model, label, checked);
this.$formCustom.append($radio);
}
}
this.$modal.modal('show'); this.$modal.modal('show');
} }
@ -262,6 +298,11 @@ class NodeEditModal {
node.coreNode.name = formData.name; node.coreNode.name = formData.name;
this.coreNetwork.nodes.update(node); this.coreNetwork.nodes.update(node);
} }
if (formData.emane !== undefined) {
node.coreNode.emane = formData.emane;
}
this.$modal.modal('hide'); this.$modal.modal('hide');
} }
} }
@ -427,7 +468,12 @@ class InfoPanel {
this.$infoCard.data('node', nodeId); this.$infoCard.data('node', nodeId);
this.$infoCardHeader.text(node.name); this.$infoCardHeader.text(node.name);
this.$infoCardTable.find('tbody tr').remove(); this.$infoCardTable.find('tbody tr').remove();
if (node.model) {
this.addInfoTable('Model', node.model); this.addInfoTable('Model', node.model);
}
if (node.emane) {
this.addInfoTable('EMANE', node.emane);
}
this.addInfoTable('X', node.x); this.addInfoTable('X', node.x);
this.addInfoTable('Y', node.y); this.addInfoTable('Y', node.y);
for (let interfaceId in node.interfaces) { for (let interfaceId in node.interfaces) {

View file

@ -92,6 +92,7 @@
<a class="dropdown-item" href="#" data-node="5">Hub</a> <a class="dropdown-item" href="#" data-node="5">Hub</a>
<a class="dropdown-item" href="#" data-node="4">Switch</a> <a class="dropdown-item" href="#" data-node="4">Switch</a>
<a class="dropdown-item" href="#" data-node="6">WLAN</a> <a class="dropdown-item" href="#" data-node="6">WLAN</a>
<a class="dropdown-item" href="#" data-node="10">EMANE</a>
</div> </div>
</div> </div>
</div> </div>
@ -119,20 +120,18 @@
{% include 'services_modal.html' %} {% include 'services_modal.html' %}
<div id="node-context" class="list-group context d-none"> <div id="node-context" class="list-group context d-none">
<button type="button" class="list-group-item list-group-item-action" href="#" data-option="edit">Edit Node</button> <button type="button" class="list-group-item list-group-item-action" data-option="edit">Edit Node</button>
<button id="node-linkrf-button" type="button" class="list-group-item list-group-item-action d-none" href="#" <button id="node-linkrf-button" type="button" class="list-group-item list-group-item-action d-none"
data-option="linkrf">Link All Routers data-option="linkrf">Link All Routers
</button> </button>
<button type="button" class="list-group-item list-group-item-action" href="#" <button type="button" class="list-group-item list-group-item-action" data-option="services">Services</button>
data-option="services">Services <button id="node-delete-button" type="button" class="list-group-item list-group-item-action" data-option="delete">
</button> Delete
<button id="node-delete-button" type="button" class="list-group-item list-group-item-action" href="#"
data-option="delete">Delete
</button> </button>
</div> </div>
<div id="edge-context" class="list-group context d-none"> <div id="edge-context" class="list-group context d-none">
<button type="button" class="list-group-item list-group-item-action" href="#" data-option="edit">Edit Link</button> <button type="button" class="list-group-item list-group-item-action" data-option="edit">Edit Link</button>
</div> </div>
<script src="static/jquery-3.3.1.min.js"></script> <script src="static/jquery-3.3.1.min.js"></script>
@ -147,6 +146,11 @@
<script src="static/coreui.js"></script> <script src="static/coreui.js"></script>
<script> <script>
// configure global toastr
console.log(toastr.options);
toastr.options.timeOut = 2000;
toastr.options.positionClass = 'toast-bottom-right';
const $linkButton = $('#link-button'); const $linkButton = $('#link-button');
const $nodeButtons = $('.node-buttons a'); const $nodeButtons = $('.node-buttons a');
const $sessionId = $('#session-id'); const $sessionId = $('#session-id');
@ -177,7 +181,7 @@
const serviceModal = new ServiceModal(coreRest); const serviceModal = new ServiceModal(coreRest);
const servicesModal = new ServicesModal(coreRest, coreNetwork, serviceModal); const servicesModal = new ServicesModal(coreRest, coreNetwork, serviceModal);
const sessionsModal = new SessionsModal(coreRest, coreNetwork, joinSession); const sessionsModal = new SessionsModal(coreRest, coreNetwork, joinSession);
const nodeEditModal = new NodeEditModal(coreNetwork); const nodeEditModal = new NodeEditModal(coreNetwork, coreRest);
const nodeContext = new NodeContext(coreNetwork, coreRest, nodeEditModal, servicesModal); const nodeContext = new NodeContext(coreNetwork, coreRest, nodeEditModal, servicesModal);
const edgeEditModal = new EdgeEditModal(coreNetwork, coreRest); const edgeEditModal = new EdgeEditModal(coreNetwork, coreRest);
const edgeContext = new EdgeContext(coreNetwork, edgeEditModal); const edgeContext = new EdgeContext(coreNetwork, edgeEditModal);

View file

@ -14,6 +14,8 @@
<input id="node-name" name="name" class="form-control" type="text" placeholder="Node Name" <input id="node-name" name="name" class="form-control" type="text" placeholder="Node Name"
required> required>
</div> </div>
<div id="nodeedit-custom"></div>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">