[TASK] add consoleView
This commit is contained in:
parent
6046e7dd95
commit
2e5873a541
14
ssh/list.go
14
ssh/list.go
|
@ -7,15 +7,15 @@ import (
|
|||
)
|
||||
|
||||
type List struct {
|
||||
cmd string
|
||||
Clients map[string]*ListResult
|
||||
cmd string `json:"cmd"`
|
||||
Clients map[string]*ListResult `json:"clients"`
|
||||
sshManager *Manager
|
||||
}
|
||||
type ListResult struct {
|
||||
ssh *ssh.Client
|
||||
Runned bool
|
||||
WithError bool
|
||||
Result string
|
||||
Running bool `json:"running"`
|
||||
WithError bool `json:"with_error"`
|
||||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
func (m *Manager) CreateList(cmd string) *List {
|
||||
|
@ -25,7 +25,7 @@ func (m *Manager) CreateList(cmd string) *List {
|
|||
Clients: make(map[string]*ListResult),
|
||||
}
|
||||
for host, client := range m.clients {
|
||||
list.Clients[host] = &ListResult{Runned: false, ssh: client}
|
||||
list.Clients[host] = &ListResult{Running: true, ssh: client}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ func (l List) Run() {
|
|||
func (l List) runlistelement(host string, client *ListResult, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
result, err := l.sshManager.run(host, client.ssh, l.cmd)
|
||||
client.Runned = true
|
||||
client.Running = false
|
||||
if err != nil {
|
||||
client.WithError = true
|
||||
return
|
||||
|
|
|
@ -19,18 +19,18 @@ func TestList(t *testing.T) {
|
|||
list := mgmt.CreateList("exit 1")
|
||||
assert.Len(list.Clients, 1)
|
||||
client := list.Clients[addr.IP.String()]
|
||||
assert.False(client.Runned)
|
||||
assert.True(client.Running)
|
||||
list.Run()
|
||||
assert.True(client.Runned)
|
||||
assert.False(client.Running)
|
||||
assert.True(client.WithError)
|
||||
assert.Equal("", client.Result)
|
||||
|
||||
list = mgmt.CreateList("echo 15")
|
||||
assert.Len(list.Clients, 1)
|
||||
client = list.Clients[addr.IP.String()]
|
||||
assert.False(client.Runned)
|
||||
assert.True(client.Running)
|
||||
list.Run()
|
||||
assert.True(client.Runned)
|
||||
assert.False(client.Running)
|
||||
assert.False(client.WithError)
|
||||
assert.Equal("15", client.Result)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
.prompt {
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
background-color: #ccc;
|
||||
width: 100%;
|
||||
}
|
||||
.prompt .btn {
|
||||
width: 10%;
|
||||
}
|
||||
.prompt input {
|
||||
width: 85%;
|
||||
margin-left: 2%;
|
||||
}
|
||||
.console {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
}
|
||||
.console .cmd {
|
||||
min-height: 22px;
|
||||
clear: both;
|
||||
}
|
||||
.console .cmd > .time, .console .cmd .host{
|
||||
display: inline-block;
|
||||
color: #009ee0;
|
||||
|
||||
width: 15%;
|
||||
}
|
||||
.console .cmd > div {
|
||||
clear: both;
|
||||
background-color: #ccc;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.console .cmd .status {
|
||||
width: 15%;
|
||||
height: 20px;
|
||||
}
|
|
@ -9,14 +9,16 @@ body {
|
|||
.status {
|
||||
float: right;
|
||||
background: #009ee0;
|
||||
color: white;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
.status.connecting {
|
||||
.status.connecting,.status.running {
|
||||
background: #ffb400;
|
||||
}
|
||||
.status.offline {
|
||||
.status.offline, .status.failed {
|
||||
background: #dc0067;
|
||||
color: white;
|
||||
}
|
||||
span.online {
|
||||
color: #009ee0;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<link rel="stylesheet" href="/css/leaflet.css">
|
||||
<link rel="stylesheet" href="/css/main.css">
|
||||
<link rel="stylesheet" href="/css/map.css">
|
||||
<link rel="stylesheet" href="/css/console.css">
|
||||
<script src="/js/moment.js"></script>
|
||||
<script src="/js/navigo.js"></script>
|
||||
<script src="/js/leaflet.js"></script>
|
||||
|
@ -17,6 +18,7 @@
|
|||
<script src="/js/domlib.js"></script>
|
||||
<script src="/js/store.js"></script>
|
||||
<script src="/js/notify.js"></script>
|
||||
<script src="/js/gui_console.js"></script>
|
||||
<script src="/js/gui_list.js"></script>
|
||||
<script src="/js/gui_map.js"></script>
|
||||
<script src="/js/gui_node.js"></script>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* exported gui,router */
|
||||
/* globals socket,notify,domlib,guiList,guiMap,guiStats,guiNode */
|
||||
/* globals socket,notify,domlib,guiList,guiMap,guiStats,guiNode,guiConsole */
|
||||
|
||||
const gui = {},
|
||||
router = new Navigo(null, true, '#');
|
||||
|
@ -58,6 +58,9 @@ const gui = {},
|
|||
}
|
||||
|
||||
router.on({
|
||||
'/console': function routerConsole () {
|
||||
setView(guiConsole);
|
||||
},
|
||||
'/list': function routerList () {
|
||||
setView(guiList);
|
||||
},
|
||||
|
@ -75,6 +78,7 @@ const gui = {},
|
|||
'/statistics': function routerStats () {
|
||||
setView(guiStats);
|
||||
}
|
||||
|
||||
});
|
||||
router.on(() => {
|
||||
router.navigate('/list');
|
||||
|
|
|
@ -0,0 +1,256 @@
|
|||
/* exported guiConsole */
|
||||
/* globals domlib,store,socket */
|
||||
const guiConsole = {};
|
||||
|
||||
(function init () {
|
||||
'use strict';
|
||||
|
||||
const view = guiConsole,
|
||||
ownCMDs = ['0'],
|
||||
cmdRow = {};
|
||||
let container = null,
|
||||
el = null,
|
||||
output = null,
|
||||
ownfilter = false;
|
||||
|
||||
function createID () {
|
||||
let digit = new Date().getTime();
|
||||
|
||||
// Use high-precision timer if available
|
||||
/* eslint-disable */
|
||||
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
|
||||
digit += performance.now();
|
||||
}
|
||||
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
|
||||
const result = (digit + Math.random() * 16) % 16 | 0;
|
||||
|
||||
digit = Math.floor(digit / 16);
|
||||
|
||||
return (char === 'x'
|
||||
? result
|
||||
: result & 0x3 | 0x8).toString(16);
|
||||
});
|
||||
/* eslint-enable*/
|
||||
}
|
||||
|
||||
function updateCMD (row, cmd) {
|
||||
if (cmd.cmd === '' && cmd.timestemp === 0) {
|
||||
return;
|
||||
}
|
||||
row.cmd.innerHTML = cmd.cmd;
|
||||
row.timestemp.innerHTML = moment(cmd.timestemp).fromNow(true);
|
||||
|
||||
let running = 0,
|
||||
failed = 0,
|
||||
sum = 0;
|
||||
|
||||
if (cmd.clients) {
|
||||
sum = Object.keys(cmd.clients).length;
|
||||
|
||||
Object.keys(cmd.clients).forEach((addr) => {
|
||||
const client = cmd.clients[addr],
|
||||
clientRow = row.clients[addr];
|
||||
|
||||
clientRow.status.classList.remove('running', 'failed', 'success');
|
||||
if (client.running) {
|
||||
running += 1;
|
||||
clientRow.status.classList.add('running');
|
||||
} else if (client.with_error) {
|
||||
failed += 1;
|
||||
clientRow.status.classList.add('failed');
|
||||
} else {
|
||||
clientRow.status.classList.add('success');
|
||||
}
|
||||
|
||||
clientRow.result.innerHTML = client.result;
|
||||
clientRow.host.innerHTML = addr;
|
||||
});
|
||||
}
|
||||
|
||||
row.status.classList.remove('running', 'failed', 'success');
|
||||
if (running > 0) {
|
||||
row.status.innerHTML = `running (${running}`;
|
||||
row.status.classList.add('running');
|
||||
} else if (failed > 0 || sum === 0) {
|
||||
row.status.innerHTML = `failed (${failed}`;
|
||||
row.status.classList.add('failed');
|
||||
} else {
|
||||
row.status.innerHTML = `success (${sum}`;
|
||||
row.status.classList.add('success');
|
||||
}
|
||||
row.status.innerHTML += `/${sum})`;
|
||||
}
|
||||
|
||||
function createRow (cmd) {
|
||||
const row = {
|
||||
'clients': {},
|
||||
'clientsContainer': document.createElement('div'),
|
||||
'clientsEl': {},
|
||||
'el': document.createElement('div')
|
||||
};
|
||||
|
||||
|
||||
if (cmd.cmd === '' && cmd.timestemp === 0) {
|
||||
row.el.innerHTML = '\n' +
|
||||
' _______ ________ __\n' +
|
||||
' | |.-----.-----.-----.| | | |.----.| |_\n' +
|
||||
' | - || _ | -__| || | | || _|| _|\n' +
|
||||
' |_______|| __|_____|__|__||________||__| |____|\n' +
|
||||
' |__| W I R E L E S S F R E E D O M\n' +
|
||||
' -----------------------------------------------------\n' +
|
||||
' FreifunkManager shell for openwrt/Lede/gluon systems \n' +
|
||||
' -----------------------------------------------------\n' +
|
||||
' * 1 1/2 oz Gin Shake with a glassful\n' +
|
||||
' * 1/4 oz Triple Sec of broken ice and pour\n' +
|
||||
' * 3/4 oz Lime Juice unstrained into a goblet.\n' +
|
||||
' * 1 1/2 oz Orange Juice\n' +
|
||||
' * 1 tsp. Grenadine Syrup\n' +
|
||||
' -----------------------------------------------------\n';
|
||||
|
||||
return row;
|
||||
}
|
||||
row.timestemp = domlib.newAt(row.el, 'span');
|
||||
row.cmd = domlib.newAt(row.el, 'span');
|
||||
row.status = domlib.newAt(row.el, 'span');
|
||||
|
||||
row.el.classList.add('cmd');
|
||||
row.timestemp.classList.add('time');
|
||||
row.status.classList.add('status');
|
||||
|
||||
if (cmd.clients) {
|
||||
Object.keys(cmd.clients).forEach((addr) => {
|
||||
const clientEl = domlib.newAt(row.clientsContainer, 'div'),
|
||||
clients = {
|
||||
'host': domlib.newAt(clientEl, 'span'),
|
||||
'result': domlib.newAt(clientEl, 'span'),
|
||||
'status': domlib.newAt(clientEl, 'span')
|
||||
};
|
||||
|
||||
clients.host.classList.add('host');
|
||||
clients.status.classList.add('status');
|
||||
|
||||
row.clientsEl[addr] = clientEl;
|
||||
row.clients[addr] = clients;
|
||||
});
|
||||
row.cmd.addEventListener('click', () => {
|
||||
if (row.clientsContainer.parentElement) {
|
||||
row.el.removeChild(row.clientsContainer);
|
||||
} else {
|
||||
row.el.appendChild(row.clientsContainer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
updateCMD(row, cmd);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
function update () {
|
||||
let cmds = store.getCMDs();
|
||||
|
||||
if (ownfilter) {
|
||||
const tmp = cmds;
|
||||
|
||||
cmds = {};
|
||||
Object.keys(tmp).
|
||||
forEach((id) => {
|
||||
if (ownCMDs.indexOf(id) >= 0) {
|
||||
cmds[id] = tmp[id];
|
||||
}
|
||||
});
|
||||
}
|
||||
Object.keys(cmdRow).forEach((id) => {
|
||||
if (cmdRow[id].el.parentElement) {
|
||||
output.removeChild(cmdRow[id].el);
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(cmds).forEach((id) => {
|
||||
const cmd = cmds[id];
|
||||
|
||||
if (cmdRow[id]) {
|
||||
updateCMD(cmdRow[id], cmd);
|
||||
} else {
|
||||
cmdRow[id] = createRow(cmd);
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(cmdRow).
|
||||
sort((aID, bID) => {
|
||||
if (!cmds[aID] || !cmds[bID]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return cmds[aID].timestemp - cmds[bID].timestemp;
|
||||
}).
|
||||
forEach((id) => {
|
||||
if (cmds[id] && !cmdRow[id].el.parentElement) {
|
||||
output.appendChild(cmdRow[id].el);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
view.bind = function bind (bindEl) {
|
||||
container = bindEl;
|
||||
};
|
||||
view.render = function render () {
|
||||
if (!container) {
|
||||
return;
|
||||
} else if (el) {
|
||||
container.appendChild(el);
|
||||
update();
|
||||
|
||||
return;
|
||||
}
|
||||
console.log('generate new view for console');
|
||||
el = domlib.newAt(container, 'div');
|
||||
|
||||
store.updateCMD({
|
||||
'cmd': '',
|
||||
'id': '0',
|
||||
'timestemp': 0
|
||||
});
|
||||
|
||||
output = domlib.newAt(el, 'div');
|
||||
output.classList.add('console');
|
||||
|
||||
const prompt = domlib.newAt(el, 'div'),
|
||||
filterBtn = domlib.newAt(prompt, 'span'),
|
||||
promptInput = domlib.newAt(prompt, 'input');
|
||||
|
||||
prompt.classList.add('prompt');
|
||||
|
||||
promptInput.addEventListener('keyup', (event) => {
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
if (event.keyCode !== 13) {
|
||||
return;
|
||||
}
|
||||
const cmd = {
|
||||
'cmd': promptInput.value,
|
||||
'id': createID(),
|
||||
'timestemp': new Date()
|
||||
};
|
||||
|
||||
ownCMDs.push(cmd.id);
|
||||
socket.sendcmd(cmd);
|
||||
promptInput.value = '';
|
||||
});
|
||||
|
||||
filterBtn.classList.add('btn');
|
||||
filterBtn.innerHTML = 'Show all';
|
||||
filterBtn.addEventListener('click', () => {
|
||||
ownfilter = !ownfilter;
|
||||
filterBtn.classList.toggle('active');
|
||||
filterBtn.innerHTML = ownfilter
|
||||
? 'Show own'
|
||||
: 'Show all';
|
||||
update();
|
||||
});
|
||||
|
||||
|
||||
update();
|
||||
};
|
||||
})();
|
|
@ -35,6 +35,11 @@ let socket = {'readyState': 0};
|
|||
store.stats = msg.body;
|
||||
}
|
||||
break;
|
||||
case 'cmd':
|
||||
if (msg.body) {
|
||||
store.updateCMD(msg.body);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
notify.send('warn', `unable to identify message: ${raw}`);
|
||||
break;
|
||||
|
@ -57,10 +62,18 @@ let socket = {'readyState': 0};
|
|||
'type': 'system'
|
||||
});
|
||||
|
||||
socket.send(socketMsg);
|
||||
notify.send('success', notifyMsg);
|
||||
}
|
||||
|
||||
function sendcmd (cmd) {
|
||||
const notifyMsg = `Befehl '${cmd.cmd}' wird überall ausgeführt.`,
|
||||
socketMsg = JSON.stringify({
|
||||
'body': cmd,
|
||||
'type': 'cmd'
|
||||
});
|
||||
|
||||
socket.send(socketMsg);
|
||||
|
||||
|
||||
notify.send('success', notifyMsg);
|
||||
}
|
||||
|
||||
|
@ -71,6 +84,7 @@ let socket = {'readyState': 0};
|
|||
socket.onmessage = onmessage;
|
||||
socket.onclose = onclose;
|
||||
socket.sendnode = sendnode;
|
||||
socket.sendcmd = sendcmd;
|
||||
}
|
||||
|
||||
connect();
|
||||
|
|
|
@ -18,7 +18,8 @@ const store = {
|
|||
'use strict';
|
||||
|
||||
const current = {},
|
||||
list = {};
|
||||
list = {},
|
||||
cmds = {};
|
||||
|
||||
function getNode (nodeid) {
|
||||
let node = {};
|
||||
|
@ -51,9 +52,18 @@ const store = {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
store.getNode = getNode;
|
||||
|
||||
store.getNodes = function getNodes () {
|
||||
return Object.keys(list).map(getNode);
|
||||
};
|
||||
|
||||
store.updateCMD = function updateCMD (cmd) {
|
||||
cmds[cmd.id] = cmd;
|
||||
};
|
||||
|
||||
store.getCMDs = function getCMDs () {
|
||||
return cmds;
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -12,4 +12,5 @@ const (
|
|||
MessageTypeSystemNode = "system"
|
||||
MessageTypeCurrentNode = "current"
|
||||
MessageTypeStats = "stats"
|
||||
MessageTypeCmd = "cmd"
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue