web app implemented ui to display and configure services for a node
This commit is contained in:
parent
8347debda9
commit
d9db4a427a
7 changed files with 170 additions and 3 deletions
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
92
webapp/static/coreui.js
Normal 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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
28
webapp/templates/services_modal.html
Normal file
28
webapp/templates/services_modal.html
Normal 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">×</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>
|
Loading…
Add table
Add a link
Reference in a new issue