web app implemented ui to display and configure services for a node

This commit is contained in:
Blake J. Harnden 2018-05-14 09:28:25 -07:00
parent 8347debda9
commit d9db4a427a
7 changed files with 170 additions and 3 deletions

View file

@ -512,8 +512,8 @@ class EmuSession(Session):
# add services to default and physical nodes only # add services to default and physical nodes only
if _type in [NodeTypes.DEFAULT, NodeTypes.PHYSICAL]: if _type in [NodeTypes.DEFAULT, NodeTypes.PHYSICAL]:
node.type = node_options.model node.type = node_options.model
logger.debug("set node type: %s", node.type)
services = "|".join(node_options.services) or None services = "|".join(node_options.services) or None
logger.debug("set node type: %s - services(%s)", node.type, services)
self.services.addservicestonode(node, node.type, services) self.services.addservicestonode(node, node.type, services)
# boot nodes if created after runtime, LcxNodes, Physical, and RJ45 are all PyCoreNodes # boot nodes if created after runtime, LcxNodes, Physical, and RJ45 are all PyCoreNodes

View file

@ -10,6 +10,7 @@ from flask_socketio import SocketIO
from flask_socketio import emit from flask_socketio import emit
from core import logger from core import logger
from core.data import ConfigData
from core.emulator.coreemu import CoreEmu from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import InterfaceData from core.emulator.emudata import InterfaceData
from core.emulator.emudata import LinkOptions from core.emulator.emudata import LinkOptions
@ -172,6 +173,7 @@ def create_node(session_id):
) )
node_options.icon = data.get("icon") node_options.icon = data.get("icon")
node_options.opaque = data.get("opaque") node_options.opaque = data.get("opaque")
node_options.services = data.get("services", [])
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)
@ -268,6 +270,37 @@ def delete_node(session_id, node_id):
return jsonify(error="failure to delete node"), 404 return jsonify(error="failure to delete node"), 404
@app.route("/sessions/<int:session_id>/nodes/<node_id>/services")
def node_services(session_id, node_id):
session = coreemu.sessions.get(session_id)
if not session:
return jsonify(error="session does not exist"), 404
config_data = ConfigData(
node=node_id,
object="services",
type=1,
)
logger.debug("configuration message for %s node %s", config_data.object, config_data.node)
# dispatch to any registered callback for this object type
replies = session.config_object(config_data)
if len(replies) != 1:
return jsonify(error="failure getting node services"), 404
service_data = replies[0]
names = service_data.captions.split("|")
services = {}
for group in service_data.groups.split("|"):
group = group.split(":")
group_name = group[0]
group_start, group_stop = [int(x) for x in group[1].split("-")]
group_start -= 1
services[group_name] = names[group_start:group_stop]
return jsonify(services)
@app.route("/sessions/<int:session_id>/state", methods=["PUT"]) @app.route("/sessions/<int:session_id>/state", methods=["PUT"])
@synchronized @synchronized
def set_session_state(session_id): def set_session_state(session_id):

View file

@ -108,7 +108,8 @@ class CoreNode {
y: this.y, y: this.y,
lat: this.lat, lat: this.lat,
lon: this.lon, lon: this.lon,
alt: this.alt alt: this.alt,
services: this.services
} }
} }
} }

View file

