initial basic mockup on gui layout before integrating with api calls

This commit is contained in:
Blake J. Harnden 2018-05-03 20:28:00 -07:00
parent aaa125a896
commit b10c7fe502
15 changed files with 771 additions and 2 deletions

View file

@ -1,6 +1,11 @@
import os
from flask import Flask
from flask import jsonify
from flask import request
from flask import render_template
from flask_socketio import SocketIO
from flask_socketio import emit
from core import logger
from core.emulator.coreemu import CoreEmu
@ -12,7 +17,12 @@ from core.enumerations import LinkTypes
from core.enumerations import NodeTypes
from core.misc import nodeutils
app = Flask(__name__)
_TEMPLATE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates")
logger.info("template folder: %s", _TEMPLATE_PATH)
app = Flask(__name__, template_folder=_TEMPLATE_PATH)
app.config["SECRET_KEY"] = "core"
socketio = SocketIO(app)
coreemu = CoreEmu()
@ -23,6 +33,31 @@ def link_data_str(link, key):
link[key] = str(value)
@socketio.on("connect")
def websocket_connect():
emit("info", {"message": "You are connected!"})
socketio.emit("node", {
"id": 1,
"x": 100,
"y": 101
})
socketio.emit("node", {
"id": 1,
"x": 100,
"y": 150
})
@socketio.on("disconnect")
def websocket_disconnect():
logger.info("websocket client disconnected")
@app.route("/")
def home():
return render_template('index.html')
@app.route("/sessions")
def get_sessions():
sessions = []
@ -295,4 +330,5 @@ def get_node_links(session_id, node_id):
def start():
app.run(host="0.0.0.0")
socketio.run(app, host="0.0.0.0")
# app.run(host="0.0.0.0")

328
webapp/app.py Normal file
View file

