initial rough working service edit, with special way to retrieve custom values

This commit is contained in:
Blake J. Harnden 2018-05-23 12:41:29 -07:00
parent 517ef4c3d3
commit 3e5cd61ecc
6 changed files with 331 additions and 13 deletions

View file

@ -17,7 +17,7 @@ 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
from core.emulator.emudata import NodeOptions from core.emulator.emudata import NodeOptions
from core.enumerations import EventTypes, ConfigFlags from core.enumerations import EventTypes, ConfigFlags, ConfigDataTypes
from core.enumerations import LinkTypes from core.enumerations import LinkTypes
from core.enumerations import NodeTypes from core.enumerations import NodeTypes
from core.misc import nodeutils from core.misc import nodeutils
@ -32,6 +32,13 @@ socketio = SocketIO(app)
coreemu = CoreEmu() coreemu = CoreEmu()
def custom_service_values(service):
valmap = [service._dirs, service._configs, service._startindex, service._startup,
service._shutdown, service._validate, service._meta, service._starttime]
vals = map(lambda a, b: "%s=%s" % (a, str(b)), service.keys, valmap)
return "|".join(vals)
def synchronized(function): def synchronized(function):
global CORE_LOCK global CORE_LOCK
@ -142,7 +149,7 @@ def get_sessions():
@synchronized @synchronized
def create_session(): def create_session():
session = coreemu.create_session() session = coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE) session.set_state(EventTypes.DEFINITION_STATE)
# set session location # set session location
session.location.setrefgeo(47.57917, -122.13232, 2.0) session.location.setrefgeo(47.57917, -122.13232, 2.0)
@ -340,6 +347,9 @@ def create_node(session_id):
) )
session.config_object(config_data) session.config_object(config_data)
logger.info("custom services: %s", session.services.customservices)
for service in node.services:
logger.info("node services: %s - %s", service._name, service._custom)
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)
@ -459,15 +469,18 @@ def delete_node(session_id, node_id):
@app.route("/sessions/<int:session_id>/nodes/<node_id>/services") @app.route("/sessions/<int:session_id>/nodes/<node_id>/services")
def node_services(session_id, node_id): def get_node_services(session_id, node_id):
session = coreemu.sessions.get(session_id) session = coreemu.sessions.get(session_id)
if not session: if not session:
return jsonify(error="session does not exist"), 404 return jsonify(error="session does not exist"), 404
if node_id.isdigit():
node_id = int(node_id)
config_data = ConfigData( config_data = ConfigData(
node=node_id, node=node_id,
object="services", object="services",
type=1, type=ConfigFlags.REQUEST.value,
) )
logger.debug("configuration message for %s node %s", config_data.object, config_data.node) logger.debug("configuration message for %s node %s", config_data.object, config_data.node)
@ -489,6 +502,148 @@ def node_services(session_id, node_id):
return jsonify(services) return jsonify(services)
@app.route("/sessions/<int:session_id>/nodes/<node_id>/services/<service>")
def get_node_service(session_id, node_id, service):
session = coreemu.sessions.get(session_id)
if not session:
return jsonify(error="session does not exist"), 404
if node_id.isdigit():
node_id = int(node_id)
config_data = ConfigData(
node=node_id,
object="services",
opaque="service:%s" % service,
type=ConfigFlags.REQUEST.value,
)
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
response = replies[0]
data_values = response.data_values
# check if this is a custom service, since core cant currently handle this right
node_services, _ = session.services.servicesfromopaque("service:%s" % service, node_id)
node_service = node_services[0]
if node_service._custom:
data_values = custom_service_values(node_service)
data_values = data_values.split("|")
service_config = {}
for data_value in data_values:
name, value = data_value.split("=")
if value.startswith("("):
value = value.strip("()").split(",")
value = [x.strip(" '") for x in value if x]
service_config[name] = value
return jsonify(service_config)
@app.route("/sessions/<int:session_id>/nodes/<node_id>/services/<service>", methods=["PUT"])
def set_node_service(session_id, node_id, service):
session = coreemu.sessions.get(session_id)
if not session:
return jsonify(error="session does not exist"), 404
if node_id.isdigit():
node_id = int(node_id)
node = session.objects.get(node_id)
if not node:
return jsonify(error="node does not exist"), 404
data = request.get_json() or {}
data_types = list()
data_values = list()
data_types.append(ConfigDataTypes.STRING.value)
data_values.append("startidx=%s" % data["index"])
index = data["time"]
if index:
data_types.append(ConfigDataTypes.STRING.value)
data_values.append("starttime=%s" % index)
startup = create_tuple_str(data["startup"])
if startup:
data_types.append(ConfigDataTypes.STRING.value)
data_values.append("cmdup=%s" % startup)
shutdown = create_tuple_str(data["shutdown"])
if shutdown:
data_types.append(ConfigDataTypes.STRING.value)
data_values.append("cmddown=%s" % shutdown)
validate = create_tuple_str(data["validate"])
if validate:
data_types.append(ConfigDataTypes.STRING.value)
data_values.append("cmdval=%s" % validate)
data_values = "|".join(data_values)
logger.info("service types: %s", data_types)
logger.info("service values: %s", data_values)
config_data = ConfigData(
node=node_id,
object="services",
opaque="service:%s" % service,
type=ConfigFlags.NONE.value,
data_types=data_types,
data_values=data_values
)
session.config_object(config_data)
logger.info("custom services: %s", session.services.customservices)
return jsonify()
def create_tuple_str(data):
if not data:
return None
data = data.strip()
output = "("
for line in data.split("\n"):
output += "'%s'," % line.strip()
output += ")"
return output
@app.route("/sessions/<int:session_id>/nodes/<node_id>/services/<service>/file")
def get_node_service_file(session_id, node_id, service):
session = coreemu.sessions.get(session_id)
if not session:
return jsonify(error="session does not exist"), 404
if node_id.isdigit():
node_id = int(node_id)
node = session.objects.get(node_id)
if not node:
return jsonify(error="node does not exist"), 404
service_file = request.args["file"]
services, _ = session.services.servicesfromopaque("service:%s" % service, node_id)
file_data = session.services.getservicefile(services, node, service_file)
return jsonify(file_data.data)
@app.route("/sessions/<int:session_id>/nodes/<node_id>/services/<service>/file", methods=["PUT"])
def set_node_service_file(session_id, node_id, service):
session = coreemu.sessions.get(session_id)
if not session:
return jsonify(error="session does not exist"), 404
if node_id.isdigit():
node_id = int(node_id)
node = session.objects.get(node_id)
if not node:
return jsonify(error="node does not exist"), 404
data = request.get_json() or {}
file_name = data["name"]
data = data["data"]
session.add_node_service_file(node_id, service, file_name, None, data)
return jsonify()
@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

