[TASK] nice list
This commit is contained in:
parent
ac87050fa0
commit
b190bd43c4
|
@ -7,6 +7,6 @@ ssh_key = "/etc/id_rsa"
|
|||
ssh_interface = "wlp4s0"
|
||||
|
||||
[yanic]
|
||||
enable = true
|
||||
enable = false
|
||||
type = "unix"
|
||||
address = "/tmp/yanic-database.socket"
|
||||
|
|
|
@ -4,10 +4,10 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/jsontime"
|
||||
yanicRuntime "github.com/FreifunkBremen/yanic/runtime"
|
||||
)
|
||||
|
||||
|
@ -15,10 +15,11 @@ const (
|
|||
SSHUpdateHostname = "uci set system.@system[0].hostname='%s';uci commit system;echo $(uci get system.@system[0].hostname) > /proc/sys/kernel/hostname"
|
||||
SSHUpdateOwner = "uci set gluon-node-info.@owner[0].contact='%s';uci commit gluon-node-info;"
|
||||
SSHUpdateLocation = "uci set gluon-node-info.@location[0].latitude='%d';uci set gluon-node-info.@location[0].longitude='%d';uci set gluon-node-info.@location[0].share_location=1;uci commit gluon-node-info;"
|
||||
SSHUpdateWifiFreq = "uci set gluon-node-info.@location[0].latitude='%d';uci set gluon-node-info.@location[0].longitude='%d';uci set gluon-node-info.@location[0].share_location=1;uci commit gluon-node-info;wifi"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
Lastseen time.Time `json:"lastseen"`
|
||||
Lastseen jsontime.Time `json:"lastseen"`
|
||||
NodeID string `json:"node_id"`
|
||||
Hostname string `json:"hostname"`
|
||||
Location data.Location `json:"location"`
|
||||
|
|
|
@ -3,8 +3,9 @@ package runtime
|
|||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/jsontime"
|
||||
yanic "github.com/FreifunkBremen/yanic/runtime"
|
||||
|
||||
"github.com/FreifunkBremen/freifunkmanager/lib/log"
|
||||
|
@ -18,6 +19,7 @@ type Nodes struct {
|
|||
statePath string
|
||||
iface string
|
||||
notifyFunc []func(*Node, bool)
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewNodes(path string, iface string, mgmt *ssh.Manager) *Nodes {
|
||||
|
@ -37,13 +39,15 @@ func (nodes *Nodes) AddNode(n *yanic.Node) {
|
|||
if node == nil {
|
||||
return
|
||||
}
|
||||
node.Lastseen = jsontime.Now()
|
||||
logger := log.Log.WithField("method", "AddNode").WithField("node_id", node.NodeID)
|
||||
|
||||
nodes.Lock()
|
||||
nodes.Unlock()
|
||||
if cNode := nodes.List[node.NodeID]; cNode != nil {
|
||||
cNode.Lastseen = time.Now()
|
||||
cNode.Lastseen = jsontime.Now()
|
||||
cNode.Stats = node.Stats
|
||||
if uNode, ok := nodes.ToUpdate[node.NodeID]; ok {
|
||||
uNode.Lastseen = time.Now()
|
||||
uNode.Lastseen = jsontime.Now()
|
||||
uNode.Stats = node.Stats
|
||||
if nodes.List[node.NodeID].IsEqual(node) {
|
||||
delete(nodes.ToUpdate, node.NodeID)
|
||||
|
@ -59,7 +63,6 @@ func (nodes *Nodes) AddNode(n *yanic.Node) {
|
|||
logger.Debugf("know already these node")
|
||||
return
|
||||
}
|
||||
node.Lastseen = time.Now()
|
||||
// session := nodes.ssh.ConnectTo(node.Address)
|
||||
result, err := nodes.ssh.RunOn(node.GetAddress(nodes.iface), "uptime")
|
||||
if err != nil {
|
||||
|
@ -87,6 +90,8 @@ func (nodes *Nodes) UpdateNode(node *Node) {
|
|||
log.Log.Warn("no new node to update")
|
||||
return
|
||||
}
|
||||
nodes.Lock()
|
||||
defer nodes.Unlock()
|
||||
if n, ok := nodes.List[node.NodeID]; ok {
|
||||
go node.SSHUpdate(nodes.ssh, nodes.iface, n)
|
||||
}
|
||||
|
@ -95,6 +100,8 @@ func (nodes *Nodes) UpdateNode(node *Node) {
|
|||
}
|
||||
|
||||
func (nodes *Nodes) Updater() {
|
||||
nodes.Lock()
|
||||
defer nodes.Unlock()
|
||||
for nodeid := range nodes.ToUpdate {
|
||||
if node := nodes.List[nodeid]; node != nil {
|
||||
go node.SSHSet(nodes.ssh, nodes.iface)
|
||||
|
@ -116,6 +123,8 @@ func (nodes *Nodes) load() {
|
|||
}
|
||||
|
||||
func (nodes *Nodes) Saver() {
|
||||
nodes.Lock()
|
||||
yanic.SaveJSON(nodes, nodes.statePath)
|
||||
nodes.Unlock()
|
||||
log.Log.Debug("saved state file")
|
||||
}
|
||||
|
|
|
@ -104,23 +104,54 @@ thead {
|
|||
thead tr th{
|
||||
border-bottom: 4px solid #dc0067;
|
||||
}
|
||||
|
||||
table th.sort-down:after {
|
||||
content: " \25BE"
|
||||
table th > input {
|
||||
border: none;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
background: #fff;
|
||||
}
|
||||
table th.sort-up:after {
|
||||
content: " \25B4"
|
||||
table th.sortable.sort-down:after {
|
||||
content: " \25BC"
|
||||
}
|
||||
table th.sortable.sort-up:after {
|
||||
content: " \25B2"
|
||||
}
|
||||
table th.sortable:not(.sort-down):not(.sort-up):after {
|
||||
content: " \25B4\25BE";
|
||||
}
|
||||
table.nodes {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
table.nodes td > span{
|
||||
display: block;
|
||||
}
|
||||
table.nodes tbody tr:nth-child(even) {
|
||||
background: #eee;
|
||||
}
|
||||
table.nodes tbody tr:nth-child(odd) {
|
||||
background: #fff;
|
||||
}
|
||||
table.nodes tbody tr:hover {
|
||||
background: #ccc;
|
||||
}
|
||||
table.nodes tbody tr.offline{
|
||||
background: #ffb400;
|
||||
}
|
||||
table.nodes tbody tr.offline:hover{
|
||||
background: #dc0067;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: .5em 1em;
|
||||
border-radius: 2em;
|
||||
padding: .3em .5em;
|
||||
border-radius: 1em;
|
||||
color: #fff;
|
||||
background-color: #dc0067;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
.btn:hover {
|
||||
background: lighten(#dc0067, 5%);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>FreifunkManager</title>
|
||||
<link rel="stylesheet" href="/css/main.css">
|
||||
<script src="/js/moment.js"></script>
|
||||
<script src="/js/navigo.js"></script>
|
||||
<script src="/js/config.js"></script>
|
||||
<script src="/js/domlib.js"></script>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var config = {
|
||||
title: 'FreifunkManager - Breminale',
|
||||
backend: 'ws://localhost:8080/websocket'
|
||||
backend: 'ws://'+location.host+'/websocket'
|
||||
};
|
||||
|
|
|
@ -8,18 +8,15 @@ var router = new Navigo(null, true, '#');
|
|||
router.on({
|
||||
'/list': function () {
|
||||
clean();
|
||||
console.log("list view");
|
||||
guiList.bind(document.querySelector('main'));
|
||||
guiList.render();
|
||||
},
|
||||
'/map':function(){
|
||||
clean();
|
||||
console.log("map view");
|
||||
domlib.newAt(main,"div").innerHTML = "Map";
|
||||
},
|
||||
'/statistics':function(){
|
||||
clean();
|
||||
console.log("stats view");
|
||||
domlib.newAt(document.querySelector('main'),"div").innerHTML = "Stats";
|
||||
},
|
||||
'/n/:nodeID': {
|
||||
|
|
|
@ -6,10 +6,41 @@ var guiList = {};
|
|||
var sortReverse = false;
|
||||
var sortIndex;
|
||||
|
||||
var hostnameFilter,nodeidFilter;
|
||||
|
||||
function sort(a,b){
|
||||
function sortNumber(a,b){
|
||||
return a - b;
|
||||
}
|
||||
if(sortIndex === undefined)
|
||||
return a.node_id.localeCompare(b.node_id);
|
||||
switch (sortIndex.innerHTML) {
|
||||
case "Lastseen":
|
||||
return a.lastseen - b.lastseen;
|
||||
case "CurPower":
|
||||
return a._wireless.txpower24 - b._wireless.txpower24;
|
||||
case "Power":
|
||||
return a.wireless.txpower24 - b.wireless.txpower24;
|
||||
case "CurChannel":
|
||||
return a._wireless.channel24 - b._wireless.channel24;
|
||||
case "Channel":
|
||||
return a.wireless.channel24 - b.wireless.channel24;
|
||||
case "Clients":
|
||||
return a.statistics.clients.wifi24 - b.statistics.clients.wifi24;
|
||||
case "ChanUtil":
|
||||
var aMax = a.statistics.wireless.map(function(d){
|
||||
return d.ChanUtil
|
||||
}).sort(sortNumber);
|
||||
|
||||
var bMax = b.statistics.wireless.map(function(d){
|
||||
return d.ChanUtil
|
||||
}).sort(sortNumber);
|
||||
|
||||
if(!sortReverse){
|
||||
aMax = aMax.reverse();
|
||||
bMax = bMax.reverse();
|
||||
}
|
||||
return bMax[0] - aMax[0];
|
||||
case "Hostname":
|
||||
return a.hostname.localeCompare(b.hostname);
|
||||
default:
|
||||
|
@ -19,13 +50,56 @@ var guiList = {};
|
|||
|
||||
function renderRow(data){
|
||||
var tr = document.createElement('tr');
|
||||
var startdate = new Date();
|
||||
startdate.setMinutes(startdate.getMinutes() - 1);
|
||||
if(new Date(data.lastseen) < startdate)
|
||||
tr.classList.add('offline')
|
||||
var td;
|
||||
|
||||
domlib.newAt(tr,'td').innerHTML = moment(data.lastseen).fromNow(true);
|
||||
domlib.newAt(tr,'td').innerHTML = data.node_id;
|
||||
|
||||
var cell1 = domlib.newAt(tr,'td');
|
||||
cell1.innerHTML = data.hostname;
|
||||
cell1.addEventListener('click',function(){
|
||||
domlib.newAt(tr,'td').innerHTML = data.hostname;
|
||||
|
||||
var freq = domlib.newAt(tr,'td');
|
||||
domlib.newAt(freq,'span').innerHTML = '2.4 Ghz';
|
||||
domlib.newAt(freq,'span').innerHTML = '5 Ghz';
|
||||
|
||||
var curchannel = domlib.newAt(tr,'td');
|
||||
domlib.newAt(curchannel,'span').innerHTML = data._wireless.channel24||'-';
|
||||
domlib.newAt(curchannel,'span').innerHTML = data._wireless.channel5||'-';
|
||||
|
||||
var channel = domlib.newAt(tr,'td');
|
||||
domlib.newAt(channel,'span').innerHTML = data.wireless.channel24||'-';
|
||||
domlib.newAt(channel,'span').innerHTML = data.wireless.channel5||'-';
|
||||
|
||||
var curpower = domlib.newAt(tr,'td');
|
||||
domlib.newAt(curpower,'span').innerHTML = data._wireless.txpower24||'-';
|
||||
domlib.newAt(curpower,'span').innerHTML = data._wireless.txpower5||'-';
|
||||
|
||||
var power = domlib.newAt(tr,'td');
|
||||
domlib.newAt(power,'span').innerHTML = data.wireless.txpower24||'-';
|
||||
domlib.newAt(power,'span').innerHTML = data.wireless.txpower5||'-';
|
||||
|
||||
var client = domlib.newAt(tr,'td');
|
||||
domlib.newAt(client,'span').innerHTML = data.statistics.clients.wifi24;
|
||||
domlib.newAt(client,'span').innerHTML = data.statistics.clients.wifi5;
|
||||
|
||||
var chanUtil = domlib.newAt(tr,'td');
|
||||
var chanUtil24 = data.statistics.wireless.filter(function(d){
|
||||
return d.frequency < 5000;
|
||||
})[0];
|
||||
var chanUtil5 = data.statistics.wireless.filter(function(d){
|
||||
return d.frequency > 5000;
|
||||
})[0];
|
||||
domlib.newAt(chanUtil,'span').innerHTML = chanUtil24.ChanUtil||'-';
|
||||
domlib.newAt(chanUtil,'span').innerHTML = chanUtil5.ChanUtil||'-';
|
||||
|
||||
var option = domlib.newAt(tr,'td');
|
||||
edit = domlib.newAt(option,'div');
|
||||
edit.classList.add('btn');
|
||||
edit.innerHTML = 'Edit';
|
||||
edit.addEventListener('click',function(){
|
||||
router.navigate(router.generate('node', { nodeID: data.node_id }));
|
||||
});
|
||||
|
||||
|
@ -36,11 +110,20 @@ var guiList = {};
|
|||
domlib.removeChildren(tbody);
|
||||
var data = store.will();
|
||||
|
||||
if(sortReverse)
|
||||
data = data.reverse(sort);
|
||||
else
|
||||
if(hostnameFilter && hostnameFilter.value != "")
|
||||
data = data.filter(function(d){
|
||||
return d.hostname.toLowerCase().indexOf(hostnameFilter.value) > -1;
|
||||
})
|
||||
if(nodeidFilter && nodeidFilter.value != "")
|
||||
data = data.filter(function(d){
|
||||
return d.node_id.indexOf(nodeidFilter.value) > -1;
|
||||
})
|
||||
|
||||
data = data.sort(sort);
|
||||
|
||||
if(sortReverse)
|
||||
data = data.reverse();
|
||||
|
||||
for(var i=0; i<data.length; i++){
|
||||
var row = renderRow(data[i]);
|
||||
tbody.appendChild(row);
|
||||
|
@ -63,7 +146,6 @@ var guiList = {};
|
|||
|
||||
guiList.render = function render(){
|
||||
if (container === undefined){
|
||||
console.log("unable to render guiList");
|
||||
return;
|
||||
} else if (tbody !== undefined){
|
||||
container.appendChild(tbody.parentNode);
|
||||
|
@ -78,18 +160,73 @@ var guiList = {};
|
|||
var tr = domlib.newAt(thead,'tr');
|
||||
|
||||
var cell1 = domlib.newAt(tr,'th');
|
||||
cell1.innerHTML = "NodeID";
|
||||
cell1.innerHTML = "Lastseen";
|
||||
cell1.addEventListener('click', function(){
|
||||
sortTable(cell1);
|
||||
});
|
||||
|
||||
var cell2 = domlib.newAt(tr,'th');
|
||||
cell2.innerHTML = "Hostname";
|
||||
cell2.addEventListener('click', function(){
|
||||
cell2.classList.add('sortable');
|
||||
nodeidFilter = domlib.newAt(cell2,'input');
|
||||
nodeidFilter.setAttribute("placeholder","NodeID");
|
||||
nodeidFilter.setAttribute("size","9");
|
||||
nodeidFilter.addEventListener('keyup', updateTable);
|
||||
cell2.addEventListener('dblclick', function(){
|
||||
sortTable(cell2);
|
||||
});
|
||||
|
||||
table.classList.add('sorttable');
|
||||
var cell3 = domlib.newAt(tr,'th');
|
||||
cell3.classList.add('sortable');
|
||||
hostnameFilter = domlib.newAt(cell3,'input');
|
||||
hostnameFilter.setAttribute("placeholder","Hostname");
|
||||
hostnameFilter.addEventListener('keyup', updateTable);
|
||||
cell3.addEventListener('dblclick', function(){
|
||||
sortTable(cell3);
|
||||
});
|
||||
|
||||
domlib.newAt(tr,'th').innerHTML = 'Freq';
|
||||
|
||||
var cell4 = domlib.newAt(tr,'th');
|
||||
cell4.innerHTML = "CurChannel";
|
||||
cell4.classList.add('sortable');
|
||||
cell4.addEventListener('click', function(){
|
||||
sortTable(cell4);
|
||||
});
|
||||
var cell5 = domlib.newAt(tr,'th');
|
||||
cell5.innerHTML = "Channel";
|
||||
cell5.classList.add('sortable');
|
||||
cell5.addEventListener('click', function(){
|
||||
sortTable(cell5);
|
||||
});
|
||||
|
||||
var cell6 = domlib.newAt(tr,'th');
|
||||
cell6.innerHTML = "CurPower";
|
||||
cell6.classList.add('sortable');
|
||||
cell6.addEventListener('click', function(){
|
||||
sortTable(cell6);
|
||||
});
|
||||
var cell7 = domlib.newAt(tr,'th');
|
||||
cell7.innerHTML = "Power";
|
||||
cell7.classList.add('sortable');
|
||||
cell7.addEventListener('click', function(){
|
||||
sortTable(cell7);
|
||||
});
|
||||
|
||||
var cell8 = domlib.newAt(tr,'th');
|
||||
cell8.innerHTML = "Clients";
|
||||
cell8.classList.add('sortable');
|
||||
cell8.addEventListener('click', function(){
|
||||
sortTable(cell8);
|
||||
});
|
||||
var cell9 = domlib.newAt(tr,'th');
|
||||
cell9.innerHTML = "ChanUtil";
|
||||
cell9.classList.add('sortable');
|
||||
cell9.addEventListener('click', function(){
|
||||
sortTable(cell9);
|
||||
});
|
||||
domlib.newAt(tr,'th').innerHTML = "Option";
|
||||
|
||||
table.classList.add('nodes');
|
||||
|
||||
updateTable();
|
||||
};
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -12,10 +12,14 @@ var store = {
|
|||
};
|
||||
store.will = function() {
|
||||
return Object.keys(store.list).map(function(nodeid){
|
||||
var node;
|
||||
if (store.toupdate[nodeid]) {
|
||||
return store.toupdate[nodeid];
|
||||
node = store.toupdate[nodeid];
|
||||
} else{
|
||||
node = store.list[nodeid];
|
||||
}
|
||||
return store.list[nodeid];
|
||||
node._wireless = store.list[nodeid].wireless;
|
||||
return node;
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -37,7 +37,9 @@ func (c *Client) Write(msg *Message) {
|
|||
select {
|
||||
case c.ch <- msg:
|
||||
default:
|
||||
clientsMutex.Lock()
|
||||
delete(clients, c.ip)
|
||||
clientsMutex.Unlock()
|
||||
log.HTTP(c.ws.Request()).Error("client disconnected")
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +83,9 @@ func (c *Client) listenWrite() {
|
|||
case <-c.writeQuit:
|
||||
close(c.ch)
|
||||
close(c.writeQuit)
|
||||
clientsMutex.Lock()
|
||||
delete(clients, c.ip)
|
||||
clientsMutex.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +98,9 @@ func (c *Client) listenRead() {
|
|||
|
||||
case <-c.readQuit:
|
||||
close(c.readQuit)
|
||||
clientsMutex.Lock()
|
||||
delete(clients, c.ip)
|
||||
clientsMutex.Unlock()
|
||||
return
|
||||
|
||||
default:
|
||||
|
|
|
@ -2,6 +2,7 @@ package websocket
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
|
||||
var nodes *runtime.Nodes
|
||||
var clients map[string]*Client
|
||||
var clientsMutex sync.Mutex
|
||||
|
||||
func Start(nodeBind *runtime.Nodes) {
|
||||
nodes = nodeBind
|
||||
|
@ -23,14 +25,18 @@ func Start(nodeBind *runtime.Nodes) {
|
|||
|
||||
defer func() {
|
||||
ws.Close()
|
||||
clientsMutex.Lock()
|
||||
delete(clients, ip)
|
||||
clientsMutex.Unlock()
|
||||
log.HTTP(r).Info("client disconnected")
|
||||
}()
|
||||
|
||||
log.HTTP(r).Infof("new client")
|
||||
|
||||
client := NewClient(ip, ws)
|
||||
clientsMutex.Lock()
|
||||
clients[ip] = client
|
||||
clientsMutex.Unlock()
|
||||
client.Listen()
|
||||
|
||||
}))
|
||||
|
@ -43,9 +49,11 @@ func Notify(node *runtime.Node, real bool) {
|
|||
if real {
|
||||
msgType = MessageTypeCurrentNode
|
||||
}
|
||||
clientsMutex.Lock()
|
||||
for _, c := range clients {
|
||||
c.Write(&Message{Type: msgType, Node: node})
|
||||
}
|
||||
clientsMutex.Unlock()
|
||||
}
|
||||
|
||||
func Close() {
|
||||
|
|
Loading…
Reference in New Issue