add webinterface WIP

This commit is contained in:
Martin/Geno 2019-03-01 10:54:19 +01:00
parent d20e749038
commit 3503e6abe6
No known key found for this signature in database
GPG Key ID: 9D7D3C6BFF600C6A
22 changed files with 14872 additions and 14 deletions

View File

@ -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

View File

@ -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,22 @@ 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)
}
return ctr.Handler(addr, msg)
}, configObj.Interfaces)
defer coll.Close() defer coll.Close()
ctr.Send = coll.Send ctr.Send = coll.Send
@ -47,5 +65,5 @@ var controllerCmd = &cobra.Command{
} }
func init() { func init() {
RootCmd.AddCommand(controllerCmd) RootCMD.AddCommand(controllerCmd)
} }

View File

@ -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, "")
} }

View File

@ -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(&timestamps, "timestamps", false, "Enables timestamps for log output")
} }

View File

@ -2,9 +2,11 @@ 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"`
Webserver *web.Config `toml:"webserver"`
Interfaces []*capture.IFaceConfig `toml:"interfaces"` Interfaces []*capture.IFaceConfig `toml:"interfaces"`
} }

View File

@ -1,6 +1,11 @@
state_path = "/tmp/wifictld.json" state_path = "/tmp/wifictld.json"
[webserver]
enable = true
bind = ":8080"
webroot = "./webroot/"
[[interfaces]] [[interfaces]]
ifname = "wlp4s0" ifname = "wlp4s0"
#port = 1000 #port = 1000
#ip_address = "ff02::31f1" #ip_address = "ff02::31f1"

7
web/config.go Normal file
View File

@ -0,0 +1,7 @@
package web
type Config struct {
Enable bool `toml:"enable"`
Bind string `toml:"bind"`
Webroot string `toml:"webroot"`
}

50
web/webserver.go Normal file
View File

@ -0,0 +1,50 @@
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{
Body: 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()
}

28
web/webserver_test.go Normal file
View File

@ -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()
}

6
webroot/assets/get_assets.sh Executable file
View File

@ -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

2626
webroot/assets/vue-route.js Normal file

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

11907
webroot/assets/vue.js Normal file

File diff suppressed because it is too large Load Diff

6
webroot/assets/vuex.js Normal file

File diff suppressed because one or more lines are too long

4
webroot/css/main.css Normal file
View File

@ -0,0 +1,4 @@
.p-navigation__logo {
font-size: 1.5rem;
line-height: 3rem;
}

44
webroot/index.html Normal file
View File

@ -0,0 +1,44 @@
<!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">
<div class="p-navigation__logo">
<a class="p-navigation__link" href="/">Wifictld</a>
</div>
<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">
<li class="p-navigation__link is-selected">
<router-link to="/">Access Points</router-link>
</li>
</ul>
</nav>
</header>
<div class="content">
<router-view></router-view>
</div>
</div>
<script src="js/view/accesspoint.js"></script>
<script src="js/store.js"></script>
<script src="js/data.js"></script>
<script src="js/main.js"></script>
</body>
</html>

3
webroot/js/data.js Normal file
View File

@ -0,0 +1,3 @@
store.commit('setEvent', "wifictld_pkg", (msg)=>{
console.log(msg)
})

14
webroot/js/main.js Normal file
View File

@ -0,0 +1,14 @@
const router = new VueRouter({
routes: [
{ path: '/', component: ViewAccessPoints }
]
})
VueNativeSock.default.install(Vue, `//${location.host}${location.pathname}ws`, {
store: store,
format: 'json',
})
const app = new Vue({
router
}).$mount('#app')

98
webroot/js/store.js Normal file
View File

@ -0,0 +1,98 @@
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: {},
}
},
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 = eventMSGID[msg.id];
if (msgFunc) {
msgFunc(msg);
delete eventMSGID[msg.id];
return;
}
const eventFuncs = eventTo[msg.subject];
if (typeof eventFuncs === 'object' && eventFuncs.length > 0) {
for (const key in eventFuncs) {
const func = eventFuncs[key];
if (func) {
func(msg);
}
}
return;
}
console.log(`unable to identify message: ${msg.subject}`);
},
// mutations for reconnect methods
SOCKET_RECONNECT(state, count) {
console.info(state, count)
},
SOCKET_RECONNECT_ERROR(state) {
state.socket.reconnectError = true;
},
setEvent (state, to, func) {
state.socket.eventTo[to] = [func];
},
addEvent (state, to, func) {
if (typeof state.socket.eventTo[to] !== 'object') {
state.socket.eventTo[to] = [];
}
state.socket.eventTo[to].push(func);
},
delEvent (state, to, func) {
if (typeof state.socket.eventTo[to] === 'object' && eventTo[to].length > 1) {
state.socket.eventTo[to].pop(func);
} else {
state.socket.eventTo[to] = [];
}
},
call (state, msg, callback) {
if (!msg.id) {
msg.id = newUUID();
}
const ret = Vue.prototype.$socket.sendObj(msg);
if (typeof callback === 'function') {
state.socket.eventMSGID[msg.id] = callback;
}
return ret;
}
}
})

View File

@ -0,0 +1,25 @@
const ViewAccessPoints = {
template: `<div class="row">
<h1>AccessPoints</h1>
<table class="p-table--mobile-card" role="grid">
<thead>
<tr role="row">
<th scope="col" role="columnheader" id="t-name" aria-sort="none">Name</th>
<th scope="col" role="columnheader" id="t-users" aria-sort="none" class="u-align--right">Users</th>
<th scope="col" role="columnheader" id="t-units" aria-sort="none" class="u-align--right">Units</th>
</tr>
</thead>
<tbody>
<accesspoint-table-item/>
</tbody>
</table>
</div>`
}
Vue.component('accesspoint-table-item', {
template: `<tr role="row">
<td role="rowheader" aria-label="Name">Grape</td>
<td role="gridcell" aria-label="Users" class="u-align--right">8</td>
<td role="gridcell" aria-label="Units" class="u-align--right">19</td>
</tr>`
})