@ -31,11 +31,18 @@
padding: .5rem 1.25rem; padding: .5rem 1.25rem;
} }
#config-modal { .modal {
overflow-y: initial !important; overflow-y: initial !important;
} }
#config-modal .modal-body { .modal-body {
height: calc(100vh - 200px); height: calc(100vh - 200px);
overflow-y: auto; overflow-y: auto;
} }
.file-content {
background-color: #000 !important;
color: #00ff26 !important;
font-size: small;
font-family: monospace;
}

View file

@ -401,6 +401,10 @@ class CoreNetwork {
} }
async start() { async start() {
// clear current session and set for nodes to start
await coreRest.setSessionState(SessionStates.definition);
await coreRest.setSessionState(SessionStates.configuration);
const nodes = coreNetwork.getCoreNodes(); const nodes = coreNetwork.getCoreNodes();
for (let node of nodes) { for (let node of nodes) {
const response = await coreRest.createNode(node); const response = await coreRest.createNode(node);

View file

@ -86,6 +86,10 @@ class CoreRest {
return await putJson(`/sessions/${this.currentSession}/config`, config); return await putJson(`/sessions/${this.currentSession}/config`, config);
} }
async getNode(nodeId) {
return await $.getJSON(`/sessions/${this.currentSession}/nodes/${nodeId}`);
}
async createNode(node) { async createNode(node) {
return await postJson(`/sessions/${this.currentSession}/nodes`, node); return await postJson(`/sessions/${this.currentSession}/nodes`, node);
} }
@ -111,11 +115,31 @@ class CoreRest {
} }
async getLinks(nodeId) { async getLinks(nodeId) {
return await $.getJSON(`/sessions/${this.currentSession}/nodes/${nodeId}/links`) return await $.getJSON(`/sessions/${this.currentSession}/nodes/${nodeId}/links`);
} }
async getServices(nodeId) { async getServices(nodeId) {
return await $.getJSON(`/sessions/${this.currentSession}/nodes/${nodeId}/services`) return await $.getJSON(`/sessions/${this.currentSession}/nodes/${nodeId}/services`);
}
async getService(nodeId, service) {
return await $.getJSON(`/sessions/${this.currentSession}/nodes/${nodeId}/services/${service}`);
}
async setService(nodeId, service, data) {
return await putJson(`/sessions/${this.currentSession}/nodes/${nodeId}/services/${service}`, data);
}
async getServiceFile(nodeId, service, serviceFile) {
return await $.getJSON(`/sessions/${this.currentSession}/nodes/${nodeId}/services/${service}/file`,
{file: serviceFile});
}
async setServiceFile(nodeId, service, serviceFile, data) {
return await putJson(`/sessions/${this.currentSession}/nodes/${nodeId}/services/${service}/file`, {
name: serviceFile,
data
});
} }
async getNodeIps(nodeId, ip4Prefix, ip6Prefix) { async getNodeIps(nodeId, ip4Prefix, ip6Prefix) {

View file

@ -149,18 +149,88 @@ class ServiceModal {
constructor(coreRest) { constructor(coreRest) {
this.coreRest = coreRest; this.coreRest = coreRest;
this.$modal = $('#service-modal'); this.$modal = $('#service-modal');
this.$form = this.$modal.find('form');
this.$files = this.$modal.find('select[name=file]');
this.$files.change(this.fileChange.bind(this));
this.$fileContent = this.$modal.find('textarea[name=filecontent]');
this.$startIndex = this.$modal.find('input[name=index]');
this.$startTime = this.$modal.find('input[name=time]');
this.$startup = this.$modal.find('textarea[name=startup]');
this.$shutdown = this.$modal.find('textarea[name=shutdown]');
this.$validate = this.$modal.find('textarea[name=validate]');
this.$title = this.$modal.find('.modal-title'); this.$title = this.$modal.find('.modal-title');
this.$saveButton = $('#service-button'); this.$saveButton = $('#service-button');
this.$saveButton.click(this.onClick.bind(this)); this.$saveButton.click(this.saveClicked.bind(this));
this.node = null;
this.service = null;
} }
async show(service) { async fileChange(event) {
const currentFile = this.$files.val();
console.log('current file: ', currentFile);
if (currentFile) {
const fileData = await this.coreRest.getServiceFile(this.node.id, this.service, currentFile);
this.$fileContent.val(fileData);
}
}
async show(node, service) {
this.node = node;
this.service = service;
this.$title.text(`Edit ${service}`); this.$title.text(`Edit ${service}`);
this.$modal.modal('show');
try {
await this.coreRest.getNode(node.id);
} catch (err) {
console.log('node does not exist, creating for editing services');
await this.coreRest.createNode(node);
}
try {
const response = await this.coreRest.getService(node.id, service);
console.log('service data: ', response);
this.$files.html('');
for (let fileName of response.files) {
const $option = $('<option>', {value: fileName, text: fileName});
this.$files.append($option);
}
this.$fileContent.val('');
this.$files.change();
this.$startIndex.val(response.startidx);
this.$startTime.val(response.starttime);
this.$startup.val(response.cmdup.join('\n'));
this.$shutdown.val(response.cmddown.join('\n'));
this.$validate.val(response.cmdval.join('\n'));
this.$modal.modal('show');
} catch (err) {
console.log('error getting service data: ', err);
toastr.error('Get service error', 'Internal Error');
}
} }
async onClick() { async saveClicked() {
const formData = formToJson(this.$form);
console.log('saved service data: ', formData);
// update current service file data
try {
await this.coreRest.setServiceFile(this.node.id, this.service, formData.file, formData.filecontent);
} catch (err) {
console.log('error saving service file data: ', err);
}
// update all other service settings
delete formData.file;
delete formData.filecontent;
try {
await this.coreRest.setService(this.node.id, this.service, formData);
} catch (err) {
console.log('error saving service data: ', err);
}
this.$modal.modal('hide');
} }
} }
@ -196,7 +266,7 @@ class ServicesModal {
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);
this.$modal.modal('hide'); this.$modal.modal('hide');
this.serviceModal.show(service); this.serviceModal.show(this.node, service);
return false; return false;
} }

View file

@ -8,7 +8,65 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<ul class="nav nav-tabs mb-2">
<li class="nav-item">
<a class="nav-link active" data-toggle="tab" href="#service-files" role="tab"
aria-selected="true">
Files
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#service-lifecycle" role="tab"
aria-selected="true">
Lifecycle
</a>
</li>
</ul>
<form> <form>
<div class="tab-content">
<div class="tab-pane fade show active" id="service-files" role="tabpanel">
<div class="form-group">
<label>File Name</label>
<select name="file" class="form-control">
</select>
</div>
<div class="form-group">
<label>File Content</label>
<textarea name="filecontent" class="form-control file-content" rows="15"></textarea>
</div>
</div>
<div class="tab-pane fade" id="service-lifecycle" role="tabpanel">
<div class="form-row">
<div class="form-group col">
<label>Startup Index</label>
<input name="index" class="form-control" type="text" required>
</div>
<div class="form-group col">
<label>Startup Time</label>
<input name="time" class="form-control" type="text">
</div>
</div>
<div class="form-group">
<label>Startup</label>
<textarea name="startup" class="form-control file-content" rows="3"></textarea>
</div>
<div class="form-group">
<label>Shutdown</label>
<textarea name="shutdown" class="form-control file-content" rows="3"></textarea>
</div>
<div class="form-group">
<label>Validate</label>
<textarea name="validate" class="form-control file-content" rows="3"></textarea>
</div>
</div>
</div>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">