[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 {
|
type List struct {
|
||||||
cmd string
|
cmd string `json:"cmd"`
|
||||||
Clients map[string]*ListResult
|
Clients map[string]*ListResult `json:"clients"`
|
||||||
sshManager *Manager
|
sshManager *Manager
|
||||||
}
|
}
|
||||||
type ListResult struct {
|
type ListResult struct {
|
||||||
ssh *ssh.Client
|
ssh *ssh.Client
|
||||||
Runned bool
|
Running bool `json:"running"`
|
||||||
WithError bool
|
WithError bool `json:"with_error"`
|
||||||
Result string
|
Result string `json:"result"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) CreateList(cmd string) *List {
|
func (m *Manager) CreateList(cmd string) *List {
|
||||||
|
@ -25,7 +25,7 @@ func (m *Manager) CreateList(cmd string) *List {
|
||||||
Clients: make(map[string]*ListResult),
|
Clients: make(map[string]*ListResult),
|
||||||
}
|
}
|
||||||
for host, client := range m.clients {
|
for host, client := range m.clients {
|
||||||
list.Clients[host] = &ListResult{Runned: false, ssh: client}
|
list.Clients[host] = &ListResult{Running: true, ssh: client}
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ func (l List) Run() {
|
||||||
func (l List) runlistelement(host string, client *ListResult, wg *sync.WaitGroup) {
|
func (l List) runlistelement(host string, client *ListResult, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
result, err := l.sshManager.run(host, client.ssh, l.cmd)
|
result, err := l.sshManager.run(host, client.ssh, l.cmd)
|
||||||
client.Runned = true
|
client.Running = false
|
||||||
if err != nil {
|
if err != nil {
|
||||||
client.WithError = true
|
client.WithError = true
|
||||||
return
|
return
|
||||||
|
|
|
@ -19,18 +19,18 @@ func TestList(t *testing.T) {
|
||||||
list := mgmt.CreateList("exit 1")
|
list := mgmt.CreateList("exit 1")
|
||||||
assert.Len(list.Clients, 1)
|
assert.Len(list.Clients, 1)
|
||||||
client := list.Clients[addr.IP.String()]
|
client := list.Clients[addr.IP.String()]
|
||||||
assert.False(client.Runned)
|
assert.True(client.Running)
|
||||||
list.Run()
|
list.Run()
|
||||||
assert.True(client.Runned)
|
assert.False(client.Running)
|
||||||
assert.True(client.WithError)
|
assert.True(client.WithError)
|
||||||
assert.Equal("", client.Result)
|
assert.Equal("", client.Result)
|
||||||
|
|
||||||
list = mgmt.CreateList("echo 15")
|
list = mgmt.CreateList("echo 15")
|
||||||
assert.Len(list.Clients, 1)
|
assert.Len(list.Clients, 1)
|
||||||
client = list.Clients[addr.IP.String()]
|
client = list.Clients[addr.IP.String()]
|
||||||
assert.False(client.Runned)
|
assert.True(client.Running)
|
||||||
list.Run()
|
list.Run()
|
||||||
assert.True(client.Runned)
|
assert.False(client.Running)
|
||||||
assert.False(client.WithError)
|
assert.False(client.WithError)
|
||||||
assert.Equal("15", client.Result)
|
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 {
|
.status {
|
||||||
float: right;
|
float: right;
|
||||||
background: #009ee0;
|
background: #009ee0;
|
||||||
|
color: white;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
.status.connecting {
|
.status.connecting,.status.running {
|
||||||
background: #ffb400;
|
background: #ffb400;
|
||||||
}
|
}
|
||||||
.status.offline {
|
.status.offline, .status.failed {
|
||||||
background: #dc0067;
|
background: #dc0067;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
span.online {
|
span.online {
|
||||||
color: #009ee0;
|
color: #009ee0;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<link rel="stylesheet" href="/css/leaflet.css">
|
<link rel="stylesheet" href="/css/leaflet.css">
|
||||||
<link rel="stylesheet" href="/css/main.css">
|
<link rel="stylesheet" href="/css/main.css">
|
||||||
<link rel="stylesheet" href="/css/map.css">
|
<link rel="stylesheet" href="/css/map.css">
|
||||||
|
<link rel="stylesheet" href="/css/console.css">
|
||||||
<script src="/js/moment.js"></script>
|
<script src="/js/moment.js"></script>
|
||||||
<script src="/js/navigo.js"></script>
|
<script src="/js/navigo.js"></script>
|
||||||
<script src="/js/leaflet.js"></script>
|
<script src="/js/leaflet.js"></script>
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
<script src="/js/domlib.js"></script>
|
<script src="/js/domlib.js"></script>
|
||||||
<script src="/js/store.js"></script>
|
<script src="/js/store.js"></script>
|
||||||
<script src="/js/notify.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_list.js"></script>
|
||||||
<script src="/js/gui_map.js"></script>
|
<script src="/js/gui_map.js"></script>
|
||||||
<script src="/js/gui_node.js"></script>
|
<script src="/js/gui_node.js"></script>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* exported gui,router */
|
/* exported gui,router */
|
||||||
/* globals socket,notify,domlib,guiList,guiMap,guiStats,guiNode */
|
/* globals socket,notify,domlib,guiList,guiMap,guiStats,guiNode,guiConsole */
|
||||||
|
|
||||||
const gui = {},
|
const gui = {},
|
||||||
router = new Navigo(null, true, '#');
|
router = new Navigo(null, true, '#');
|
||||||
|
@ -58,6 +58,9 @@ const gui = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
router.on({
|
router.on({
|
||||||
|
'/console': function routerConsole () {
|
||||||
|
setView(guiConsole);
|
||||||
|
},
|
||||||
'/list': function routerList () {
|
'/list': function routerList () {
|
||||||
setView(guiList);
|
setView(guiList);
|
||||||
},
|
},
|
||||||
|
@ -75,6 +78,7 @@ const gui = {},
|
||||||
'/statistics': function routerStats () {
|
'/statistics': function routerStats () {
|
||||||
setView(guiStats);
|
setView(guiStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
router.on(() => {
|
router.on(() => {
|
||||||
router.navigate('/list');
|
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;
|
store.stats = msg.body;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'cmd':
|
||||||
|
if (msg.body) {
|
||||||
|
store.updateCMD(msg.body);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
notify.send('warn', `unable to identify message: ${raw}`);
|
notify.send('warn', `unable to identify message: ${raw}`);
|
||||||
break;
|
break;
|
||||||
|
@ -57,10 +62,18 @@ let socket = {'readyState': 0};
|
||||||
'type': 'system'
|
'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);
|
socket.send(socketMsg);
|
||||||
|
|
||||||
|
|
||||||
notify.send('success', notifyMsg);
|
notify.send('success', notifyMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +84,7 @@ let socket = {'readyState': 0};
|
||||||
socket.onmessage = onmessage;
|
socket.onmessage = onmessage;
|
||||||
socket.onclose = onclose;
|
socket.onclose = onclose;
|
||||||
socket.sendnode = sendnode;
|
socket.sendnode = sendnode;
|
||||||
|
socket.sendcmd = sendcmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
connect();
|
connect();
|
||||||
|
|
|
@ -18,7 +18,8 @@ const store = {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const current = {},
|
const current = {},
|
||||||
list = {};
|
list = {},
|
||||||
|
cmds = {};
|
||||||
|
|
||||||
function getNode (nodeid) {
|
function getNode (nodeid) {
|
||||||
let node = {};
|
let node = {};
|
||||||
|
@ -51,9 +52,18 @@ const store = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
store.getNode = getNode;
|
store.getNode = getNode;
|
||||||
|
|
||||||
store.getNodes = function getNodes () {
|
store.getNodes = function getNodes () {
|
||||||
return Object.keys(list).map(getNode);
|
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"
|
MessageTypeSystemNode = "system"
|
||||||
MessageTypeCurrentNode = "current"
|
MessageTypeCurrentNode = "current"
|
||||||
MessageTypeStats = "stats"
|
MessageTypeStats = "stats"
|
||||||
|
MessageTypeCmd = "cmd"
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue