cleanup and migrate to newer libraries (use NEW Javascript env) + add secret
This commit is contained in:
parent
a86ac56b75
commit
82270e6fa3
|
@ -124,12 +124,6 @@ __pycache__
|
||||||
# IDE's go
|
# IDE's go
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
# webroot
|
|
||||||
webroot/node_modules
|
|
||||||
webroot/data
|
|
||||||
webroot/app.js
|
|
||||||
webroot/app.js.map
|
|
||||||
|
|
||||||
# go project
|
# go project
|
||||||
profile.cov
|
profile.cov
|
||||||
config.conf
|
config.conf
|
||||||
|
|
|
@ -3,6 +3,7 @@ state_path = "/tmp/freifunkmanager.json"
|
||||||
webserver_bind = ":8080"
|
webserver_bind = ":8080"
|
||||||
webroot = "./webroot"
|
webroot = "./webroot"
|
||||||
|
|
||||||
|
secret = "passw0rd"
|
||||||
|
|
||||||
ssh_key = "~/.ssh/id_rsa"
|
ssh_key = "~/.ssh/id_rsa"
|
||||||
ssh_interface = "wlp4s0"
|
ssh_interface = "wlp4s0"
|
||||||
|
|
11
main.go
11
main.go
|
@ -54,8 +54,9 @@ func main() {
|
||||||
go nodesSaveWorker.Start()
|
go nodesSaveWorker.Start()
|
||||||
go nodesUpdateWorker.Start()
|
go nodesUpdateWorker.Start()
|
||||||
|
|
||||||
websocket.Start(nodes)
|
ws := websocket.NewWebsocketServer(config.Secret, nodes)
|
||||||
db.NotifyStats = websocket.NotifyStats
|
nodes.AddNotifyStats(ws.SendStats)
|
||||||
|
nodes.AddNotifyNode(ws.SendNode)
|
||||||
|
|
||||||
if config.Yanic.Enable {
|
if config.Yanic.Enable {
|
||||||
collector = respondYanic.NewCollector(db, nodesYanic, make(map[string][]string), []respondYanic.InterfaceConfig{respondYanic.InterfaceConfig{
|
collector = respondYanic.NewCollector(db, nodesYanic, make(map[string][]string), []respondYanic.InterfaceConfig{respondYanic.InterfaceConfig{
|
||||||
|
@ -71,7 +72,7 @@ func main() {
|
||||||
httpLib.Write(w, nodes)
|
httpLib.Write(w, nodes)
|
||||||
})
|
})
|
||||||
http.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) {
|
||||||
httpLib.Write(w, db.Statistics)
|
httpLib.Write(w, nodes.Statistics)
|
||||||
})
|
})
|
||||||
http.Handle("/", gziphandler.GzipHandler(http.FileServer(http.Dir(config.Webroot))))
|
http.Handle("/", gziphandler.GzipHandler(http.FileServer(http.Dir(config.Webroot))))
|
||||||
|
|
||||||
|
@ -80,7 +81,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := srv.ListenAndServe(); err != nil {
|
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -92,7 +93,7 @@ func main() {
|
||||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||||
sig := <-sigs
|
sig := <-sigs
|
||||||
|
|
||||||
websocket.Close()
|
ws.Close()
|
||||||
|
|
||||||
// Stop services
|
// Stop services
|
||||||
srv.Close()
|
srv.Close()
|
||||||
|
|
|
@ -10,6 +10,9 @@ type Config struct {
|
||||||
// path to deliver static content
|
// path to deliver static content
|
||||||
Webroot string `toml:"webroot"`
|
Webroot string `toml:"webroot"`
|
||||||
|
|
||||||
|
// auth secret
|
||||||
|
Secret string `toml:"secret"`
|
||||||
|
|
||||||
// SSH private key
|
// SSH private key
|
||||||
SSHPrivateKey string `toml:"ssh_key"`
|
SSHPrivateKey string `toml:"ssh_key"`
|
||||||
SSHInterface string `toml:"ssh_interface"`
|
SSHInterface string `toml:"ssh_interface"`
|
||||||
|
|
|
@ -2,38 +2,25 @@ package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
|
|
||||||
yanicData "github.com/FreifunkBremen/yanic/data"
|
yanicData "github.com/FreifunkBremen/yanic/data"
|
||||||
"github.com/FreifunkBremen/yanic/lib/jsontime"
|
"github.com/FreifunkBremen/yanic/lib/jsontime"
|
||||||
yanicRuntime "github.com/FreifunkBremen/yanic/runtime"
|
yanicRuntime "github.com/FreifunkBremen/yanic/runtime"
|
||||||
|
|
||||||
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
SSHUpdateHostname = "uci set system.@system[0].hostname='%s'; uci set wireless.priv_radio0.ssid=\"offline-$(uci get system.@system[0].hostname)\"; uci set wireless.priv_radio1.ssid=\"offline-$(uci get system.@system[0].hostname)\"; uci commit; echo $(uci get system.@system[0].hostname) > /proc/sys/kernel/hostname; wifi"
|
|
||||||
SSHUpdateOwner = "uci set gluon-node-info.@owner[0].contact='%s';uci commit gluon-node-info;"
|
|
||||||
SSHUpdateLocation = "uci set gluon-node-info.@location[0].latitude='%f';uci set gluon-node-info.@location[0].longitude='%f';uci set gluon-node-info.@location[0].share_location=1;uci commit gluon-node-info;"
|
|
||||||
SSHUpdateWifiFreq24 = "if [ \"$(uci get wireless.radio0.hwmode | grep -c g)\" -ne 0 ]; then uci set wireless.radio0.channel='%d'; uci set wireless.radio0.txpower='%d'; elif [ \"$(uci get wireless.radio1.hwmode | grep -c g)\" -ne 0 ]; then uci set wireless.radio1.channel='%d'; uci set wireless.radio1.txpower='%d'; fi;"
|
|
||||||
SSHUpdateWifiFreq5 = "if [ \"$(uci get wireless.radio0.hwmode | grep -c a)\" -ne 0 ]; then uci set wireless.radio0.channel='%d'; uci set wireless.radio0.txpower='%d'; elif [ \"$(uci get wireless.radio1.hwmode | grep -c a)\" -ne 0 ]; then uci set wireless.radio1.channel='%d'; uci set wireless.radio1.txpower='%d'; fi;"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
Lastseen jsontime.Time `json:"lastseen"`
|
Lastseen jsontime.Time `json:"lastseen" mapstructure:"-"`
|
||||||
NodeID string `json:"node_id"`
|
NodeID string `json:"node_id" mapstructure:"node_id"`
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
Location yanicData.Location `json:"location"`
|
Location yanicData.Location `json:"location"`
|
||||||
Wireless yanicData.Wireless `json:"wireless"`
|
Wireless yanicData.Wireless `json:"wireless"`
|
||||||
Owner string `json:"owner"`
|
Owner string `json:"owner"`
|
||||||
Address net.IP `json:"-"`
|
Address net.IP `json:"ip" mapstructure:"-"`
|
||||||
Stats struct {
|
Stats struct {
|
||||||
Wireless yanicData.WirelessStatistics `json:"wireless"`
|
Wireless yanicData.WirelessStatistics `json:"wireless"`
|
||||||
Clients yanicData.Clients `json:"clients"`
|
Clients yanicData.Clients `json:"clients"`
|
||||||
} `json:"statistics"`
|
} `json:"statistics" mapstructure:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNode(nodeOrigin *yanicRuntime.Node) *Node {
|
func NewNode(nodeOrigin *yanicRuntime.Node) *Node {
|
||||||
|
@ -65,28 +52,6 @@ func NewNode(nodeOrigin *yanicRuntime.Node) *Node {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) SSHUpdate(ssh *ssh.Manager, iface string, oldnode *Node) {
|
|
||||||
addr := n.GetAddress(iface)
|
|
||||||
|
|
||||||
if oldnode == nil || n.Hostname != oldnode.Hostname {
|
|
||||||
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateHostname, n.Hostname))
|
|
||||||
}
|
|
||||||
if oldnode == nil || n.Owner != oldnode.Owner {
|
|
||||||
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateOwner, n.Owner))
|
|
||||||
}
|
|
||||||
if oldnode == nil || !locationEqual(n.Location, oldnode.Location) {
|
|
||||||
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateLocation, n.Location.Latitude, n.Location.Longitude))
|
|
||||||
}
|
|
||||||
if oldnode == nil || !wirelessEqual(n.Wireless, oldnode.Wireless) {
|
|
||||||
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateWifiFreq24, n.Wireless.Channel24, n.Wireless.TxPower24, n.Wireless.Channel24, n.Wireless.TxPower24))
|
|
||||||
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateWifiFreq5, n.Wireless.Channel5, n.Wireless.TxPower5, n.Wireless.Channel5, n.Wireless.TxPower5))
|
|
||||||
ssh.ExecuteOn(addr, "wifi")
|
|
||||||
// send warning for running wifi, because it kicks clients from node
|
|
||||||
log.Warn("[cmd] wifi ", n.NodeID)
|
|
||||||
}
|
|
||||||
oldnode = n
|
|
||||||
}
|
|
||||||
func (n *Node) GetAddress(iface string) net.TCPAddr {
|
func (n *Node) GetAddress(iface string) net.TCPAddr {
|
||||||
return net.TCPAddr{IP: n.Address, Port: 22, Zone: iface}
|
return net.TCPAddr{IP: n.Address, Port: 22, Zone: iface}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (n *Node) SSHUpdate(sshmgmt *ssh.Manager, iface string, oldnode *Node) {
|
||||||
|
addr := n.GetAddress(iface)
|
||||||
|
client, err := sshmgmt.ConnectTo(addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
if oldnode == nil || n.Hostname != oldnode.Hostname {
|
||||||
|
ssh.Execute(n.Address.String(), client, fmt.Sprintf(`
|
||||||
|
uci set system.@system[0].hostname='%s';
|
||||||
|
uci set wireless.priv_radio0.ssid="offline-$(uci get system.@system[0].hostname)";
|
||||||
|
uci set wireless.priv_radio1.ssid="offline-$(uci get system.@system[0].hostname)";
|
||||||
|
uci commit; echo $(uci get system.@system[0].hostname) > /proc/sys/kernel/hostname;`,
|
||||||
|
n.Hostname))
|
||||||
|
}
|
||||||
|
if oldnode == nil || n.Owner != oldnode.Owner {
|
||||||
|
ssh.Execute(n.Address.String(), client, fmt.Sprintf(`
|
||||||
|
uci set gluon-node-info.@owner[0].contact='%s';
|
||||||
|
uci commit gluon-node-info;`,
|
||||||
|
n.Owner))
|
||||||
|
}
|
||||||
|
if oldnode == nil || !locationEqual(n.Location, oldnode.Location) {
|
||||||
|
ssh.Execute(n.Address.String(), client, fmt.Sprintf(`
|
||||||
|
uci set gluon-node-info.@location[0].latitude='%f';
|
||||||
|
uci set gluon-node-info.@location[0].longitude='%f';
|
||||||
|
uci set gluon-node-info.@location[0].share_location=1;
|
||||||
|
uci commit gluon-node-info;`,
|
||||||
|
n.Location.Latitude, n.Location.Longitude))
|
||||||
|
|
||||||
|
}
|
||||||
|
runWifi := false
|
||||||
|
defer func() {
|
||||||
|
if runWifi {
|
||||||
|
ssh.Execute(n.Address.String(), client, "wifi")
|
||||||
|
// send warning for running wifi, because it kicks clients from node
|
||||||
|
log.Warn("[cmd] wifi ", n.NodeID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
result, err := ssh.Run(n.Address.String(), client, `
|
||||||
|
if [ "$(uci get wireless.radio0.hwmode | grep -c g)" -ne 0 ]; then
|
||||||
|
echo "radio0";
|
||||||
|
elif [ "$(uci get wireless.radio1.hwmode | grep -c g)" -ne 0 ]; then
|
||||||
|
echo "radio1";
|
||||||
|
fi;`)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
radio := ssh.SSHResultToString(result)
|
||||||
|
if radio != "" {
|
||||||
|
if oldnode == nil || n.Wireless.TxPower24 != oldnode.Wireless.TxPower24 {
|
||||||
|
ssh.Execute(n.Address.String(), client, fmt.Sprintf(`
|
||||||
|
uci set wireless.%s.txpower='%d';
|
||||||
|
uci commit wireless;`,
|
||||||
|
radio, n.Wireless.TxPower24))
|
||||||
|
runWifi = true
|
||||||
|
}
|
||||||
|
if oldnode == nil || n.Wireless.Channel24 != oldnode.Wireless.Channel24 {
|
||||||
|
ssh.Execute(n.Address.String(), client, fmt.Sprintf(`
|
||||||
|
uci set wireless.%s.channel='%d';
|
||||||
|
uci commit wireless;`,
|
||||||
|
radio, n.Wireless.Channel24))
|
||||||
|
runWifi = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result, err = ssh.Run(n.Address.String(), client, `
|
||||||
|
if [ "$(uci get wireless.radio0.hwmode | grep -c a)" -ne 0 ]; then
|
||||||
|
echo "radio0";
|
||||||
|
elif [ "$(uci get wireless.radio1.hwmode | grep -c a)" -ne 0 ]; then
|
||||||
|
echo "radio1";
|
||||||
|
fi;`)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
radio = ssh.SSHResultToString(result)
|
||||||
|
if radio != "" {
|
||||||
|
if oldnode == nil || n.Wireless.TxPower5 != oldnode.Wireless.TxPower5 {
|
||||||
|
ssh.Execute(n.Address.String(), client, fmt.Sprintf(`
|
||||||
|
uci set wireless.%s.txpower='%d';
|
||||||
|
uci commit wireless;`,
|
||||||
|
radio, n.Wireless.TxPower5))
|
||||||
|
runWifi = true
|
||||||
|
}
|
||||||
|
if oldnode == nil || n.Wireless.Channel5 != oldnode.Wireless.Channel5 {
|
||||||
|
ssh.Execute(n.Address.String(), client, fmt.Sprintf(`
|
||||||
|
uci set wireless.%s.channel='%d';
|
||||||
|
uci commit wireless;`,
|
||||||
|
radio, n.Wireless.Channel5))
|
||||||
|
runWifi = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oldnode = n
|
||||||
|
}
|
|
@ -3,22 +3,23 @@ package runtime
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/FreifunkBremen/yanic/lib/jsontime"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
yanicRuntime "github.com/FreifunkBremen/yanic/runtime"
|
runtimeYanic "github.com/FreifunkBremen/yanic/runtime"
|
||||||
"github.com/genofire/golang-lib/file"
|
"github.com/genofire/golang-lib/file"
|
||||||
|
|
||||||
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Nodes struct {
|
type Nodes struct {
|
||||||
List map[string]*Node `json:"nodes"`
|
List map[string]*Node `json:"nodes"`
|
||||||
Current map[string]*Node `json:"-"`
|
Current map[string]*Node `json:"-"`
|
||||||
ssh *ssh.Manager
|
Statistics *runtimeYanic.GlobalStats `json:"-"`
|
||||||
statePath string
|
ssh *ssh.Manager
|
||||||
iface string
|
statePath string
|
||||||
notifyFunc []func(*Node, bool)
|
iface string
|
||||||
|
notifyNodeFunc []func(*Node, bool)
|
||||||
|
notifyStatsFunc []func(*runtimeYanic.GlobalStats)
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,53 +35,25 @@ func NewNodes(path string, iface string, mgmt *ssh.Manager) *Nodes {
|
||||||
return nodes
|
return nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nodes *Nodes) LearnNode(n *yanicRuntime.Node) {
|
func (nodes *Nodes) AddNotifyNode(f func(*Node, bool)) {
|
||||||
node := NewNode(n)
|
nodes.notifyNodeFunc = append(nodes.notifyNodeFunc, f)
|
||||||
if node == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
node.Lastseen = jsontime.Now()
|
|
||||||
logger := log.WithField("method", "LearnNode").WithField("node_id", node.NodeID)
|
|
||||||
nodes.Lock()
|
|
||||||
defer nodes.Unlock()
|
|
||||||
if lNode := nodes.List[node.NodeID]; lNode != nil {
|
|
||||||
lNode.Lastseen = jsontime.Now()
|
|
||||||
lNode.Stats = node.Stats
|
|
||||||
} else {
|
|
||||||
nodes.List[node.NodeID] = node
|
|
||||||
nodes.notify(node, true)
|
|
||||||
}
|
|
||||||
if _, ok := nodes.Current[node.NodeID]; ok {
|
|
||||||
nodes.Current[node.NodeID] = node
|
|
||||||
nodes.notify(node, false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// session := nodes.ssh.ConnectTo(node.Address)
|
|
||||||
result, err := nodes.ssh.RunOn(node.GetAddress(nodes.iface), "uptime")
|
|
||||||
if err != nil {
|
|
||||||
logger.Debug("init ssh command not run", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uptime := ssh.SSHResultToString(result)
|
|
||||||
logger.Infof("new node with uptime: %s", uptime)
|
|
||||||
|
|
||||||
nodes.Current[node.NodeID] = node
|
|
||||||
if lNode := nodes.List[node.NodeID]; lNode != nil {
|
|
||||||
lNode.Address = node.Address
|
|
||||||
go lNode.SSHUpdate(nodes.ssh, nodes.iface, node)
|
|
||||||
}
|
|
||||||
nodes.notify(node, false)
|
|
||||||
}
|
}
|
||||||
|
func (nodes *Nodes) notifyNode(node *Node, system bool) {
|
||||||
func (nodes *Nodes) AddNotify(f func(*Node, bool)) {
|
for _, f := range nodes.notifyNodeFunc {
|
||||||
nodes.notifyFunc = append(nodes.notifyFunc, f)
|
|
||||||
}
|
|
||||||
func (nodes *Nodes) notify(node *Node, system bool) {
|
|
||||||
for _, f := range nodes.notifyFunc {
|
|
||||||
f(node, system)
|
f(node, system)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (nodes *Nodes) AddNotifyStats(f func(stats *runtimeYanic.GlobalStats)) {
|
||||||
|
nodes.notifyStatsFunc = append(nodes.notifyStatsFunc, f)
|
||||||
|
}
|
||||||
|
func (nodes *Nodes) notifyStats(stats *runtimeYanic.GlobalStats) {
|
||||||
|
nodes.Statistics = stats
|
||||||
|
for _, f := range nodes.notifyStatsFunc {
|
||||||
|
f(stats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (nodes *Nodes) UpdateNode(node *Node) {
|
func (nodes *Nodes) UpdateNode(node *Node) {
|
||||||
if node == nil {
|
if node == nil {
|
||||||
log.Warn("no new node to update")
|
log.Warn("no new node to update")
|
||||||
|
@ -94,7 +67,7 @@ func (nodes *Nodes) UpdateNode(node *Node) {
|
||||||
log.Info("update node", node.NodeID)
|
log.Info("update node", node.NodeID)
|
||||||
}
|
}
|
||||||
nodes.List[node.NodeID] = node
|
nodes.List[node.NodeID] = node
|
||||||
nodes.notify(node, true)
|
nodes.notifyNode(node, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nodes *Nodes) Updater() {
|
func (nodes *Nodes) Updater() {
|
||||||
|
|
|
@ -3,15 +3,18 @@ package runtime
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
databaseYanic "github.com/FreifunkBremen/yanic/database"
|
databaseYanic "github.com/FreifunkBremen/yanic/database"
|
||||||
|
"github.com/FreifunkBremen/yanic/lib/jsontime"
|
||||||
runtimeYanic "github.com/FreifunkBremen/yanic/runtime"
|
runtimeYanic "github.com/FreifunkBremen/yanic/runtime"
|
||||||
|
|
||||||
|
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type YanicDB struct {
|
type YanicDB struct {
|
||||||
databaseYanic.Connection
|
databaseYanic.Connection
|
||||||
nodes *Nodes
|
nodes *Nodes
|
||||||
Statistics *runtimeYanic.GlobalStats
|
|
||||||
NotifyStats func(data *runtimeYanic.GlobalStats)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewYanicDB(nodes *Nodes) *YanicDB {
|
func NewYanicDB(nodes *Nodes) *YanicDB {
|
||||||
|
@ -20,18 +23,52 @@ func NewYanicDB(nodes *Nodes) *YanicDB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *YanicDB) InsertNode(node *runtimeYanic.Node) {
|
func (conn *YanicDB) InsertNode(n *runtimeYanic.Node) {
|
||||||
conn.nodes.LearnNode(node)
|
node := NewNode(n)
|
||||||
|
if node == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node.Lastseen = jsontime.Now()
|
||||||
|
logger := log.WithField("method", "LearnNode").WithField("node_id", node.NodeID)
|
||||||
|
conn.nodes.Lock()
|
||||||
|
defer conn.nodes.Unlock()
|
||||||
|
if lNode := conn.nodes.List[node.NodeID]; lNode != nil {
|
||||||
|
lNode.Lastseen = jsontime.Now()
|
||||||
|
lNode.Stats = node.Stats
|
||||||
|
} else {
|
||||||
|
conn.nodes.List[node.NodeID] = node
|
||||||
|
conn.nodes.notifyNode(node, true)
|
||||||
|
}
|
||||||
|
if _, ok := conn.nodes.Current[node.NodeID]; ok {
|
||||||
|
conn.nodes.Current[node.NodeID] = node
|
||||||
|
conn.nodes.notifyNode(node, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// session := nodes.ssh.ConnectTo(node.Address)
|
||||||
|
result, err := conn.nodes.ssh.RunOn(node.GetAddress(conn.nodes.iface), "uptime")
|
||||||
|
if err != nil {
|
||||||
|
logger.Debug("init ssh command not run", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uptime := ssh.SSHResultToString(result)
|
||||||
|
logger.Infof("new node with uptime: %s", uptime)
|
||||||
|
|
||||||
|
conn.nodes.Current[node.NodeID] = node
|
||||||
|
if lNode := conn.nodes.List[node.NodeID]; lNode != nil {
|
||||||
|
lNode.Address = node.Address
|
||||||
|
go lNode.SSHUpdate(conn.nodes.ssh, conn.nodes.iface, node)
|
||||||
|
}
|
||||||
|
conn.nodes.notifyNode(node, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *YanicDB) InsertLink(link *runtimeYanic.Link, time time.Time) {
|
func (conn *YanicDB) InsertLink(link *runtimeYanic.Link, time time.Time) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *YanicDB) InsertGlobals(stats *runtimeYanic.GlobalStats, time time.Time, site string, domain string) {
|
func (conn *YanicDB) InsertGlobals(stats *runtimeYanic.GlobalStats, time time.Time, site string, domain string) {
|
||||||
conn.Statistics = stats
|
if runtimeYanic.GLOBAL_SITE == site && runtimeYanic.GLOBAL_DOMAIN == domain {
|
||||||
if conn.NotifyStats != nil {
|
conn.nodes.notifyStats(stats)
|
||||||
conn.NotifyStats(stats)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *YanicDB) PruneNodes(deleteAfter time.Duration) {
|
func (conn *YanicDB) PruneNodes(deleteAfter time.Duration) {
|
||||||
|
|
|
@ -14,10 +14,10 @@ func (m *Manager) ExecuteOn(addr net.TCPAddr, cmd string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
return m.execute(addr.IP.String(), client, cmd)
|
return Execute(addr.IP.String(), client, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) execute(host string, client *ssh.Client, cmd string) error {
|
func Execute(host string, client *ssh.Client, cmd string) error {
|
||||||
session, err := client.NewSession()
|
session, err := client.NewSession()
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,10 @@ func (m *Manager) RunOn(addr net.TCPAddr, cmd string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
return m.run(addr.IP.String(), client, cmd)
|
return Run(addr.IP.String(), client, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) run(host string, client *ssh.Client, cmd string) (string, error) {
|
func Run(host string, client *ssh.Client, cmd string) (string, error) {
|
||||||
session, err := client.NewSession()
|
session, err := client.NewSession()
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"presets": ["env"]
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
node_modules
|
||||||
|
data
|
||||||
|
app.js
|
||||||
|
app.js.map
|
||||||
|
styles.css
|
||||||
|
styles.css.map
|
|
@ -0,0 +1,16 @@
|
||||||
|
nav .login {
|
||||||
|
float: right;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none !important;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 1em .5em;
|
||||||
|
height: 50px;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #fff;
|
||||||
|
padding-left: 2px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
|
||||||
|
header {
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nav {
|
||||||
|
background-color: #373636;
|
||||||
|
position: fixed;
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: 700;
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul > li {
|
||||||
|
float:left;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
a,
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none !important;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 1.1em .5em;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.item-1 {
|
||||||
|
background: #ffb400;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.item-2 {
|
||||||
|
background: #dc0067;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.item-3 {
|
||||||
|
background: #ccc;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
.notifications {
|
||||||
|
position: absolute;
|
||||||
|
right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notify {
|
||||||
|
position: relative;
|
||||||
|
min-height: 1em;
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 1em 1.5em;
|
||||||
|
color: rgba(0,0,0,.87);
|
||||||
|
-webkit-transition: opacity .1s ease,color .1s ease,background .1s ease,box-shadow .1s ease;
|
||||||
|
transition: opacity .1s ease,color .1s ease,background .1s ease,box-shadow .1s ease;
|
||||||
|
box-shadow: 0 0 0 1px rgba(34,36,38,.22) inset, 0 0 0 0 transparent;
|
||||||
|
background: #ccc;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.notify.success {
|
||||||
|
background: #009ee0;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.notify.warn {
|
||||||
|
background: #ffb400;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.notify.error {
|
||||||
|
background: #dc0067;
|
||||||
|
color: #fff;
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
nav .status {
|
||||||
|
float: right;
|
||||||
|
background-color: #009ee0;
|
||||||
|
color: white;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
|
||||||
|
&.connecting,
|
||||||
|
&.running {
|
||||||
|
background-color: #ffb400;
|
||||||
|
animation: blinkDot 2s infinite;
|
||||||
|
}
|
||||||
|
&.offline,
|
||||||
|
&.failed {
|
||||||
|
background-color: #dc0067;
|
||||||
|
animation: blinkDot 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes blinkDot {
|
||||||
|
50% {
|
||||||
|
background-color: rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,62 +0,0 @@
|
||||||
.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;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.console {
|
|
||||||
width: 100%;
|
|
||||||
border-spacing: 0px;
|
|
||||||
}
|
|
||||||
.console > tr {
|
|
||||||
clear: both;
|
|
||||||
border-spacing: 0px;
|
|
||||||
|
|
||||||
}
|
|
||||||
.console > tr > td {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0px;
|
|
||||||
border-spacing: 0px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
.console > tr.cmd > td {
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
.console > tr:not(.cmd) > td {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
.console table,
|
|
||||||
.console table tr,
|
|
||||||
.console table td {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0px;
|
|
||||||
margin: 0px;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
.console .time, .console .host{
|
|
||||||
color: #009ee0;
|
|
||||||
width: 1%;
|
|
||||||
}
|
|
||||||
.console .status {
|
|
||||||
text-align: right;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.console table {
|
|
||||||
background-color: #ccc;
|
|
||||||
}
|
|
||||||
.console table .status {
|
|
||||||
width: 18px;
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 696 B |
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 618 B |
|
@ -1,624 +0,0 @@
|
||||||
/* required styles */
|
|
||||||
|
|
||||||
.leaflet-pane,
|
|
||||||
.leaflet-tile,
|
|
||||||
.leaflet-marker-icon,
|
|
||||||
.leaflet-marker-shadow,
|
|
||||||
.leaflet-tile-container,
|
|
||||||
.leaflet-pane > svg,
|
|
||||||
.leaflet-pane > canvas,
|
|
||||||
.leaflet-zoom-box,
|
|
||||||
.leaflet-image-layer,
|
|
||||||
.leaflet-layer {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
.leaflet-container {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.leaflet-tile,
|
|
||||||
.leaflet-marker-icon,
|
|
||||||
.leaflet-marker-shadow {
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-drag: none;
|
|
||||||
}
|
|
||||||
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
|
|
||||||
.leaflet-safari .leaflet-tile {
|
|
||||||
image-rendering: -webkit-optimize-contrast;
|
|
||||||
}
|
|
||||||
/* hack that prevents hw layers "stretching" when loading new tiles */
|
|
||||||
.leaflet-safari .leaflet-tile-container {
|
|
||||||
width: 1600px;
|
|
||||||
height: 1600px;
|
|
||||||
-webkit-transform-origin: 0 0;
|
|
||||||
}
|
|
||||||
.leaflet-marker-icon,
|
|
||||||
.leaflet-marker-shadow {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
|
||||||
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
|
||||||
.leaflet-container .leaflet-overlay-pane svg,
|
|
||||||
.leaflet-container .leaflet-marker-pane img,
|
|
||||||
.leaflet-container .leaflet-shadow-pane img,
|
|
||||||
.leaflet-container .leaflet-tile-pane img,
|
|
||||||
.leaflet-container img.leaflet-image-layer {
|
|
||||||
max-width: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-container.leaflet-touch-zoom {
|
|
||||||
-ms-touch-action: pan-x pan-y;
|
|
||||||
touch-action: pan-x pan-y;
|
|
||||||
}
|
|
||||||
.leaflet-container.leaflet-touch-drag {
|
|
||||||
-ms-touch-action: pinch-zoom;
|
|
||||||
}
|
|
||||||
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
|
|
||||||
-ms-touch-action: none;
|
|
||||||
touch-action: none;
|
|
||||||
}
|
|
||||||
.leaflet-tile {
|
|
||||||
filter: inherit;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
.leaflet-tile-loaded {
|
|
||||||
visibility: inherit;
|
|
||||||
}
|
|
||||||
.leaflet-zoom-box {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
z-index: 800;
|
|
||||||
}
|
|
||||||
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
|
|
||||||
.leaflet-overlay-pane svg {
|
|
||||||
-moz-user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-pane { z-index: 400; }
|
|
||||||
|
|
||||||
.leaflet-tile-pane { z-index: 200; }
|
|
||||||
.leaflet-overlay-pane { z-index: 400; }
|
|
||||||
.leaflet-shadow-pane { z-index: 500; }
|
|
||||||
.leaflet-marker-pane { z-index: 600; }
|
|
||||||
.leaflet-tooltip-pane { z-index: 650; }
|
|
||||||
.leaflet-popup-pane { z-index: 700; }
|
|
||||||
|
|
||||||
.leaflet-map-pane canvas { z-index: 100; }
|
|
||||||
.leaflet-map-pane svg { z-index: 200; }
|
|
||||||
|
|
||||||
.leaflet-vml-shape {
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
.lvml {
|
|
||||||
behavior: url(#default#VML);
|
|
||||||
display: inline-block;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* control positioning */
|
|
||||||
|
|
||||||
.leaflet-control {
|
|
||||||
position: relative;
|
|
||||||
z-index: 800;
|
|
||||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
.leaflet-top,
|
|
||||||
.leaflet-bottom {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1000;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.leaflet-top {
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
.leaflet-right {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
.leaflet-bottom {
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
.leaflet-left {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
.leaflet-control {
|
|
||||||
float: left;
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
.leaflet-right .leaflet-control {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.leaflet-top .leaflet-control {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
.leaflet-bottom .leaflet-control {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.leaflet-left .leaflet-control {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
.leaflet-right .leaflet-control {
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* zoom and fade animations */
|
|
||||||
|
|
||||||
.leaflet-fade-anim .leaflet-tile {
|
|
||||||
will-change: opacity;
|
|
||||||
}
|
|
||||||
.leaflet-fade-anim .leaflet-popup {
|
|
||||||
opacity: 0;
|
|
||||||
-webkit-transition: opacity 0.2s linear;
|
|
||||||
-moz-transition: opacity 0.2s linear;
|
|
||||||
-o-transition: opacity 0.2s linear;
|
|
||||||
transition: opacity 0.2s linear;
|
|
||||||
}
|
|
||||||
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
.leaflet-zoom-animated {
|
|
||||||
-webkit-transform-origin: 0 0;
|
|
||||||
-ms-transform-origin: 0 0;
|
|
||||||
transform-origin: 0 0;
|
|
||||||
}
|
|
||||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
|
||||||
will-change: transform;
|
|
||||||
}
|
|
||||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
|
||||||
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
|
||||||
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
|
||||||
-o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
|
|
||||||
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
|
||||||
}
|
|
||||||
.leaflet-zoom-anim .leaflet-tile,
|
|
||||||
.leaflet-pan-anim .leaflet-tile {
|
|
||||||
-webkit-transition: none;
|
|
||||||
-moz-transition: none;
|
|
||||||
-o-transition: none;
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-zoom-anim .leaflet-zoom-hide {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* cursors */
|
|
||||||
|
|
||||||
.leaflet-interactive {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.leaflet-grab {
|
|
||||||
cursor: -webkit-grab;
|
|
||||||
cursor: -moz-grab;
|
|
||||||
}
|
|
||||||
.leaflet-crosshair,
|
|
||||||
.leaflet-crosshair .leaflet-interactive {
|
|
||||||
cursor: crosshair;
|
|
||||||
}
|
|
||||||
.leaflet-popup-pane,
|
|
||||||
.leaflet-control {
|
|
||||||
cursor: auto;
|
|
||||||
}
|
|
||||||
.leaflet-dragging .leaflet-grab,
|
|
||||||
.leaflet-dragging .leaflet-grab .leaflet-interactive,
|
|
||||||
.leaflet-dragging .leaflet-marker-draggable {
|
|
||||||
cursor: move;
|
|
||||||
cursor: -webkit-grabbing;
|
|
||||||
cursor: -moz-grabbing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* marker & overlays interactivity */
|
|
||||||
.leaflet-marker-icon,
|
|
||||||
.leaflet-marker-shadow,
|
|
||||||
.leaflet-image-layer,
|
|
||||||
.leaflet-pane > svg path,
|
|
||||||
.leaflet-tile-container {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-marker-icon.leaflet-interactive,
|
|
||||||
.leaflet-image-layer.leaflet-interactive,
|
|
||||||
.leaflet-pane > svg path.leaflet-interactive {
|
|
||||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* visual tweaks */
|
|
||||||
|
|
||||||
.leaflet-container {
|
|
||||||
background: #ddd;
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
.leaflet-container a {
|
|
||||||
color: #0078A8;
|
|
||||||
}
|
|
||||||
.leaflet-container a.leaflet-active {
|
|
||||||
outline: 2px solid orange;
|
|
||||||
}
|
|
||||||
.leaflet-zoom-box {
|
|
||||||
border: 2px dotted #38f;
|
|
||||||
background: rgba(255,255,255,0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* general typography */
|
|
||||||
.leaflet-container {
|
|
||||||
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* general toolbar styles */
|
|
||||||
|
|
||||||
.leaflet-bar {
|
|
||||||
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.leaflet-bar a,
|
|
||||||
.leaflet-bar a:hover {
|
|
||||||
background-color: #fff;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
width: 26px;
|
|
||||||
height: 26px;
|
|
||||||
line-height: 26px;
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
.leaflet-bar a,
|
|
||||||
.leaflet-control-layers-toggle {
|
|
||||||
background-position: 50% 50%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.leaflet-bar a:hover {
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
}
|
|
||||||
.leaflet-bar a:first-child {
|
|
||||||
border-top-left-radius: 4px;
|
|
||||||
border-top-right-radius: 4px;
|
|
||||||
}
|
|
||||||
.leaflet-bar a:last-child {
|
|
||||||
border-bottom-left-radius: 4px;
|
|
||||||
border-bottom-right-radius: 4px;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
.leaflet-bar a.leaflet-disabled {
|
|
||||||
cursor: default;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
color: #bbb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-touch .leaflet-bar a {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* zoom control */
|
|
||||||
|
|
||||||
.leaflet-control-zoom-in,
|
|
||||||
.leaflet-control-zoom-out {
|
|
||||||
font: bold 18px 'Lucida Console', Monaco, monospace;
|
|
||||||
text-indent: 1px;
|
|
||||||
}
|
|
||||||
.leaflet-control-zoom-out {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-touch .leaflet-control-zoom-in {
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
.leaflet-touch .leaflet-control-zoom-out {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* layers control */
|
|
||||||
|
|
||||||
.leaflet-control-layers {
|
|
||||||
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers-toggle {
|
|
||||||
background-image: url(images/layers.png);
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
.leaflet-retina .leaflet-control-layers-toggle {
|
|
||||||
background-image: url(images/layers-2x.png);
|
|
||||||
background-size: 26px 26px;
|
|
||||||
}
|
|
||||||
.leaflet-touch .leaflet-control-layers-toggle {
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers .leaflet-control-layers-list,
|
|
||||||
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers-expanded {
|
|
||||||
padding: 6px 10px 6px 6px;
|
|
||||||
color: #333;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers-scrollbar {
|
|
||||||
overflow-y: scroll;
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers-selector {
|
|
||||||
margin-top: 2px;
|
|
||||||
position: relative;
|
|
||||||
top: 1px;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers label {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.leaflet-control-layers-separator {
|
|
||||||
height: 0;
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
margin: 5px -10px 5px -6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Default icon URLs */
|
|
||||||
.leaflet-default-icon-path {
|
|
||||||
background-image: url(images/marker-icon.png);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* attribution and scale controls */
|
|
||||||
|
|
||||||
.leaflet-container .leaflet-control-attribution {
|
|
||||||
background: #fff;
|
|
||||||
background: rgba(255, 255, 255, 0.7);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.leaflet-control-attribution,
|
|
||||||
.leaflet-control-scale-line {
|
|
||||||
padding: 0 5px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.leaflet-control-attribution a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.leaflet-control-attribution a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.leaflet-container .leaflet-control-attribution,
|
|
||||||
.leaflet-container .leaflet-control-scale {
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
.leaflet-left .leaflet-control-scale {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
.leaflet-bottom .leaflet-control-scale {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
.leaflet-control-scale-line {
|
|
||||||
border: 2px solid #777;
|
|
||||||
border-top: none;
|
|
||||||
line-height: 1.1;
|
|
||||||
padding: 2px 5px 1px;
|
|
||||||
font-size: 11px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
background: #fff;
|
|
||||||
background: rgba(255, 255, 255, 0.5);
|
|
||||||
}
|
|
||||||
.leaflet-control-scale-line:not(:first-child) {
|
|
||||||
border-top: 2px solid #777;
|
|
||||||
border-bottom: none;
|
|
||||||
margin-top: -2px;
|
|
||||||
}
|
|
||||||
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
|
||||||
border-bottom: 2px solid #777;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-touch .leaflet-control-attribution,
|
|
||||||
.leaflet-touch .leaflet-control-layers,
|
|
||||||
.leaflet-touch .leaflet-bar {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
.leaflet-touch .leaflet-control-layers,
|
|
||||||
.leaflet-touch .leaflet-bar {
|
|
||||||
border: 2px solid rgba(0,0,0,0.2);
|
|
||||||
background-clip: padding-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* popup */
|
|
||||||
|
|
||||||
.leaflet-popup {
|
|
||||||
position: absolute;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.leaflet-popup-content-wrapper {
|
|
||||||
padding: 1px;
|
|
||||||
text-align: left;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
.leaflet-popup-content {
|
|
||||||
margin: 13px 19px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
.leaflet-popup-content p {
|
|
||||||
margin: 18px 0;
|
|
||||||
}
|
|
||||||
.leaflet-popup-tip-container {
|
|
||||||
width: 40px;
|
|
||||||
height: 20px;
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
margin-left: -20px;
|
|
||||||
overflow: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.leaflet-popup-tip {
|
|
||||||
width: 17px;
|
|
||||||
height: 17px;
|
|
||||||
padding: 1px;
|
|
||||||
|
|
||||||
margin: -10px auto 0;
|
|
||||||
|
|
||||||
-webkit-transform: rotate(45deg);
|
|
||||||
-moz-transform: rotate(45deg);
|
|
||||||
-ms-transform: rotate(45deg);
|
|
||||||
-o-transform: rotate(45deg);
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
.leaflet-popup-content-wrapper,
|
|
||||||
.leaflet-popup-tip {
|
|
||||||
background: white;
|
|
||||||
color: #333;
|
|
||||||
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
|
||||||
}
|
|
||||||
.leaflet-container a.leaflet-popup-close-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 4px 4px 0 0;
|
|
||||||
border: none;
|
|
||||||
text-align: center;
|
|
||||||
width: 18px;
|
|
||||||
height: 14px;
|
|
||||||
font: 16px/14px Tahoma, Verdana, sans-serif;
|
|
||||||
color: #c3c3c3;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: bold;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.leaflet-container a.leaflet-popup-close-button:hover {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
.leaflet-popup-scrolled {
|
|
||||||
overflow: auto;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-oldie .leaflet-popup-content-wrapper {
|
|
||||||
zoom: 1;
|
|
||||||
}
|
|
||||||
.leaflet-oldie .leaflet-popup-tip {
|
|
||||||
width: 24px;
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
|
||||||
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
|
||||||
}
|
|
||||||
.leaflet-oldie .leaflet-popup-tip-container {
|
|
||||||
margin-top: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-oldie .leaflet-control-zoom,
|
|
||||||
.leaflet-oldie .leaflet-control-layers,
|
|
||||||
.leaflet-oldie .leaflet-popup-content-wrapper,
|
|
||||||
.leaflet-oldie .leaflet-popup-tip {
|
|
||||||
border: 1px solid #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* div icon */
|
|
||||||
|
|
||||||
.leaflet-div-icon {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Tooltip */
|
|
||||||
/* Base styles for the element that has a tooltip */
|
|
||||||
.leaflet-tooltip {
|
|
||||||
position: absolute;
|
|
||||||
padding: 6px;
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid #fff;
|
|
||||||
border-radius: 3px;
|
|
||||||
color: #222;
|
|
||||||
white-space: nowrap;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
pointer-events: none;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
|
||||||
}
|
|
||||||
.leaflet-tooltip.leaflet-clickable {
|
|
||||||
cursor: pointer;
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-top:before,
|
|
||||||
.leaflet-tooltip-bottom:before,
|
|
||||||
.leaflet-tooltip-left:before,
|
|
||||||
.leaflet-tooltip-right:before {
|
|
||||||
position: absolute;
|
|
||||||
pointer-events: none;
|
|
||||||
border: 6px solid transparent;
|
|
||||||
background: transparent;
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Directions */
|
|
||||||
|
|
||||||
.leaflet-tooltip-bottom {
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-top {
|
|
||||||
margin-top: -6px;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-bottom:before,
|
|
||||||
.leaflet-tooltip-top:before {
|
|
||||||
left: 50%;
|
|
||||||
margin-left: -6px;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-top:before {
|
|
||||||
bottom: 0;
|
|
||||||
margin-bottom: -12px;
|
|
||||||
border-top-color: #fff;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-bottom:before {
|
|
||||||
top: 0;
|
|
||||||
margin-top: -12px;
|
|
||||||
margin-left: -6px;
|
|
||||||
border-bottom-color: #fff;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-left {
|
|
||||||
margin-left: -6px;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-right {
|
|
||||||
margin-left: 6px;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-left:before,
|
|
||||||
.leaflet-tooltip-right:before {
|
|
||||||
top: 50%;
|
|
||||||
margin-top: -6px;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-left:before {
|
|
||||||
right: 0;
|
|
||||||
margin-right: -12px;
|
|
||||||
border-left-color: #fff;
|
|
||||||
}
|
|
||||||
.leaflet-tooltip-right:before {
|
|
||||||
left: 0;
|
|
||||||
margin-left: -12px;
|
|
||||||
border-right-color: #fff;
|
|
||||||
}
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
@import "_notify.less";
|
||||||
|
@import "_menu.less";
|
||||||
|
@import "_status.less";
|
||||||
|
@import "_login.less";
|
||||||
|
@import "../node_modules/leaflet/dist/leaflet.css";
|
||||||
|
@import "_map.less";
|
||||||
|
|
||||||
body {
|
body {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
@ -6,20 +13,6 @@ body {
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||||
}
|
}
|
||||||
.status {
|
|
||||||
float: right;
|
|
||||||
background: #009ee0;
|
|
||||||
color: white;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
.status.connecting,.status.running {
|
|
||||||
background: #ffb400;
|
|
||||||
}
|
|
||||||
.status.offline, .status.failed {
|
|
||||||
background: #dc0067;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
span.online {
|
span.online {
|
||||||
color: #009ee0;
|
color: #009ee0;
|
||||||
}
|
}
|
||||||
|
@ -29,85 +22,6 @@ span.offline {
|
||||||
h1 {
|
h1 {
|
||||||
border-bottom: 4px solid #dc0067;
|
border-bottom: 4px solid #dc0067;
|
||||||
}
|
}
|
||||||
header {
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
header > div {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
nav {
|
|
||||||
background-color: #373636;
|
|
||||||
position: fixed;
|
|
||||||
display: inline-block;
|
|
||||||
font-weight: 700;
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
nav ul {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
nav li {
|
|
||||||
float:left;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
nav li:hover, nav.active {
|
|
||||||
background: rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
|
||||||
nav li a, nav li span {
|
|
||||||
display: inline-block;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none !important;
|
|
||||||
text-align: center;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: inherit;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 1.1em .5em;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
nav > ul > .item-1 {
|
|
||||||
background: #ffb400;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
nav > ul > .item-2 {
|
|
||||||
background: #dc0067;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
nav > ul > .item-3 {
|
|
||||||
background: #ccc;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.notifications {
|
|
||||||
position: absolute;
|
|
||||||
right: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notify {
|
|
||||||
position: relative;
|
|
||||||
min-height: 1em;
|
|
||||||
margin: 1em 0;
|
|
||||||
padding: 1em 1.5em;
|
|
||||||
color: rgba(0,0,0,.87);
|
|
||||||
-webkit-transition: opacity .1s ease,color .1s ease,background .1s ease,box-shadow .1s ease;
|
|
||||||
transition: opacity .1s ease,color .1s ease,background .1s ease,box-shadow .1s ease;
|
|
||||||
box-shadow: 0 0 0 1px rgba(34,36,38,.22) inset, 0 0 0 0 transparent;
|
|
||||||
background: #ccc;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.notify.success {
|
|
||||||
background: #009ee0;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.notify.warn {
|
|
||||||
background: #ffb400;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.notify.error {
|
|
||||||
background: #dc0067;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
thead {
|
thead {
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
|
@ -0,0 +1,94 @@
|
||||||
|
import browserSync from 'browser-sync';
|
||||||
|
import browserify from 'browserify';
|
||||||
|
import buffer from 'vinyl-buffer';
|
||||||
|
import gulp from 'gulp';
|
||||||
|
import gulpLoadPlugins from 'gulp-load-plugins';
|
||||||
|
import source from 'vinyl-source-stream';
|
||||||
|
import sourcemaps from 'gulp-sourcemaps';
|
||||||
|
import watchify from 'watchify';
|
||||||
|
|
||||||
|
const gulpPlugins = gulpLoadPlugins();
|
||||||
|
|
||||||
|
function bundle (watching = false) {
|
||||||
|
const browserifyConf = {
|
||||||
|
'debug': true,
|
||||||
|
'entries': ['js/index.js'],
|
||||||
|
'transform': ['babelify']};
|
||||||
|
|
||||||
|
if (watching) {
|
||||||
|
browserifyConf.plugin = [watchify];
|
||||||
|
}
|
||||||
|
|
||||||
|
const browser = browserify(browserifyConf);
|
||||||
|
|
||||||
|
function bundler () {
|
||||||
|
return browser.bundle().
|
||||||
|
on('error', (err) => {
|
||||||
|
console.log(err.message);
|
||||||
|
}).
|
||||||
|
pipe(source('app.js')).
|
||||||
|
pipe(buffer()).
|
||||||
|
pipe(sourcemaps.init({'loadMaps': true})).
|
||||||
|
pipe(gulpPlugins.uglify({
|
||||||
|
'mangle': {'reserved': ['moment']}
|
||||||
|
})).
|
||||||
|
pipe(sourcemaps.write('./')).
|
||||||
|
pipe(gulp.dest('./'));
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.on('update', () => {
|
||||||
|
bundler();
|
||||||
|
console.log('scripts rebuild');
|
||||||
|
});
|
||||||
|
|
||||||
|
return bundler();
|
||||||
|
}
|
||||||
|
|
||||||
|
gulp.task('scripts', () => {
|
||||||
|
bundle();
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('styles', () => {
|
||||||
|
gulp.src('css/styles.less').
|
||||||
|
pipe(gulpPlugins.plumber()).
|
||||||
|
pipe(sourcemaps.init()).
|
||||||
|
pipe(gulpPlugins.less({
|
||||||
|
'includePaths': ['.']
|
||||||
|
})).
|
||||||
|
pipe(gulpPlugins.autoprefixer()).
|
||||||
|
pipe(sourcemaps.write('./')).
|
||||||
|
pipe(gulp.dest('./'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('build', [
|
||||||
|
'scripts',
|
||||||
|
'styles'
|
||||||
|
]);
|
||||||
|
|
||||||
|
gulp.task('watch', () => {
|
||||||
|
bundle(true);
|
||||||
|
gulp.watch('css/**/*.less', ['styles']);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('serve', ['watch'], () => {
|
||||||
|
browserSync({
|
||||||
|
'notify': false,
|
||||||
|
'port': 9000,
|
||||||
|
// Proxy: 'example.com',
|
||||||
|
'server': {
|
||||||
|
'baseDir': '.'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.watch([
|
||||||
|
'**/*.html',
|
||||||
|
'**/*.php',
|
||||||
|
'styles.css',
|
||||||
|
'app.js'
|
||||||
|
]).on('change', browserSync.reload);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('default', [
|
||||||
|
'build',
|
||||||
|
'serve'
|
||||||
|
]);
|
|
@ -10,42 +10,10 @@
|
||||||
<link rel="manifest" href="/manifest.json">
|
<link rel="manifest" href="/manifest.json">
|
||||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
<link rel="stylesheet" href="/css/leaflet.css">
|
<link rel="stylesheet" href="/styles.css">
|
||||||
<link rel="stylesheet" href="/css/main.css">
|
<script src="/app.js"></script>
|
||||||
<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>
|
|
||||||
<script src="/js/leaflet.ajax.min.js"></script>
|
|
||||||
<script src="/js/webgl-heatmap.js"></script>
|
|
||||||
<script src="/js/leaflet-webgl-heatmap.min.js"></script>
|
|
||||||
<script src="/js/config.js"></script>
|
|
||||||
<script src="/js/domlib.js"></script>
|
|
||||||
<script src="/js/store.js"></script>
|
|
||||||
<script src="/js/notify.js"></script>
|
|
||||||
<script src="/js/gui_list.js"></script>
|
|
||||||
<script src="/js/gui_map.js"></script>
|
|
||||||
<script src="/js/gui_node.js"></script>
|
|
||||||
<script src="/js/gui_stats.js"></script>
|
|
||||||
<script src="/js/gui.js"></script>
|
|
||||||
<script src="/js/socket.js"></script>
|
|
||||||
<script src="/js/app.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
|
||||||
<nav>
|
|
||||||
<ul>
|
|
||||||
<li class="logo"><img src="/img/logo.svg"></li>
|
|
||||||
<li class="item-1"><a href="#/list">List</a></li>
|
|
||||||
<li class="item-2"><a href="#/map">Map</a></li>
|
|
||||||
<li class="item-3"><a href="#/statistics">Statistics</a></li>
|
|
||||||
<li class="status offline"><span onclick="location.reload(true)"></span></li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<div class="notifications"></div>
|
|
||||||
<main></main>
|
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong>JavaScript required</strong>
|
<strong>JavaScript required</strong>
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
/* exported config */
|
|
||||||
|
|
||||||
/* eslint no-magic-numbers: "off"*/
|
/* eslint no-magic-numbers: "off"*/
|
||||||
/* eslint sort-keys: "off"*/
|
/* eslint sort-keys: "off"*/
|
||||||
|
|
||||||
const config = {
|
export default {
|
||||||
'title': 'FreifunkManager - Breminale',
|
'title': 'FreifunkManager - Breminale',
|
||||||
'backend': `ws${location.protocol == 'https:' ? 's' : ''}://${location.host}/websocket`,
|
'backend': `ws${location.protocol == 'https:' ? 's' : ''}://${location.host}/ws`,
|
||||||
'node': {
|
'node': {
|
||||||
// Minuten till is shown as offline
|
// Minuten till is shown as offline
|
||||||
'offline': 5
|
'offline': 5
|
||||||
|
@ -20,22 +18,16 @@ const config = {
|
||||||
},
|
},
|
||||||
'maxZoom': 20,
|
'maxZoom': 20,
|
||||||
'tileLayer': 'https://tiles.bremen.freifunk.net/{z}/{x}/{y}.png',
|
'tileLayer': 'https://tiles.bremen.freifunk.net/{z}/{x}/{y}.png',
|
||||||
|
|
||||||
/* Heatmap settings
|
|
||||||
size: in meters (default: 30km)
|
|
||||||
opacity: in percent/100 (default: 1)
|
|
||||||
gradientTexture: url-to-texture-image (default: false)
|
|
||||||
alphaRange: change transparency in heatmap (default: 1)
|
|
||||||
autoresize: resize heatmap when map size changes (default: false)
|
|
||||||
*/
|
|
||||||
'heatmap': {
|
'heatmap': {
|
||||||
'wifi24': {
|
'wifi24': {
|
||||||
'size': 30,
|
'size': 10,
|
||||||
|
'units': 'm',
|
||||||
'opacity': 0.5,
|
'opacity': 0.5,
|
||||||
'alphaRange': 1
|
'alphaRange': 1
|
||||||
},
|
},
|
||||||
'wifi5': {
|
'wifi5': {
|
||||||
'size': 30,
|
'size': 10,
|
||||||
|
'units': 'm',
|
||||||
'opacity': 0.5,
|
'opacity': 0.5,
|
||||||
'alphaRange': 1
|
'alphaRange': 1
|
||||||
}
|
}
|
||||||
|
@ -51,7 +43,7 @@ const config = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'geojson': {
|
'geojson': {
|
||||||
'url': 'https://events.ffhb.de/data/ground.geojson',
|
'url': 'https://raw.githubusercontent.com/FreifunkBremen/internal-maps/master/breminale.geojson',
|
||||||
'pointToLayer': function pointToLayer (feature, latlng) {
|
'pointToLayer': function pointToLayer (feature, latlng) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,54 @@
|
||||||
/* exported domlin */
|
export function setProps (el, props) {
|
||||||
|
if (props) {
|
||||||
const domlib = {};
|
if (props.class) {
|
||||||
|
let classList = props.class;
|
||||||
(function init () {
|
if (typeof props.class === 'string') {
|
||||||
'use strict';
|
classList = classList.split(' ');
|
||||||
|
|
||||||
domlib.newAt = function newAt (at, eltype) {
|
|
||||||
const el = document.createElement(eltype);
|
|
||||||
|
|
||||||
at.appendChild(el);
|
|
||||||
|
|
||||||
return el;
|
|
||||||
};
|
|
||||||
domlib.removeChildren = function removeChildren (el) {
|
|
||||||
if (el) {
|
|
||||||
while (el.firstChild) {
|
|
||||||
el.removeChild(el.firstChild);
|
|
||||||
}
|
}
|
||||||
|
el.classList.add(...classList);
|
||||||
|
delete props.class;
|
||||||
}
|
}
|
||||||
};
|
Object.keys(props).map((key) => {
|
||||||
})();
|
if (key.indexOf('on') === 0 && typeof props[key] === 'function') {
|
||||||
|
// eslint-disable-next-line no-magic-numbers
|
||||||
|
return el.addEventListener(key.slice(2), props[key]);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
Object.keys(props).map((key) => el.setAttribute(key, props[key]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function newEl (eltype, props, content) {
|
||||||
|
const el = document.createElement(eltype);
|
||||||
|
setProps(el, props);
|
||||||
|
if (content) {
|
||||||
|
el.innerHTML = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function appendChild (el, child) {
|
||||||
|
if (child && !child.parentNode) {
|
||||||
|
el.appendChild(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeChild (el) {
|
||||||
|
if (el && el.parentNode) {
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-params
|
||||||
|
export function newAt (at, eltype, props, content) {
|
||||||
|
const el = document.createElement(eltype);
|
||||||
|
setProps(el, props);
|
||||||
|
if (content) {
|
||||||
|
el.innerHTML = content;
|
||||||
|
}
|
||||||
|
at.appendChild(el);
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
import * as V from 'superfine';
|
||||||
|
import * as domlib from '../domlib';
|
||||||
|
import * as socket from '../socket';
|
||||||
|
import * as store from '../store';
|
||||||
|
import View from '../view';
|
||||||
|
import {singelton as notify} from './notify';
|
||||||
|
import {render} from '../gui';
|
||||||
|
|
||||||
|
export const WINDOW_HEIGHT_MENU = 50;
|
||||||
|
|
||||||
|
export class MenuView extends View {
|
||||||
|
constructor () {
|
||||||
|
super();
|
||||||
|
this.el = document.createElement('header');
|
||||||
|
const menuContainer = domlib.newAt(this.el, 'nav');
|
||||||
|
this.menuList = domlib.newAt(menuContainer, 'ul');
|
||||||
|
|
||||||
|
const logo = domlib.newAt(this.menuList, 'li', {'class':'logo'});
|
||||||
|
domlib.newAt(logo, 'img', {'src':'/img/logo.svg'});
|
||||||
|
|
||||||
|
const aList = domlib.newAt(this.menuList, 'li', {'class':'item-1'});
|
||||||
|
domlib.newAt(aList, 'a', {'href':'#/list'}, 'List');
|
||||||
|
|
||||||
|
const aMap = domlib.newAt(this.menuList, 'li', {'class':'item-2'});
|
||||||
|
domlib.newAt(aMap, 'a', {'href':'#/map'}, 'Map');
|
||||||
|
|
||||||
|
const aStatistics= domlib.newAt(this.menuList, 'li', {'class':'item-3'});
|
||||||
|
domlib.newAt(aStatistics, 'a', {'href':'#/statistics'}, 'Statistics');
|
||||||
|
|
||||||
|
}
|
||||||
|
loginTyping(e) {
|
||||||
|
this._loginInput = e.target.value;
|
||||||
|
}
|
||||||
|
login() {
|
||||||
|
socket.sendjson({'subject': 'login', 'body': this._loginInput}, (msg) => {
|
||||||
|
if (msg.body) {
|
||||||
|
store.isLogin = true;
|
||||||
|
render();
|
||||||
|
}else {
|
||||||
|
notify.send({
|
||||||
|
'header': 'Anmeldung ist fehlgeschlagen',
|
||||||
|
'type': 'error'
|
||||||
|
}, 'Login');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._loginInput = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
socket.sendjson({'subject': 'logout'}, (msg) => {
|
||||||
|
if (msg.body) {
|
||||||
|
store.isLogin = false;
|
||||||
|
render();
|
||||||
|
} else {
|
||||||
|
notify.send({
|
||||||
|
'header': 'Abmeldung ist fehlgeschlagen',
|
||||||
|
'type': 'error'
|
||||||
|
}, 'Logout');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const socketStatus = socket.getStatus();
|
||||||
|
let statusClass = 'status ',
|
||||||
|
vLogin = V.h('li', {
|
||||||
|
'class': 'login',
|
||||||
|
}, [
|
||||||
|
V.h('input', {
|
||||||
|
'type': 'password',
|
||||||
|
'value': this._loginInput,
|
||||||
|
'oninput': this.loginTyping.bind(this),
|
||||||
|
}),
|
||||||
|
V.h('a', {
|
||||||
|
'onclick': this.login.bind(this)
|
||||||
|
}, 'Login'
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (store.isLogin) {
|
||||||
|
vLogin = V.h('li', {
|
||||||
|
'class': 'login',
|
||||||
|
'onclick': this.logout.bind(this)
|
||||||
|
}, 'Logout');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socketStatus !== 1) {
|
||||||
|
// eslint-disable-next-line no-magic-numbers
|
||||||
|
if (socketStatus === 0 || socketStatus === 2) {
|
||||||
|
statusClass += 'connecting';
|
||||||
|
} else {
|
||||||
|
statusClass += 'offline';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
V.render(this.vMenu, this.vMenu = V.h('span',{},[V.h('li', {
|
||||||
|
'class': statusClass,
|
||||||
|
'onclick': () => location.reload(true)
|
||||||
|
}), vLogin]), this.menuList);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
import * as V from 'superfine';
|
||||||
|
import View from '../view';
|
||||||
|
|
||||||
|
|
||||||
|
const DELAY_OF_NOTIFY = 15000,
|
||||||
|
MAX_MESSAGE_SHOW = 5;
|
||||||
|
|
||||||
|
class NotifyView extends View {
|
||||||
|
constructor () {
|
||||||
|
super();
|
||||||
|
this.el.classList.add('notifications');
|
||||||
|
if ('Notification' in window) {
|
||||||
|
window.Notification.requestPermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messages = [];
|
||||||
|
window.setInterval(this.removeLast.bind(this), DELAY_OF_NOTIFY);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeLast () {
|
||||||
|
this.messages.splice(0, 1);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMSG (msg) {
|
||||||
|
const {messages} = this,
|
||||||
|
content = [msg.content];
|
||||||
|
|
||||||
|
let {render} = this;
|
||||||
|
render = render.bind(this);
|
||||||
|
|
||||||
|
if (msg.header) {
|
||||||
|
content.unshift(V.h('div', {'class': 'header'}, msg.header));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return V.h(
|
||||||
|
'div', {
|
||||||
|
'class': `notify ${msg.type}`,
|
||||||
|
'onclick': () => {
|
||||||
|
const index = messages.indexOf(msg);
|
||||||
|
if (index !== -1) {
|
||||||
|
messages.splice(index, 1);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, V.h('div', {'class': 'content'}, content));
|
||||||
|
}
|
||||||
|
|
||||||
|
send (props, content) {
|
||||||
|
let msg = props;
|
||||||
|
if (typeof props === 'object') {
|
||||||
|
msg.content = content;
|
||||||
|
} else {
|
||||||
|
msg = {
|
||||||
|
'content': content,
|
||||||
|
'type': props
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ('Notification' in window &&
|
||||||
|
window.Notification.permission === 'granted') {
|
||||||
|
let body = msg.type,
|
||||||
|
title = content;
|
||||||
|
if (msg.header) {
|
||||||
|
title = msg.header;
|
||||||
|
body = msg.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-new
|
||||||
|
new window.Notification(title, {
|
||||||
|
'body': body,
|
||||||
|
'icon': '/favicon-32x32.png'
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.messages.length > MAX_MESSAGE_SHOW) {
|
||||||
|
this.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messages.push(msg);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
V.render(this.vel, this.vel = V.h('div', {'class': 'notifications'}, this.messages.map(this.renderMSG.bind(this))), this.el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line one-var
|
||||||
|
const singelton = new NotifyView();
|
||||||
|
export {singelton, NotifyView};
|
|
@ -1,101 +1,43 @@
|
||||||
/* exported gui,router */
|
import * as domlib from './domlib';
|
||||||
/* globals socket,notify,domlib,guiList,guiMap,guiStats,guiNode */
|
import {MenuView} from './element/menu';
|
||||||
|
import Navigo from '../node_modules/navigo/lib/navigo';
|
||||||
|
import View from './view';
|
||||||
|
import {singelton as notify} from './element/notify';
|
||||||
|
|
||||||
const gui = {},
|
const router = new Navigo(null, true, '#'),
|
||||||
router = new Navigo(null, true, '#');
|
elMain = domlib.newEl('main'),
|
||||||
|
elMenu = new MenuView();
|
||||||
|
|
||||||
|
export {router};
|
||||||
|
|
||||||
(function init () {
|
let init = false,
|
||||||
'use strict';
|
currentView = new View();
|
||||||
|
|
||||||
const GUI_RENDER_DEBOUNCER_TIME = 100;
|
export function render () {
|
||||||
|
if (!document.body) {
|
||||||
let currentView = {
|
return;
|
||||||
'bind': function bind () {
|
|
||||||
console.warn('Do not run dummies');
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line func-name-matching
|
|
||||||
'render': function renderDummy () {
|
|
||||||
console.warn('DO not run dummies');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function renderView () {
|
|
||||||
// eslint-disable-next-line prefer-destructuring
|
|
||||||
const status = document.getElementsByClassName('status')[0];
|
|
||||||
|
|
||||||
if (!status) {
|
|
||||||
console.log('unable to render, render later');
|
|
||||||
window.setTimeout(renderView, GUI_RENDER_DEBOUNCER_TIME);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
status.classList.remove('connecting', 'offline');
|
|
||||||
if (socket.readyState !== 1) {
|
|
||||||
let statusClass = 'offline';
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-magic-numbers
|
|
||||||
if (socket.readyState === 0 || socket.readyState === 2) {
|
|
||||||
statusClass = 'connecting';
|
|
||||||
}
|
|
||||||
status.classList.add(statusClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-destructuring
|
|
||||||
notify.bind(document.getElementsByClassName('notifications')[0]);
|
|
||||||
|
|
||||||
currentView.render();
|
|
||||||
router.resolve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setView (toView) {
|
if (!init) {
|
||||||
currentView = toView;
|
elMenu.bind(document.body);
|
||||||
const main = document.querySelector('main');
|
notify.bind(document.body);
|
||||||
|
|
||||||
domlib.removeChildren(main);
|
document.body.appendChild(elMain);
|
||||||
currentView.bind(main);
|
|
||||||
currentView.render();
|
init = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
router.on({
|
currentView.render();
|
||||||
'/list': function routerList () {
|
|
||||||
setView(guiList);
|
|
||||||
},
|
|
||||||
'/map': function routerMap () {
|
|
||||||
setView(guiMap);
|
|
||||||
},
|
|
||||||
'/n/:nodeID': {
|
|
||||||
'as': 'node',
|
|
||||||
// eslint-disable-next-line func-name-matching
|
|
||||||
'uses': function routerNode (params) {
|
|
||||||
guiNode.setNodeID(params.nodeID.toLowerCase());
|
|
||||||
setView(guiNode);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'/statistics': function routerStats () {
|
|
||||||
setView(guiStats);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
notify.render();
|
||||||
router.on(() => {
|
elMenu.render();
|
||||||
router.navigate('/list');
|
|
||||||
});
|
|
||||||
|
|
||||||
gui.render = function render () {
|
router.resolve();
|
||||||
let timeout = false;
|
}
|
||||||
|
|
||||||
function reset () {
|
export function setView (toView) {
|
||||||
timeout = null;
|
currentView.unbind();
|
||||||
}
|
currentView = toView;
|
||||||
|
currentView.bind(elMain);
|
||||||
if (timeout) {
|
currentView.render();
|
||||||
console('skip rendering, because to often');
|
}
|
||||||
window.clearTimeout(timeout);
|
|
||||||
} else {
|
|
||||||
renderView();
|
|
||||||
}
|
|
||||||
timeout = window.setTimeout(reset, GUI_RENDER_DEBOUNCER_TIME);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.onload = gui.render;
|
|
||||||
})();
|
|
||||||
|
|
|
@ -1,199 +0,0 @@
|
||||||
/* exported guiNode */
|
|
||||||
/* globals store, socket, domlib, config,notify */
|
|
||||||
|
|
||||||
const guiNode = {};
|
|
||||||
|
|
||||||
(function init () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const view = guiNode;
|
|
||||||
|
|
||||||
let container = null,
|
|
||||||
el = null,
|
|
||||||
|
|
||||||
titleName = null,
|
|
||||||
titleID = null,
|
|
||||||
ago = null,
|
|
||||||
|
|
||||||
hostnameInput = null,
|
|
||||||
|
|
||||||
marker = null,
|
|
||||||
map = null,
|
|
||||||
geoJsonLayer = null,
|
|
||||||
btnGPS = null,
|
|
||||||
|
|
||||||
editLocationGPS = null,
|
|
||||||
storePosition = null,
|
|
||||||
currentNodeID = null,
|
|
||||||
editing = false;
|
|
||||||
|
|
||||||
function updatePosition (lat, lng) {
|
|
||||||
const node = store.getNode(currentNodeID),
|
|
||||||
newLat = lat || storePosition.latitude || false,
|
|
||||||
newlng = lng || storePosition.longitude || false;
|
|
||||||
|
|
||||||
if (!newLat || !newlng) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
node.location = {
|
|
||||||
'latitude': newLat,
|
|
||||||
'longitude': newlng
|
|
||||||
};
|
|
||||||
socket.sendnode(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
function update () {
|
|
||||||
geoJsonLayer.refresh();
|
|
||||||
titleID.innerHTML = currentNodeID;
|
|
||||||
const node = store.getNode(currentNodeID),
|
|
||||||
startdate = new Date();
|
|
||||||
|
|
||||||
if (!node) {
|
|
||||||
console.log(`node not found: ${currentNodeID}`);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
startdate.setMinutes(startdate.getMinutes() - config.node.offline);
|
|
||||||
if (new Date(node.lastseen) < startdate) {
|
|
||||||
ago.classList.add('offline');
|
|
||||||
ago.classList.remove('online');
|
|
||||||
} else {
|
|
||||||
ago.classList.remove('offline');
|
|
||||||
ago.classList.add('online');
|
|
||||||
}
|
|
||||||
ago.innerHTML = `${moment(node.lastseen).fromNow()} (${node.lastseen})`;
|
|
||||||
if (editLocationGPS || editing || !node.location || !node.location.latitude || !node.location.longitude) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
titleName.innerHTML = node.hostname;
|
|
||||||
hostnameInput.value = node.hostname;
|
|
||||||
// eslint-disable-next-line one-var
|
|
||||||
const latlng = [node.location.latitude, node.location.longitude];
|
|
||||||
|
|
||||||
map.setView(latlng);
|
|
||||||
marker.setLatLng(latlng);
|
|
||||||
marker.setOpacity(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
view.setNodeID = function setNodeID (nodeID) {
|
|
||||||
currentNodeID = nodeID;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 node');
|
|
||||||
el = domlib.newAt(container, 'div');
|
|
||||||
|
|
||||||
const title = domlib.newAt(el, 'h1'),
|
|
||||||
lastseen = domlib.newAt(el, 'p'),
|
|
||||||
hostname = domlib.newAt(el, 'p'),
|
|
||||||
mapEl = domlib.newAt(el, 'div');
|
|
||||||
|
|
||||||
titleName = domlib.newAt(title, 'span');
|
|
||||||
title.appendChild(document.createTextNode(' - '));
|
|
||||||
titleID = domlib.newAt(title, 'i');
|
|
||||||
|
|
||||||
|
|
||||||
domlib.newAt(lastseen, 'span').innerHTML = 'Lastseen: ';
|
|
||||||
ago = domlib.newAt(lastseen, 'span');
|
|
||||||
|
|
||||||
domlib.newAt(hostname, 'span').innerHTML = 'Hostname: ';
|
|
||||||
hostnameInput = domlib.newAt(hostname, 'input');
|
|
||||||
hostnameInput.setAttribute('placeholder', 'Hostname');
|
|
||||||
hostnameInput.addEventListener('focusin', () => {
|
|
||||||
editing = true;
|
|
||||||
});
|
|
||||||
hostnameInput.addEventListener('focusout', () => {
|
|
||||||
editing = false;
|
|
||||||
|
|
||||||
const node = store.getNode(currentNodeID);
|
|
||||||
|
|
||||||
node.hostname = hostnameInput.value;
|
|
||||||
|
|
||||||
socket.sendnode(node);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
mapEl.style.height = '300px';
|
|
||||||
map = L.map(mapEl).setView(config.map.view.bound, config.map.view.zoom);
|
|
||||||
|
|
||||||
L.tileLayer(config.map.tileLayer, {
|
|
||||||
'maxZoom': config.map.maxZoom
|
|
||||||
}).addTo(map);
|
|
||||||
|
|
||||||
geoJsonLayer = L.geoJson.ajax(config.map.geojson.url,
|
|
||||||
config.map.geojson);
|
|
||||||
geoJsonLayer.addTo(map);
|
|
||||||
|
|
||||||
marker = L.marker(config.map.view.bound, {'draggable': true,
|
|
||||||
'opacity': 0.5}).addTo(map);
|
|
||||||
|
|
||||||
marker.on('dragstart', () => {
|
|
||||||
editing = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
marker.on('dragend', () => {
|
|
||||||
editing = false;
|
|
||||||
const pos = marker.getLatLng();
|
|
||||||
|
|
||||||
updatePosition(pos.lat, pos.lng);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
btnGPS = domlib.newAt(el, 'span');
|
|
||||||
btnGPS.classList.add('btn');
|
|
||||||
btnGPS.innerHTML = 'Start follow position';
|
|
||||||
btnGPS.addEventListener('click', () => {
|
|
||||||
if (editLocationGPS) {
|
|
||||||
if (btnGPS.innerHTML === 'Stop following') {
|
|
||||||
updatePosition();
|
|
||||||
}
|
|
||||||
btnGPS.innerHTML = 'Start follow position';
|
|
||||||
navigator.geolocation.clearWatch(editLocationGPS);
|
|
||||||
editLocationGPS = false;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
btnGPS.innerHTML = 'Following position';
|
|
||||||
if (navigator.geolocation) {
|
|
||||||
editLocationGPS = navigator.geolocation.watchPosition((position) => {
|
|
||||||
btnGPS.innerHTML = 'Stop following';
|
|
||||||
storePosition = position.coords;
|
|
||||||
const latlng = [position.coords.latitude, position.coords.longitude];
|
|
||||||
|
|
||||||
marker.setLatLng(latlng);
|
|
||||||
map.setView(latlng);
|
|
||||||
}, (error) => {
|
|
||||||
switch (error.code) {
|
|
||||||
case error.TIMEOUT:
|
|
||||||
notify.send('error', 'Find Location timeout');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error('a navigator geolocation error: ', error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'enableHighAccuracy': true,
|
|
||||||
'maximumAge': 30000,
|
|
||||||
'timeout': 27000
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
notify.send('error', 'Browser did not support Location');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
update();
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -1,33 +0,0 @@
|
||||||
/* exported guiSkel */
|
|
||||||
/* globals domlib */
|
|
||||||
const guiSkel = {};
|
|
||||||
|
|
||||||
(function init () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const view = guiSkel;
|
|
||||||
let container = null,
|
|
||||||
el = null;
|
|
||||||
|
|
||||||
function update () {
|
|
||||||
console.warn('Do not run dummies');
|
|
||||||
}
|
|
||||||
|
|
||||||
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 skel');
|
|
||||||
el = domlib.newAt(container, 'div');
|
|
||||||
|
|
||||||
update();
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -1,107 +0,0 @@
|
||||||
/* exported guiStats */
|
|
||||||
/* globals store, domlib */
|
|
||||||
const guiStats = {};
|
|
||||||
|
|
||||||
(function init () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const view = guiStats;
|
|
||||||
|
|
||||||
let container = null,
|
|
||||||
el = null,
|
|
||||||
channelTabelle = null,
|
|
||||||
|
|
||||||
nodes = null,
|
|
||||||
clients = null,
|
|
||||||
clientsWifi = null,
|
|
||||||
clientsWifi24 = null,
|
|
||||||
clientsWifi5 = null;
|
|
||||||
|
|
||||||
function update () {
|
|
||||||
nodes.innerHTML = store.stats.Nodes;
|
|
||||||
clients.innerHTML = store.stats.Clients;
|
|
||||||
clientsWifi.innerHTML = store.stats.ClientsWifi;
|
|
||||||
clientsWifi24.innerHTML = store.stats.ClientsWifi24;
|
|
||||||
clientsWifi5.innerHTML = store.stats.ClientsWifi5;
|
|
||||||
|
|
||||||
domlib.removeChildren(channelTabelle);
|
|
||||||
|
|
||||||
|
|
||||||
let tr = domlib.newAt(channelTabelle, 'tr');
|
|
||||||
|
|
||||||
let title = domlib.newAt(tr, 'th');
|
|
||||||
title.innerHTML = '2.4 Ghz';
|
|
||||||
title.setAttribute('colspan', '2');
|
|
||||||
|
|
||||||
title = domlib.newAt(tr, 'th');
|
|
||||||
title.innerHTML = '5 Ghz';
|
|
||||||
title.setAttribute('colspan', '2');
|
|
||||||
const storeNodes = store.getNodes();
|
|
||||||
for (let ch = 1; ch <= 33; ch++) {
|
|
||||||
tr = domlib.newAt(channelTabelle, 'tr');
|
|
||||||
if (ch < 14) {
|
|
||||||
domlib.newAt(tr, 'td').innerHTML = ch;
|
|
||||||
domlib.newAt(tr, 'td').innerHTML = storeNodes.reduce((c, node) => node.wireless.channel24 === ch ? c + 1 : c, 0);
|
|
||||||
} else {
|
|
||||||
domlib.newAt(tr, 'td');
|
|
||||||
domlib.newAt(tr, 'td');
|
|
||||||
}
|
|
||||||
const ch5 = 32 + ch * 4;
|
|
||||||
domlib.newAt(tr, 'td').innerHTML = ch5;
|
|
||||||
domlib.newAt(tr, 'td').innerHTML = storeNodes.reduce((c, node) => node.wireless.channel5 === ch5 ? c + 1 : c, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 stats');
|
|
||||||
el = domlib.newAt(container, 'div');
|
|
||||||
domlib.newAt(el, 'h1').innerHTML = 'Statistics';
|
|
||||||
|
|
||||||
const table = domlib.newAt(el, 'table');
|
|
||||||
|
|
||||||
table.classList.add('stats');
|
|
||||||
|
|
||||||
let tr = domlib.newAt(table, 'tr'),
|
|
||||||
title = domlib.newAt(tr, 'th');
|
|
||||||
|
|
||||||
title.innerHTML = 'Nodes';
|
|
||||||
title.setAttribute('colspan', '2');
|
|
||||||
nodes = domlib.newAt(tr, 'td');
|
|
||||||
|
|
||||||
tr = domlib.newAt(table, 'tr');
|
|
||||||
title = domlib.newAt(tr, 'th');
|
|
||||||
title.innerHTML = 'Clients';
|
|
||||||
title.setAttribute('colspan', '2');
|
|
||||||
clients = domlib.newAt(tr, 'td');
|
|
||||||
|
|
||||||
tr = domlib.newAt(table, 'tr');
|
|
||||||
tr.classList.add('line');
|
|
||||||
domlib.newAt(tr, 'th').innerHTML = 'Wifi';
|
|
||||||
domlib.newAt(tr, 'th').innerHTML = '2.4 Ghz';
|
|
||||||
domlib.newAt(tr, 'th').innerHTML = '5 Ghz';
|
|
||||||
|
|
||||||
tr = domlib.newAt(table, 'tr');
|
|
||||||
clientsWifi = domlib.newAt(tr, 'td');
|
|
||||||
clientsWifi24 = domlib.newAt(tr, 'td');
|
|
||||||
clientsWifi5 = domlib.newAt(tr, 'td');
|
|
||||||
|
|
||||||
// Channels table
|
|
||||||
domlib.newAt(el, 'h1').innerHTML = 'Channels';
|
|
||||||
channelTabelle = domlib.newAt(el, 'table');
|
|
||||||
channelTabelle.classList.add('stats');
|
|
||||||
|
|
||||||
update();
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import '../node_modules/leaflet/dist/leaflet.js';
|
||||||
|
import '../node_modules/leaflet-ajax/dist/leaflet.ajax.min.js';
|
||||||
|
import '../node_modules/leaflet-webgl-heatmap/src/webgl-heatmap/webgl-heatmap.js';
|
||||||
|
import '../node_modules/leaflet-webgl-heatmap/src/leaflet-webgl-heatmap.js';
|
||||||
|
|
||||||
|
import * as gui from './gui';
|
||||||
|
import config from './config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Self binding with router
|
||||||
|
*/
|
||||||
|
import {ListView} from './view/list';
|
||||||
|
import {MapView} from './view/map';
|
||||||
|
import {StatisticsView} from './view/statistics';
|
||||||
|
|
||||||
|
import {NodeView} from './view/node';
|
||||||
|
|
||||||
|
document.title = config.title;
|
||||||
|
window.onload = () => {
|
||||||
|
const listView = new ListView();
|
||||||
|
const mapView = new MapView();
|
||||||
|
const statisticsView = new StatisticsView();
|
||||||
|
|
||||||
|
const nodeView = new NodeView();
|
||||||
|
|
||||||
|
gui.router.on({
|
||||||
|
'/list': () => gui.setView(listView),
|
||||||
|
'/map': () => gui.setView(mapView),
|
||||||
|
'/statistics': () => gui.setView(statisticsView),
|
||||||
|
'/n/:nodeID': {
|
||||||
|
'as': 'node',
|
||||||
|
// eslint-disable-next-line func-name-matching
|
||||||
|
'uses': (params) => {
|
||||||
|
nodeView.setNodeID(params.nodeID.toLowerCase());
|
||||||
|
gui.setView(nodeView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).on(() => {
|
||||||
|
gui.router.navigate('/list');
|
||||||
|
});
|
||||||
|
|
||||||
|
gui.render();
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
/*
|
|
||||||
* MIT Copyright 2016 Ursudio <info@ursudio.com>
|
|
||||||
* http://www.ursudio.com/
|
|
||||||
* Please attribute Ursudio in any production associated with this JavaScript plugin.
|
|
||||||
*/
|
|
||||||
L.WebGLHeatMap=L.Renderer.extend({version:"0.2.2",options:{size:3e4,units:"m",opacity:1,gradientTexture:!1,alphaRange:1,padding:0},_initContainer:function(){var t=this._container=L.DomUtil.create("canvas","leaflet-zoom-animated"),i=this.options;t.id="webgl-leaflet-"+L.Util.stamp(this),t.style.opacity=i.opacity,t.style.position="absolute";try{this.gl=window.createWebGLHeatmap({canvas:t,gradientTexture:i.gradientTexture,alphaRange:[0,i.alphaRange]})}catch(t){console.error(t),this.gl={clear:function(){},update:function(){},multiply:function(){},addPoint:function(){},display:function(){},adjustSize:function(){}}}this._container=t},onAdd:function(){this.size=this.options.size,L.Renderer.prototype.onAdd.call(this),this.resize()},getEvents:function(){var t=L.Renderer.prototype.getEvents.call(this);return L.Util.extend(t,{resize:this.resize,move:L.Util.throttle(this._update,49,this)}),t},resize:function(){var t=this._container,i=this._map.getSize();t.width=i.x,t.height=i.y,this.gl.adjustSize(),this.draw()},reposition:function(){var t=this._map._getMapPanePos().multiplyBy(-1);L.DomUtil.setPosition(this._container,t)},_update:function(){L.Renderer.prototype._update.call(this),this.draw()},draw:function(){var t=this._map,i=this.gl,e=this.data,a=e.length,n=Math.floor,s=this["_scale"+this.options.units].bind(this),o=this._multiply;if(t){if(i.clear(),this.reposition(),a){for(var r=0;r<a;r++){var l=e[r],h=L.latLng(l),u=t.latLngToContainerPoint(h);i.addPoint(n(u.x),n(u.y),s(h),l[2])}i.update(),o&&(i.multiply(o),i.update())}i.display()}},_scalem:function(t){var i=this._map,e=this.size/40075017*360/Math.cos(Math.PI/180*t.lat),a=new L.LatLng(t.lat,t.lng-e),n=i.latLngToLayerPoint(t),s=i.latLngToLayerPoint(a);return Math.max(Math.round(n.x-s.x),1)},_scalepx:function(){return this.size},data:[],addDataPoint:function(t,i,e){this.data.push([t,i,e/100])},setData:function(t){this.data=t,this._multiply=null,this.draw()},clear:function(){this.setData([])},multiply:function(t){this._multiply=t,this.draw()}}),L.webGLHeatmap=function(t){return new L.WebGLHeatMap(t)};
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
export function FromNowAgo(timeString) {
|
||||||
|
let time = new Date(timeString).getTime();
|
||||||
|
|
||||||
|
if(time <= 0) {
|
||||||
|
return 'NaN';
|
||||||
|
}
|
||||||
|
|
||||||
|
time = (new Date().getTime()) - time;
|
||||||
|
|
||||||
|
time /= 1000;
|
||||||
|
if (Math.abs(time) < 60) {
|
||||||
|
return Math.round(time) + ' s';
|
||||||
|
}
|
||||||
|
time /= 60;
|
||||||
|
if (Math.abs(time) < 60) {
|
||||||
|
return Math.round(time) + ' m';
|
||||||
|
}
|
||||||
|
time /= 60;
|
||||||
|
if (Math.abs(time) < 24) {
|
||||||
|
return Math.round(time) + ' h';
|
||||||
|
}
|
||||||
|
time /= 24;
|
||||||
|
return Math.round(time) + ' d';
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,67 +0,0 @@
|
||||||
/* exported notify */
|
|
||||||
|
|
||||||
const notify = {};
|
|
||||||
|
|
||||||
(function init () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const DELAY_OF_NOTIFY = 15000,
|
|
||||||
MAX_MESSAGE_SHOW = 10,
|
|
||||||
messages = [];
|
|
||||||
|
|
||||||
let container = null;
|
|
||||||
|
|
||||||
if ('Notification' in window) {
|
|
||||||
window.Notification.requestPermission();
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeLast () {
|
|
||||||
messages.splice(0, 1);
|
|
||||||
if (container && container.firstElementChild) {
|
|
||||||
container.removeChild(container.firstElementChild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderMsg (msg) {
|
|
||||||
const msgBox = document.createElement('div');
|
|
||||||
|
|
||||||
msgBox.classList.add('notify', msg.type);
|
|
||||||
msgBox.innerHTML = msg.text;
|
|
||||||
container.appendChild(msgBox);
|
|
||||||
msgBox.addEventListener('click', () => {
|
|
||||||
container.removeChild(msgBox);
|
|
||||||
if (messages.indexOf(msg) !== -1) {
|
|
||||||
messages.splice(messages.indexOf(msg), 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.setInterval(removeLast, DELAY_OF_NOTIFY);
|
|
||||||
|
|
||||||
notify.bind = function bind (el) {
|
|
||||||
container = el;
|
|
||||||
};
|
|
||||||
|
|
||||||
notify.send = function send (type, text) {
|
|
||||||
if ('Notification' in window &&
|
|
||||||
window.Notification.permission === 'granted') {
|
|
||||||
// eslint-disable-next-line no-new
|
|
||||||
new window.Notification(text, {
|
|
||||||
'body': type,
|
|
||||||
'icon': '/img/logo.jpg'
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (messages.length > MAX_MESSAGE_SHOW) {
|
|
||||||
removeLast();
|
|
||||||
}
|
|
||||||
const msg = {
|
|
||||||
'text': text,
|
|
||||||
'type': type
|
|
||||||
};
|
|
||||||
|
|
||||||
messages.push(msg);
|
|
||||||
renderMsg(msg);
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -1,89 +1,200 @@
|
||||||
/* exported socket */
|
import * as store from './store';
|
||||||
/* globals notify,gui,store,config*/
|
import config from './config';
|
||||||
let socket = {'readyState': 0};
|
import {singelton as notify} from './element/notify';
|
||||||
|
import {render} from './gui';
|
||||||
|
|
||||||
(function init () {
|
const RECONNECT_AFTER = 5000,
|
||||||
'use strict';
|
RETRY_QUERY = 300,
|
||||||
|
query = [],
|
||||||
|
eventMSGID = {},
|
||||||
|
eventTo = {};
|
||||||
|
|
||||||
const RECONNECT_AFTER = 5000;
|
let connectionID = localStorage.getItem('session'),
|
||||||
|
socket = null;
|
||||||
|
|
||||||
function onerror (err) {
|
function newUUID () {
|
||||||
console.warn(err);
|
/* eslint-disable */
|
||||||
// eslint-disable-next-line no-magic-numbers
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||||
if (socket.readyState !== 3) {
|
const r = Math.random() * 16 | 0,
|
||||||
notify.send('error', 'Es gibt Übertragungsprobleme!');
|
v = c === 'x' ? r : r & 0x3 | 0x8;
|
||||||
gui.render();
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
/* eslint-enable */
|
||||||
|
}
|
||||||
|
|
||||||
|
function correctMSG (obj) {
|
||||||
|
if (!obj.id) {
|
||||||
|
obj.id = newUUID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onerror (err) {
|
||||||
|
console.warn(err);
|
||||||
|
// eslint-disable-next-line no-magic-numbers
|
||||||
|
if (socket.readyState !== 3) {
|
||||||
|
notify.send({
|
||||||
|
'header': 'Verbindung',
|
||||||
|
'type': 'error'
|
||||||
|
}, 'Verbindung zum Server unterbrochen!');
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onopen () {
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function sendjson (obj, callback) {
|
||||||
|
if (socket.readyState !== 1) {
|
||||||
|
query.push({
|
||||||
|
'callback': callback,
|
||||||
|
'obj': obj
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
correctMSG(obj);
|
||||||
|
const socketMSG = JSON.stringify(obj);
|
||||||
|
socket.send(socketMSG);
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
eventMSGID[obj.id] = callback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmessage (raw) {
|
||||||
|
const msg = JSON.parse(raw.data),
|
||||||
|
msgFunc = eventMSGID[msg.id],
|
||||||
|
eventFuncs = eventTo[msg.subject];
|
||||||
|
|
||||||
|
if (msg.subject === 'session_init') {
|
||||||
|
if (connectionID === null) {
|
||||||
|
connectionID = newUUID();
|
||||||
|
localStorage.setItem('session', connectionID);
|
||||||
}
|
}
|
||||||
|
msg.id = connectionID;
|
||||||
|
sendjson(msg);
|
||||||
|
render();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onopen () {
|
if (msgFunc) {
|
||||||
gui.render();
|
msgFunc(msg);
|
||||||
|
delete eventMSGID[msg.id];
|
||||||
|
render();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onmessage (raw) {
|
if (typeof eventFuncs === 'object' && eventFuncs.length > 0) {
|
||||||
const msg = JSON.parse(raw.data);
|
// eslint-disable-next-line guard-for-in
|
||||||
|
for (const key in eventFuncs) {
|
||||||
switch (msg.type) {
|
const func = eventFuncs[key];
|
||||||
case 'system':
|
if (func) {
|
||||||
store.updateNode(msg.node, true);
|
func(msg);
|
||||||
break;
|
|
||||||
case 'current':
|
|
||||||
store.updateNode(msg.node);
|
|
||||||
break;
|
|
||||||
case 'stats':
|
|
||||||
if (msg.body) {
|
|
||||||
store.stats = msg.body;
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case 'cmd':
|
|
||||||
store.updateCMD(msg.cmd);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
notify.send('warn', `unable to identify message: ${raw}`);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
gui.render();
|
render();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onclose () {
|
notify.send('warning', `unable to identify message: ${msg.subject}`);
|
||||||
console.log('socket closed by server');
|
render();
|
||||||
notify.send('warn', 'Es besteht ein Verbindungsproblem!');
|
}
|
||||||
gui.render();
|
|
||||||
// eslint-disable-next-line no-use-before-define
|
function onclose () {
|
||||||
window.setTimeout(connect, RECONNECT_AFTER);
|
console.log('socket closed by server');
|
||||||
|
notify.send({
|
||||||
|
'header': 'Verbindung',
|
||||||
|
'type': 'warning'
|
||||||
|
}, 'Verbindung zum Server beendet!');
|
||||||
|
render();
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
window.setTimeout(connect, RECONNECT_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect () {
|
||||||
|
socket = new window.WebSocket(config.backend);
|
||||||
|
socket.onopen = onopen;
|
||||||
|
socket.onerror = onerror;
|
||||||
|
socket.onmessage = onmessage;
|
||||||
|
socket.onclose = onclose;
|
||||||
|
sendjson({'subject': 'auth_status'});
|
||||||
|
sendjson({'subject': 'connect'});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setInterval(() => {
|
||||||
|
const queryEntry = query.pop();
|
||||||
|
if (queryEntry) {
|
||||||
|
sendjson(queryEntry.obj, queryEntry.callback);
|
||||||
}
|
}
|
||||||
|
}, RETRY_QUERY);
|
||||||
|
|
||||||
function sendnode (node) {
|
|
||||||
const notifyMsg = `Einstellungen für '${node.node_id}' gespeichert.`,
|
|
||||||
socketMsg = JSON.stringify({
|
|
||||||
'node': node,
|
|
||||||
'type': 'system'
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.send(socketMsg);
|
export function getStatus () {
|
||||||
notify.send('success', notifyMsg);
|
if (socket) {
|
||||||
|
return socket.readyState;
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
function sendcmd (cmd) {
|
export function setEvent (to, func) {
|
||||||
const notifyMsg = `Befehl '${cmd.cmd}' wird überall ausgeführt.`,
|
eventTo[to] = [func];
|
||||||
socketMsg = JSON.stringify({
|
}
|
||||||
'cmd': cmd,
|
|
||||||
'type': 'cmd'
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.send(socketMsg);
|
export function addEvent (to, func) {
|
||||||
notify.send('success', notifyMsg);
|
if (typeof eventTo[to] !== 'object') {
|
||||||
|
eventTo[to] = [];
|
||||||
}
|
}
|
||||||
|
eventTo[to].push(func);
|
||||||
|
}
|
||||||
|
|
||||||
function connect () {
|
export function delEvent (to, func) {
|
||||||
socket = new window.WebSocket(config.backend);
|
if (typeof eventTo[to] === 'object' && eventTo[to].length > 1) {
|
||||||
socket.onopen = onopen;
|
eventTo[to].pop(func);
|
||||||
socket.onerror = onerror;
|
} else {
|
||||||
socket.onmessage = onmessage;
|
eventTo[to] = [];
|
||||||
socket.onclose = onclose;
|
|
||||||
socket.sendnode = sendnode;
|
|
||||||
socket.sendcmd = sendcmd;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connect();
|
export function sendnode(node, callback) {
|
||||||
})();
|
sendjson({'subject':'node-system','body': node}, (msg) => {
|
||||||
|
if(msg.body){
|
||||||
|
notify.send({
|
||||||
|
'header': 'Speichern',
|
||||||
|
'type': 'success',
|
||||||
|
}, `Einstellungen für '${node.node_id}' gespeichert.`);
|
||||||
|
}else{
|
||||||
|
notify.send({
|
||||||
|
'header': 'Speichern',
|
||||||
|
'type': 'error',
|
||||||
|
}, `Einstellungen für '${node.node_id}' wurden nicht gespeichert.`);
|
||||||
|
}
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setEvent('auth_status', (msg) => {
|
||||||
|
if (msg.body) {
|
||||||
|
store.isLogin = true;
|
||||||
|
|
||||||
|
notify.send({
|
||||||
|
'header': 'Login',
|
||||||
|
'type': 'success'
|
||||||
|
},'Willkommen zurück!');
|
||||||
|
} else {
|
||||||
|
store.isLogin = false;
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
setEvent('node-system', (msg) => {
|
||||||
|
store.updateNode(msg.body, true);
|
||||||
|
});
|
||||||
|
setEvent('node-current', (msg) => {
|
||||||
|
store.updateNode(msg.body);
|
||||||
|
});
|
||||||
|
|
||||||
|
connect();
|
||||||
|
|
|
@ -1,69 +1,37 @@
|
||||||
/* exported store */
|
const current = {},
|
||||||
|
list = {};
|
||||||
|
|
||||||
|
export function getNode (nodeid) {
|
||||||
|
let node = {};
|
||||||
|
|
||||||
const store = {
|
if (list[nodeid]) {
|
||||||
'stats': {
|
node = list[nodeid];
|
||||||
'Clients': 0,
|
if (current[nodeid]) {
|
||||||
'ClientsWifi': 0,
|
const cNode = current[nodeid];
|
||||||
'ClientsWifi24': 0,
|
|
||||||
'ClientsWifi5': 0,
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
'Firmwares': {},
|
node._wireless = cNode.wireless;
|
||||||
'Gateways': 0,
|
node.lastseen = cNode.lastseen;
|
||||||
'Models': {},
|
}
|
||||||
'Nodes': 0
|
} else {
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
node.node_id = nodeid;
|
||||||
|
node.wireless = {};
|
||||||
|
node.location = {};
|
||||||
|
list[nodeid] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function updateNode (node, system) {
|
||||||
|
if (system) {
|
||||||
|
list[node.node_id] = node;
|
||||||
|
} else {
|
||||||
|
current[node.node_id] = node;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
(function init () {
|
export function getNodes () {
|
||||||
'use strict';
|
return Object.keys(list).map(getNode);
|
||||||
|
};
|
||||||
const current = {},
|
|
||||||
list = {},
|
|
||||||
cmds = {};
|
|
||||||
|
|
||||||
function getNode (nodeid) {
|
|
||||||
let node = {};
|
|
||||||
|
|
||||||
if (list[nodeid]) {
|
|
||||||
node = list[nodeid];
|
|
||||||
if (current[nodeid]) {
|
|
||||||
const cNode = current[nodeid];
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
node._wireless = cNode.wireless;
|
|
||||||
node.lastseen = cNode.lastseen;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
node.node_id = nodeid;
|
|
||||||
node.wireless = {};
|
|
||||||
node.location = {};
|
|
||||||
list[nodeid] = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
store.updateNode = function updateNode (node, system) {
|
|
||||||
if (system) {
|
|
||||||
list[node.node_id] = node;
|
|
||||||
} else {
|
|
||||||
current[node.node_id] = node;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
export default class View {
|
||||||
|
constructor () {
|
||||||
|
this.el = document.createElement('div');
|
||||||
|
}
|
||||||
|
|
||||||
|
unbind () {
|
||||||
|
if (this.el && this.el.parentNode) {
|
||||||
|
this.el.parentNode.removeChild(this.el);
|
||||||
|
} else {
|
||||||
|
console.warn('unbind view not possible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bind (el) {
|
||||||
|
el.appendChild(this.el);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
render () {
|
||||||
|
//console.log('abstract view');
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,71 +1,166 @@
|
||||||
/* exported guiList */
|
import * as domlib from '../domlib';
|
||||||
/* global config,domlib,store,router,socket */
|
import * as gui from '../gui';
|
||||||
/* eslint max-lines: [off] */
|
import * as socket from '../socket';
|
||||||
|
import * as store from '../store';
|
||||||
|
import config from '../config';
|
||||||
|
import View from '../view';
|
||||||
|
import {FromNowAgo} from '../lib';
|
||||||
|
|
||||||
const guiList = {};
|
export class ListView extends View {
|
||||||
|
|
||||||
(function init () {
|
constructor () {
|
||||||
'use strict';
|
super();
|
||||||
|
const table = domlib.newAt(this.el, 'table'),
|
||||||
|
thead = domlib.newAt(table, 'thead');
|
||||||
|
|
||||||
const view = guiList;
|
this.tbody = domlib.newAt(table, 'tbody');
|
||||||
|
|
||||||
let container = null,
|
// eslint-disable-next-line one-var
|
||||||
el = null,
|
const tr = domlib.newAt(thead, 'tr'),
|
||||||
tbody = null,
|
cell1 = domlib.newAt(tr, 'th'),
|
||||||
sortReverse = false,
|
cell2 = domlib.newAt(tr, 'th'),
|
||||||
sortIndex = null,
|
cell3 = domlib.newAt(tr, 'th'),
|
||||||
hostnameFilter = null,
|
cell4 = domlib.newAt(tr, 'th'),
|
||||||
nodeidFilter = null,
|
cell5 = domlib.newAt(tr, 'th'),
|
||||||
editing = false;
|
cell6 = domlib.newAt(tr, 'th'),
|
||||||
|
cell7 = domlib.newAt(tr, 'th'),
|
||||||
|
cell8 = domlib.newAt(tr, 'th'),
|
||||||
|
cell9 = domlib.newAt(tr, 'th'),
|
||||||
|
cell10 = domlib.newAt(tr, 'th'),
|
||||||
|
cell11 = domlib.newAt(tr, 'th');
|
||||||
|
|
||||||
|
cell1.innerHTML = 'Lastseen';
|
||||||
|
cell1.addEventListener('click', () => {
|
||||||
|
this.sortTable(cell1);
|
||||||
|
});
|
||||||
|
|
||||||
|
cell2.classList.add('sortable');
|
||||||
|
this.nodeidFilter = domlib.newAt(cell2, 'input');
|
||||||
|
this.nodeidFilter.setAttribute('placeholder', 'NodeID');
|
||||||
|
this.nodeidFilter.setAttribute('size', '9');
|
||||||
|
this.nodeidFilter.addEventListener('keyup', this.render);
|
||||||
|
cell2.addEventListener('dblclick', () => {
|
||||||
|
this.sortTable(cell2);
|
||||||
|
});
|
||||||
|
this.nodeidFilter.addEventListener('focusin', () => {
|
||||||
|
this.editing = true;
|
||||||
|
});
|
||||||
|
this.nodeidFilter.addEventListener('focusout', () => {
|
||||||
|
this.editing = false;
|
||||||
|
this.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
cell3.classList.add('sortable');
|
||||||
|
cell3.classList.add('hostname');
|
||||||
|
this.hostnameFilter = domlib.newAt(cell3, 'input');
|
||||||
|
this.hostnameFilter.setAttribute('placeholder', 'Hostname');
|
||||||
|
this.hostnameFilter.addEventListener('keyup', this.render);
|
||||||
|
cell3.addEventListener('dblclick', () => {
|
||||||
|
this.sortTable(cell3);
|
||||||
|
});
|
||||||
|
this.hostnameFilter.addEventListener('focusin', () => {
|
||||||
|
this.editing = true;
|
||||||
|
});
|
||||||
|
this.hostnameFilter.addEventListener('focusout', () => {
|
||||||
|
this.editing = false;
|
||||||
|
this.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
cell4.innerHTML = 'Freq';
|
||||||
|
|
||||||
|
|
||||||
|
cell5.innerHTML = 'CurChannel';
|
||||||
|
cell5.classList.add('sortable');
|
||||||
|
cell5.addEventListener('click', () => {
|
||||||
|
this.sortTable(cell5);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
cell6.innerHTML = 'Channel';
|
||||||
|
cell6.classList.add('sortable');
|
||||||
|
cell6.addEventListener('click', () => {
|
||||||
|
this.sortTable(cell6);
|
||||||
|
});
|
||||||
|
|
||||||
|
cell7.innerHTML = 'CurPower';
|
||||||
|
cell7.classList.add('sortable');
|
||||||
|
cell7.addEventListener('click', () => {
|
||||||
|
this.sortTable(cell7);
|
||||||
|
});
|
||||||
|
|
||||||
|
cell8.innerHTML = 'Power';
|
||||||
|
cell8.classList.add('sortable');
|
||||||
|
cell8.addEventListener('click', () => {
|
||||||
|
this.sortTable(cell8);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
cell9.innerHTML = 'Clients';
|
||||||
|
cell9.classList.add('sortable');
|
||||||
|
cell9.addEventListener('click', () => {
|
||||||
|
this.sortTable(cell9);
|
||||||
|
});
|
||||||
|
|
||||||
|
cell10.innerHTML = 'ChanUtil';
|
||||||
|
cell10.classList.add('sortable');
|
||||||
|
cell10.addEventListener('click', () => {
|
||||||
|
this.sortTable(cell10);
|
||||||
|
});
|
||||||
|
cell11.innerHTML = 'Option';
|
||||||
|
|
||||||
|
table.classList.add('nodes');
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line id-length
|
// eslint-disable-next-line id-length
|
||||||
function sort (a, b) {
|
sort (sortIndex, sortReverse) {
|
||||||
function sortNumber (aNum, bNum) {
|
function sortNumber (aNum, bNum) {
|
||||||
return aNum - bNum;
|
return aNum - bNum;
|
||||||
}
|
}
|
||||||
if (!sortIndex) {
|
return (a, b) => {
|
||||||
return a.node_id.localeCompare(b.node_id);
|
if (!sortIndex) {
|
||||||
}
|
return a.node_id.localeCompare(b.node_id);
|
||||||
switch (sortIndex.innerHTML) {
|
|
||||||
case 'Lastseen':
|
|
||||||
return a.lastseen - b.lastseen;
|
|
||||||
case 'CurPower':
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
return a._wireless.txpower24 - b._wireless.txpower24;
|
|
||||||
case 'Power':
|
|
||||||
return a.wireless.txpower24 - b.wireless.txpower24;
|
|
||||||
case 'CurChannel':
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
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;
|
|
||||||
// eslint-disable-next-line no-case-declarations
|
|
||||||
case 'ChanUtil':
|
|
||||||
// eslint-disable-next-line id-length
|
|
||||||
let aMax = a.statistics.wireless.map((d) =>
|
|
||||||
d.ChanUtil
|
|
||||||
).sort(sortNumber),
|
|
||||||
// eslint-disable-next-line id-length
|
|
||||||
bMax = b.statistics.wireless.map((d) =>
|
|
||||||
d.ChanUtil
|
|
||||||
).sort(sortNumber);
|
|
||||||
|
|
||||||
if (!sortReverse) {
|
|
||||||
aMax = aMax.reverse();
|
|
||||||
bMax = bMax.reverse();
|
|
||||||
}
|
}
|
||||||
|
if (sortIndex.classList.contains("hostname")) {
|
||||||
|
return a.hostname.localeCompare(b.hostname);
|
||||||
|
}
|
||||||
|
switch (sortIndex.innerText) {
|
||||||
|
case 'Lastseen':
|
||||||
|
return a.lastseen - b.lastseen;
|
||||||
|
case 'CurPower':
|
||||||
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
|
return a._wireless.txpower24 - b._wireless.txpower24;
|
||||||
|
case 'Power':
|
||||||
|
return a.wireless.txpower24 - b.wireless.txpower24;
|
||||||
|
case 'CurChannel':
|
||||||
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
|
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;
|
||||||
|
// eslint-disable-next-line no-case-declarations
|
||||||
|
case 'ChanUtil':
|
||||||
|
if(a.statistics.wireless === null) return 1;
|
||||||
|
if(b.statistics.wireless === null) return -1;
|
||||||
|
// eslint-disable-next-line id-length
|
||||||
|
let aMax = a.statistics.wireless.map((d) => d.ChanUtil).sort(sortNumber),
|
||||||
|
// eslint-disable-next-line id-length
|
||||||
|
bMax = b.statistics.wireless.map((d) => d.ChanUtil).sort(sortNumber);
|
||||||
|
|
||||||
return bMax[0] - aMax[0];
|
if (!sortReverse) {
|
||||||
case 'Hostname':
|
aMax = aMax.reverse();
|
||||||
return a.hostname.localeCompare(b.hostname);
|
bMax = bMax.reverse();
|
||||||
default:
|
}
|
||||||
return a.node_id.localeCompare(b.node_id);
|
|
||||||
|
return bMax[0] - aMax[0];
|
||||||
|
default:
|
||||||
|
return a.node_id.localeCompare(b.node_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderRow (node) {
|
renderRow (node) {
|
||||||
const startdate = new Date(),
|
const startdate = new Date(),
|
||||||
tr = document.createElement('tr'),
|
tr = document.createElement('tr'),
|
||||||
lastseen = domlib.newAt(tr, 'td'),
|
lastseen = domlib.newAt(tr, 'td'),
|
||||||
|
@ -95,7 +190,7 @@ const guiList = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
lastseen.innerHTML = moment(node.lastseen).fromNow(true);
|
lastseen.innerHTML = FromNowAgo(node.lastseen);
|
||||||
|
|
||||||
nodeID.innerHTML = node.node_id;
|
nodeID.innerHTML = node.node_id;
|
||||||
|
|
||||||
|
@ -103,17 +198,23 @@ const guiList = {};
|
||||||
hostnameInput.readOnly = true;
|
hostnameInput.readOnly = true;
|
||||||
hostnameInput.setAttribute('placeholder', 'Hostname');
|
hostnameInput.setAttribute('placeholder', 'Hostname');
|
||||||
hostnameInput.addEventListener('dblclick', () => {
|
hostnameInput.addEventListener('dblclick', () => {
|
||||||
editing = true;
|
this.editing = true;
|
||||||
hostnameInput.readOnly = false;
|
hostnameInput.readOnly = false;
|
||||||
});
|
});
|
||||||
hostnameInput.addEventListener('focusout', () => {
|
hostnameInput.addEventListener('focusout', () => {
|
||||||
if (hostnameInput.readOnly) {
|
if (hostnameInput.readOnly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
editing = false;
|
this.editing = false;
|
||||||
hostnameInput.readOnly = true;
|
hostnameInput.readOnly = true;
|
||||||
|
const old = node.hostname;
|
||||||
node.hostname = hostnameInput.value;
|
node.hostname = hostnameInput.value;
|
||||||
socket.sendnode(node);
|
socket.sendnode(node, (msg)=>{
|
||||||
|
if (!msg.body) {
|
||||||
|
node.hostname = old;
|
||||||
|
hostnameInput.value = old;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
domlib.newAt(freq, 'span').innerHTML = '2.4 Ghz';
|
domlib.newAt(freq, 'span').innerHTML = '2.4 Ghz';
|
||||||
|
@ -134,17 +235,23 @@ const guiList = {};
|
||||||
channel24Input.readOnly = true;
|
channel24Input.readOnly = true;
|
||||||
channel24Input.setAttribute('placeholder', '-');
|
channel24Input.setAttribute('placeholder', '-');
|
||||||
channel24Input.addEventListener('dblclick', () => {
|
channel24Input.addEventListener('dblclick', () => {
|
||||||
editing = true;
|
this.editing = true;
|
||||||
channel24Input.readOnly = false;
|
channel24Input.readOnly = false;
|
||||||
});
|
});
|
||||||
channel24Input.addEventListener('focusout', () => {
|
channel24Input.addEventListener('focusout', () => {
|
||||||
if (channel24Input.readOnly) {
|
if (channel24Input.readOnly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
editing = false;
|
this.editing = false;
|
||||||
channel24Input.readOnly = true;
|
channel24Input.readOnly = true;
|
||||||
|
const old = node.wireless.channel24;
|
||||||
node.wireless.channel24 = parseInt(channel24Input.value, 10);
|
node.wireless.channel24 = parseInt(channel24Input.value, 10);
|
||||||
socket.sendnode(node);
|
socket.sendnode(node, (msg)=>{
|
||||||
|
if (!msg.body) {
|
||||||
|
node.wireless.channel24 = old;
|
||||||
|
channel24Input.value = old;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
channel5Input.value = node.wireless.channel5 || '';
|
channel5Input.value = node.wireless.channel5 || '';
|
||||||
|
@ -155,17 +262,23 @@ const guiList = {};
|
||||||
channel5Input.readOnly = true;
|
channel5Input.readOnly = true;
|
||||||
channel5Input.setAttribute('placeholder', '-');
|
channel5Input.setAttribute('placeholder', '-');
|
||||||
channel5Input.addEventListener('dblclick', () => {
|
channel5Input.addEventListener('dblclick', () => {
|
||||||
editing = true;
|
this.editing = true;
|
||||||
channel5Input.readOnly = false;
|
channel5Input.readOnly = false;
|
||||||
});
|
});
|
||||||
channel5Input.addEventListener('focusout', () => {
|
channel5Input.addEventListener('focusout', () => {
|
||||||
if (channel5Input.readOnly) {
|
if (channel5Input.readOnly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
editing = false;
|
this.editing = false;
|
||||||
channel5Input.readOnly = true;
|
channel5Input.readOnly = true;
|
||||||
|
const old = node.wireless.channel5;
|
||||||
node.wireless.channel5 = parseInt(channel5Input.value, 10);
|
node.wireless.channel5 = parseInt(channel5Input.value, 10);
|
||||||
socket.sendnode(node);
|
socket.sendnode(node, (msg)=>{
|
||||||
|
if (!msg.body) {
|
||||||
|
node.wireless.channel5 = old;
|
||||||
|
channel5Input.value = old;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/* eslint-disable no-underscore-dangle */
|
/* eslint-disable no-underscore-dangle */
|
||||||
|
@ -182,17 +295,23 @@ const guiList = {};
|
||||||
power24Input.readOnly = true;
|
power24Input.readOnly = true;
|
||||||
power24Input.setAttribute('placeholder', '-');
|
power24Input.setAttribute('placeholder', '-');
|
||||||
power24Input.addEventListener('dblclick', () => {
|
power24Input.addEventListener('dblclick', () => {
|
||||||
editing = true;
|
this.editing = true;
|
||||||
power24Input.readOnly = false;
|
power24Input.readOnly = false;
|
||||||
});
|
});
|
||||||
power24Input.addEventListener('focusout', () => {
|
power24Input.addEventListener('focusout', () => {
|
||||||
if (power24Input.readOnly) {
|
if (power24Input.readOnly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
editing = false;
|
this.editing = false;
|
||||||
power24Input.readOnly = true;
|
power24Input.readOnly = true;
|
||||||
|
const old = node.wireless.txpower24;
|
||||||
node.wireless.txpower24 = parseInt(power24Input.value, 10);
|
node.wireless.txpower24 = parseInt(power24Input.value, 10);
|
||||||
socket.sendnode(node);
|
socket.sendnode(node, (msg)=>{
|
||||||
|
if (!msg.body) {
|
||||||
|
node.wireless.txpower24 = old;
|
||||||
|
power24Input.value = old;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
power5Input.value = node.wireless.txpower5 || '';
|
power5Input.value = node.wireless.txpower5 || '';
|
||||||
|
@ -202,17 +321,23 @@ const guiList = {};
|
||||||
power5Input.readOnly = true;
|
power5Input.readOnly = true;
|
||||||
power5Input.setAttribute('placeholder', '-');
|
power5Input.setAttribute('placeholder', '-');
|
||||||
power5Input.addEventListener('dblclick', () => {
|
power5Input.addEventListener('dblclick', () => {
|
||||||
editing = true;
|
this.editing = true;
|
||||||
power5Input.readOnly = false;
|
power5Input.readOnly = false;
|
||||||
});
|
});
|
||||||
power5Input.addEventListener('focusout', () => {
|
power5Input.addEventListener('focusout', () => {
|
||||||
if (power5Input.readOnly) {
|
if (power5Input.readOnly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
editing = false;
|
this.editing = false;
|
||||||
power5Input.readOnly = true;
|
power5Input.readOnly = true;
|
||||||
|
const old = node.wireless.txpower5;
|
||||||
node.wireless.txpower5 = parseInt(power5Input.value, 10);
|
node.wireless.txpower5 = parseInt(power5Input.value, 10);
|
||||||
socket.sendnode(node);
|
socket.sendnode(node, (msg)=>{
|
||||||
|
if (!msg.body) {
|
||||||
|
node.wireless.txpower5 = old;
|
||||||
|
power5Input.value = old;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
domlib.newAt(client, 'span').innerHTML = node.statistics.clients.wifi24;
|
domlib.newAt(client, 'span').innerHTML = node.statistics.clients.wifi24;
|
||||||
|
@ -233,173 +358,58 @@ const guiList = {};
|
||||||
edit.classList.add('btn');
|
edit.classList.add('btn');
|
||||||
edit.innerHTML = 'Edit';
|
edit.innerHTML = 'Edit';
|
||||||
edit.addEventListener('click', () => {
|
edit.addEventListener('click', () => {
|
||||||
router.navigate(router.generate('node', {'nodeID': node.node_id}));
|
gui.router.navigate(gui.router.generate('node', {'nodeID': node.node_id}));
|
||||||
});
|
});
|
||||||
|
|
||||||
return tr;
|
return tr;
|
||||||
}
|
}
|
||||||
|
|
||||||
function update () {
|
|
||||||
if (editing) {
|
|
||||||
|
sortTable (head) {
|
||||||
|
if (this.sortIndex) {
|
||||||
|
this.sortIndex.classList.remove('sort-up', 'sort-down');
|
||||||
|
}
|
||||||
|
this.sortReverse = head === this.sortIndex
|
||||||
|
? !this.sortReverse
|
||||||
|
: false;
|
||||||
|
this.sortIndex = head;
|
||||||
|
|
||||||
|
this.sortIndex.classList.add(this.sortReverse
|
||||||
|
? 'sort-up'
|
||||||
|
: 'sort-down');
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
if (this.editing && this.tbody) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
domlib.removeChildren(tbody);
|
while(this.tbody.hasChildNodes()) {
|
||||||
|
this.tbody.removeChild(this.tbody.firstElementChild);
|
||||||
|
}
|
||||||
let nodes = store.getNodes();
|
let nodes = store.getNodes();
|
||||||
|
|
||||||
if (hostnameFilter && hostnameFilter.value !== '') {
|
if (this.hostnameFilter && this.hostnameFilter.value !== '') {
|
||||||
// eslint-disable-next-line id-length
|
// eslint-disable-next-line id-length
|
||||||
nodes = nodes.filter((d) => d.hostname.toLowerCase().indexOf(hostnameFilter.value) > -1);
|
nodes = nodes.filter((d) => d.hostname.toLowerCase().indexOf(this.hostnameFilter.value.toLowerCase()) > -1);
|
||||||
}
|
}
|
||||||
if (nodeidFilter && nodeidFilter.value !== '') {
|
if (this.nodeidFilter && this.nodeidFilter.value !== '') {
|
||||||
// eslint-disable-next-line id-length
|
// eslint-disable-next-line id-length
|
||||||
nodes = nodes.filter((d) => d.node_id.indexOf(nodeidFilter.value) > -1);
|
nodes = nodes.filter((d) => d.node_id.indexOf(this.nodeidFilter.value.toLowerCase()) > -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes = nodes.sort(sort);
|
nodes = nodes.sort(this.sort(this.sortIndex, this.sortReverse));
|
||||||
|
|
||||||
if (sortReverse) {
|
if (this.sortReverse) {
|
||||||
nodes = nodes.reverse();
|
nodes = nodes.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < nodes.length; i += 1) {
|
for (let i = 0; i < nodes.length; i += 1) {
|
||||||
const row = renderRow(nodes[i]);
|
const row = this.renderRow(nodes[i]);
|
||||||
|
|
||||||
tbody.appendChild(row);
|
this.tbody.appendChild(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
function sortTable (head) {
|
|
||||||
if (sortIndex) {
|
|
||||||
sortIndex.classList.remove('sort-up', 'sort-down');
|
|
||||||
}
|
|
||||||
sortReverse = head === sortIndex
|
|
||||||
? !sortReverse
|
|
||||||
: false;
|
|
||||||
sortIndex = head;
|
|
||||||
|
|
||||||
sortIndex.classList.add(sortReverse
|
|
||||||
? 'sort-up'
|
|
||||||
: 'sort-down');
|
|
||||||
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 list');
|
|
||||||
el = domlib.newAt(container, 'div');
|
|
||||||
|
|
||||||
const table = domlib.newAt(el, 'table'),
|
|
||||||
thead = domlib.newAt(table, 'thead');
|
|
||||||
|
|
||||||
tbody = domlib.newAt(table, 'tbody');
|
|
||||||
|
|
||||||
// eslint-disable-next-line one-var
|
|
||||||
const tr = domlib.newAt(thead, 'tr'),
|
|
||||||
cell1 = domlib.newAt(tr, 'th'),
|
|
||||||
cell2 = domlib.newAt(tr, 'th'),
|
|
||||||
cell3 = domlib.newAt(tr, 'th'),
|
|
||||||
cell4 = domlib.newAt(tr, 'th'),
|
|
||||||
cell5 = domlib.newAt(tr, 'th'),
|
|
||||||
cell6 = domlib.newAt(tr, 'th'),
|
|
||||||
cell7 = domlib.newAt(tr, 'th'),
|
|
||||||
cell8 = domlib.newAt(tr, 'th'),
|
|
||||||
cell9 = domlib.newAt(tr, 'th'),
|
|
||||||
cell10 = domlib.newAt(tr, 'th'),
|
|
||||||
cell11 = domlib.newAt(tr, 'th');
|
|
||||||
|
|
||||||
cell1.innerHTML = 'Lastseen';
|
|
||||||
cell1.addEventListener('click', () => {
|
|
||||||
sortTable(cell1);
|
|
||||||
});
|
|
||||||
|
|
||||||
cell2.classList.add('sortable');
|
|
||||||
nodeidFilter = domlib.newAt(cell2, 'input');
|
|
||||||
nodeidFilter.setAttribute('placeholder', 'NodeID');
|
|
||||||
nodeidFilter.setAttribute('size', '9');
|
|
||||||
nodeidFilter.addEventListener('keyup', update);
|
|
||||||
cell2.addEventListener('dblclick', () => {
|
|
||||||
sortTable(cell2);
|
|
||||||
});
|
|
||||||
nodeidFilter.addEventListener('focusin', () => {
|
|
||||||
editing = true;
|
|
||||||
});
|
|
||||||
nodeidFilter.addEventListener('focusout', () => {
|
|
||||||
editing = false;
|
|
||||||
update();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
cell3.classList.add('sortable');
|
|
||||||
hostnameFilter = domlib.newAt(cell3, 'input');
|
|
||||||
hostnameFilter.setAttribute('placeholder', 'Hostname');
|
|
||||||
hostnameFilter.addEventListener('keyup', update);
|
|
||||||
cell3.addEventListener('dblclick', () => {
|
|
||||||
sortTable(cell3);
|
|
||||||
});
|
|
||||||
hostnameFilter.addEventListener('focusin', () => {
|
|
||||||
editing = true;
|
|
||||||
});
|
|
||||||
hostnameFilter.addEventListener('focusout', () => {
|
|
||||||
editing = false;
|
|
||||||
update();
|
|
||||||
});
|
|
||||||
|
|
||||||
cell4.innerHTML = 'Freq';
|
|
||||||
|
|
||||||
|
|
||||||
cell5.innerHTML = 'CurChannel';
|
|
||||||
cell5.classList.add('sortable');
|
|
||||||
cell5.addEventListener('click', () => {
|
|
||||||
sortTable(cell4);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
cell6.innerHTML = 'Channel';
|
|
||||||
cell6.classList.add('sortable');
|
|
||||||
cell6.addEventListener('click', () => {
|
|
||||||
sortTable(cell5);
|
|
||||||
});
|
|
||||||
|
|
||||||
cell7.innerHTML = 'CurPower';
|
|
||||||
cell7.classList.add('sortable');
|
|
||||||
cell7.addEventListener('click', () => {
|
|
||||||
sortTable(cell6);
|
|
||||||
});
|
|
||||||
|
|
||||||
cell8.innerHTML = 'Power';
|
|
||||||
cell8.classList.add('sortable');
|
|
||||||
cell8.addEventListener('click', () => {
|
|
||||||
sortTable(cell7);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
cell9.innerHTML = 'Clients';
|
|
||||||
cell9.classList.add('sortable');
|
|
||||||
cell9.addEventListener('click', () => {
|
|
||||||
sortTable(cell8);
|
|
||||||
});
|
|
||||||
|
|
||||||
cell10.innerHTML = 'ChanUtil';
|
|
||||||
cell10.classList.add('sortable');
|
|
||||||
cell10.addEventListener('click', () => {
|
|
||||||
sortTable(cell9);
|
|
||||||
});
|
|
||||||
cell11.innerHTML = 'Option';
|
|
||||||
|
|
||||||
table.classList.add('nodes');
|
|
||||||
|
|
||||||
update();
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -1,24 +1,52 @@
|
||||||
/* exported guiMap */
|
|
||||||
/* global config,store,domlib,socket */
|
|
||||||
|
|
||||||
const guiMap = {};
|
import * as domlib from '../domlib';
|
||||||
|
import * as gui from '../gui';
|
||||||
|
import * as socket from '../socket';
|
||||||
|
import * as store from '../store';
|
||||||
|
import config from '../config';
|
||||||
|
import View from '../view';
|
||||||
|
import {WINDOW_HEIGHT_MENU} from '../element/menu';
|
||||||
|
//import '../../node_modules/leaflet/dist/leaflet.js';
|
||||||
|
//import '../../node_modules/leaflet-webgl-heatmap/dist/leaflet-webgl-heatmap.min.js';
|
||||||
|
//import '../../node_modules/leaflet-ajax/dist/leaflet.ajax.min.js';
|
||||||
|
|
||||||
(function init () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const view = guiMap,
|
export class MapView extends View {
|
||||||
WINDOW_HEIGHT_MENU = 50;
|
|
||||||
|
|
||||||
let container = null,
|
constructor () {
|
||||||
el = null,
|
super();
|
||||||
|
|
||||||
geoJsonLayer = null,
|
|
||||||
nodeLayer = null,
|
|
||||||
clientLayer24 = null,
|
|
||||||
clientLayer5 = null;
|
|
||||||
// , draggingNodeID=null;
|
|
||||||
|
|
||||||
function addNode (node) {
|
this.el.style.height = `${window.innerHeight - WINDOW_HEIGHT_MENU}px`;
|
||||||
|
this.map = L.map(this.el).setView(config.map.view.bound, config.map.view.zoom);
|
||||||
|
|
||||||
|
const layerControl = L.control.layers().addTo(this.map);
|
||||||
|
|
||||||
|
L.tileLayer(config.map.tileLayer, {
|
||||||
|
'maxZoom': config.map.maxZoom
|
||||||
|
}).addTo(this.map);
|
||||||
|
|
||||||
|
|
||||||
|
this.geoJsonLayer = L.geoJson.ajax(config.map.geojson.url, config.map.geojson);
|
||||||
|
|
||||||
|
this.nodeLayer = L.layerGroup();
|
||||||
|
/* eslint-disable new-cap */
|
||||||
|
this.clientLayer24 = L.webGLHeatmap(config.map.heatmap.wifi24);
|
||||||
|
this.clientLayer5 = L.webGLHeatmap(config.map.heatmap.wifi5);
|
||||||
|
/* eslint-enable new-cap */
|
||||||
|
layerControl.addOverlay(this.geoJsonLayer, 'geojson');
|
||||||
|
layerControl.addOverlay(this.nodeLayer, 'Nodes');
|
||||||
|
layerControl.addOverlay(this.clientLayer24, 'Clients 2.4 Ghz');
|
||||||
|
layerControl.addOverlay(this.clientLayer5, 'Clients 5 Ghz');
|
||||||
|
this.nodeLayer.addTo(this.map);
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.el.style.height = `${window.innerHeight - WINDOW_HEIGHT_MENU}px`;
|
||||||
|
this.map.invalidateSize();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addNode (node) {
|
||||||
/* eslint-disable-line https://github.com/Leaflet/Leaflet/issues/4484
|
/* eslint-disable-line https://github.com/Leaflet/Leaflet/issues/4484
|
||||||
if(node.node_id === draggingNodeID){
|
if(node.node_id === draggingNodeID){
|
||||||
return
|
return
|
||||||
|
@ -88,29 +116,32 @@ const guiMap = {};
|
||||||
*/
|
*/
|
||||||
nodemarker.on('dragend', () => {
|
nodemarker.on('dragend', () => {
|
||||||
// DraggingNodeID = undefined;
|
// DraggingNodeID = undefined;
|
||||||
const pos = nodemarker.getLatLng();
|
const pos = nodemarker.getLatLng(),
|
||||||
|
old = node.location;
|
||||||
|
|
||||||
node.location = {
|
node.location = {
|
||||||
'latitude': pos.lat,
|
'latitude': pos.lat,
|
||||||
'longitude': pos.lng
|
'longitude': pos.lng
|
||||||
};
|
};
|
||||||
socket.sendnode(node);
|
socket.sendnode(node, (msg)=>{
|
||||||
|
if (!msg.body) {
|
||||||
|
node.location = old;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
nodeLayer.addLayer(nodemarker);
|
this.nodeLayer.addLayer(nodemarker);
|
||||||
}
|
}
|
||||||
|
|
||||||
function update () {
|
render () {
|
||||||
geoJsonLayer.refresh();
|
this.geoJsonLayer.refresh();
|
||||||
nodeLayer.clearLayers();
|
this.nodeLayer.clearLayers();
|
||||||
|
|
||||||
const nodes = store.getNodes();
|
const nodes = store.getNodes();
|
||||||
|
|
||||||
for (let i = 0; i < nodes.length; i += 1) {
|
for (let i = 0; i < nodes.length; i += 1) {
|
||||||
addNode(nodes[i]);
|
this.addNode(nodes[i]);
|
||||||
}
|
}
|
||||||
|
this.clientLayer24.setData(nodes.map((node) => {
|
||||||
|
|
||||||
clientLayer24.setData(nodes.map((node) => {
|
|
||||||
if (!node.location || !node.location.latitude || !node.location.longitude) {
|
if (!node.location || !node.location.latitude || !node.location.longitude) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -118,59 +149,13 @@ const guiMap = {};
|
||||||
return [node.location.latitude, node.location.longitude, node.statistics.clients.wifi24 || 0];
|
return [node.location.latitude, node.location.longitude, node.statistics.clients.wifi24 || 0];
|
||||||
}));
|
}));
|
||||||
|
|
||||||
clientLayer5.setData(nodes.map((node) => {
|
this.clientLayer5.setData(nodes.map((node) => {
|
||||||
if (!node.location || !node.location.latitude || !node.location.longitude) {
|
if (!node.location || !node.location.latitude || !node.location.longitude) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [node.location.latitude, node.location.longitude, node.statistics.clients.wifi5 || 0];
|
return [node.location.latitude, node.location.longitude, node.statistics.clients.wifi5 || 0];
|
||||||
}));
|
}));
|
||||||
|
this.map.invalidateSize();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
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 map');
|
|
||||||
el = domlib.newAt(container, 'div');
|
|
||||||
|
|
||||||
el.style.height = `${window.innerHeight - WINDOW_HEIGHT_MENU}px`;
|
|
||||||
|
|
||||||
const map = L.map(el).setView(config.map.view.bound, config.map.view.zoom),
|
|
||||||
layerControl = L.control.layers().addTo(map);
|
|
||||||
|
|
||||||
L.tileLayer(config.map.tileLayer, {
|
|
||||||
'maxZoom': config.map.maxZoom
|
|
||||||
}).addTo(map);
|
|
||||||
|
|
||||||
|
|
||||||
geoJsonLayer = L.geoJson.ajax(config.map.geojson.url, config.map.geojson);
|
|
||||||
|
|
||||||
nodeLayer = L.layerGroup();
|
|
||||||
/* eslint-disable new-cap */
|
|
||||||
clientLayer24 = new L.webGLHeatmap(config.map.heatmap.wifi24);
|
|
||||||
clientLayer5 = new L.webGLHeatmap(config.map.heatmap.wifi5);
|
|
||||||
/* eslint-enable new-cap */
|
|
||||||
layerControl.addOverlay(geoJsonLayer, 'geojson');
|
|
||||||
layerControl.addOverlay(nodeLayer, 'Nodes');
|
|
||||||
layerControl.addOverlay(clientLayer24, 'Clients 2.4 Ghz');
|
|
||||||
layerControl.addOverlay(clientLayer5, 'Clients 5 Ghz');
|
|
||||||
nodeLayer.addTo(map);
|
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
el.style.height = `${window.innerHeight - WINDOW_HEIGHT_MENU}px`;
|
|
||||||
map.invalidateSize();
|
|
||||||
});
|
|
||||||
|
|
||||||
update();
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -0,0 +1,203 @@
|
||||||
|
|
||||||
|
import * as domlib from '../domlib';
|
||||||
|
import * as gui from '../gui';
|
||||||
|
import * as socket from '../socket';
|
||||||
|
import * as store from '../store';
|
||||||
|
import config from '../config';
|
||||||
|
import View from '../view';
|
||||||
|
import {singelton as notify} from '../element/notify';
|
||||||
|
import {FromNowAgo} from '../lib';
|
||||||
|
//import '../../node_modules/leaflet/dist/leaflet.js';
|
||||||
|
//import '../../node_modules/leaflet-ajax/dist/leaflet.ajax.min.js';
|
||||||
|
//import '../../node_modules/moment/min/moment.min.js';
|
||||||
|
|
||||||
|
export class NodeView extends View {
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
super();
|
||||||
|
|
||||||
|
const title = domlib.newAt(this.el, 'h1'),
|
||||||
|
lastseen = domlib.newAt(this.el, 'p'),
|
||||||
|
hostname = domlib.newAt(this.el, 'p'),
|
||||||
|
owner = domlib.newAt(this.el, 'p'),
|
||||||
|
mapEl = domlib.newAt(this.el, 'div');
|
||||||
|
|
||||||
|
this.titleName = domlib.newAt(title, 'span');
|
||||||
|
title.appendChild(document.createTextNode(' - '));
|
||||||
|
this.titleID = domlib.newAt(title, 'i');
|
||||||
|
|
||||||
|
|
||||||
|
domlib.newAt(lastseen, 'span').innerHTML = 'Lastseen: ';
|
||||||
|
this.ago = domlib.newAt(lastseen, 'span');
|
||||||
|
|
||||||
|
domlib.newAt(hostname, 'span').innerHTML = 'Hostname: ';
|
||||||
|
this.hostnameInput = domlib.newAt(hostname, 'input');
|
||||||
|
this.hostnameInput.setAttribute('placeholder', 'Hostname');
|
||||||
|
this.hostnameInput.addEventListener('focusin', () => {
|
||||||
|
this.editing = true;
|
||||||
|
});
|
||||||
|
this.hostnameInput.addEventListener('focusout', () => {
|
||||||
|
this.editing = false;
|
||||||
|
|
||||||
|
const node = store.getNode(this.currentNodeID),
|
||||||
|
old = node.hostname;
|
||||||
|
|
||||||
|
node.hostname = this.hostnameInput.value;
|
||||||
|
|
||||||
|
socket.sendnode(node, (msg)=>{
|
||||||
|
if (!msg.body) {
|
||||||
|
node.hostname = old;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
domlib.newAt(owner, 'span').innerHTML = 'Owner: ';
|
||||||
|
this.ownerInput = domlib.newAt(owner, 'input');
|
||||||
|
this.ownerInput.setAttribute('placeholder', 'Owner');
|
||||||
|
this.ownerInput.addEventListener('focusin', () => {
|
||||||
|
this.editing = true;
|
||||||
|
});
|
||||||
|
this.ownerInput.addEventListener('focusout', () => {
|
||||||
|
this.editing = false;
|
||||||
|
|
||||||
|
const node = store.getNode(this.currentNodeID),
|
||||||
|
old = node.owner;
|
||||||
|
|
||||||
|
node.owner = this.ownerInput.value;
|
||||||
|
|
||||||
|
socket.sendnode(node, (msg)=>{
|
||||||
|
if (!msg.body) {
|
||||||
|
node.owner = old;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
mapEl.style.height = '300px';
|
||||||
|
|
||||||
|
this.map = L.map(mapEl).setView(config.map.view.bound, config.map.view.zoom);
|
||||||
|
|
||||||
|
L.tileLayer(config.map.tileLayer, {
|
||||||
|
'maxZoom': config.map.maxZoom
|
||||||
|
}).addTo(this.map);
|
||||||
|
|
||||||
|
this.geoJsonLayer = L.geoJson.ajax(config.map.geojson.url,
|
||||||
|
config.map.geojson);
|
||||||
|
this.geoJsonLayer.addTo(this.map);
|
||||||
|
|
||||||
|
this.marker = L.marker(config.map.view.bound, {'draggable': true,
|
||||||
|
'opacity': 0.5}).addTo(this.map);
|
||||||
|
|
||||||
|
this.marker.on('dragstart', () => {
|
||||||
|
this.editing = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.marker.on('dragend', () => {
|
||||||
|
this.editing = false;
|
||||||
|
const pos = this.marker.getLatLng();
|
||||||
|
|
||||||
|
this.updatePosition(pos.lat, pos.lng);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
this.btnGPS = domlib.newAt(this.el, 'span');
|
||||||
|
this.btnGPS.classList.add('btn');
|
||||||
|
this.btnGPS.innerHTML = 'Start follow position';
|
||||||
|
this.btnGPS.addEventListener('click', () => {
|
||||||
|
if (this.editLocationGPS) {
|
||||||
|
if (this.btnGPS.innerHTML === 'Stop following') {
|
||||||
|
updatePosition();
|
||||||
|
}
|
||||||
|
this.btnGPS.innerHTML = 'Start follow position';
|
||||||
|
navigator.geolocation.clearWatch(this.editLocationGPS);
|
||||||
|
this.editLocationGPS = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.btnGPS.innerHTML = 'Following position';
|
||||||
|
if (navigator.geolocation) {
|
||||||
|
this.editLocationGPS = navigator.geolocation.watchPosition((position) => {
|
||||||
|
this.btnGPS.innerHTML = 'Stop following';
|
||||||
|
this.this.storePosition = position.coords;
|
||||||
|
const latlng = [position.coords.latitude, position.coords.longitude];
|
||||||
|
|
||||||
|
this.marker.setLatLng(latlng);
|
||||||
|
this.map.setView(latlng);
|
||||||
|
}, (error) => {
|
||||||
|
switch (error.code) {
|
||||||
|
case error.TIMEOUT:
|
||||||
|
notify.send('error', 'Find Location timeout');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error('a navigator geolocation error: ', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'enableHighAccuracy': true,
|
||||||
|
'maximumAge': 30000,
|
||||||
|
'timeout': 27000
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notify.send('error', 'Browser did not support Location');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePosition (lat, lng) {
|
||||||
|
const node = store.getNode(this.currentNodeID),
|
||||||
|
newLat = lat || this.storePosition.latitude || false,
|
||||||
|
newlng = lng || this.storePosition.longitude || false;
|
||||||
|
|
||||||
|
if (!newLat || !newlng) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.location = {
|
||||||
|
'latitude': newLat,
|
||||||
|
'longitude': newlng
|
||||||
|
};
|
||||||
|
socket.sendnode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
this.geoJsonLayer.refresh();
|
||||||
|
this.titleID.innerHTML = this.currentNodeID;
|
||||||
|
const node = store.getNode(this.currentNodeID),
|
||||||
|
startdate = new Date();
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
console.log(`node not found: ${this.currentNodeID}`);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
startdate.setMinutes(startdate.getMinutes() - config.node.offline);
|
||||||
|
if (new Date(node.lastseen) < startdate) {
|
||||||
|
this.ago.classList.add('offline');
|
||||||
|
this.ago.classList.remove('online');
|
||||||
|
} else {
|
||||||
|
this.ago.classList.remove('offline');
|
||||||
|
this.ago.classList.add('online');
|
||||||
|
}
|
||||||
|
this.ago.innerHTML = `${FromNowAgo(node.lastseen)} (${node.lastseen})`;
|
||||||
|
if (this.editLocationGPS || this.editing || !node.location || !node.location.latitude || !node.location.longitude) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.titleName.innerHTML = node.hostname;
|
||||||
|
this.hostnameInput.value = node.hostname;
|
||||||
|
this.ownerInput.value = node.owner;
|
||||||
|
|
||||||
|
// eslint-disable-next-line one-var
|
||||||
|
const latlng = [node.location.latitude, node.location.longitude];
|
||||||
|
|
||||||
|
this.map.setView(latlng);
|
||||||
|
this.marker.setLatLng(latlng);
|
||||||
|
this.marker.setOpacity(1);
|
||||||
|
this.map.invalidateSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
setNodeID (nodeID) {
|
||||||
|
this.currentNodeID = nodeID;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
import * as domlib from '../domlib';
|
||||||
|
import * as gui from '../gui';
|
||||||
|
import * as socket from '../socket';
|
||||||
|
import * as store from '../store';
|
||||||
|
import View from '../view';
|
||||||
|
|
||||||
|
export class StatisticsView extends View {
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
super();
|
||||||
|
domlib.newAt(this.el, 'h1').innerHTML = 'Statistics';
|
||||||
|
|
||||||
|
const table = domlib.newAt(this.el, 'table');
|
||||||
|
|
||||||
|
table.classList.add('stats');
|
||||||
|
|
||||||
|
let tr = domlib.newAt(table, 'tr'),
|
||||||
|
title = domlib.newAt(tr, 'th');
|
||||||
|
|
||||||
|
title.innerHTML = 'Nodes';
|
||||||
|
title.setAttribute('colspan', '2');
|
||||||
|
this.nodes = domlib.newAt(tr, 'td');
|
||||||
|
|
||||||
|
tr = domlib.newAt(table, 'tr');
|
||||||
|
title = domlib.newAt(tr, 'th');
|
||||||
|
title.innerHTML = 'Clients';
|
||||||
|
title.setAttribute('colspan', '2');
|
||||||
|
this.clients = domlib.newAt(tr, 'td');
|
||||||
|
|
||||||
|
tr = domlib.newAt(table, 'tr');
|
||||||
|
tr.classList.add('line');
|
||||||
|
domlib.newAt(tr, 'th').innerHTML = 'Wifi';
|
||||||
|
domlib.newAt(tr, 'th').innerHTML = '2.4 Ghz';
|
||||||
|
domlib.newAt(tr, 'th').innerHTML = '5 Ghz';
|
||||||
|
|
||||||
|
tr = domlib.newAt(table, 'tr');
|
||||||
|
this.clientsWifi = domlib.newAt(tr, 'td');
|
||||||
|
this.clientsWifi24 = domlib.newAt(tr, 'td');
|
||||||
|
this.clientsWifi5 = domlib.newAt(tr, 'td');
|
||||||
|
|
||||||
|
// Channels table
|
||||||
|
domlib.newAt(this.el, 'h1').innerHTML = 'Channels';
|
||||||
|
this.channelTabelle = domlib.newAt(this.el, 'table');
|
||||||
|
this.channelTabelle.classList.add('stats');
|
||||||
|
|
||||||
|
socket.setEvent('stats', (msg) => {
|
||||||
|
if (msg.body) {
|
||||||
|
this.nodes.innerHTML = msg.body.Nodes;
|
||||||
|
this.clients.innerHTML = msg.body.Clients;
|
||||||
|
this.clientsWifi.innerHTML = msg.body.ClientsWifi;
|
||||||
|
this.clientsWifi24.innerHTML = msg.body.ClientsWifi24;
|
||||||
|
this.clientsWifi5.innerHTML = msg.body.ClientsWifi5;
|
||||||
|
|
||||||
|
while(this.channelTabelle.hasChildNodes()) {
|
||||||
|
this.channelTabelle.removeChild(this.channelTabelle.firstElementChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tr = domlib.newAt(this.channelTabelle, 'tr');
|
||||||
|
|
||||||
|
let title = domlib.newAt(tr, 'th');
|
||||||
|
title.innerHTML = '2.4 Ghz';
|
||||||
|
title.setAttribute('colspan', '2');
|
||||||
|
|
||||||
|
title = domlib.newAt(tr, 'th');
|
||||||
|
title.innerHTML = '5 Ghz';
|
||||||
|
title.setAttribute('colspan', '2');
|
||||||
|
const storeNodes = store.getNodes();
|
||||||
|
for (let ch = 1; ch <= 33; ch++) {
|
||||||
|
tr = domlib.newAt(this.channelTabelle, 'tr');
|
||||||
|
if (ch < 14) {
|
||||||
|
domlib.newAt(tr, 'td').innerHTML = ch;
|
||||||
|
domlib.newAt(tr, 'td').innerHTML = storeNodes.reduce((c, node) => node.wireless.channel24 === ch ? c + 1 : c, 0);
|
||||||
|
} else {
|
||||||
|
domlib.newAt(tr, 'td');
|
||||||
|
domlib.newAt(tr, 'td');
|
||||||
|
}
|
||||||
|
const ch5 = 32 + ch * 4;
|
||||||
|
domlib.newAt(tr, 'td').innerHTML = ch5;
|
||||||
|
domlib.newAt(tr, 'td').innerHTML = storeNodes.reduce((c, node) => node.wireless.channel5 === ch5 ? c + 1 : c, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"name": "freifunkmanager",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"dependencies": {
|
||||||
|
"babel-core": "^6.26.0",
|
||||||
|
"babel-preset-env": "^1.6.1",
|
||||||
|
"babel-preset-es2017": "^6.24.1",
|
||||||
|
"babel-register": "^6.26.0",
|
||||||
|
"babelify": "^8.0.0",
|
||||||
|
"browser-sync": "^2.18.13",
|
||||||
|
"browserify": "^14.5.0",
|
||||||
|
"gulp": "3.9.0",
|
||||||
|
"gulp-autoprefixer": "^4.0.0",
|
||||||
|
"gulp-cli": "^1.4.0",
|
||||||
|
"gulp-less": "^3.3.2",
|
||||||
|
"gulp-load-plugins": "^1.5.0",
|
||||||
|
"gulp-plumber": "^1.1.0",
|
||||||
|
"gulp-sourcemaps": "^2.6.1",
|
||||||
|
"gulp-uglify": "^3.0.0",
|
||||||
|
"leaflet": "^1.3.1",
|
||||||
|
"leaflet-ajax": "^2.1.0",
|
||||||
|
"leaflet-webgl-heatmap": "^0.2.7",
|
||||||
|
"navigo": "^5.3.3",
|
||||||
|
"semantic-ui-less": "^2.2.12",
|
||||||
|
"superfine": "^5.0.1",
|
||||||
|
"vinyl-buffer": "^1.0.0",
|
||||||
|
"vinyl-source-stream": "^1.1.0",
|
||||||
|
"watchify": "^3.9.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"gulp": "gulp"
|
||||||
|
},
|
||||||
|
"babel": {
|
||||||
|
"presets": [
|
||||||
|
"es2017"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,123 +0,0 @@
|
||||||
package websocket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/net/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
const channelBufSize = 100
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
ip string
|
|
||||||
ws *websocket.Conn
|
|
||||||
ch chan *Message
|
|
||||||
writeQuit chan bool
|
|
||||||
readQuit chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClient(ip string, ws *websocket.Conn) *Client {
|
|
||||||
|
|
||||||
if ws == nil {
|
|
||||||
log.Panic("ws cannot be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Client{
|
|
||||||
ws: ws,
|
|
||||||
ch: make(chan *Message, channelBufSize),
|
|
||||||
writeQuit: make(chan bool),
|
|
||||||
readQuit: make(chan bool),
|
|
||||||
ip: ip,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Write(msg *Message) {
|
|
||||||
select {
|
|
||||||
case c.ch <- msg:
|
|
||||||
default:
|
|
||||||
clientsMutex.Lock()
|
|
||||||
delete(clients, c.ip)
|
|
||||||
clientsMutex.Unlock()
|
|
||||||
log.Error("client disconnected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Close() {
|
|
||||||
c.writeQuit <- true
|
|
||||||
c.readQuit <- true
|
|
||||||
log.Info("client disconnecting...")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen Write and Read request via chanel
|
|
||||||
func (c *Client) Listen() {
|
|
||||||
go c.listenWrite()
|
|
||||||
if stats != nil {
|
|
||||||
c.Write(&Message{Type: MessageTypeStats, Body: stats})
|
|
||||||
}
|
|
||||||
c.publishAllData()
|
|
||||||
c.listenRead()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) publishAllData() {
|
|
||||||
for _, node := range nodes.List {
|
|
||||||
c.Write(&Message{Type: MessageTypeSystemNode, Node: node})
|
|
||||||
}
|
|
||||||
for _, node := range nodes.Current {
|
|
||||||
c.Write(&Message{Type: MessageTypeCurrentNode, Node: node})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) handleMessage(msg *Message) {
|
|
||||||
switch msg.Type {
|
|
||||||
case MessageTypeSystemNode:
|
|
||||||
nodes.UpdateNode(msg.Node)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen write request via chanel
|
|
||||||
func (c *Client) listenWrite() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case msg := <-c.ch:
|
|
||||||
websocket.JSON.Send(c.ws, msg)
|
|
||||||
|
|
||||||
case <-c.writeQuit:
|
|
||||||
clientsMutex.Lock()
|
|
||||||
close(c.ch)
|
|
||||||
close(c.writeQuit)
|
|
||||||
delete(clients, c.ip)
|
|
||||||
clientsMutex.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen read request via chanel
|
|
||||||
func (c *Client) listenRead() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
|
|
||||||
case <-c.readQuit:
|
|
||||||
clientsMutex.Lock()
|
|
||||||
close(c.readQuit)
|
|
||||||
delete(clients, c.ip)
|
|
||||||
clientsMutex.Unlock()
|
|
||||||
return
|
|
||||||
|
|
||||||
default:
|
|
||||||
var msg Message
|
|
||||||
err := websocket.JSON.Receive(c.ws, &msg)
|
|
||||||
if err == io.EOF {
|
|
||||||
close(c.readQuit)
|
|
||||||
c.writeQuit <- true
|
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
} else {
|
|
||||||
c.handleMessage(&msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/genofire/golang-lib/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WebsocketHandlerFunc func(*log.Entry, *websocket.Message) error
|
||||||
|
|
||||||
|
func (ws *WebsocketServer) MessageHandler() {
|
||||||
|
for msg := range ws.inputMSG {
|
||||||
|
logger := log.WithFields(log.Fields{
|
||||||
|
"session": msg.Session,
|
||||||
|
"id": msg.ID,
|
||||||
|
"subject": msg.Subject,
|
||||||
|
})
|
||||||
|
if handler, ok := ws.handlers[msg.Subject]; ok {
|
||||||
|
err := handler(logger, msg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("websocket message '%s' cound not handle: %s", msg.Subject, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Warnf("websocket message '%s' cound not handle", msg.Subject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/genofire/golang-lib/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ws *WebsocketServer) loginHandler(logger *log.Entry, msg *websocket.Message) error {
|
||||||
|
_, ok := ws.loggedIn[msg.Session]
|
||||||
|
if ok {
|
||||||
|
msg.Answer(msg.Subject, true)
|
||||||
|
logger.Warn("already loggedIn")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
secret, ok := msg.Body.(string)
|
||||||
|
if !ok {
|
||||||
|
logger.Warn("invalid secret format")
|
||||||
|
msg.Answer(msg.Subject, false)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ok = (ws.secret == secret)
|
||||||
|
if ok {
|
||||||
|
ws.loggedIn[msg.Session] = true
|
||||||
|
logger.Debug("done")
|
||||||
|
} else {
|
||||||
|
logger.Warn("wrong secret")
|
||||||
|
}
|
||||||
|
msg.Answer(msg.Subject, ok)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WebsocketServer) authStatusHandler(logger *log.Entry, msg *websocket.Message) error {
|
||||||
|
login, ok := ws.loggedIn[msg.Session]
|
||||||
|
defer logger.Debug("done")
|
||||||
|
if !ok {
|
||||||
|
msg.Answer(msg.Subject, false)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
msg.Answer(msg.Subject, login)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (ws *WebsocketServer) logoutHandler(logger *log.Entry, msg *websocket.Message) error {
|
||||||
|
_, ok := ws.loggedIn[msg.Session]
|
||||||
|
if !ok {
|
||||||
|
msg.Answer(msg.Subject, false)
|
||||||
|
logger.Warn("logout without login")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ws.loggedIn[msg.Session] = false
|
||||||
|
delete(ws.loggedIn, msg.Session)
|
||||||
|
logger.Debug("done")
|
||||||
|
msg.Answer(msg.Subject, true)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
wsLib "github.com/genofire/golang-lib/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ws *WebsocketServer) connectHandler(logger *log.Entry, msg *wsLib.Message) error {
|
||||||
|
msg.From.Write(&wsLib.Message{Subject: MessageTypeStats, Body: ws.nodes.Statistics})
|
||||||
|
|
||||||
|
for _, node := range ws.nodes.List {
|
||||||
|
msg.From.Write(&wsLib.Message{Subject: MessageTypeSystemNode, Body: node})
|
||||||
|
}
|
||||||
|
for _, node := range ws.nodes.Current {
|
||||||
|
msg.From.Write(&wsLib.Message{Subject: MessageTypeCurrentNode, Body: node})
|
||||||
|
}
|
||||||
|
logger.Debug("done")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
wsLib "github.com/genofire/golang-lib/websocket"
|
||||||
|
|
||||||
|
"github.com/FreifunkBremen/freifunkmanager/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ws *WebsocketServer) nodeHandler(logger *log.Entry, msg *wsLib.Message) error {
|
||||||
|
if ok, exists := ws.loggedIn[msg.Session]; !ok || !exists {
|
||||||
|
msg.Answer(msg.Subject, false)
|
||||||
|
logger.Warn("not logged in")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
node := runtime.Node{}
|
||||||
|
if err := mapstructure.Decode(msg.Body, &node); err != nil {
|
||||||
|
msg.Answer(msg.Subject, false)
|
||||||
|
logger.Warnf("not able to decode data: %s", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if node.NodeID == "" {
|
||||||
|
msg.Answer(msg.Subject, false)
|
||||||
|
logger.Warnf("not able to find nodeid")
|
||||||
|
logger.Debugf("%v", node)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ws.nodes.UpdateNode(&node)
|
||||||
|
msg.Answer(msg.Subject, true)
|
||||||
|
logger.Infof("change %s", node.NodeID)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,15 +1,13 @@
|
||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import "github.com/FreifunkBremen/freifunkmanager/runtime"
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Body interface{} `json:"body,omitempty"`
|
|
||||||
Node *runtime.Node `json:"node,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MessageTypeSystemNode = "system"
|
MessageTypeConnect = "connect"
|
||||||
MessageTypeCurrentNode = "current"
|
|
||||||
|
MessageTypeLogin = "login"
|
||||||
|
MessageTypeAuthStatus = "auth_status"
|
||||||
|
MessageTypeLogout = "logout"
|
||||||
|
|
||||||
|
MessageTypeSystemNode = "node-system"
|
||||||
|
MessageTypeCurrentNode = "node-current"
|
||||||
MessageTypeStats = "stats"
|
MessageTypeStats = "stats"
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
wsLib "github.com/genofire/golang-lib/websocket"
|
||||||
|
|
||||||
|
yanicRuntime "github.com/FreifunkBremen/yanic/runtime"
|
||||||
|
|
||||||
|
"github.com/FreifunkBremen/freifunkmanager/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ws *WebsocketServer) SendNode(node *runtime.Node, system bool) {
|
||||||
|
msgType := MessageTypeCurrentNode
|
||||||
|
if system {
|
||||||
|
msgType = MessageTypeSystemNode
|
||||||
|
}
|
||||||
|
ws.ws.SendAll(&wsLib.Message{Subject: msgType, Body: node})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WebsocketServer) SendStats(data *yanicRuntime.GlobalStats) {
|
||||||
|
ws.ws.SendAll(&wsLib.Message{Subject: MessageTypeStats, Body: data})
|
||||||
|
}
|
|
@ -2,69 +2,47 @@ package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
|
||||||
|
|
||||||
runtimeYanic "github.com/FreifunkBremen/yanic/runtime"
|
wsLib "github.com/genofire/golang-lib/websocket"
|
||||||
httpLib "github.com/genofire/golang-lib/http"
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/net/websocket"
|
|
||||||
|
|
||||||
"github.com/FreifunkBremen/freifunkmanager/runtime"
|
"github.com/FreifunkBremen/freifunkmanager/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
var nodes *runtime.Nodes
|
type WebsocketServer struct {
|
||||||
var clients map[string]*Client
|
nodes *runtime.Nodes
|
||||||
var clientsMutex sync.Mutex
|
secret string
|
||||||
var stats *runtimeYanic.GlobalStats
|
loggedIn map[uuid.UUID]bool
|
||||||
|
|
||||||
func Start(nodeBind *runtime.Nodes) {
|
inputMSG chan *wsLib.Message
|
||||||
nodes = nodeBind
|
ws *wsLib.Server
|
||||||
clients = make(map[string]*Client)
|
handlers map[string]WebsocketHandlerFunc
|
||||||
|
|
||||||
http.Handle("/websocket", websocket.Handler(func(ws *websocket.Conn) {
|
|
||||||
r := ws.Request()
|
|
||||||
ip := httpLib.GetRemoteIP(r)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
ws.Close()
|
|
||||||
clientsMutex.Lock()
|
|
||||||
delete(clients, ip)
|
|
||||||
clientsMutex.Unlock()
|
|
||||||
log.Info("client disconnected")
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Infof("new client")
|
|
||||||
|
|
||||||
client := NewClient(ip, ws)
|
|
||||||
clientsMutex.Lock()
|
|
||||||
clients[ip] = client
|
|
||||||
clientsMutex.Unlock()
|
|
||||||
client.Listen()
|
|
||||||
|
|
||||||
}))
|
|
||||||
|
|
||||||
nodes.AddNotify(NotifyNode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NotifyNode(node *runtime.Node, system bool) {
|
func NewWebsocketServer(secret string, nodes *runtime.Nodes) *WebsocketServer {
|
||||||
msgType := MessageTypeCurrentNode
|
ownWS := WebsocketServer{
|
||||||
if system {
|
nodes: nodes,
|
||||||
msgType = MessageTypeSystemNode
|
handlers: make(map[string]WebsocketHandlerFunc),
|
||||||
|
loggedIn: make(map[uuid.UUID]bool),
|
||||||
|
inputMSG: make(chan *wsLib.Message),
|
||||||
|
secret: secret,
|
||||||
}
|
}
|
||||||
SendAll(Message{Type: msgType, Node: node})
|
ownWS.ws = wsLib.NewServer(ownWS.inputMSG, wsLib.NewSessionManager())
|
||||||
}
|
|
||||||
func NotifyStats(data *runtimeYanic.GlobalStats) {
|
// Register Handlers
|
||||||
stats = data
|
ownWS.handlers[MessageTypeConnect] = ownWS.connectHandler
|
||||||
SendAll(Message{Type: MessageTypeStats, Body: data})
|
|
||||||
}
|
ownWS.handlers[MessageTypeLogin] = ownWS.loginHandler
|
||||||
func SendAll(msg Message) {
|
ownWS.handlers[MessageTypeAuthStatus] = ownWS.authStatusHandler
|
||||||
clientsMutex.Lock()
|
ownWS.handlers[MessageTypeLogout] = ownWS.logoutHandler
|
||||||
for _, c := range clients {
|
|
||||||
c.Write(&msg)
|
ownWS.handlers[MessageTypeSystemNode] = ownWS.nodeHandler
|
||||||
}
|
|
||||||
clientsMutex.Unlock()
|
http.HandleFunc("/ws", ownWS.ws.Handler)
|
||||||
|
go ownWS.MessageHandler()
|
||||||
|
return &ownWS
|
||||||
}
|
}
|
||||||
|
|
||||||
func Close() {
|
func (ws *WebsocketServer) Close() {
|
||||||
log.Infof("websocket stopped with %d clients", len(clients))
|
close(ws.inputMSG)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package websocket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/FreifunkBremen/freifunkmanager/runtime"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStart(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
nodes := &runtime.Nodes{}
|
|
||||||
assert.Nil(clients)
|
|
||||||
Start(nodes)
|
|
||||||
assert.NotNil(clients)
|
|
||||||
Close()
|
|
||||||
}
|
|
Loading…
Reference in New Issue