This commit is contained in:
Martin Geno 2016-06-24 03:17:12 +02:00
parent 62dc7384aa
commit 6c1691a9c5
11 changed files with 228 additions and 44 deletions

View File

@ -16,6 +16,10 @@
"tests" "tests"
], ],
"dependencies": { "dependencies": {
"material-design-lite": "^1.1.3" "material-design-lite": "^1.1.3",
"dialog-polyfill": "^0.4.3",
"Leaflet.label": "~0.2.1",
"leaflet": "~0.7.3",
"moment": "~2.9.0"
} }
} }

View File

@ -2,6 +2,20 @@
"api":"http://localhost:8080/api", "api":"http://localhost:8080/api",
"reload":60000, "reload":60000,
"map":"http://localhost:8080/meshviewer", "map":"http://localhost:8080/meshviewer",
"editmap":{
"view":[53.0702,8.815],
"zoom":16,
"geojson":"/data/meshviewer.geojson",
"tiles":{
"url":"https://otile{s}-s.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg",
"option":{
"subdomains": "1234",
"type": "osm",
"attribution": "Tiles &copy; <a href=\"https://www.mapquest.com/\" target=\"_blank\">MapQuest</a>, Data CC-BY-SA OpenStreetMap",
"maxZoom": 18
}
}
},
"statistics":{ "statistics":{
"one":"http://ffhb.h.sum7.de:8080/dashboard-solo/file/Short.json?panelId=1&fullscreen&var-interval_group=60s&theme=light&from=1466165052000&var-title={{NODEID}}&var-node={{NODEID}}", "one":"http://ffhb.h.sum7.de:8080/dashboard-solo/file/Short.json?panelId=1&fullscreen&var-interval_group=60s&theme=light&from=1466165052000&var-title={{NODEID}}&var-node={{NODEID}}",
"all":"http://ffhb.h.sum7.de:8080/dashboard-solo/file/Short.json?panelId=1&fullscreen&var-interval_group=60s&theme=light&from=1466165052000&var-title=All" "all":"http://ffhb.h.sum7.de:8080/dashboard-solo/file/Short.json?panelId=1&fullscreen&var-interval_group=60s&theme=light&from=1466165052000&var-title=All"

View File

@ -9,7 +9,7 @@ html, body {
border-radius: 24px; border-radius: 24px;
}*/ }*/
container > iframe{ #container > iframe{
border: 0 solid #000; border: 0 solid #000;
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -32,3 +32,10 @@ container > iframe{
padding:20px 0 0; padding:20px 0 0;
margin:0px; margin:0px;
} }
#dialog-editNode{
min-width:75%;
}
#dialog-editNode-map{
height:300px;
}

View File

