add easy webinterface
This commit is contained in:
parent
d20e749038
commit
16fdeb85d4
|
@ -24,7 +24,7 @@ test-my-project:
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- go get github.com/client9/misspell/cmd/misspell
|
- go get github.com/client9/misspell/cmd/misspell
|
||||||
- misspell -error .
|
- find . -type f | grep -v webroot/assets | xargs misspell -error
|
||||||
- ./.ci/check-gofmt
|
- ./.ci/check-gofmt
|
||||||
- ./.ci/check-testfiles
|
- ./.ci/check-testfiles
|
||||||
- go test $(go list ./... | grep -v /vendor/) -v -coverprofile .testCoverage.txt
|
- go test $(go list ./... | grep -v /vendor/) -v -coverprofile .testCoverage.txt
|
||||||
|
@ -44,7 +44,10 @@ deploy:
|
||||||
script:
|
script:
|
||||||
- go install "dev.sum7.eu/$CI_PROJECT_PATH"
|
- go install "dev.sum7.eu/$CI_PROJECT_PATH"
|
||||||
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
|
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
|
||||||
|
- 'which rsync || ( apt-get update -y && apt-get install rsync -y )'
|
||||||
- eval $(ssh-agent -s)
|
- eval $(ssh-agent -s)
|
||||||
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
|
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
|
||||||
|
- rsync -e "ssh -6 -o StrictHostKeyChecking=no -p $SSH_PORT" -a --delete "/builds/$CI_PROJECT_PATH/webroot/" "$CI_PROJECT_NAME@$SSH_HOST":/opt/$CI_PROJECT_NAME/webroot/
|
||||||
|
- ssh -6 -o StrictHostKeyChecking=no -p $SSH_PORT "$CI_PROJECT_NAME@$SSH_HOST" sudo /usr/bin/systemctl stop $CI_PROJECT_NAME
|
||||||
- scp -6 -o StrictHostKeyChecking=no -P $SSH_PORT "/go/bin/$CI_PROJECT_NAME" "$CI_PROJECT_NAME@$SSH_HOST":/opt/$CI_PROJECT_NAME/bin
|
- scp -6 -o StrictHostKeyChecking=no -P $SSH_PORT "/go/bin/$CI_PROJECT_NAME" "$CI_PROJECT_NAME@$SSH_HOST":/opt/$CI_PROJECT_NAME/bin
|
||||||
- ssh -6 -o StrictHostKeyChecking=no -p $SSH_PORT "$CI_PROJECT_NAME@$SSH_HOST" sudo /usr/bin/systemctl restart $CI_PROJECT_NAME
|
- ssh -6 -o StrictHostKeyChecking=no -p $SSH_PORT "$CI_PROJECT_NAME@$SSH_HOST" sudo /usr/bin/systemctl start $CI_PROJECT_NAME
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -12,7 +13,9 @@ import (
|
||||||
"dev.sum7.eu/genofire/wifictld-analyzer/capture"
|
"dev.sum7.eu/genofire/wifictld-analyzer/capture"
|
||||||
"dev.sum7.eu/genofire/wifictld-analyzer/config"
|
"dev.sum7.eu/genofire/wifictld-analyzer/config"
|
||||||
"dev.sum7.eu/genofire/wifictld-analyzer/controller"
|
"dev.sum7.eu/genofire/wifictld-analyzer/controller"
|
||||||
|
"dev.sum7.eu/genofire/wifictld-analyzer/data"
|
||||||
"dev.sum7.eu/genofire/wifictld-analyzer/database"
|
"dev.sum7.eu/genofire/wifictld-analyzer/database"
|
||||||
|
"dev.sum7.eu/genofire/wifictld-analyzer/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
// queryCmd represents the query command
|
// queryCmd represents the query command
|
||||||
|
@ -31,7 +34,26 @@ var controllerCmd = &cobra.Command{
|
||||||
ctr := controller.NewController(db)
|
ctr := controller.NewController(db)
|
||||||
defer ctr.Close()
|
defer ctr.Close()
|
||||||
|
|
||||||
coll := capture.NewCollector(ctr.Handler, configObj.Interfaces)
|
var handlers []data.Handler
|
||||||
|
|
||||||
|
if configObj.Webserver.Enable {
|
||||||
|
log.Infof("starting webserver on %s", configObj.Webserver.Bind)
|
||||||
|
srv := web.New(configObj.Webserver)
|
||||||
|
go srv.Start()
|
||||||
|
handlers = append(handlers, srv.Handler)
|
||||||
|
defer srv.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
coll := capture.NewCollector(func(addr *net.UDPAddr, msg *data.SocketMSG) (*data.SocketMSG, error) {
|
||||||
|
for _, a := range handlers {
|
||||||
|
a(addr, msg)
|
||||||
|
}
|
||||||
|
if !configObj.Answer {
|
||||||
|
ctr.Handler(addr, msg)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return ctr.Handler(addr, msg)
|
||||||
|
}, configObj.Interfaces)
|
||||||
defer coll.Close()
|
defer coll.Close()
|
||||||
|
|
||||||
ctr.Send = coll.Send
|
ctr.Send = coll.Send
|
||||||
|
@ -47,5 +69,5 @@ var controllerCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RootCmd.AddCommand(controllerCmd)
|
RootCMD.AddCommand(controllerCmd)
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ var dumpCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RootCmd.AddCommand(dumpCmd)
|
RootCMD.AddCommand(dumpCmd)
|
||||||
dumpCmd.Flags().IntVar(&port, "port", capture.Port, "define a port to listen (if not set or set to 0 the kernel will use a random free port at its own)")
|
dumpCmd.Flags().IntVar(&port, "port", capture.Port, "define a port to listen (if not set or set to 0 the kernel will use a random free port at its own)")
|
||||||
dumpCmd.Flags().StringVar(&ipAddress, "listen", capture.MulticastAddressDefault, "")
|
dumpCmd.Flags().StringVar(&ipAddress, "listen", capture.MulticastAddressDefault, "")
|
||||||
}
|
}
|
||||||
|
|
19
cmd/root.go
19
cmd/root.go
|
@ -6,19 +6,22 @@ import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var debug bool
|
var (
|
||||||
|
debug bool
|
||||||
|
timestamps bool
|
||||||
|
)
|
||||||
|
|
||||||
// RootCmd represents the base command when called without any subcommands
|
// RootCMD represents the base command when called without any subcommands
|
||||||
var RootCmd = &cobra.Command{
|
var RootCMD = &cobra.Command{
|
||||||
Use: "analyzer",
|
Use: "analyzer",
|
||||||
Short: "wifictld analyzer",
|
Short: "wifictld analyzer",
|
||||||
Long: `capture wifictld traffic and display thus`,
|
Long: `capture wifictld traffic and display thus`,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
// This is called by main.main(). It only needs to happen once to the RootCMD.
|
||||||
func Execute() {
|
func Execute() {
|
||||||
if err := RootCmd.Execute(); err != nil {
|
if err := RootCMD.Execute(); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +30,11 @@ func init() {
|
||||||
if debug {
|
if debug {
|
||||||
log.SetLevel(log.DebugLevel)
|
log.SetLevel(log.DebugLevel)
|
||||||
}
|
}
|
||||||
|
log.SetFormatter(&log.TextFormatter{
|
||||||
|
DisableTimestamp: timestamps,
|
||||||
|
})
|
||||||
log.Debug("show debug")
|
log.Debug("show debug")
|
||||||
})
|
})
|
||||||
RootCmd.PersistentFlags().BoolVar(&debug, "v", false, "show debug log")
|
RootCMD.PersistentFlags().BoolVar(&debug, "v", false, "show debug log")
|
||||||
|
RootCMD.PersistentFlags().BoolVar(×tamps, "timestamps", false, "Enables timestamps for log output")
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,12 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"dev.sum7.eu/genofire/wifictld-analyzer/capture"
|
"dev.sum7.eu/genofire/wifictld-analyzer/capture"
|
||||||
|
"dev.sum7.eu/genofire/wifictld-analyzer/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
StatePath string `toml:"state_path"`
|
StatePath string `toml:"state_path"`
|
||||||
|
Answer bool `toml:"answer"`
|
||||||
|
Webserver *web.Config `toml:"webserver"`
|
||||||
Interfaces []*capture.IFaceConfig `toml:"interfaces"`
|
Interfaces []*capture.IFaceConfig `toml:"interfaces"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
state_path = "/tmp/wifictld.json"
|
state_path = "/tmp/wifictld.json"
|
||||||
|
answer = false
|
||||||
|
|
||||||
|
|
||||||
|
[webserver]
|
||||||
|
enable = true
|
||||||
|
bind = ":8080"
|
||||||
|
webroot = "./webroot/"
|
||||||
|
|
||||||
[[interfaces]]
|
[[interfaces]]
|
||||||
ifname = "wlp4s0"
|
ifname = "wlp4s0"
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
type HardwareAddr struct{ net.HardwareAddr }
|
||||||
|
|
||||||
|
//MarshalJSON to bytearray
|
||||||
|
func (a HardwareAddr) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(a.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON from bytearray
|
||||||
|
func (a HardwareAddr) UnmarshalText(data []byte) (err error) {
|
||||||
|
a.HardwareAddr, err = net.ParseMAC(string(data))
|
||||||
|
return
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ package data
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bdlm/log"
|
"github.com/bdlm/log"
|
||||||
|
@ -31,8 +30,8 @@ func (a SocketMSGType) Is(b SocketMSGType) bool {
|
||||||
|
|
||||||
// SocketMSG package of wifictld format
|
// SocketMSG package of wifictld format
|
||||||
type SocketMSG struct {
|
type SocketMSG struct {
|
||||||
Types SocketMSGType
|
Types SocketMSGType `json:"types"`
|
||||||
Client *WifiClient
|
Client *WifiClient `json:"client,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSocketMSG(obj []byte) (*SocketMSG, error) {
|
func NewSocketMSG(obj []byte) (*SocketMSG, error) {
|
||||||
|
@ -51,17 +50,17 @@ func (msg *SocketMSG) Marshal() ([]byte, error) {
|
||||||
pos += 4
|
pos += 4
|
||||||
|
|
||||||
if msg.Types.Is(SocketMSGTypeClient) {
|
if msg.Types.Is(SocketMSGTypeClient) {
|
||||||
obj[pos] = msg.Client.Addr[0]
|
obj[pos] = msg.Client.Addr.HardwareAddr[0]
|
||||||
pos++
|
pos++
|
||||||
obj[pos] = msg.Client.Addr[1]
|
obj[pos] = msg.Client.Addr.HardwareAddr[1]
|
||||||
pos++
|
pos++
|
||||||
obj[pos] = msg.Client.Addr[2]
|
obj[pos] = msg.Client.Addr.HardwareAddr[2]
|
||||||
pos++
|
pos++
|
||||||
obj[pos] = msg.Client.Addr[3]
|
obj[pos] = msg.Client.Addr.HardwareAddr[3]
|
||||||
pos++
|
pos++
|
||||||
obj[pos] = msg.Client.Addr[4]
|
obj[pos] = msg.Client.Addr.HardwareAddr[4]
|
||||||
pos++
|
pos++
|
||||||
obj[pos] = msg.Client.Addr[5]
|
obj[pos] = msg.Client.Addr.HardwareAddr[5]
|
||||||
pos++
|
pos++
|
||||||
binary.BigEndian.PutUint32(obj[pos:(pos+4)], uint32(msg.Client.Time.Unix()))
|
binary.BigEndian.PutUint32(obj[pos:(pos+4)], uint32(msg.Client.Time.Unix()))
|
||||||
pos += 4
|
pos += 4
|
||||||
|
@ -100,7 +99,7 @@ func (msg *SocketMSG) Unmarshal(obj []byte) error {
|
||||||
|
|
||||||
if msg.Types.Is(SocketMSGTypeClient) {
|
if msg.Types.Is(SocketMSGTypeClient) {
|
||||||
msg.Client = &WifiClient{
|
msg.Client = &WifiClient{
|
||||||
Addr: net.HardwareAddr(obj[pos:(pos + 6)]),
|
Addr: HardwareAddr{HardwareAddr: obj[pos:(pos + 6)]},
|
||||||
Time: time.Unix(int64(binary.BigEndian.Uint32(obj[(pos+6):(pos+10)])), 0),
|
Time: time.Unix(int64(binary.BigEndian.Uint32(obj[(pos+6):(pos+10)])), 0),
|
||||||
TryProbe: binary.BigEndian.Uint16(obj[(pos + 10):(pos + 12)]),
|
TryProbe: binary.BigEndian.Uint16(obj[(pos + 10):(pos + 12)]),
|
||||||
TryAuth: binary.BigEndian.Uint16(obj[(pos + 12):(pos + 14)]),
|
TryAuth: binary.BigEndian.Uint16(obj[(pos + 12):(pos + 14)]),
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import "time"
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// default multicast group used by announced
|
// default multicast group used by announced
|
||||||
|
@ -18,13 +15,13 @@ const (
|
||||||
|
|
||||||
// WifiClient datatype of wifictld
|
// WifiClient datatype of wifictld
|
||||||
type WifiClient struct {
|
type WifiClient struct {
|
||||||
Addr net.HardwareAddr
|
Addr HardwareAddr `json:"addr"`
|
||||||
Time time.Time
|
Time time.Time `json:"time"`
|
||||||
TryProbe uint16
|
TryProbe uint16 `json:"try_probe"`
|
||||||
TryAuth uint16
|
TryAuth uint16 `json:"try_auth"`
|
||||||
Connected bool
|
Connected bool `json:"connected"`
|
||||||
Authed bool
|
Authed bool `json:"authed"`
|
||||||
FreqHighest uint16
|
FreqHighest uint16 `json:"freq_highest"`
|
||||||
SignalLowFreq int16
|
SignalLowFreq int16 `json:"signal_low_freq"`
|
||||||
SignalHighFreq int16
|
SignalHighFreq int16 `json:"signal_high_freq"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
type Client struct {
|
type Client struct {
|
||||||
AP *AP `json:"-"`
|
AP *AP `json:"-"`
|
||||||
APAddr string `json:"ap"`
|
APAddr string `json:"ap"`
|
||||||
Addr net.HardwareAddr `json:"-"`
|
Addr data.HardwareAddr `json:"-"`
|
||||||
TryProbe uint16 `json:"try_probe"`
|
TryProbe uint16 `json:"try_probe"`
|
||||||
TryAuth uint16 `json:"try_auth"`
|
TryAuth uint16 `json:"try_auth"`
|
||||||
Connected bool `json:"connected"`
|
Connected bool `json:"connected"`
|
||||||
|
@ -70,7 +70,7 @@ func (db *DB) LearnClient(apIP net.IP, clientWifictl *data.WifiClient) bool {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) GetClient(addr net.HardwareAddr) *data.WifiClient {
|
func (db *DB) GetClient(addr data.HardwareAddr) *data.WifiClient {
|
||||||
client, ok := db.Clients[addr.String()]
|
client, ok := db.Clients[addr.String()]
|
||||||
wClient := &data.WifiClient{
|
wClient := &data.WifiClient{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bdlm/log"
|
"github.com/bdlm/log"
|
||||||
|
@ -25,7 +24,7 @@ func NewDB(path string) *DB {
|
||||||
file.ReadJSON(path, db)
|
file.ReadJSON(path, db)
|
||||||
|
|
||||||
for addr, client := range db.Clients {
|
for addr, client := range db.Clients {
|
||||||
client.Addr, _ = net.ParseMAC(addr)
|
client.Addr.UnmarshalText([]byte(addr))
|
||||||
if ap, ok := db.APs[client.APAddr]; ok {
|
if ap, ok := db.APs[client.APAddr]; ok {
|
||||||
client.AP = ap
|
client.AP = ap
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package web
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Enable bool `toml:"enable"`
|
||||||
|
Bind string `toml:"bind"`
|
||||||
|
Webroot string `toml:"webroot"`
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/NYTimes/gziphandler"
|
||||||
|
"github.com/bdlm/log"
|
||||||
|
|
||||||
|
"dev.sum7.eu/genofire/golang-lib/websocket"
|
||||||
|
|
||||||
|
"dev.sum7.eu/genofire/wifictld-analyzer/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
web *http.Server
|
||||||
|
ws *websocket.WebsocketHandlerService
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new webserver and starts it
|
||||||
|
func New(config *Config) *Server {
|
||||||
|
ws := websocket.NewWebsocketHandlerService()
|
||||||
|
ws.Listen("/ws")
|
||||||
|
|
||||||
|
http.Handle("/", gziphandler.GzipHandler(http.FileServer(http.Dir(config.Webroot))))
|
||||||
|
return &Server{
|
||||||
|
web: &http.Server{
|
||||||
|
Addr: config.Bind,
|
||||||
|
},
|
||||||
|
ws: ws,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) Handler(addr *net.UDPAddr, msg *data.SocketMSG) (*data.SocketMSG, error) {
|
||||||
|
srv.ws.SendAll(&websocket.Message{
|
||||||
|
Subject: "wifictld_pkg",
|
||||||
|
Body: map[string]interface{}{
|
||||||
|
"ip": addr.IP,
|
||||||
|
"msg": msg,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
func (srv *Server) Start() {
|
||||||
|
// service connections
|
||||||
|
if err := srv.web.ListenAndServe(); err != http.ErrServerClosed {
|
||||||
|
log.Panicf("webserver crashed: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *Server) Close() {
|
||||||
|
srv.web.Close()
|
||||||
|
srv.ws.Close()
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWebserver(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
srv := New(&Config{
|
||||||
|
Bind: ":12345",
|
||||||
|
Webroot: "/tmp",
|
||||||
|
})
|
||||||
|
assert.NotNil(srv)
|
||||||
|
|
||||||
|
go srv.Start()
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond * 200)
|
||||||
|
|
||||||
|
assert.Panics(func() {
|
||||||
|
srv.Start()
|
||||||
|
}, "not allowed to listen twice")
|
||||||
|
|
||||||
|
srv.Close()
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
wget -O vanilla-framework.css https://assets.ubuntu.com/v1/vanilla-framework-version-1.8.1.min.css
|
||||||
|
wget -O vue.js https://cdn.jsdelivr.net/npm/vue@2.6.7/dist/vue.js
|
||||||
|
wget -O vue-route.js https://unpkg.com/vue-router/dist/vue-router.js
|
||||||
|
wget -O vuex.js https://unpkg.com/vuex@2.0.0/dist/vuex.min.js
|
||||||
|
wget -O vue-websocket.js https://raw.githubusercontent.com/nathantsoi/vue-native-websocket/master/dist/build.js
|
||||||
|
wget -O vue-websocket.js.map https://raw.githubusercontent.com/nathantsoi/vue-native-websocket/master/dist/build.js.map
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,10 @@
|
||||||
|
.p-navigation__logo {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 3rem;
|
||||||
|
}
|
||||||
|
.p-navigation .p-navigation__logo .p-navigation__link {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.p-navigation .p-navigation__logo.online .p-navigation__link {
|
||||||
|
color: black;
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<title>Wifictld</title>
|
||||||
|
<link rel="stylesheet" href="assets/vanilla-framework.css" />
|
||||||
|
<link rel="stylesheet" href="css/main.css" />
|
||||||
|
<script src="assets/vue.js"></script>
|
||||||
|
<script src="assets/vue-route.js"></script>
|
||||||
|
<script src="assets/vuex.js"></script>
|
||||||
|
<script src="assets/vue-websocket.js"></script>
|
||||||
|
</head>
|
||||||
|
<body role="document">
|
||||||
|
<div id="app">
|
||||||
|
<header id="navigation" class="p-navigation">
|
||||||
|
<div class="p-navigation__banner">
|
||||||
|
<navbar-logo></navbar-logo>
|
||||||
|
<a href="#navigation" class="p-navigation__toggle--open" title="menu">Menu</a>
|
||||||
|
<a href="#navigation-closed" class="p-navigation__toggle--close" title="close menu">Close menu</a>
|
||||||
|
</div>
|
||||||
|
<nav class="p-navigation__nav" role="menubar">
|
||||||
|
<span class="u-off-screen">
|
||||||
|
<a href="#main-content">Jump to main content</a>
|
||||||
|
</span>
|
||||||
|
<ul class="p-navigation__links" role="menu">
|
||||||
|
<router-link :to="{name: 'aps' }" tag="li" class="p-navigation__link" active-class="is-selected">
|
||||||
|
<a>Access Points</a>
|
||||||
|
</router-link>
|
||||||
|
<router-link :to="{name: 'clients' }" tag="li" class="p-navigation__link" active-class="is-selected">
|
||||||
|
<a>Clients</a>
|
||||||
|
</router-link>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<div class="content">
|
||||||
|
<router-view></router-view>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="js/view/accesspoint.js"></script>
|
||||||
|
<script src="js/view/clients.js"></script>
|
||||||
|
<script src="js/store.js"></script>
|
||||||
|
<script src="js/data.js"></script>
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,20 @@
|
||||||
|
store.commit('setEvent', {
|
||||||
|
subject:"wifictld_pkg",
|
||||||
|
callback: (state, msg) => {
|
||||||
|
|
||||||
|
// add to ap list
|
||||||
|
const ip = msg.body.ip;
|
||||||
|
if(state.controller._ap[ip] === undefined){
|
||||||
|
state.controller.ap.push(ip)
|
||||||
|
state.controller._ap[ip] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add clients
|
||||||
|
const client = msg.body.msg.client;
|
||||||
|
if (state.controller.clients[client.addr] === undefined) {
|
||||||
|
state.controller._clients.push(client.addr)
|
||||||
|
}
|
||||||
|
client.ap = ip;
|
||||||
|
state.controller.clients[client.addr] = client;
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,35 @@
|
||||||
|
const router = new VueRouter({
|
||||||
|
store,
|
||||||
|
routes: [
|
||||||
|
{ path: '/ap', component: ViewAccessPoints, name: "aps" },
|
||||||
|
{ path: '/ap/clients/:ip', component: ViewClients, name: "ap.clients"},
|
||||||
|
{ path: '/clients', component: ViewClients, name: "clients" },
|
||||||
|
{ path: '/', redirect: '/ap' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
VueNativeSock.default.install(Vue, `//${location.host}${location.pathname}ws`, {
|
||||||
|
store: store,
|
||||||
|
reconnection: true,
|
||||||
|
reconnectionDelay: 5000,
|
||||||
|
format: 'json',
|
||||||
|
})
|
||||||
|
|
||||||
|
const NavbarLogo = {
|
||||||
|
template: `<div class="p-navigation__logo" v-bind:class="{ online: isOnline }">
|
||||||
|
<a class="p-navigation__link" href="/">Wifictld</a>
|
||||||
|
</div>`,
|
||||||
|
computed: {
|
||||||
|
isOnline () {
|
||||||
|
return this.$store.state.socket.isConnected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
store,
|
||||||
|
router,
|
||||||
|
components: { NavbarLogo },
|
||||||
|
})
|
|
@ -0,0 +1,104 @@
|
||||||
|
|
||||||
|
function newUUID () {
|
||||||
|
/* eslint-disable */
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||||
|
const r = Math.random() * 16 | 0,
|
||||||
|
v = c === 'x' ? r : r & 0x3 | 0x8;
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
/* eslint-enable */
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
state: {
|
||||||
|
socket: {
|
||||||
|
_session: localStorage.getItem('session'),
|
||||||
|
isConnected: false,
|
||||||
|
reconnectError: false,
|
||||||
|
eventMSGID: {},
|
||||||
|
eventTo: {},
|
||||||
|
},
|
||||||
|
controller: {
|
||||||
|
_ap : {},
|
||||||
|
ap: [],
|
||||||
|
_clients: [],
|
||||||
|
clients: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutations:{
|
||||||
|
SOCKET_ONOPEN (state, event) {
|
||||||
|
state.socket.isConnected = true
|
||||||
|
},
|
||||||
|
SOCKET_ONCLOSE (state, event) {
|
||||||
|
state.socket.isConnected = false
|
||||||
|
},
|
||||||
|
SOCKET_ONERROR (state, event) {
|
||||||
|
console.error(state, event)
|
||||||
|
},
|
||||||
|
// default handler called for all methods
|
||||||
|
SOCKET_ONMESSAGE (state, msg) {
|
||||||
|
if (msg.subject === 'session_init') {
|
||||||
|
if (state.socket._session === null) {
|
||||||
|
state.socket._session = newUUID();
|
||||||
|
localStorage.setItem('session', state.socket._session);
|
||||||
|
}
|
||||||
|
msg.id = state.socket._session;
|
||||||
|
Vue.prototype.$socket.sendObj(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgFunc = state.socket.eventMSGID[msg.id];
|
||||||
|
if (msgFunc) {
|
||||||
|
msgFunc(state, msg);
|
||||||
|
delete state.socket.eventMSGID[msg.id];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventFuncs = state.socket.eventTo[msg.subject];
|
||||||
|
if (typeof eventFuncs === 'object' && eventFuncs.length > 0) {
|
||||||
|
for (const key in eventFuncs) {
|
||||||
|
const func = eventFuncs[key];
|
||||||
|
if (func) {
|
||||||
|
func(state, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(`unable to identify message: ${msg.subject}`);
|
||||||
|
|
||||||
|
},
|
||||||
|
// mutations for reconnect methods
|
||||||
|
SOCKET_RECONNECT(state, count) {
|
||||||
|
console.info(state, "reconnect:", count)
|
||||||
|
},
|
||||||
|
SOCKET_RECONNECT_ERROR(state) {
|
||||||
|
state.socket.reconnectError = true;
|
||||||
|
},
|
||||||
|
setEvent (state, data) {
|
||||||
|
state.socket.eventTo[data.subject] = [data.callback];
|
||||||
|
},
|
||||||
|
addEvent (state, data) {
|
||||||
|
if (typeof state.socket.eventTo[data.subject] !== 'object') {
|
||||||
|
state.socket.eventTo[data.subject] = [];
|
||||||
|
}
|
||||||
|
state.socket.eventTo[data.subject].push(data.callback);
|
||||||
|
},
|
||||||
|
delEvent (state, data) {
|
||||||
|
if (typeof state.socket.eventTo[data.subject] === 'object' && state.socket.eventTo[data.subject].length > 1) {
|
||||||
|
state.socket.eventTo[data.subject].pop(data.callback);
|
||||||
|
} else {
|
||||||
|
state.socket.eventTo[data.subject] = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
call (state, data) {
|
||||||
|
if (!data.msg.id) {
|
||||||
|
data.msg.id = newUUID();
|
||||||
|
}
|
||||||
|
const ret = Vue.prototype.$socket.sendObj(data.msg);
|
||||||
|
if (typeof data.callback === 'function') {
|
||||||
|
state.socket.eventMSGID[data.msg.id] = data.callback;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,37 @@
|
||||||
|
const ViewAccessPoints = {
|
||||||
|
template: `<div class="row">
|
||||||
|
<p><span class="p-heading--one">AccessPoints</span></p>
|
||||||
|
<table class="p-table--mobile-card" role="grid">
|
||||||
|
<thead>
|
||||||
|
<tr role="row">
|
||||||
|
<th scope="col" role="columnheader" aria-sort="none">IP</th>
|
||||||
|
<th scope="col" role="columnheader" aria-sort="none" class="u-align--right">Users</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr role="row" v-for="item in getAPs">
|
||||||
|
<td role="rowheader" aria-label="IP">{{ item.ip.substring(21) }}</td>
|
||||||
|
<td role="gridcell" aria-label="Users" class="u-align--right">
|
||||||
|
<router-link :to="{ name: 'ap.clients', params: { ip: item.ip }}">
|
||||||
|
{{ item.clients }}
|
||||||
|
</router-link>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>`,
|
||||||
|
computed: {
|
||||||
|
getAPs () {
|
||||||
|
const state = this.$store.state;
|
||||||
|
return state.controller.ap.map(function (ip) {
|
||||||
|
return {
|
||||||
|
ip: ip,
|
||||||
|
clients: state.controller._clients.filter(function (hwaddr) {
|
||||||
|
const client = state.controller.clients[hwaddr];
|
||||||
|
return client.ap === ip;
|
||||||
|
}).length,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
const ViewClients = {
|
||||||
|
template: `<div class="row">
|
||||||
|
<p>
|
||||||
|
<span class="p-heading--one">Clients</span>
|
||||||
|
<sub v-if="$route.params.ip">of Router {{ $route.params.ip.substring(21) }}</sub>
|
||||||
|
</p>
|
||||||
|
<table class="p-table--mobile-card" role="grid">
|
||||||
|
<thead>
|
||||||
|
<tr role="row">
|
||||||
|
<th scope="col" role="columnheader" aria-sort="none">Addr</th>
|
||||||
|
<th scope="col" role="columnheader" aria-sort="none">AP</th>
|
||||||
|
<th scope="col" role="columnheader" aria-sort="none" class="u-align--right">FreqHighs</th>
|
||||||
|
<th scope="col" role="columnheader" aria-sort="none" class="u-align--right">SignalLow</th>
|
||||||
|
<th scope="col" role="columnheader" aria-sort="none" class="u-align--right">SignalHigh</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr role="row" v-for="item in getClients">
|
||||||
|
<td role="rowheader" aria-label="Addr">{{ item.addr.substring(12) }}</td>
|
||||||
|
<td role="gridcell" aria-label="AP">
|
||||||
|
<router-link :to="{ name: 'ap.clients', params: { ip: item.ap }}">
|
||||||
|
{{ item.ap.substring(21) }}
|
||||||
|
</router-link>
|
||||||
|
</td>
|
||||||
|
<td role="gridcell" aria-label="Freq Highs" class="u-align--right">{{ item.freq_highest }}</td>
|
||||||
|
<td role="gridcell" aria-label="Signal LowF" class="u-align--right">{{ item.signal_low_freq }}</td>
|
||||||
|
<td role="gridcell" aria-label="Signal HighF" class="u-align--right">{{ item.signal_high_freq }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>`,
|
||||||
|
computed: {
|
||||||
|
getClients () {
|
||||||
|
const state = this.$store.state,
|
||||||
|
apIP = this.$route.params.ip;
|
||||||
|
|
||||||
|
return state.controller._clients.map(function(addr) {
|
||||||
|
return state.controller.clients[addr];
|
||||||
|
}).filter((client)=> (apIP === undefined || client.ap === apIP));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue