diff --git a/js/ui/webui/webapp.js b/js/ui/webui/webapp.js new file mode 100644 index 0000000..d461a81 --- /dev/null +++ b/js/ui/webui/webapp.js @@ -0,0 +1,1908 @@ +/** + ** ============================== + ** O O O OOOO + ** O O O O O O + ** O O O O O O + ** OOOO OOOO O OOO OOOO + ** O O O O O O O + ** O O O O O O O + ** OOOO OOOO O O OOOO + ** ============================== + ** Dr. Stefan Bosse http://www.bsslab.de + ** + ** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED + ** BY THE AUTHOR(S). + ** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED, + ** MODIFIED, OR OTHERWISE USED IN A CONTEXT + ** OUTSIDE OF THE SOFTWARE SYSTEM. + ** + ** $AUTHORS: Stefan Bosse + ** $INITIAL: (C) 2006-2022 bLAB + ** $CREATED: 1-1-19 by sbosse. + ** $VERSION: 1.8.1 + ** $RCS: $Id:$ + ** $INFO: + ** + ** Main Application JAM LAB WEBUI + ** + ** $ENDOFINFO + */ + +var version = "1.8.3" +console.log(version) +var UI= $$; +var logtext= ''; +var treemap=[]; +var sourcetext=''; +var printloc=false; + +var shell=[]; +var shellWin=[]; +var infoShellWin=[]; +var configShellWin=[]; +var shellNum=1; +var monitorWin=[]; +var monitorNum=1; +var monitor=[] +var reporterWin=[]; +var reporterNum=1; +var editor=[]; +var editorWin=[]; +var editorNum=1; +var configEditorWin=[]; +var netNodes=[] +var state=0; +var cookieConfig = 'jam.webui.config'; +var help={md:{}}; +var Marked; + +if (typeof jamConfig == 'undefined') info ('No custom configuration file config.js loaded.') +else info ('Custom configuration file config.js loaded.'); + +/** Main Options **/ + +// Optional App configuration loaded from file (config.js) +if (typeof jamConfig == 'undefined') jamConfig = { + name : '?', + link1:{ip:"ag-0.de", ipport:10001, proto:'http', secure:'', enable:true, shells:[1]}, + link2:{ip:"localhost",ipport:10001, proto:'http', secure:'', enable:false, shells:[1]}, + link3:{ip:"",ipport:0, proto:'http', secure:'', enable:false, shells:[1]}, + link4:{ip:"",ipport:0, proto:'http', secure:'', enable:false, shells:[1]}, + log:{agent:true,parent:false,time:false,Time:true,class:false}, // Message flags: agent parent time class + fontsize: 14, + group:'jamweb', + user:'user', + workdir : '/', + webclipTime:300, + webclipUrl:'edu-9.de:9176', +} +jamConfig.version=version; + +// load config from cookie +function loadConfig() { + var data = typeof localStorage=='undefined'?Utils.getCookie(cookieConfig):localStorage.getItem(cookieConfig); + if (!data) data=Utils.getCookie(cookieConfig); + if (data) { + console.log('Loaded jamConfig from cookie '+cookieConfig); + var obj = JSON.parse(data); + delete obj.version; + Object.assign(jamConfig,obj); + /* + jamConfig.link1=obj.link1; + jamConfig.link2=obj.link2; + jamConfig.link3=obj.link3; + jamConfig.link4=obj.link4; + jamConfig.log=obj.log; + jamConfig.version=version; + */ + } +} +function saveConfig() { + var obj = {} + /* + link1:jamConfig.link1, + link2:jamConfig.link2, + link3:jamConfig.link3, + link4:jamConfig.link4, + log:jamConfig.log, + } + */ + Object.assign(obj,jamConfig); + console.log('Saving jamConfig in cookie '+cookieConfig); + var data = JSON.stringify(obj); + setCookie(cookieConfig,obj,2) + if (typeof localStorage!='undefined') localStorage.setItem(cookieConfig, data); +} +function updateConfig(shell) { + shell.config({log:jamConfig.log}); +} + +// get config updates from URL params +function urlConfig() { + var params = parseUrl(document.URL); + console.log(document.URL,params); + if (params && (params.link||params.link1)) { + var tokens=(params.link||params.link1).split(':'); + if (tokens.length==2) { + jamConfig.link1.ip=tokens[0]; + jamConfig.link1.ipport=Number(tokens[1]); + jamConfig.link1.enable=true; + } + } + if (params && params.link2) { + var tokens=params.link2.split(':'); + if (tokens.length==2) { + jamConfig.link2.ip=tokens[0]; + jamConfig.link2.ipport=Number(tokens[1]); + jamConfig.link2.enable=true; + } + } + if (params && (params.secure||params.secure1)) { + jamConfig.link1.secure=(params.secure||params.secure1); + } + if (params && params.secure2) { + jamConfig.link2.secure=params.secure2; + } + if (params.proto) { + jamConfig.link1.proto=params.proto; + jamConfig.link2.proto=params.proto; + jamConfig.link3.proto=params.proto; + jamConfig.link4.proto=params.proto; + } +} +loadConfig(); +urlConfig(); + +var classTemplate = "function ac (options) {\n this.x=null;\n this.act = {\n a1: () => {log('Start')},\n a2: () => {log('End');kill()} }\n this.trans = {\n a1:a2\n }\n this.on = {\n signal : function () {}\n }\n this.next = a1\n} " + + +// Info and error message toasts ... +function info (text) { + webix.message({text:text,type:'Info'}) +} + +function error (text) { + webix.message({text:text,type:'error'}) +} + +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 @'+SHELL.Io.Time() } + tree.add(root); +} + +function maketree() { + var i=0,tree = this.UI('navTree'); + for(var p in netNodes) { + tree.add({id:p,value:netNodes[p]+' '+p},i,'root'); + i++; + } +} + + +// Update node information view in shell window +async function updateInfo(shellcmd, shellnum) { + var node = await shellcmd.info('node').id, + stats = await shellcmd.stats('node'); + function makeTree(element,root) { + var i=0; + var rows=[]; + if (element == undefined) + return { id:root+'-'+i, value:'null' } + else if (typeof element == 'number' || + typeof element == 'string' || + typeof element == 'boolean') + return { id:root+'-'+i, value:String(element) } + else if (typeof element == 'function') + return { id:root+'-'+i, value:'function' } + else if (typeof element == 'object') { + for(var p in element) { + if (p=='self') continue; + var tree=makeTree(element[p],root+'-'+i); + if (!tree) continue; + if (tree && tree.length) + rows.push({ id:root+'-'+i, open:false, value:p, data : tree}); + else { + tree.value=p+'='+tree.value; + rows.push(tree); + } + i++; + } + return rows + } + } + var rows = [ + { view:'label', label:'Node '+node+'', height:18} + ] + var agents = await shellcmd.stats('agent'); + var i = 1, height=50; + var treeAgents = []; + function click(o) { console.log(o) } + for(var p in agents) { + var info='agent.'+p+' pid='+agents[p].pid+' class='+agents[p].class+' state='+agents[p].state; + var agent = await shellcmd.info('agent-data',p); + treeAgents.push({ id:info, open:false, value:p, data : [ + { id:info+'-'+0, value:'class='+agents[p].class }, + { id:info+'-'+1, value:'pid='+agents[p].pid }, + { id:info+'-'+2, value:'gid='+agents[p].gid }, + { id:info+'-'+3, value:'parent='+agents[p].parent }, + { id:info+'-'+4, value:'state='+agents[p].state }, + { id:info+'-'+5, value:'next='+agents[p].next }, + { id:info+'-'+6, value:'time='+agents[p].resources.consumed }, + { id:info+'-'+7, open:false, value:'agent', data : makeTree(agent,info+'-'+7)}, + ]}); + i++; + } + height=50+i*25; + var tree1 = { + view:"tree", + id:'treeAgentsNode'+shellnum, + select:true, + tooltip:true, + height:'auto', + data: [ + {id:"root", value:"Agents", open:true, data:treeAgents} + ], + on:{ + // the default click behavior that is true for any datatable cell + "onItemClick":function(id, e, trg){ + if (id.indexOf('agent.')==0) { + + } + } + }, + } + rows.push(tree1) + for (var p in stats) rows.push({ view:"text", label:p, readonly:true, type:'text', value:stats[p].toString()}) + return rows; +} + +// Update window pull-down menus +function updateMenu() { + var i,items=[]; + UI('menuShells').clearAll(); + for(i in shellWin) items.push((i)+' : '+shellWin[i]._options.name); + UI('menuShells').add({value:"Shells", submenu:items}); + items=[]; + for(i in editorWin) items.push((i)+' : '+editorWin[i]._options.file); + UI('menuEditors').clearAll(); + UI('menuEditors').add({value:"Editors", submenu:items}); + items=[]; + for(i in monitorWin) items.push((i)+' : '+monitorWin[i]._options.name); + UI('menuMonitors').clearAll(); + UI('menuMonitors').add({value:"Monitors", submenu:items}); +} + +// Update conenction tree +async function updateConn(cmd,name) { + var links = await cmd.connected(DIR.IP('%')), + ips = await cmd.connected(DIR.IP('*')); + // netNodes={}; + for(var i in netNodes) { + if (i.indexOf(name)==0) delete netNodes[i]; + } + for(var i in links) netNodes[name+':'+links[i]]=ips[i]; + deltree(); + maketree(); +} + +function removeConn(name) { + for(var i in netNodes) { + if (i.indexOf(name)==0) delete netNodes[i]; + } + deltree(); + maketree(); +} +function autoLayout() { + var x=0, + y=50; + for(i in shellWin) { + var shell=shellWin[i]; + if (!shell) continue; + var size=shell.collapse(true); + x=$('body').width()-size.width; + shell.window.setPosition(x,y); + y+=(size.height+1); + } + for(i in editorWin) { + var shell=editorWin[i]; + if (!shell) continue; + var size=shell.collapse(true); + x=$('body').width()-size.width; + shell.window.setPosition(x,y); + y+=(size.height+1); + } + for(i in monitorWin) { + var shell=monitorWin[i]; + if (!shell) continue; + var size=shell.collapse(true); + x=$('body').width()-size.width; + shell.window.setPosition(x,y); + y+=(size.height+1); + } +} + +/************ NETWORK ***************/ +Network = { + state:0, + start: async function () { + if (Network.state<2) { + var proto=jamConfig.link1.proto||'http'; + for(var i in shell) { + if (shell[i] && shell[i].options.shell && !shell[i].port) + shell[i].port=await shell[i].options.cmd.port(DIR.IP(),{proto:proto,verbose:shell[i].options.log.Network}); + } + Network.state=1; + } + for(var i in shell) { + if (!shell[i] || !shell[i].options.shell) continue; + if (jamConfig.link1.enable && jamConfig.link1.ipport) + shell[i].options.cmd.connect(DIR.IP(jamConfig.link1.proto+'://'+jamConfig.link1.ip+':'+ + jamConfig.link1.ipport+ + (jamConfig.link1.secure!=''?'?secure='+jamConfig.link1.secure:''))); + if (jamConfig.link2.enable && jamConfig.link2.ipport) + shell[i].options.cmd.connect(DIR.IP(jamConfig.link2.proto+'://'+jamConfig.link2.ip+':'+ + jamConfig.link2.ipport+ + (jamConfig.link2.secure!=''?'?secure='+jamConfig.link2.secure:''))); + if (jamConfig.link3.enable && jamConfig.link3.ipport) + shell[i].options.cmd.connect(DIR.IP(jamConfig.link3.proto+'://'+jamConfig.link3.ip+':'+ + jamConfig.link3.ipport+ + (jamConfig.link3.secure!=''?'?secure='+jamConfig.link3.secure:''))); + if (jamConfig.link4.enable && jamConfig.link4.ipport) + shell[i].options.cmd.connect(DIR.IP(jamConfig.link4.proto+'://'+jamConfig.link4.ip+':'+ + jamConfig.link4.ipport+ + (jamConfig.link4.secure!=''?'?secure='+jamConfig.link4.secure:''))); + } + Network.state=2; + }, + stop: function () { + for(var i in shell) { + if (!shell[i] || !shell[i].options.shell) continue; + // disconnect w/o proto!! + if (jamConfig.link1.enable) + shell[i].options.cmd.disconnect(DIR.IP(jamConfig.link1.ip+':'+ + jamConfig.link1.ipport)); + if (jamConfig.link2.enable) + shell[i].options.cmd.disconnect(DIR.IP(jamConfig.link2.ip+':'+ + jamConfig.link2.ipport)); + if (jamConfig.link3.enable) + shell[i].options.cmd.disconnect(DIR.IP(jamConfig.link3.ip+':'+ + jamConfig.link3.ipport)); + if (jamConfig.link4.enable) + shell[i].options.cmd.disconnect(DIR.IP(jamConfig.link4.ip+':'+ + jamConfig.link4.ipport)); + } + Network.state=1; + }, + update : function (options,num) { + if (shell[num].port) shell[num].port.amp.config(options); + } +} +jam = { + state:0, + start: function () { + for(var i in shell) { + if (shell[i] && shell[i].options.shell) shell[i].options.shell.env.start(); + } + jam.state=1; + }, + stop: function () { + for(var i in shell) { + if (shell[i] && shell[i].options.shell) shell[i].options.shell.env.stop(); + } + jam.state=0; + } + +} + + + + + +/************ 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.jam.start', tooltip:'Start', width:30, click:function () { + if (jam.state==0) { + UI('button.jam.start').disable(); + UI('button.jam.stop').enable(); + jam.start(); + } + }}, + { view:"button", type:"icon", icon:"stop", id:'button.jam.stop', tooltip:'Stop', width:30, click:function () { + UI('button.jam.start').enable(); + UI('button.jam.stop').disable(); + jam.stop(); + }}, + { view:"button", type:"icon", icon:"chain", id:'button.net.start', tooltip:'Connect', width:30, click:function () { + if (Network.state<2) { + UI('button.net.start').disable(); + UI('button.net.stop').enable(); + Network.start(); + } + }}, + { view:"button", type:"icon", icon:"chain-broken", id:'button.net.stop', tooltip:'Disconnect', width:30, click:function () { + UI('button.net.start').enable(); + UI('button.net.stop').disable(); + Network.stop(); + }}, + { view:"button", type:"icon", icon:"navicon", tooltip:'Configuration', width:30, click:function () { + mainConfigWin.show(); + }}, + { view:"button", type:"icon", icon:"plus", tooltip:'New Shell Worker', width:30, click:function () { + createShell(shellNum,{worker:true}); + shellNum++; + }}, + { view:"button", type:"icon", icon:"sort-amount-asc", tooltip:'Bigger Fonts', width:30, click:function () { + jamConfig.fontsize++; + changeCSS('.input','font-size',jamConfig.fontsize+'px') + changeCSS('.normalInput','font-size',jamConfig.fontsize+'px') + changeCSS('.normalOutput','font-size',jamConfig.fontsize+'px') + changeCSS('.error','font-size',jamConfig.fontsize+'px') + changeCSS('.webix_view','font-size',jamConfig.fontsize+'px') + }}, + { view:"button", type:"icon", icon:"sort-amount-desc", tooltip:'Smaller Fonts', width:30, click:function () { + jamConfig.fontsize--; + changeCSS('.input','font-size',jamConfig.fontsize+'px') + changeCSS('.normalInput','font-size',jamConfig.fontsize+'px') + changeCSS('.normalOutput','font-size',jamConfig.fontsize+'px') + changeCSS('.error','font-size',jamConfig.fontsize+'px') + changeCSS('.webix_view','font-size',jamConfig.fontsize+'px') + }}, + { view:"button", type:"icon", icon:"user-plus", tooltip:'Chat Dialog', width:30, click:function () { + if (chatWin.isVisible()) + chatWin.hide(); + else { + chatWin.show(); + chatInit(); + } + }}, + { view:"button", type:"icon", icon:"hand-o-right", tooltip:'Visual Pointer', width:30, click:function () { + var el = document.getElementsByTagName("body")[0]; + self._cursorToggle=!self._cursorToggle; + if (self._cursorToggle) + el.style.cursor = "url(redpointer.png), auto"; + else + el.style.cursor = "auto"; + ['webix_view'].forEach(function (id) { + var ell = document.getElementsByClassName(id); + if (!ell) return; + ell.forEach(function (el) { + if (self._cursorToggle) + el.style.cursor = "url(redpointer.png), auto"; + else + el.style.cursor = "auto"; + }); + }) + }}, + { view:"button", type:"icon", icon:"question", tooltip:'Help', width:30, click:function () { + helpWin.show(); + }}, + { view:"button", type:"icon", icon:"th", tooltip:'Auto Layout', width:30, click:function () { + autoLayout() + }}, + { view:"button", type:"icon", icon:"terminal", tooltip:'New Developer Console', width:30, click:function () { + var shell = Shell.default({ + label:'Developer Console (JS)', + hide:false, + editor:true, + }); + shell.run = function (code) { + var ___error; + with ({ + error:shell.env.error, + keys:Object.keys.bind(Object), + load:Utils.loadScript, + print:shell.env.print, + time:Date.now, + }) { try { var result = eval(code) } catch (e) { ___error=result=e }}; + if (!code.match(/;[ ]*$/)) + shell.env.print(result); + else if (___error) shell.env.error(___error); + } + }}, + { + view:"menu", + id:"menuShells", + autowidth:true, + width:100, + data:[ //menu data + { value:"Shells", submenu:["1"] }, + ], + type:{ + subsign:true, + }, + on:{ + onMenuItemClick:function(id){ + var name = this.getMenuItem(id).value.split(':'); + var win=shellWin[Number(name[0])].window; + if (win) win.isVisible()?win.hide():win.show(); + } + } + }, + { + view:"menu", + id:"menuEditors", + autowidth:true, + width:100, + data:[ //menu data + { value:"Editors", submenu:[] }, + ], + type:{ + subsign:true, + }, + on:{ + onMenuItemClick:function(id){ + var name = this.getMenuItem(id).value.split(':'); + var win=editorWin[Number(name[0])].window; + if (win) win.isVisible()?win.hide():win.show(); + } + } + }, + { + view:"menu", + id:"menuMonitors", + autowidth:true, + width:100, + data:[ //menu data + { value:"Monitors", submenu:[] }, + ], + type:{ + subsign:true, + }, + on:{ + onMenuItemClick:function(id){ + var name = this.getMenuItem(id).value.split(':'); + var win=monitorWin[Number(name[0])].window; + if (win) win.isVisible()?win.hide():win.show(); + } + } + }, + { view:"label", id:'myTopLabel', label:'JAM Laboratory WEB API (C) Dr. Stefan Bosse Ver. '+jamConfig.version, align:'right'}, + { view:"button", type:"icon", icon:"close", tooltip:'Exit', width:30, click:function () { + }}, + ] + }); +UI('button.jam.stop').disable(); +UI('button.net.stop').disable(); +toolbar.show(); + + +/************ 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 () { + jamConfig.link1.ip=UI('mainConfigWinLink1IPAddress').getValue(); + jamConfig.link1.ipport=UI('mainConfigWinLink1IPPort').getValue(); + jamConfig.link1.proto=UI('mainConfigWinLink1IPProto').getValue(); + jamConfig.link1.secure=UI('mainConfigWinLink1SecPort').getValue(); + jamConfig.link2.ip=UI('mainConfigWinLink2IPAddress').getValue(); + jamConfig.link2.ipport=UI('mainConfigWinLink2IPPort').getValue(); + jamConfig.link2.proto=UI('mainConfigWinLink2IPProto').getValue(); + jamConfig.link2.secure=UI('mainConfigWinLink2SecPort').getValue(); + jamConfig.link3.ip=UI('mainConfigWinLink3IPAddress').getValue(); + jamConfig.link3.ipport=UI('mainConfigWinLink3IPPort').getValue(); + jamConfig.link3.proto=UI('mainConfigWinLink3IPProto').getValue(); + jamConfig.link3.secure=UI('mainConfigWinLink3SecPort').getValue(); + jamConfig.link4.ip=UI('mainConfigWinLink4IPAddress').getValue(); + jamConfig.link4.ipport=UI('mainConfigWinLink4IPPort').getValue(); + jamConfig.link4.proto=UI('mainConfigWinLink4IPProto').getValue(); + jamConfig.link4.secure=UI('mainConfigWinLink4SecPort').getValue(); + jamConfig.webclipUrl=UI('mainConfigWinClipUrl').getValue(); + jamConfig.user=UI('mainConfigWinUser').getValue(); + jamConfig.workdir=UI('mainConfigWinWorkdir').getValue(); + saveConfig(); + mainConfigWin.hide(); + }} + ] + }, + body: { + rows:[ + { view:"form", scroll:true, width:300, height:400, elements: [ + + { view:"label", label:"JAM Network Link 1", height:'18px', align:'left'}, + { view:"checkbox", customCheckbox:false, labelRight:"Enable", height:'18px', value:jamConfig.link1.enable?1:0, + click: function (o) {jamConfig.link1.enable=Number(self.UI(o).getValue())?true:false}}, + { view:"text", id:"mainConfigWinLink1IPAddress", label:"IP Address", value:jamConfig.link1.ip}, + { view:"text", id:"mainConfigWinLink1IPPort", label:"IP Port", value:jamConfig.link1.ipport}, + { view:"text", id:"mainConfigWinLink1IPProto", label:"IP Proto", value:jamConfig.link1.proto}, + { view:"text", id:"mainConfigWinLink1SecPort", label:"Secure Port (Opt.)", value:jamConfig.link1.secure}, + + { view:"label", label:"JAM Network Link 2", height:'18px', align:'left'}, + { view:"checkbox", customCheckbox:false, labelRight:"Enable", height:'18px', value:jamConfig.link2.enable?1:0, + click: function (o) {jamConfig.link2.enable=Number(self.UI(o).getValue())?true:false}}, + { view:"text", id:"mainConfigWinLink2IPAddress", label:"IP Address", value:jamConfig.link2.ip}, + { view:"text", id:"mainConfigWinLink2IPPort", label:"IP Port", value:jamConfig.link2.ipport}, + { view:"text", id:"mainConfigWinLink2IPProto", label:"IP Proto", value:jamConfig.link2.proto}, + { view:"text", id:"mainConfigWinLink2SecPort", label:"Secure Port (Opt.)", value:jamConfig.link2.secure}, + + { view:"label", label:"JAM Network Link 3", height:'18px', align:'left'}, + { view:"checkbox", customCheckbox:false, labelRight:"Enable", height:'18px', value:jamConfig.link3.enable?1:0, + click: function (o) {jamConfig.link3.enable=Number(self.UI(o).getValue())?true:false}}, + { view:"text", id:"mainConfigWinLink3IPAddress", label:"IP Address", value:jamConfig.link3.ip}, + { view:"text", id:"mainConfigWinLink3IPPort", label:"IP Port", value:jamConfig.link3.ipport}, + { view:"text", id:"mainConfigWinLink3IPProto", label:"IP Proto", value:jamConfig.link3.proto}, + { view:"text", id:"mainConfigWinLink3SecPort", label:"Secure Port (Opt.)", value:jamConfig.link3.secure}, + + { view:"label", label:"JAM Network Link 4", height:'18px', align:'left'}, + { view:"checkbox", customCheckbox:false, labelRight:"Enable", height:'18px', value:jamConfig.link4.enable?1:0, + click: function (o) {jamConfig.link4.enable=Number(self.UI(o).getValue())?true:false}}, + { view:"text", id:"mainConfigWinLink4IPAddress", label:"IP Address", value:jamConfig.link4.ip}, + { view:"text", id:"mainConfigWinLink4IPPort", label:"IP Port", value:jamConfig.link4.ipport}, + { view:"text", id:"mainConfigWinLink4IPProto", label:"IP Proto", value:jamConfig.link4.proto}, + { view:"text", id:"mainConfigWinLink4SecPort", label:"Secure Port (Opt.)", value:jamConfig.link4.secure}, + + { view:"label", label:"WebClipboard", height:'18px', align:'left'}, + { view:"text", id:"mainConfigWinClipUrl", label:"URL", value:jamConfig.webclipUrl}, + { view:"text", id:"mainConfigWinUser", label:"User", value:jamConfig.user}, + { view:"text", id:"mainConfigWinWorkdir", label:"WorkDir", value:jamConfig.workdir}, + ]} + ] + } + }); + + +helpWin=webix.ui({ + id:'helpWin', + view:"window", + width:500, + height:450, + left:90, top:90, + move:true, + resize: true, + toFront:true, + css:'gray_toolbar', + head: { + view:"toolbar", + cols:[ + { view:"button", value:'JAMSH', align:'center', click: function () { + if (helpWin._view) UI('helpWinScrollView').removeView(helpWin._view); + var helpRendered=help.md.jamsh?help.md.jamsh:Marked(help.jamsh); + help.md.jamsh=helpRendered; + helpWin._view=UI('helpWinScrollView').addView( + { + rows : [ + {template:'