@ -20,10 +20,13 @@
<link rel="shortcut icon" href="img/logo.png"> <link rel="shortcut icon" href="img/logo.png">
<!--<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:regular,bold,italic,thin,light,bolditalic,black,medium&amp;lang=en">--> <link rel="stylesheet" href="bower_components/leaflet/dist/leaflet.css" />
<link rel="stylesheet" href="css/icon.css"> <link rel="stylesheet" href="bower_components/Leaflet.label/dist/leaflet.label.css" />
<link rel="stylesheet" href="css/material.pink-amber.min.css"> <link rel="stylesheet" href="css/font.css" />
<link rel="stylesheet" href="css/styles.css"> <link rel="stylesheet" href="css/icon.css" />
<link rel="stylesheet" href="css/material.pink-amber.min.css" />
<link rel="stylesheet" href="bower_components/dialog-polyfill/dialog-polyfill.css" />
<link rel="stylesheet" href="css/styles.css" />
</head> </head>
<body> <body>
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-drawer mdl-layout--fixed-header"> <div class="mdl-layout mdl-js-layout mdl-layout--fixed-drawer mdl-layout--fixed-header">
@ -40,15 +43,17 @@
<label class="mdl-textfield__label" for="search">Enter Node name</label> <label class="mdl-textfield__label" for="search">Enter Node name</label>
</div> </div>
</div> </div>
<div id="lastLoad">
</div>
<label class="mdl-button mdl-js-button mdl-button--icon" id="refresh"> <label class="mdl-button mdl-js-button mdl-button--icon" id="refresh">
<i class="material-icons">refresh</i> <i class="material-icons">refresh</i>
</label> </label>
</div> </div>
</header> </header>
<div class="mdl-layout__drawer"> <div class="mdl-layout__drawer">
<header> <span class="mdl-layout-title">
<img src="img/logo.png" class="logo"/> Event Manager <img src="img/logo.png" class="logo"/> Event Manager
</header> </span>
<nav class="mdl-navigation"> <nav class="mdl-navigation">
<a class="mdl-navigation__link" href="#nodes"><i class="material-icons mdl-badge mdl-badge--overlap" role="presentation" id="menu_nodes">view_list</i>Nodes</a> <a class="mdl-navigation__link" href="#nodes"><i class="material-icons mdl-badge mdl-badge--overlap" role="presentation" id="menu_nodes">view_list</i>Nodes</a>
<a class="mdl-navigation__link" href="#aliases"><i class="material-icons mdl-badge mdl-badge--overlap" role="presentation" id="menu_aliases">replay</i>Undone Changes</a> <a class="mdl-navigation__link" href="#aliases"><i class="material-icons mdl-badge mdl-badge--overlap" role="presentation" id="menu_aliases">replay</i>Undone Changes</a>
@ -65,9 +70,26 @@
<div class="mdl-snackbar__text"></div> <div class="mdl-snackbar__text"></div>
<button class="mdl-snackbar__action" type="button"></button> <button class="mdl-snackbar__action" type="button"></button>
</div> </div>
<dialog class="mdl-dialog" id="dialog-editNode">
<h4 class="mdl-dialog__title">Loading...</h4>
<div class="mdl-dialog__content">
<p>Loading...</p>
</div>
<div id="dialog-editNode-map"></div>
<div class="mdl-dialog__actions">
<button type="button" class="mdl-button save">Save</button>
<button type="button" class="mdl-button close">Close</button>
</div>
</dialog>
<script src="bower_components/leaflet/dist/leaflet.js"></script>
<script src="bower_components/Leaflet.label/dist/leaflet.label.js"></script>
<script src="bower_components/material-design-lite/material.min.js"></script> <script src="bower_components/material-design-lite/material.min.js"></script>
<script src="bower_components/dialog-polyfill/dialog-polyfill.js"></script>
<script src="bower_components/moment/min/moment.min.js"></script>
<script src="js/lib.js"></script> <script src="js/lib.js"></script>
<script src="js/store.js"></script> <script src="js/store.js"></script>
<script src="js/global.js"></script>
<script src="js/editModel.js"></script>
<script src="js/route/nodes.js"></script> <script src="js/route/nodes.js"></script>
<script src="js/route/aliases.js"></script> <script src="js/route/aliases.js"></script>
<script src="js/route/map.js"></script> <script src="js/route/map.js"></script>

92
js/editModel.js Normal file
View File