@ -0,0 +1,328 @@
from flask import Flask
from flask import jsonify
from flask import render_template
from flask import request
from flask_socketio import SocketIO
from flask_socketio import emit
from core import logger
from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import InterfaceData
from core.emulator.emudata import LinkOptions
from core.emulator.emudata import NodeOptions
from core.enumerations import EventTypes
from core.enumerations import LinkTypes
from core.enumerations import NodeTypes
from core.misc import nodeutils
app = Flask(__name__)
app.config["SECRET_KEY"] = "core"
socketio = SocketIO(app)
coreemu = CoreEmu()
def link_data_str(link, key):
value = link.get(key)
if value:
link[key] = str(value)
@socketio.on("connect")
def websocket_connect():
emit("info", {"message": "You are connected!"})
socketio.emit("node", {
"id": 1,
"x": 100,
"y": 101
})
socketio.emit("node", {
"id": 1,
"x": 100,
"y": 150
})
@socketio.on("disconnect")
def websocket_disconnect():
logger.info("websocket client disconnected")
@app.route("/")
def home():
return render_template('index.html')
@app.route("/sessions")
def get_sessions():
sessions = []
for session in coreemu.sessions.itervalues():
sessions.append({
"id": session.session_id,
"nodes": session.get_node_count()
})
return jsonify(sessions=sessions)
@app.route("/sessions", methods=["POST"])
def create_session():
session = coreemu.create_session()
session.set_state(EventTypes.CONFIGURATION_STATE)
response_data = jsonify(
id=session.session_id,
url="/sessions/%s" % session.session_id
)
return response_data, 201
@app.route("/sessions/<int:session_id>", methods=["DELETE"])
def delete_session(session_id):
result = coreemu.delete_session(session_id)
if result:
return jsonify()
else:
return jsonify(error="session does not exist"), 404
@app.route("/sessions/<int:session_id>")
def get_session(session_id):
session = coreemu.sessions.get(session_id)
if not session:
return jsonify(error="session does not exist"), 404
nodes = []
for node in session.objects.itervalues():
nodes.append({
"id": node.objid,
"name": node.name,
"type": nodeutils.get_node_type(node.__class__).value,
"position": {
"x": node.position.x,
"y": node.position.y,
"z": node.position.z
},
"url": "/sessions/%s/nodes/%s" % (session_id, node.objid)
})
state = EventTypes(session.state)
return jsonify(
state=state.name,
nodes=nodes
)
@app.route("/sessions/<int:session_id>/nodes", methods=["POST"])
def create_node(session_id):
session = coreemu.sessions.get(session_id)
if not session:
return jsonify(error="session does not exist"), 404
data = request.get_json() or {}
node_type = data.get("type", NodeTypes.DEFAULT.value)
node_type = NodeTypes(node_type)
logger.info("creating node: %s - %s", node_type.name, data)
node_options = NodeOptions(
name=data.get("name"),
model=data.get("model")
)
node_options.icon = data.get("icon")
node_options.opaque = data.get("opaque")
node_options.set_position(data.get("x"), data.get("y"))
node_options.set_location(data.get("lat"), data.get("lon"), data.get("alt"))
node = session.add_node(_type=node_type, node_options=node_options)
return jsonify(
id=node.objid,
url="/sessions/%s/nodes/%s" % (session_id, node.objid)
), 201
@app.route("/sessions/<int:session_id>/nodes/<node_id>")
def get_node(session_id, node_id):
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
interfaces = []
for interface_id, interface in node._netif.iteritems():
net_id = None
if interface.net:
net_id = interface.net.objid
interfaces.append({
"id": interface_id,
"netid": net_id,
"name": interface.name,
"mac": str(interface.hwaddr),
"mtu": interface.mtu,
"flowid": interface.flow_id
})
return jsonify(
name=node.name,
type=nodeutils.get_node_type(node.__class__).value,
model=node.type,
interfaces=interfaces,
linksurl="/sessions/%s/nodes/%s/links" % (session_id, node.objid)
)
@app.route("/sessions/<int:session_id>/nodes/<node_id>", methods=["DELETE"])
def delete_node(session_id, node_id):
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
result = session.delete_node(node_id)
if result:
return jsonify()
else:
return jsonify(error="failure to delete node"), 404
@app.route("/sessions/<int:session_id>/state", methods=["PUT"])
def set_session_state(session_id):
session = coreemu.sessions.get(session_id)
if not session:
return jsonify(error="session does not exist"), 404
data = request.get_json()
try:
state = EventTypes(data["state"])
session.set_state(state)
if state == EventTypes.INSTANTIATION_STATE:
session.instantiate()
elif state == EventTypes.SHUTDOWN_STATE:
session.shutdown()
elif state == EventTypes.DATACOLLECT_STATE:
session.data_collect()
elif state == EventTypes.DEFINITION_STATE:
session.clear()
return jsonify()
except KeyError:
return jsonify(error="invalid state"), 404
@app.route("/sessions/<int:session_id>/links", methods=["POST"])
def add_link(session_id):
session = coreemu.sessions.get(session_id)
if not session:
return jsonify(error="session does not exist"), 404
data = request.get_json()
node_one = data.get("node_one")
node_two = data.get("node_two")
interface_one = None
interface_one_data = data.get("interface_one")
if interface_one_data:
interface_one = InterfaceData(
_id=interface_one_data.get("id"),
name=interface_one_data.get("name"),
mac=interface_one_data.get("mac"),
ip4=interface_one_data.get("ip4"),
ip4_mask=interface_one_data.get("ip4mask"),
ip6=interface_one_data.get("ip6"),
ip6_mask=interface_one_data.get("ip6mask"),
)
interface_two = None
interface_two_data = data.get("interface_two")
if interface_two_data:
interface_two = InterfaceData(
_id=interface_two_data.get("id"),
name=interface_two_data.get("name"),
mac=interface_two_data.get("mac"),
ip4=interface_two_data.get("ip4"),
ip4_mask=interface_two_data.get("ip4mask"),
ip6=interface_two_data.get("ip6"),
ip6_mask=interface_two_data.get("ip6mask"),
)
link_type = None
link_type_value = data.get("type")
if link_type_value is not None:
link_type = LinkTypes(link_type_value)
options_data = data.get("options")
link_options = LinkOptions(_type=link_type)
if options_data:
link_options.delay = options_data.get("delay")
link_options.bandwidth = options_data.get("bandwidth")
link_options.session = options_data.get("session")
link_options.per = options_data.get("per")
link_options.dup = options_data.get("dup")
link_options.jitter = options_data.get("jitter")
link_options.mer = options_data.get("mer")
link_options.burst = options_data.get("burst")
link_options.mburst = options_data.get("mburst")
link_options.unidirectional = options_data.get("unidirectional")
link_options.key = options_data.get("key")
link_options.opaque = options_data.get("opaque")
link_options = LinkOptions()
session.add_link(node_one, node_two, interface_one, interface_two, link_options=link_options)
return jsonify(), 201
@app.route("/sessions/<int:session_id>/links", methods=["DELETE"])
def delete_link(session_id):
session = coreemu.sessions.get(session_id)
if not session:
return jsonify(error="session does not exist"), 404
data = request.get_json()
node_one = data.get("node_one")
node_two = data.get("node_two")
interface_one = data.get("interface_one")
interface_two = data.get("interface_two")
session.delete_link(node_one, node_two, interface_one, interface_two)
return jsonify()
@app.route("/sessions/<int:session_id>/nodes/<node_id>/links")
def get_node_links(session_id, node_id):
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
links_data = node.all_link_data(0)
links = []
for link_data in links_data:
link = link_data._asdict()
del link["message_type"]
link_data_str(link, "interface1_ip4")
link_data_str(link, "interface1_ip6")
link_data_str(link, "interface1_mac")
link_data_str(link, "interface2_ip4")
link_data_str(link, "interface2_ip6")
link_data_str(link, "interface2_mac")
links.append(link)
return jsonify(links=links)
if __name__ == "__main__":
socketio.run(app, host="0.0.0.0", debug=True)

