From bc42f21219eda64c066590099df4e9d0a87cb2f9 Mon Sep 17 00:00:00 2001 From: sbosse Date: Mon, 21 Jul 2025 23:20:03 +0200 Subject: [PATCH] Mon 21 Jul 22:43:21 CEST 2025 --- js/ui/webui/win.js | 705 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 705 insertions(+) create mode 100644 js/ui/webui/win.js diff --git a/js/ui/webui/win.js b/js/ui/webui/win.js new file mode 100644 index 0000000..f6e2d7f --- /dev/null +++ b/js/ui/webui/win.js @@ -0,0 +1,705 @@ +/* +** Main Application +*/ + +var UI= $$; +var logtext= ''; +var treemap=[]; +var sourcetext=''; +var printloc=false; + +var shell=[]; +var shellWin=[]; +var configShellWin=[]; +var shellNum=1; +var monitorWin=[]; +var monitorNum=1; +var monitor=[] +var reporterWin=[]; +var reporterNum=1; +var editorWin=[]; +var editorNum=1; +var configEditorWin=[]; +var netNodes=[] +var state=0; + +var portRoot = 5000; + + +/** Main Options **/ + +var options = { + ipmask:'192.168.8.*', + ipportdef:5000, // Default LUAOS IP port + pollDelta:100, // Polling time between two different node scans + pollIntervall:10000, // Polling time between two full scans + portRange:[140,160], + version:'1.1.6', +} + +var luaTemplate = "local m = monitor:new('mydata','time:%8d x:%4d')\nfunction f(x)\n print(x)\n m:log({x=x})\nend\nf(math.random())\n" + +function log (widget,data) { + var view = UI(widget); + var log = UI(widget+'LogText'); + var scroll = view.getBody(); + logtext += data + '\n'; + log.removeView(widget+'WinLogTextView'); + log.addView({template:'
'+logtext+'
', autoheight:true, borderless:true, id:widget+'LogTextView'}); + scroll.scrollTo(0,1000000) + view.show(); +} + +function clear (widget) { + var view = UI(widget); + var log = UI(widget+'LogText'); + var scroll = view.getBody(); + logtext = ''; + log.removeView(widget+'LogTextView'); + log.addView({template:'
'+logtext+'
', autoheight:true, borderless:true, id:widget+'LogTextView'}); + scroll.scrollTo(0,1000000) + view.show(); +} + +function deltree() { + var root,tree = this.UI('navTree'); + if (tree._roots) { + for (var p in tree._roots) { + p=tree._roots[p]; + tree.remove(p); + } + } else tree.remove('root'); + root={ id:"root", open:true, value:'Nodes @'+Date.now() } + tree.add(root); +} + +function maketree() { + var i=0,tree = this.UI('navTree'); + for(var p in netNodes) { + tree.add({id:p,value:p},i,'root'); + i++; + } +} +var ipNext=0; +var pollTimer; + +function poll() { + if (network.state==0) return; + if (ipNext != 0) return setTimeout(poll,10000); + + function get(ip, callback) { + var url='http://'+ip+':'+options.ipportdef+'?ping=true'; + // read text from URL location + var request = new XMLHttpRequest(); + + request.open('GET', url, true); + request.send(null); + request.onreadystatechange = function() { + if (request.readyState === 4) { + var type = request.getResponseHeader('Content-Type'); + if (type && type.indexOf("text") !== 1) { + callback(ip,request.responseText); + } + } + } + } + function check(ip,text) { + if (text=='LUAOS') { + if (!netNodes[ip]) + webix.message({ + text:"Found node "+ip+": "+text, + type:"debug", + expire: 5000, + }); + netNodes[ip]=Date.now(); + } + } + function onepoll() { + if (network.state!=1) return; + if (ipNext==0) { + get('localhost', check); + ipNext=options.portRange[0]; + } else { + get(options.ipmask.replace(/\*/,ipNext),check); + ipNext++; + } + if (ipNextoptions.pollIntervall*2) { + delete netNodes[p]; + webix.message({ + text:"Unreachable node "+p, + type:"error", + expire: 5000, + }); + } + } + maketree(); + } + } + onepoll(); + pollTimer=setTimeout(poll,options.pollIntervall); + +}; +network = { + state:0, + start: function () { + network.state=1; + setTimeout(poll,100) + }, + stop: function () { + network.state=0; + if (pollTimer) clearTimeout(pollTimer); + pollTimer=undefined; + ipNext=0; + for(var i in shell) { + if (shell[i]) shell[i].stop(); + } + for(var i in monitor) { + if (monitor[i]) monitor[i].stop(); + } + } +} + + + + +/************ MAIN CONFIG ********/ +mainConfigWin=webix.ui({ + id:'mainConfigWin', + view:"window", + width:300, + height:450, + left:90, top:90, + move:true, + resize: true, + toFront:true, + head: { + view:"toolbar", + cols:[ + { view:"label", label:"Main Configuration", align:'right'}, + { view:"button", type:"icon", icon:"check-square", align:'center', width:30, click: function () { + options.ipmask=UI('mainConfigWinIp').getValue(); + options.ipportdef=UI('mainConfigWinIpPortDef').getValue(); + options.portRange[0]=UI('mainConfigWinIpPortRangeA').getValue(); + options.portRange[1]=UI('mainConfigWinIpPortRangeB').getValue(); + mainConfigWin.hide(); + }} + ] + }, + body: { + rows:[ + { view:"form", scroll:true, width:300, height:400, elements: [ + + { view:"label", label:"LUAOS Network", height:'18px', align:'left'}, + { view:"text", id:"mainConfigWinIp", label:"IP Mask", value:options.ipmask}, + { view:"text", id:"mainConfigWinIpPortDef", label:"IP Port", value:options.ipportdef}, + { view:"text", id:"mainConfigWinIpPortRangeA", label:"IP Range A", value:options.portRange[0]}, + { view:"text", id:"mainConfigWinIpPortRangeB", label:"IP Range B", value:options.portRange[1]}, + + + ]} + ] + } + }); + + +/************ TOP TOOLBAR ********/ + +toolbar = this.webix.ui({ + view:"toolbar", + id:"myToolbar", + left:0, top:0, width:'100%', + cols:[ + { view:"button", type:"icon", icon:"play", id:'button.network.start', tooltip:'Connect', width:30, click:function () { + if (network.state==0) { + UI('button.network.start').disable(); + UI('button.network.stop').enable(); + network.start(); + } + }}, + { view:"button", type:"icon", icon:"stop", id:'button.network.stop', tooltip:'Disconnect', width:30, click:function () { + UI('button.network.start').enable(); + UI('button.network.stop').disable(); + network.stop(); + }}, + { view:"button", type:"icon", icon:"repeat", tooltip:'Reload', width:30, click:function () { + window.location.reload(true) + }}, + { view:"button", type:"icon", icon:"navicon", tooltip:'Configuration', width:30, click:function () { + mainConfigWin.show(); + }}, + { view:"label", id:'myTopLabel', label:'LUANET WEB API (C) Dr. Stefan Bosse Ver. '+options.version, align:'right'}, + { view:"button", type:"icon", icon:"close", tooltip:'Exit', width:30, click:function () { + }}, + ] + }); +UI('button.network.stop').disable(); +toolbar.show(); + +/*********** Network Tree **********/ +navTree=webix.ui({ + id:'navTree', + left:50, top:50, + view:'tree', + type:'lineTree', + select:true, + data: [ + { id:"root", open:true, value:"Not connected!"} + ] +}) +navTree.show(); +navTree.attachEvent("onAfterSelect", function(id){ + webix.message({text:id,type:'Info'}); + if (id!='root') for (var i in configShellWin) { + if (configShellWin[i] && configShellWin[i].isVisible()) { + UI('configRemoteHostIp'+i).setValue(id); + } + } +}); + + +/************ EDITOR *****************/ + +function createEditor(num,opts) { + var options = { + file:'untitled', + shell:opts.shell, + ip:shell[opts.shell].options.ip, + port:shell[opts.shell].options.port + } + function Label() { + return options.file+' / '+options.ip+':'+options.port+" / Shell "+options.shell+" / Editor "+num + } + editorWin[num]=webix.ui({ + id:'SourceTextWin'+num, + view:"window", + height:350, + width:600, + left:250, top:50, + move:true, + resize: true, + toFront:true, + head:{ + view:"toolbar", + cols:[ + { view:"button", type:"icon", icon:"folder-open", tooltip:'Open File', width:30, click:function () { + loadScript(undefined,'.lua',function (text,file) { + if (text) { + UI('SourceText'+num).setValue(text) + options.file=file + UI('SourceTextWinLabel'+num).setValue(Label()) + } + }); + }}, + { view:"button", type:"icon", icon:"save", tooltip:'Save File', width:30, click:function () { + var code = UI('SourceText'+num).getValue(); + saveFile(code,options.file,'text/plain'); + }}, + { view:"button", type:"icon", icon:"file", tooltip:'New File', width:30, click:function () { + }}, + { view:"button", type:"icon", icon:"navicon", tooltip:'Config', width:30, click:function () { + configEditorWin[num].show(); + }}, + { view:"button", type:"icon", icon:"play", tooltip:'Execute Script', width:30, click:function () { + var code = UI('SourceText'+num).getValue(); + shell[options.shell].ask('thread',code,function (res) { + createMonitor(monitorNum,{ + ip:options.ip, + port:Number(shell[options.shell].options.port)+Number(res), + thread:Number(res), + shell:options.shell, + tmoFixed:200, + }); + monitor[monitorNum].print("Ready."); + monitorNum++; + webix.message({ + text:"Thread created: #"+res, + type:"debug", + expire: 10000, + }); + }); + }}, + { view:"label", label:Label(), id:'SourceTextWinLabel'+num, align:'right'}, + { view:"button", type:"icon", icon:"close", tooltip:'Close Edutor', width:30, click:function () { + var self=editorWin[num]; + editorWin[num]=undefined; + self.close(); + }}, + ] + }, + body:{ + id : 'SourceText'+num, + view: "codemirror-editor", + attributes : { spellcheck:false}, + } +/* + body:{ + id : 'SourceText'+num, + view : 'textarea', + attributes : { spellcheck:false}, + value:'function f(x)\n print(x)\nend\nf(1)' + } +*/ + }); + UI('SourceText'+num).setValue(luaTemplate); + + editorWin[num].show(); + configEditorWin[num]=webix.ui({ + id:'configEditorWin'+num, + view:"window", + width:300, + height:450, + left:90, top:90, + move:true, + resize: true, + toFront:true, + head: { + view:"toolbar", + cols:[ + { view:"label", label:"Configuration", align:'right'}, + { view:"button", type:"icon", icon:"check-square", align:'center', width:30, click: function () { + options.shell=UI('configEditorShellNum'+num).getValue(); + options.ip=shell[options.shell].options.ip; + options.port=shell[options.shell].options.port; + UI('SourceTextWinLabel'+num).setValue(options.ip+':'+options.port+" / Shell "+options.shell+" / Editor "+num); + configEditorWin[num].hide(); + }} + ] + }, + body: { + rows:[ + { view:"form", scroll:true, width:300, height:400, elements: [ + + { view:"label", label:"Attached Shell", height:'18px', align:'left'}, + { view:"text", id:"configEditorShellNum"+num, label:"Shell #", value:options.shell}, + + ]} + ] + } + }); +} + + + +/* + var tree = UI('ASTree'), + Syntax, + root={ id:"root", open:true, value:"Processes" }; + sourcetext = UI('SourceText').getValue(); + tree.remove('root'); + treemap=[]; + tree.add(root); + log('Compiling:\n'+sourcetext+''); + try { + var tree = {}; + log('Ok.'); + log('Printing tree ..'); + maketree(tree,'root',0); + log('Done.'); + } catch (e) { + log('Failed:\n'+e+''); + } + tree.render(); + +*/ + +// Shell windows + + +function createShell(num,params) { + shell[num] = winShell({ip:params.ip,port:params.port||5001}); + var options = shell[num].options; + + shellWin[num]=webix.ui({ + id:'ShellWin'+num, + view:"window", + height:350, + width:600, + left:250, top:250, + move:true, + resize: true, + toFront:true, + head: { + view:"toolbar", + cols:[ + { view:"button", type:"icon", icon:"eraser", tooltip:'Clear', width:30, click:function () { + shell[num].commands.clear() + }}, + { view:"button", type:"icon", icon:"repeat", tooltip:'Update', width:30, click:function () { + }}, + { view:"button", type:"icon", icon:"navicon", tooltip:'Config', width:30, click:function () { + configShellWin[num].show(); + }}, + { view:"button", type:"icon", icon:"edit", tooltip:'New Editor', width:30, click:function () { + createEditor(editorNum,{shell:num}); + editorNum++; + }}, + { view:"button", type:"icon", icon:"link", tooltip:'Connect', width:30, click:function () { + }}, + { view:"button", type:"icon", icon:"unlink", tooltip:'Disconnect', width:30, click:function () { + }}, + { view:"button", type:"icon", icon:"flag", tooltip:'Scroll Auto', id:'ShellWinLogFilmBut'+num, width:30, click:function () { + UI('ShellWinLog'+num).scrollAuto=!UI('ShellWinLog'+num).scrollAuto; + if (UI('ShellWinLog'+num).scrollAuto) UI('ShellWinLogFilmBut'+num).define('icon','flag-o') + else UI('ShellWinLogFilmBut'+num).define('icon','flag'); + UI('ShellWinLogFilmBut'+num).refresh(); + }}, + { view:"label", label:"Shell "+num, id:'ShellWinLabel'+num, align:'right'} + ] + }, + body:{ + // view:'textarea', value:'Ready.', id:'DOStext' + id : 'ShellWinLog'+num, + view : 'scrollview', + scroll : 'y', + body : { + id:'ShellWinLogText'+num, + rows : [ + {template:('
'), height:"auto", borderless:true}, + {template:('
'), height:"auto", borderless:true}, + ] + } + } + }); + shellWin[num].show(); + configShellWin[num]=webix.ui({ + id:'configShellWin'+num, + view:"window", + width:300, + height:450, + left:90, top:90, + move:true, + resize: true, + toFront:true, + head: { + view:"toolbar", + id:"myToolbarconfigWin"+num, + cols:[ + { view:"label", label:"Configuration", align:'right'}, + { view:"button", type:"icon", icon:"check-square", align:'center', width:30, click: function () { + options.ip=UI('configRemoteHostIp'+num).getValue(); + options.port=UI('configRemoteHostIpPort'+num).getValue(); + UI('ShellWinLabel'+num).setValue(options.ip+':'+options.port+" / Shell "+num); + shell[num].setup(); + configShellWin[num].hide(); + }} + ] + }, + body: { + rows:[ + { view:"form", scroll:true, width:300, height:400, elements: [ + + { view:"label", label:"Remote Host", height:'18px', align:'left'}, + { view:"text", id:"configRemoteHostIp"+num, label:"IP", value:options.ip}, + { view:"text", id:"configRemoteHostIpPort"+num, label:"IPPORT", value:options.port}, + + + ]} + ] + } + }); + shell[num].init("ShellWinInput"+num,"ShellWinOutput"+num,UI('ShellWinLog'+num)); + UI('ShellWinLog'+num).scrollAuto=false; + + shellWin[num].attachEvent("onViewMove", function(pos, e){ + }); + shellWin[num].attachEvent("onViewMoveEnd", function(pos, e){ + }); + UI('ShellWinLabel'+num).setValue(options.ip+':'+options.port+" / Shell "+num); + +} +createShell(shellNum,{ip:'localhost',port:portRoot}); +shellNum++; + +/****************** MONITOR *******************/ + +function createMonitor(num,opts) { + var tables={},reporter; + opts.cmd = function (cmd) { + // Command interpreter for commands sent by luaos + // @cmd arg1 arg2 ... + var obj,table, + prefix=cmd.substr(0,cmd.indexOf(' ')), + args=cmd.substr(cmd.indexOf(' ')+1); + console.log(prefix); + console.log(args); + switch (prefix) { + case '@monitor:new': + obj=JSON.parse(args); + options.header=obj.header; + options.id=obj.id; + if (!reporterWin[num]) createReporter(num,options); + break; + case '@monitor:data': + if (!reporterWin[num]) return; + obj=JSON.parse(args); + table=UI(reporterWin[num]._datatable.id); + table.add(obj.row) + break; + } + } + monitor[num] = winShell(opts); + var options = monitor[num].options; + options.thread=opts.thread; + options.shell=opts.shell; + options.closed = false; + + monitorWin[num]=webix.ui({ + id:'MonitorWin'+num, + view:"window", + height:350, + width:600, + left:350, top:250, + move:true, + resize: true, + toFront:true, + head: { + view:"toolbar", + cols:[ + { view:"button", type:"icon", icon:"eraser", tooltip:'Clear', width:30, click:function () { + monitor[num].commands.clear() + }}, + { view:"button", type:"icon", icon:"flag", tooltip:'Scroll Auto', id:'MonitorWinLogFilmBut'+num, width:30, click:function () { + UI('MonitorWinLog'+num).scrollAuto=!UI('MonitorWinLog'+num).scrollAuto; + if (UI('MonitorWinLog'+num).scrollAuto) UI('MonitorWinLogFilmBut'+num).define('icon','flag-o') + else UI('MonitorWinLogFilmBut'+num).define('icon','flag'); + UI('MonitorWinLogFilmBut'+num).refresh(); + }}, + { view:"button", type:"icon", icon:"table", tooltip:'Data Monitor', width:30, click:function () { + if (!reporterWin[num]) return; + // createReporter(num,options); + reporterWin[num].show(); + }}, + { view:"label", label:"Thread "+options.thread+ " / "+options.ip+':'+options.port+" / Shell "+options.shell+" / Monitor "+num, id:'MonitorWinLabel'+num, align:'right'}, + { view:"button", type:"icon", icon:"close", tooltip:'Kill', width:30, click:function () { + var self=monitorWin[num]; + monitor[num].ask('kill','',function (res) { + monitor[num].ask('end','',function (res) { + }); + options.closed=true; + monitorWin[num]=undefined; + monitor[num].stop(); + webix.message({text:'Thread closed: #'+res}); + self.close(); + }); + setTimeout(function () { + if (!options.closed) { + options.closed=true; + monitorWin[num]=undefined; + monitor[num].stop(); + webix.message({text:'Thread closed: #'+options.thread}); + self.close(); + } + },1000); + }}, + ] + }, + body:{ + id : 'MonitorWinLog'+num, + view : 'scrollview', + scroll : 'y', + body : { + id:'MonitorWinLogText'+num, + rows : [ + {template:('
'), height:"auto", borderless:true}, + ] + } + } + }); + UI('MonitorWinLog'+num).scrollAuto=false; + monitorWin[num].show(); + monitor[num].init(undefined,"MonitorWinOutput"+num,UI('MonitorWinLog'+num)); +} + +/****************** REPORTER *******************/ + +function createReporter(num,options) { + function makeReport(tbl) { + console.log(tbl) + var header = tbl.shift(), + columns = [], + hash =[], + data =[]; + header.forEach(function (col,i) { + console.log(i,col) + columns.push({id:col,header:col,editor:'text'}); hash[i]=col; + }); + tbl.forEach (function (row,i) { + var obj={id:i}; + row.forEach(function (col,j) { obj[hash[j]]=col; }); + data.push(obj); + }); + var datatbl = { + view:"datatable", + id:'DataTable'+options.id+num, + columns:columns, + select:"cell", + multiselect:true, + blockselect:true, + clipboard:"selection", + data: data, + on:{ + onBeforeBlockSelect:function(start, end, fin){ + if (start.column === "rank") + end.column = "votes"; + + if (fin && start.column == "rank"){ + var mode = this.isSelected(start) ? -1 : 1; + this.selectRange( + start.row, start.column, end.row, end.column, + mode + ); + return false; + } + } + }, + }; + return datatbl; + } + var datatable = makeReport([options.header.map(function (col) { return Object.keys(col)[0]})]); + reporterWin[num]=webix.ui({ + id:'ReporterWin'+num, + view:"window", + height:350, + width:600, + left:350, top:250, + move:true, + resize: true, + toFront:true, + head: { + view:"toolbar", + cols:[ + { view:"button", type:"icon", icon:"eraser", tooltip:'Clear', width:30, click:function () { + }}, + { view:"button", type:"icon", icon:"flag", tooltip:'Scroll Auto', id:'ReporterWinLogFilmBut'+num, width:30, click:function () { + UI('ReporterWinLog'+num).scrollAuto=!UI('ReporterWinLog'+num).scrollAuto; + if (UI('ReporterWinLog'+num).scrollAuto) UI('ReporterWinLogFilmBut'+num).define('icon','flag-o') + else UI('ReporterWinLogFilmBut'+num).define('icon','flag'); + UI('ReporterWinLogFilmBut'+num).refresh(); + }}, + { view:"button", type:"icon", icon:"table", tooltip:'Data Monitor', width:30, click:function () { + }}, + { view:"label", label:"Thread "+options.thread+ " / "+options.ip+':'+options.port+" / Shell "+options.shell+" / Report "+num, id:'ReporterWinLabel'+num, align:'right'}, + { view:"button", type:"icon", icon:"close", tooltip:'Kill', width:30, click:function () { + var self=reporterWin[num]; + self.hide(); + }}, + ] + }, + body:datatable + + }); + reporterWin[num]._datatable=datatable; + return reporterWin[num] +} +