@ -0,0 +1,92 @@
var dialogEditNode = document.getElementById("dialog-editNode")
dialogEditNodeClose = document.querySelector('#dialog-editNode .close')
dialogEditNodeSave = document.querySelector('#dialog-editNode .save')
dialogEditNodeTitle = document.querySelector('#dialog-editNode .mdl-dialog__title')
dialogEditNodeContent = document.querySelector('#dialog-editNode .mdl-dialog__content')
dialogEditNodeMap = L.map('dialog-editNode-map').setView([51.505, -0.09], 13);
var dialogEditNodeMapCurrent
function createModel(){
if (! dialogEditNode.showModal) {
dialogPolyfill.registerDialog(dialogEditNode);
}
//MAP Part
dialogEditNodeMap.setView(internal.config.editmap.view,internal.config.editmap.zoom)
L.tileLayer(internal.config.editmap.tiles.url,internal.config.editmap.tiles.option).addTo(dialogEditNodeMap)
send('GET',internal.config.editmap.geojson).then(function(data){
L.geoJson(data,{
pointToLayer: function (feature, latlng){
/*
feature.properties.radius = 10
feature.properties.color = feature.properties["marker-color"]
feature.properties.fillColor = feature.properties["marker-color"]
feature.properties.fillOpacity = 0.5
*/
m = L.marker(latlng,{
radius: 8,
fillColor: "#ff7800",
color: "#000",
weight: 1,
opacity: 1,
fillOpacity: 0.8
})
m.bindLabel(feature.properties.name)
return m
}
}).addTo(dialogEditNodeMap)
})
dialogEditNodeMapCurrent = L.circle(internal.config.editmap.view, 500,{
radius: 500,
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5,
draggable: true
}).addTo(dialogEditNodeMap)
dialogEditNodeClose.addEventListener('click',function(){
dialogEditNode.close()
})
dialogEditNodeSave.addEventListener('click',function(e){
nodeid = dialogEditNodeContent.querySelector('input[name="nodeid"]').value
if(internal.aliases[nodeid] == undefined){
internal.aliases[nodeid] = {}
}
if(internal.aliases[nodeid].location == undefined){
internal.aliases[nodeid].location = {}
}
pos = dialogEditNodeMapCurrent.getLatLng()
internal.aliases[nodeid].location.latitude = pos[0]
internal.aliases[nodeid].location.longitude = pos[1]
internal.aliases[nodeid].hostname = dialogEditNodeContent.querySelector('input[name="hostname"]').value
console.log("save",internal.aliases[nodeid],dialogEditNodeMapCurrent.getLatLng())
send('POST',internal.config.api+'/aliases/alias/'+nodeid,internal.aliases[nodeid]).then(function(){
dialogEditNode.close()
})
})
}
function editModel(key){
dialogEditNodeTitle.innerHTML = 'Edit Node: '+key
fill = '<input type="hidden" name="nodeid" value="'+key+'"/>'
+ '<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label is-dirty">'
+ '<input class="mdl-textfield__input" type="text" name="hostname" value="'+internal.nodes[key].nodeinfo.hostname+'"/>'
+ '<label class="mdl-textfield__label" for="hostname">Hostname</label>'
+'</div>'
dialogEditNodeContent.innerHTML = fill
if(internal.nodes[key].nodeinfo.location != undefined){
pos = [internal.nodes[key].nodeinfo.location.latitude, internal.nodes[key].nodeinfo.location.longitude]
dialogEditNodeMapCurrent.setLatLng(pos)
dialogEditNodeMap.setView(pos)
}else{
dialogEditNodeMapCurrent.setLatLng(internal.config.editmap.view)
dialogEditNodeMap.setView(internal.config.editmap.view)
}
dialogEditNodeMapCurrent.bindLabel("Move Node: "+key)
dialogEditNode.showModal()
}

View File