7
webapp/static/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

7
webapp/static/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

15
webapp/static/core.css Normal file
View file

@ -0,0 +1,15 @@
.container-fluid {
height: 100vh;
}
.navbar-text {
color: #17a2b8 !important;
background-color: #fff;
font-weight: bold;
}
.vis-network {
background-color: #f8f9fa !important;
border: 1px solid #dee2e6;
border-radius: 10px;
}

View file

@ -0,0 +1,135 @@
const NodeTypes = {
// default router
0: {
name: 'node'
},
// switch
4: {
name: 'switch'
},
// hub
5: {
name: 'hub'
},
// wlan
6: {
name: 'wlan'
}
};
class CoreNode {
constructor(id, name, x, y) {
this.id = id;
this.name = name;
this.model = null;
this.canvas = null;
this.icon = null;
this.opaque = null;
this.services = [];
this.x = x;
this.y = y;
this.lat = null;
this.lon = null;
this.alt = null;
this.emulation_id = null;
this.emulation_server = null;
}
getNetworkNode() {
return {
id: this.id,
x: this.x,
y: this.y,
label: this.name,
node: this
//color: '#FFF',
//shape: 'image',
//shapeProperties: {
// useBorderWithImage: true
//},
//image: nodeMode.image,
//type: nodeMode.nodeType
};
}
}
class CoreNetwork {
constructor(elementId) {
this.nodeType = NodeTypes['0'];
this.nodeModel = null;
this.nodeId = 0;
this.container = document.getElementById(elementId);
this.nodes = new vis.DataSet();
this.edges = new vis.DataSet();
this.networkData = {
nodes: this.nodes,
edges: this.edges
};
this.networkOptions = {
height: '95%',
physics: false,
interaction: {
selectConnectedEdges: false
},
edges: {
shadow: true,
width: 3,
smooth: false,
color: {
color: '#000000'
}
},
nodes: {
shadow: true
}
};
this.network = new vis.Network(this.container, this.networkData, this.networkOptions);
this.network.on('doubleClick', this.addNode.bind(this));
this.edges.on('add', this.addEdge.bind(this));
}
nextNodeId() {
this.nodeId += 1;
return this.nodeId;
}
addNode(properties) {
console.log('add node event: ', properties);
if (properties.nodes.length === 0) {
const {x, y} = properties.pointer.canvas;
const nodeId = this.nextNodeId();
const name = `${this.nodeType.name}${nodeId}`;
const coreNode = new CoreNode(nodeId, name, x, y);
coreNode.model = this.nodeModel;
this.nodes.add(coreNode.getNetworkNode());
console.log('added node: ', coreNode.getNetworkNode());
}
}
addEdge(_, properties) {
const edgeId = properties.items[0];
const edge = this.edges.get(edgeId);
console.log('added edge: ', edgeId, edge);
if (edge.from === edge.to) {
console.log('removing cyclic edge');
this.edges.remove(edge.id);
}
// keep edge mode enabled
setTimeout(() => this.network.addEdgeMode(), 250);
}
linkMode(enabled) {
console.log('link mode:', enabled);
if (enabled) {
this.network.addEdgeMode();
} else {
this.network.disableEditMode();
}
}
setNodeMode(nodeType, model) {
this.nodeType = NodeTypes[nodeType];
this.nodeModel = model || null;
}
}

View file

@ -0,0 +1,9 @@
class CoreRest {
constructor() {
}
async sessions(callback) {
const response = await $.getJSON('/sessions');
callback(response);
}
}