@ -89,6 +89,10 @@ class CoreRest {
return await $.getJSON(`/sessions/${this.currentSession}/nodes/${nodeId}/links`) return await $.getJSON(`/sessions/${this.currentSession}/nodes/${nodeId}/links`)
} }
async getServices(nodeId) {
return await $.getJSON(`/sessions/${this.currentSession}/nodes/${nodeId}/services`)
}
async getNodeIps(nodeId, ip4Prefix, ip6Prefix) { async getNodeIps(nodeId, ip4Prefix, ip6Prefix) {
return await postJson('/ips', { return await postJson('/ips', {
id: nodeId, id: nodeId,

92
webapp/static/coreui.js Normal file
View file

@ -0,0 +1,92 @@
class ServicesModal {
constructor(coreRest, coreNetwork) {
this.coreRest = coreRest;
this.coreNetwork = coreNetwork;
this.$servicesModal = $('#services-modal');
this.$servicesForm = this.$servicesModal.find('form');
this.$serviceGroup = $('#service-group');
this.$servicesList = $('#services-list');
this.$servicesButton = $('#services-button');
this.defaultServices = {
mdr: new Set(["zebra", "OSPFv3MDR", "IPForward"]),
PC: new Set(["DefaultRoute"]),
prouter: new Set(["zebra", "OSPFv2", "OSPFv3", "IPForward"]),
router: new Set(["zebra", "OSPFv2", "OSPFv3", "IPForward"]),
host: new Set(["DefaultRoute", "SSH"])
};
this.node = null;
this.nodeDefaults = null;
this.serviceGroups = null;
this.serviceOptions = new Map();
this.$currentGroup = null;
this.groupChange();
this.saveClicked();
}
async show(nodeId) {
this.node = this.coreNetwork.getCoreNode(nodeId);
if (this.node.services.length) {
this.nodeDefaults = new Set(this.node.services);
} else {
this.nodeDefaults = this.defaultServices[this.node.model] || new Set();
}
this.serviceGroups = await coreRest.getServices(nodeId);
// clear data
this.$serviceGroup.html('');
this.$servicesList.html('');
this.serviceOptions.clear();
for (let group in this.serviceGroups) {
const $option = $('<option>', {value: group, text: group});
this.$serviceGroup.append($option);
const services = this.serviceGroups[group];
console.log('services: ', services);
const $formGroup = $('<div>', {class: 'form-group d-none'});
this.serviceOptions.set(group, $formGroup);
this.$servicesList.append($formGroup);
for (let service of services) {
const checked = this.nodeDefaults.has(service);
const $formCheck = $('<div>', {class: 'form-check'});
const $input = $('<input>', {
class: 'form-check-input',
type: 'checkbox',
value: service,
name: service,
checked
});
const $label = $('<label>', {class: 'form-check-label', text: service});
$formCheck.append($input);
$formCheck.append($label);
$formGroup.append($formCheck);
}
}
this.$serviceGroup.change();
this.$servicesModal.modal('show');
}
groupChange() {
const self = this;
this.$serviceGroup.on('change', function () {
const group = $(this).val();
if (self.$currentGroup) {
self.$currentGroup.addClass('d-none');
}
self.$currentGroup = self.serviceOptions.get(group);
self.$currentGroup.removeClass('d-none');
});
}
saveClicked() {
const self = this;
this.$servicesButton.click(function () {
let services = self.$servicesForm.serializeArray();
services = services.map(x => x.value);
console.log('services save clicked: ', services);
self.node.services = services;
self.$servicesModal.modal('hide');
});
}
}

View file

@ -112,6 +112,7 @@
{% include 'sessions_modal.html' %} {% include 'sessions_modal.html' %}
{% include 'nodeedit_modal.html' %} {% include 'nodeedit_modal.html' %}
{% include 'linkedit_modal.html' %} {% include 'linkedit_modal.html' %}
{% include 'services_modal.html' %}
<ul id="node-context" class="list-group context d-none"> <ul id="node-context" class="list-group context d-none">
<a class="list-group-item list-group-item-action" href="#" data-option="edit">Edit Node</a> <a class="list-group-item list-group-item-action" href="#" data-option="edit">Edit Node</a>
@ -130,6 +131,7 @@
<script src="static/coreip.js"></script> <script src="static/coreip.js"></script>
<script src="static/corerest.js"></script> <script src="static/corerest.js"></script>
<script src="static/corenetwork.js"></script> <script src="static/corenetwork.js"></script>
<script src="static/coreui.js"></script>
<script> <script>
const $linkButton = $('#link-button'); const $linkButton = $('#link-button');
@ -165,9 +167,11 @@
return formData; return formData;
} }
// initial core setup // core setup
const coreRest = new CoreRest(); const coreRest = new CoreRest();
const coreNetwork = new CoreNetwork('core-network', coreRest); const coreNetwork = new CoreNetwork('core-network', coreRest);
const servicesModal = new ServicesModal(coreRest, coreNetwork);
coreNetwork.initialSession() coreNetwork.initialSession()
.then(function (session) { .then(function (session) {
joinSession(session); joinSession(session);
@ -327,6 +331,11 @@
$nodeEditModal.find('.modal-title').text(`Edit Node: ${node.name}`); $nodeEditModal.find('.modal-title').text(`Edit Node: ${node.name}`);
$nodeEditModal.find('#node-name').val(node.name); $nodeEditModal.find('#node-name').val(node.name);
$nodeEditModal.modal('show'); $nodeEditModal.modal('show');
} else if (option === 'services') {
servicesModal.show(nodeId)
.catch(function(err) {
console.log('error showing services modal: ', err);
});
} }
}); });

View file

@ -0,0 +1,28 @@
<div id="services-modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header bg-dark text-white">
<h5 class="modal-title"></h5>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<label for="service-group">Service Group</label>
<select class="form-control" id="service-group">
</select>
</div>
<div id="services-list" class="form-group">
</div>
</form>
</div>
<div class="modal-footer">
<button id="services-button" type="button" class="btn btn-primary">Save</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>