@ -1,10 +1,11 @@
var toast = document.getElementById("toast") var toast = document.getElementById("toast")
function notify(key){ function notify(key){
console.log("new node:",key)
toast.MaterialSnackbar.showSnackbar({ toast.MaterialSnackbar.showSnackbar({
message:"New Nodes with nodeid '"+key+"'!", message:"New Nodes with nodeid '"+key+"'!",
actionHandler: function(event) { actionHandler: function(event) {
console.log(event) editModel(key)
}, },
actionText: 'Edit', actionText: 'Edit',
timeout: 3000 timeout: 3000
@ -14,3 +15,11 @@ function notify(key){
var refreshButton = document.getElementById("refresh") var refreshButton = document.getElementById("refresh")
refreshButton.addEventListener('click', refreshData) refreshButton.addEventListener('click', refreshData)
var lastload = document.getElementById("lastLoad")
function updateTimes(){
lastload.innerHTML = moment(internal.lastload).fromNow()
}
updateTimes();
setInterval(updateTimes, 1000);

View File

@ -1,11 +1,15 @@
send('GET',"config.json").then(function(config){ send('GET',"config.json").then(function(config){
internal.config = config internal.config = config
if(localStorageTest()){ createModel()
localStorage.removeItem("nodes")
localStorage.removeItem("aliases")
if(false && localStorageTest()){
internal.nodes = JSON.parse(localStorage.getItem("nodes")) internal.nodes = JSON.parse(localStorage.getItem("nodes"))
internal.aliases = JSON.parse(localStorage.getItem("aliases")) internal.aliases = JSON.parse(localStorage.getItem("aliases"))
if(!internal.nodes){ if(!internal.nodes){
send('GET',internal.config.api+"/nodes").then(function(data){ send('GET',internal.config.api+"/nodes").then(function(data){
internal.nodes = data internal.nodes = data
internal.lastload = new Date()
}) })
} }
if(!internal.aliases){ if(!internal.aliases){
@ -13,12 +17,12 @@ send('GET',"config.json").then(function(config){
internal.aliases = data internal.aliases = data
}) })
} }
}else{
refreshData()
} }
menuAliases.setAttribute("data-badge",Object.keys(internal.aliases).length) updateBange()
menuNodes.setAttribute("data-badge",Object.keys(internal.nodes).length)
setInterval(function () { setInterval(function () {
refreshData() refreshData()
route()
}, config.reload); }, config.reload);
route() route()
}) })

View File

@ -33,3 +33,17 @@ function localStorageTest() {
return false return false
} }
} }
function sshUrl(nodeid){
if(internal.nodes[nodeid]){
node = internal.nodes[nodeid]
ip ='z-'
for(var i=0;i<node.nodeinfo.network.addresses.length;i++){
if(ip[0]> node.nodeinfo.network.addresses[i][0])
ip = node.nodeinfo.network.addresses[i]
}
return '<a href="ssh://root@['+ip+']">SSH</a>'
}
return ''
}

View File

@ -1,32 +1,33 @@
function routeAliases(){ function routeAliases(){
fill = '<table class="mdl-data-table mdl-js-data-table mdl-shadow--2dp" style="width:100%">' fill = '<table class="mdl-data-table mdl-js-data-table mdl-shadow--2dp nodes-table">'
+ '<thead>' + '<thead>'
+ '<tr>' + '<tr>'
+ '<th class="mdl-data-table__cell--non-numeric">Hostname</th>' + '<th class="mdl-data-table__cell--non-numeric">Hostname</th>'
+ '<th>Freq</th>'
+ '<th>Channel</th>'
+ '<th>Power</th>'
+ '<th class="mdl-data-table__cell--non-numeric">Location</th>' + '<th class="mdl-data-table__cell--non-numeric">Location</th>'
+ '<th>2.4 Ghz</th>' + '<th class="mdl-data-table__cell--non-numeric">SSH</th>'
+ '<th>5 Ghz</th>'
+ '</tr>' + '</tr>'
+ '</thead>' + '</thead>'
+ '<tbody>' + '<tbody>'
Object.keys(internal.aliases).map(function(key){ Object.keys(internal.aliases).map(function(key){
fill += '<tr>' fill += '<tr>'
+ '<td class="mdl-data-table__cell--non-numeric">' + '<td class="mdl-data-table__cell--non-numeric" rowspan="2">'
+ internal.aliases[key].hostname + internal.aliases[key].hostname
+ '<br/>' + '<br/>'
+ '<small>'+key+'</small>' + '<small>'+key+'</small>'
+ '</td>' + '</td>'
+ '<td class="mdl-data-table__cell--non-numeric"><i class="material-icons">place</i></td>' + '<td>2.4 Ghz</td>'
+ '<td>' + '<td>'+((internal.aliases[key].freq24 !== undefined)?internal.aliases[key].freq24.channel:'-')+ '</td>'
+ 'Ch: '+((internal.aliases[key].freq24 !== undefined)?internal.aliases[key].freq24.channel:'-') + '<td>'+((internal.aliases[key].freq24 !== undefined)?internal.aliases[key].freq24.txpower:'-')+ '</td>'
+ '<br/>' + '<td class="mdl-data-table__cell--non-numeric" rowspan="2">'+((internal.aliases[key].location)?'<i class="material-icons">place</i>':'')+'</td>'
+ 'Tx: '+((internal.aliases[key].freq24 !== undefined)?internal.aliases[key].freq24.txpower:'-') + '<td class="mdl-data-table__cell--non-numeric" rowspan="2">'+sshUrl(key)+'</td>'
+ '</td>' + '</tr>'
+ '<td>' + '<tr>'
+ 'Ch: '+((internal.aliases[key].freq5 !== undefined)?internal.aliases[key].freq5.channel:'-') + '<td class="mdl-data-table__cell--non-numeric" style="padding-left:18px;">5Ghz</td>'
+ '<br/>' + '<td>'+((internal.aliases[key].freq5 !== undefined)?internal.aliases[key].freq5.channel:'-')+ '</td>'
+ 'TX: '+((internal.aliases[key].freq5 !== undefined)?internal.aliases[key].freq5.txpower:'-') + '<td style="padding-right:18px;">'+((internal.aliases[key].freq5 !== undefined)?internal.aliases[key].freq5.txpower:'-')+ '</td>'
+ '</td>'
+ '</tr>' + '</tr>'
}) })
fill += '</tbody></table>' fill += '</tbody></table>'

View File

@ -3,16 +3,17 @@ function routeNodesPrivEvent(nodeid,attr,attr2){
return function (e){ return function (e){
var input = e.which || e.keyCode; var input = e.which || e.keyCode;
if (input === 13) { // 13 is enter if (input === 13) { // 13 is enter
value = e.target.value || e.srcElement.value || ''
if(internal.aliases[nodeid] == undefined){ if(internal.aliases[nodeid] == undefined){
internal.aliases[nodeid] = {} internal.aliases[nodeid] = {}
} }
if(attr2 == undefined){ if(attr2 == undefined){
internal.aliases[nodeid][attr] = e.srcElement.value internal.aliases[nodeid][attr] = value
}else{ }else{
if(internal.aliases[nodeid][attr] == undefined){ if(internal.aliases[nodeid][attr] == undefined){
internal.aliases[nodeid][attr] = {} internal.aliases[nodeid][attr] = {}
} }
internal.aliases[nodeid][attr][attr2] = e.srcElement.value internal.aliases[nodeid][attr][attr2] = value
} }
send('POST',internal.config.api+'/aliases/alias/'+nodeid,internal.aliases[nodeid]) send('POST',internal.config.api+'/aliases/alias/'+nodeid,internal.aliases[nodeid])
} }
@ -32,7 +33,7 @@ function routeNodes(){
+ '<th>Clients</th>' + '<th>Clients</th>'
+ '<th>Ch</th>' + '<th>Ch</th>'
+ '<th>Tx</th>' + '<th>Tx</th>'
+ '<th>Ort</th>' + '<th>Edit</th>'
+ '<th>SSH</th>' + '<th>SSH</th>'
+ '</tr>' + '</tr>'
+ '</thead>' + '</thead>'
@ -40,8 +41,10 @@ function routeNodes(){
Object.keys(internal.nodes).map(function(key){ Object.keys(internal.nodes).map(function(key){
fill += '<tr>' fill += '<tr>'
+ '<td class="mdl-data-table__cell--non-numeric" rowspan="2" style="padding:0px 2px;text-align:center;">' + '<td class="mdl-data-table__cell--non-numeric" rowspan="2" style="padding:0px 2px;text-align:center;">'
+ '<i class="material-icons" style="color:'+((internal.nodes[key].flags.online)?'green':'red')+';">camera</i></td>' + '<span style="color:'+((internal.nodes[key].flags.online)?'green':'red')+';">&#x25cf;</span><br/>'
+ '<td class="mdl-data-table__cell--non-numeric mdt-table__cell-input" rowspan="2" style="padding-left:0px;">' + moment(internal.nodes[key].lastseen).fromNow(true)
+'</td>'
+ '<td class="mdl-data-table__cell--non-numeric mdt-table__cell-input" rowspan="2">'
+ '<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label is-dirty">' + '<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label is-dirty">'
+ '<input class="mdl-textfield__input" type="text" id="hostname_'+key+'" value="'+internal.nodes[key].nodeinfo.hostname+'"/>' + '<input class="mdl-textfield__input" type="text" id="hostname_'+key+'" value="'+internal.nodes[key].nodeinfo.hostname+'"/>'
+ '<label class="mdl-textfield__label" for="hostname_'+key+'">'+key+'</label>' + '<label class="mdl-textfield__label" for="hostname_'+key+'">'+key+'</label>'
@ -56,8 +59,8 @@ function routeNodes(){
+ '<td>' + '<td>'
+ '<input class="mdl-textfield__input" type="number" id="freq24_tx_'+key+'" value="'+((internal.nodes[key].nodeinfo.settings !== undefined)?internal.nodes[key].nodeinfo.settings.freq24.txpower:'')+'"/>' + '<input class="mdl-textfield__input" type="number" id="freq24_tx_'+key+'" value="'+((internal.nodes[key].nodeinfo.settings !== undefined)?internal.nodes[key].nodeinfo.settings.freq24.txpower:'')+'"/>'
+ '</td>' + '</td>'
+ '<td class="mdl-data-table__cell--non-numeric" rowspan="2"><i class="material-icons">place</i></td>' + '<td class="mdl-data-table__cell--non-numeric" rowspan="2"><i class="material-icons" id="edit_'+key+'">edit</i></td>'
+ '<td class="mdl-data-table__cell--non-numeric" rowspan="2">SSH</td>' + '<td class="mdl-data-table__cell--non-numeric" rowspan="2">'+sshUrl(key)+'</td>'
+ '</tr>' + '</tr>'
+ '<tr>' + '<tr>'
+ '<td class="mdl-data-table__cell--non-numeric" style="padding-left:18px;">5Ghz</td>' + '<td class="mdl-data-table__cell--non-numeric" style="padding-left:18px;">5Ghz</td>'
@ -79,7 +82,9 @@ function routeNodes(){
document.getElementById("freq24_ch_"+key).addEventListener('keypress', routeNodesPrivEvent(key,'freq24','channel')) document.getElementById("freq24_ch_"+key).addEventListener('keypress', routeNodesPrivEvent(key,'freq24','channel'))
document.getElementById("freq24_tx_"+key).addEventListener('keypress', routeNodesPrivEvent(key,'freq24','txpower')) document.getElementById("freq24_tx_"+key).addEventListener('keypress', routeNodesPrivEvent(key,'freq24','txpower'))
document.getElementById("freq5_ch_"+key).addEventListener('keypress', routeNodesPrivEvent(key,'freq5','channel')) document.getElementById("freq5_ch_"+key).addEventListener('keypress', routeNodesPrivEvent(key,'freq5','channel'))
document.getElementById("freq5_tx_"+key).addEventListener('keypress', routeNodesPrivEvent(key,'freq5','txpower')) document.getElementById("edit_"+key).addEventListener('click', function(){
editModel(key)
})
}) })
} }

View File

@ -1,7 +1,8 @@
var internal = { var internal = {
config:{}, config:{},
nodes:{}, nodes:{},
aliases:{} aliases:{},
lastload:0
} }
//var toast = document.querySelector('#toast'); //var toast = document.querySelector('#toast');
var container = document.getElementById("container") var container = document.getElementById("container")
@ -9,20 +10,31 @@ var menuNodes = document.getElementById("menu_nodes")
var menuAliases = document.getElementById("menu_aliases") var menuAliases = document.getElementById("menu_aliases")
function updateBange(){
if(internal.nodes && Object.keys(internal.nodes))
menuNodes.setAttribute("data-badge",Object.keys(internal.nodes).length)
if(internal.aliases && Object.keys(internal.aliases))
menuAliases.setAttribute("data-badge",Object.keys(internal.aliases).length)
}
function refreshData(){ function refreshData(){
console.log("load new files")
send('GET',internal.config.api+"/aliases").then(function(data){ send('GET',internal.config.api+"/aliases").then(function(data){
internal.aliases = data internal.aliases = data
menuAliases.setAttribute("data-badge",Object.keys(internal.aliases).length) updateBange()
localStorage.setItem("aliases",JSON.stringify(internal.aliases))
}) })
send('GET',internal.config.api+"/nodes").then(function(data){ return send('GET',internal.config.api+"/nodes").then(function(data){
Object.keys(data).map(function(key){ Object.keys(data).map(function(key){
if(typeof internal.nodes[key]=='undefined'){ if(internal.nodes[key]== undefined){
notify(key) notify(key,data[key])
} }
internal.nodes[key] = data[key] internal.nodes[key] = data[key]
}) })
menuNodes.setAttribute("data-badge",Object.keys(internal.nodes).length) updateBange()
internal.lastload = new Date()
localStorage.setItem("nodes",JSON.stringify(internal.nodes))
route()
}) })
localStorage.setItem("nodes",JSON.stringify(internal.nodes))
localStorage.setItem("aliases",JSON.stringify(internal.aliases))
} }