2
webapp/static/jquery-3.3.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

5
webapp/static/popper.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
webapp/static/vis.min.css vendored Normal file

File diff suppressed because one or more lines are too long

47
webapp/static/vis.min.js vendored Normal file

File diff suppressed because one or more lines are too long

149
webapp/templates/index.html Normal file
View file

@ -0,0 +1,149 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CORE</title>
<link rel="stylesheet" type="text/css" href="static/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="static/vis.min.css">
<link rel="stylesheet" type="text/css" href="static/core.css">
</head>
<body>
<div class="container-fluid d-flex flex-column p-0">
<nav class="navbar navbar-expand navbar-dark bg-info mb-2">
<span class="navbar-brand mb-0 h1">CORE</span>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#nb-content"
aria-controls="nb-content" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="nb-content">
<ul class="navbar-nav mr-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="nb-file" role="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
File
</a>
<div class="dropdown-menu" aria-labelledby="nb-file">
<a class="dropdown-item" href="#">Open File</a>
<a class="dropdown-item" href="#">Save File</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="nb-session" role="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
Sessions
</a>
<div class="dropdown-menu" aria-labelledby="nb-session">
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#sessions-modal">View</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="nb-help" role="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
Help
</a>
<div class="dropdown-menu" aria-labelledby="nb-help">
<a class="dropdown-item" href="https://github.com/coreemu/core">GitHub</a>
<a class="dropdown-item" href="http://coreemu.github.io/core/">Documentation</a>
</div>
</li>
</ul>
<span class="navbar-text border rounded border-white p-2 mr-3">Session #</span>
<span class="navbar-text border rounded border-white p-2">Node: Default</span>
</div>
</nav>
<div class="row col">
<div class="col-2 col-xl-1">
<div class="btn-group-vertical w-100" role="group" aria-label="Side Menu">
<button type="button" class="btn btn-secondary">Select</button>
<button type="button" class="btn btn-secondary">Start</button>
<button id="link-button" type="button" class="btn btn-secondary" data-toggle="button">Link Mode</button>
<div class="btn-group dropright node-buttons" role="group">
<button id="menu-nodes" type="button" class="btn btn-secondary dropdown-toggle"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Nodes
</button>
<div class="dropdown-menu" aria-labelledby="menu-nodes">
<a class="dropdown-item" href="#" data-node="0" data-model="router">Router</a>
<a class="dropdown-item" href="#" data-node="0" data-model="host">Host</a>
<a class="dropdown-item" href="#" data-node="0" data-model="PC">PC</a>
<a class="dropdown-item" href="#" data-node="0" data-model="mdr">MDR</a>
</div>
</div>
<div class="btn-group dropright node-buttons" role="group">
<button id="menu-devices" type="button" class="btn btn-secondary dropdown-toggle"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Devices
</button>
<div class="dropdown-menu" aria-labelledby="menu-devices">
<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="6">WLAN</a>
</div>
</div>
</div>
</div>
<div id="core-network" class="col">
</div>
</div>
{% include 'sessions_modal.html' %}
</div>
<script src="static/jquery-3.3.1.min.js"></script>
<script src="static/popper.min.js"></script>
<script src="static/bootstrap.min.js"></script>
<script src="static/vis.min.js"></script>
<script src="static/socket.io.js"></script>
<script src="static/corerest.js"></script>
<script src="static/corenetwork.js"></script>
<script>
const core = new CoreRest();
core.sessions(function (response) {
console.log('session response: ', response);
});
const coreNetwork = new CoreNetwork('core-network');
const $linkButton = $('#link-button');
$linkButton.click(function () {
const linkMode = !$(this).hasClass('active');
coreNetwork.linkMode(linkMode);
$(this).blur();
});
const $nodeButtons = $('.node-buttons a');
$nodeButtons.click(function () {
const $this = $(this);
const nodeType = $this.data('node');
const model = $this.data('model');
console.log('node creation: ', nodeType, model);
console.log('clicked: ', this);
coreNetwork.setNodeMode(nodeType, model);
});
console.log('connecting to ws');
const ws = io.connect();
ws.on('connection', function () {
console.log('connected!');
});
ws.on('error', function (error) {
console.log(error);
});
ws.on('info', function (data) {
console.log(data.message);
});
ws.on('node', function (data) {
console.log(data);
});
</script>
</body>
</html>

View file

@ -0,0 +1,19 @@
<div id="sessions-modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>Modal body text goes here.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary">Save changes</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>