add websocket notify

This commit is contained in:
Martin/Geno 2018-04-26 21:05:27 +02:00
parent 627255139f
commit 64dbedf0c8
No known key found for this signature in database
GPG Key ID: 9D7D3C6BFF600C6A
31 changed files with 5495 additions and 143 deletions

View File

@ -1,7 +1,6 @@
package cmd
import (
"net/http"
"os"
"os/signal"
"syscall"
@ -57,7 +56,7 @@ var serverCmd = &cobra.Command{
go func() {
for a := range logChannel {
notifier.Send(a)
notifier.Send(a, nil)
}
}()
@ -71,16 +70,6 @@ var serverCmd = &cobra.Command{
go receiver.Listen()
srv := &http.Server{
Addr: config.HTTPAddress,
}
go func() {
if err := srv.ListenAndServe(); err != nil {
panic(err)
}
}()
// Wait for system signal
sigchan := make(chan os.Signal, 1)
signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1)

View File

@ -64,7 +64,7 @@ func (db *DB) SendTo(e *log.Entry) (*log.Entry, *Host, []*Notify) {
return e, host, nil
}
func (db *DB) Alert(expired time.Duration, send func(e *log.Entry) error) {
func (db *DB) Alert(expired time.Duration, send func(e *log.Entry, n *Notify) bool) {
c := time.Tick(time.Minute)
for range c {
@ -81,7 +81,7 @@ func (db *DB) Alert(expired time.Duration, send func(e *log.Entry) error) {
entry.Level = log.ErrorLevel
entry.Message = AlertMsg
entry.WithField("hostname", h.Address)
send(entry)
send(entry, nil)
}
}
}

View File

@ -8,7 +8,7 @@ import (
)
type Notify struct {
Protocoll string `json:"proto"`
Protocol string `json:"proto"`
To string `json:"to"`
RegexIn map[string]*regexp.Regexp `json:"regexIn"`
RegexReplace map[string]string `json:"regexReplace"`
@ -64,7 +64,7 @@ func (n *Notify) RunReplace(msg string) string {
}
func (n *Notify) Address() string {
return n.Protocoll + ":" + n.To
return n.Protocol + ":" + n.To
}
// -- global notify
@ -87,9 +87,9 @@ func (db *DB) AddNotify(n *Notify) {
func (db *DB) NewNotify(to string) *Notify {
addr := strings.Split(to, ":")
n := &Notify{
Protocoll: addr[0],
To: addr[1],
RegexIn: make(map[string]*regexp.Regexp),
Protocol: addr[0],
To: addr[1],
RegexIn: make(map[string]*regexp.Regexp),
}
db.AddNotify(n)
return n

View File

@ -3,10 +3,9 @@ package lib
// Struct of the configuration
// e.g. under dev.sum7.eu/genofire/logmania/logmania_example.conf
type Config struct {
Notify NotifyConfig `toml:"notify"`
Receive ReceiveConfig `toml:"receive"`
DB string `toml:"database"`
HTTPAddress string `toml:"http_address"`
Notify NotifyConfig `toml:"notify"`
Receive ReceiveConfig `toml:"receive"`
DB string `toml:"database"`
}
type NotifyConfig struct {
@ -16,7 +15,10 @@ type NotifyConfig struct {
JID string `toml:"jid"`
Password string `toml:"password"`
} `toml:"xmpp"`
File string `toml:"file"`
Websocket struct {
Address string `toml:"address"`
Webroot string `toml:"webroot"`
} `toml:"websocket"`
}
type ReceiveConfig struct {

View File

@ -1,7 +1,3 @@
[notify]
state_file = "/tmp/logmania.state.json"
debug = true
[receive.syslog]
type = "udp"
address = ":10001"
@ -9,3 +5,15 @@ address = ":10001"
[receive.journald_json]
type = "udp"
address = ":10002"
[notify]
state_file = "/tmp/logmania.state.json"
debug = true
[notify.xmpp]
jid = "user@example.org"
password = "password"
[notify.websocket]
address = ":8080"
webroot = "./webroot/"

View File

@ -9,9 +9,12 @@ import (
"dev.sum7.eu/genofire/logmania/notify"
)
var logger = log.WithField("notify", "all")
type Notifier struct {
notify.Notifier
list []notify.Notifier
db *database.DB
channelNotify chan *log.Entry
}
@ -27,6 +30,7 @@ func Init(config *lib.NotifyConfig, db *database.DB, bot *bot.Bot) notify.Notifi
}
n := &Notifier{
db: db,
list: list,
channelNotify: make(chan *log.Entry),
}
@ -36,15 +40,25 @@ func Init(config *lib.NotifyConfig, db *database.DB, bot *bot.Bot) notify.Notifi
func (n *Notifier) sender() {
for c := range n.channelNotify {
for _, item := range n.list {
item.Send(c)
e, _, tos := n.db.SendTo(c)
for _, to := range tos {
send := false
for _, item := range n.list {
send = item.Send(e, to)
if send {
break
}
}
if !send {
logger.Warn("notify not send to anybody: [%s] %s", c.Level.String(), c.Message)
}
}
}
}
func (n *Notifier) Send(e *log.Entry) error {
func (n *Notifier) Send(e *log.Entry, to *database.Notify) bool {
n.channelNotify <- e
return nil
return true
}
func (n *Notifier) Close() {

View File

@ -1,6 +1,6 @@
package all
import (
_ "dev.sum7.eu/genofire/logmania/notify/file"
_ "dev.sum7.eu/genofire/logmania/notify/websocket"
_ "dev.sum7.eu/genofire/logmania/notify/xmpp"
)

View File

@ -1,65 +0,0 @@
package xmpp
import (
"os"
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/logmania/bot"
"dev.sum7.eu/genofire/logmania/database"
"dev.sum7.eu/genofire/logmania/lib"
"dev.sum7.eu/genofire/logmania/notify"
)
const (
proto = "file:"
)
var logger = log.WithField("notify", proto)
type Notifier struct {
notify.Notifier
formatter log.Formatter
file *os.File
path string
}
func Init(config *lib.NotifyConfig, db *database.DB, bot *bot.Bot) notify.Notifier {
logger.Info("startup")
if config.File == "" {
return nil
}
file, err := os.OpenFile(config.File, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
logger.Errorf("could not open file: %s", err.Error())
return nil
}
return &Notifier{
formatter: &log.JSONFormatter{},
file: file,
path: config.File,
}
}
func (n *Notifier) Send(e *log.Entry) error {
text, err := n.formatter.Format(e)
if err != nil {
return err
}
_, err = n.file.Write(text)
if err != nil {
logger.Warnf("could not write to logfile: %s - try to reopen it", err.Error())
file, err := os.OpenFile(n.path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
n.file = file
_, err = n.file.Write(text)
}
return err
}
func init() {
notify.AddNotifier(Init)
}

View File

@ -11,7 +11,7 @@ import (
var NotifyRegister []NotifyInit
type Notifier interface {
Send(entry *log.Entry) error
Send(entry *log.Entry, to *database.Notify) bool
Close()
}

81
notify/websocket/main.go Normal file
View File

@ -0,0 +1,81 @@
package xmpp
import (
"net/http"
"dev.sum7.eu/genofire/golang-lib/websocket"
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/logmania/bot"
"dev.sum7.eu/genofire/logmania/database"
"dev.sum7.eu/genofire/logmania/lib"
"dev.sum7.eu/genofire/logmania/notify"
)
const (
proto = "ws"
)
var logger = log.WithField("notify", proto)
type Notifier struct {
notify.Notifier
ws *websocket.Server
formatter log.Formatter
}
func Init(config *lib.NotifyConfig, db *database.DB, bot *bot.Bot) notify.Notifier {
inputMSG := make(chan *websocket.Message)
ws := websocket.NewServer(inputMSG, nil)
http.HandleFunc("/ws", ws.Handler)
http.Handle("/", http.FileServer(http.Dir(config.Websocket.Webroot)))
go func() {
for msg := range inputMSG {
if msg.Subject != "bot" {
logger.Warnf("receive unknown websocket message: %s", msg.Subject)
continue
}
bot.Handle(func(answer string) {
msg.Answer("bot", answer)
}, "", msg.Body.(string))
}
}()
srv := &http.Server{
Addr: config.Websocket.Address,
}
go func() {
if err := srv.ListenAndServe(); err != nil {
panic(err)
}
}()
logger.Info("startup")
return &Notifier{
ws: ws,
formatter: &log.TextFormatter{
DisableTimestamp: true,
},
}
}
func (n *Notifier) Send(e *log.Entry, to *database.Notify) bool {
if to.Protocol != proto {
return false
}
n.ws.SendAll(&websocket.Message{
Subject: to.Address(),
Body: e,
})
return true
}
func (n *Notifier) Close() {
}
func init() {
notify.AddNotifier(Init)
}

View File

@ -1,7 +1,7 @@
package xmpp
import (
"errors"
"strings"
xmpp_client "dev.sum7.eu/genofire/yaja/client"
xmpp "dev.sum7.eu/genofire/yaja/xmpp"
@ -26,7 +26,6 @@ type Notifier struct {
notify.Notifier
client *xmpp_client.Client
channels map[string]bool
db *database.DB
formatter log.Formatter
}
@ -116,7 +115,7 @@ func Init(config *lib.NotifyConfig, db *database.DB, bot *bot.Bot) notify.Notifi
}
}()
for toAddr, toAddresses := range db.NotifiesByAddress {
if toAddresses.Protocoll == protoGroup {
if toAddresses.Protocol == protoGroup {
toJID := xmppbase.NewJID(toAddresses.To)
toJID.Resource = nickname
err := client.Send(&xmpp.PresenceClient{
@ -133,60 +132,54 @@ func Init(config *lib.NotifyConfig, db *database.DB, bot *bot.Bot) notify.Notifi
return &Notifier{
channels: channels,
client: client,
db: db,
formatter: &log.TextFormatter{
DisableTimestamp: true,
},
}
}
func (n *Notifier) Send(e *log.Entry) error {
e, _, tos := n.db.SendTo(e)
if tos == nil || len(tos) <= 0 {
return errors.New("no receiver found")
}
text, err := n.formatter.Format(e)
func (n *Notifier) Send(e *log.Entry, to *database.Notify) bool {
textByte, err := n.formatter.Format(e)
if err != nil {
return err
logger.Error("during format notify", err)
return false
}
for _, to := range tos {
modifyText := string(text)
if notify, ok := n.db.NotifiesByAddress[to.Address()]; ok {
modifyText = notify.RunReplace(modifyText)
}
if to.Protocoll == protoGroup {
if _, ok := n.channels[to.To]; ok {
toJID := xmppbase.NewJID(to.To)
toJID.Resource = nickname
err := n.client.Send(&xmpp.PresenceClient{
To: toJID,
})
if err != nil {
logger.Error("xmpp could not join ", toJID.String(), " error:", err)
} else {
n.channels[to.To] = true
}
}
err := n.client.Send(&xmpp.MessageClient{
Type: xmpp.MessageTypeGroupchat,
To: xmppbase.NewJID(to.To),
Body: modifyText,
text := strings.TrimRight(to.RunReplace(string(textByte)), "\n")
if to.Protocol == protoGroup {
if _, ok := n.channels[to.To]; ok {
toJID := xmppbase.NewJID(to.To)
toJID.Resource = nickname
err := n.client.Send(&xmpp.PresenceClient{
To: toJID,
})
if err != nil {
logger.Error("xmpp to ", to.To, " error:", err)
}
} else {
err := n.client.Send(&xmpp.MessageClient{
Type: xmpp.MessageTypeChat,
To: xmppbase.NewJID(to.To),
Body: modifyText,
})
if err != nil {
logger.Error("xmpp to ", to, " error:", err)
logger.Error("xmpp could not join ", toJID.String(), " error:", err)
} else {
n.channels[to.To] = true
}
}
err := n.client.Send(&xmpp.MessageClient{
Type: xmpp.MessageTypeGroupchat,
To: xmppbase.NewJID(to.To),
Body: text,
})
if err != nil {
logger.Error("xmpp to ", to.To, " error:", err)
}
return true
}
return nil
if to.Protocol == proto {
err := n.client.Send(&xmpp.MessageClient{
Type: xmpp.MessageTypeChat,
To: xmppbase.NewJID(to.To),
Body: text,
})
if err != nil {
logger.Error("xmpp to ", to, " error:", err)
}
return true
}
return false
}
func (n *Notifier) Close() {

3
webroot/.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["env"]
}

36
webroot/.eslintrc Normal file
View File

@ -0,0 +1,36 @@
{
extends: eslint:all,
rules: {
no-tabs: [off],
indent: [error, tab],
quotes: [error, single],
padded-blocks: [error, { blocks: never }],
no-console: [error, { allow: [log, warn, error] }],
func-style: [error, declaration],
object-curly-newline: off,
wrap-iife: [error, inside],
object-shorthand: ["error", "always", { "avoidQuotes": true }],
require-jsdoc: [off],
max-statements: [off],
no-magic-numbers: ["error", { "ignore": [0,1,-1] }],
sort-vars: [off],
max-len: [off],
id-length: [error, { exceptions: ["i"] }],
no-ternary: [off]
},
parserOptions: {
sourceType: module
},
env: {
es6: true,
},
globals: {
document: true,
window: true,
console: true,
localStorage: true,
location: true,
navigator: true,
}
}

6
webroot/css/_notify.less Normal file
View File

@ -0,0 +1,6 @@
.notifications {
position: fixed;
top: 16px;
right: 72px;
z-index: 105;
}

29
webroot/css/_status.less Normal file
View File

@ -0,0 +1,29 @@
.status {
margin: 0 auto;
width: 12px;
height: 12px;
background-color: #80FF00;
border-radius: 50%;
box-shadow: #000 0 -1px 6px 1px, inset #460 0 -1px 8px, #80FF00 0 3px 11px;
&.connecting,
&.running {
background-color: #FF0;
box-shadow: #000 0 -1px 6px 1px, inset #660 0 -1px 8px, #FF0 0 3px 11px;
animation: blinkDot 2s infinite;
}
&.offline,
&.failed {
background-color: #F00;
box-shadow: #000 0 -1px 6px 1px, inset #600 0 -1px 8px, #F00 0 3px 11px;
animation: blinkDot 1s infinite;
}
@keyframes blinkDot {
50% {
background-color: rgba(255, 255, 255, 0.25);
box-shadow: #000 0 -1px 6px 1px;
}
}
}

12
webroot/css/styles.less Normal file
View File

@ -0,0 +1,12 @@
@import "../node_modules/semantic-ui-less/semantic.less";
@import "_status.less";
@import "_notify.less";
body {
height: auto;
}
.main.container {
margin-top: 7em;
}

92
webroot/gulpfile.babel.js Normal file
View File

@ -0,0 +1,92 @@
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()).
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'
]);

15
webroot/index.html Normal file
View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>logmania</title>
<link rel="stylesheet" href="styles.css">
<script src="app.js"></script>
</head>
<body>
<noscript>
<strong>JavaScript required</strong>
</noscript>
</body>
</html>

9
webroot/js/config.js Normal file
View File

@ -0,0 +1,9 @@
/* eslint no-magic-numbers: "off"*/
/* eslint sort-keys: "off"*/
export default {
'title': 'Logmania',
'backend': 'wss://cetus.h.sum7.eu/logmania/ws'
// 'backend': `ws${location.protocol === 'https:' ? 's' :''}://${location.host}${location.pathname.replace(/\/$/, '')}/ws`
};

54
webroot/js/domlib.js Normal file
View File

@ -0,0 +1,54 @@
export function setProps (el, props) {
if (props) {
if (props.class) {
let classList = props.class;
if (typeof props.class === 'string') {
classList = classList.split(' ');
}
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;
}

View File

@ -0,0 +1,63 @@
import * as V from 'picodom';
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 class MenuView extends View {
constructor () {
super();
this.elStatus = document.createElement('div');
}
render () {
const socketStatus = socket.getStatus();
let statusClass = 'status ',
vLogin = V.h('a', {
'class': 'item',
'href': '#/log'
}, 'Logs');
if (socketStatus !== 1) {
// eslint-disable-next-line no-magic-numbers
if (socketStatus === 0 || socketStatus === 2) {
statusClass += 'connecting';
} else {
statusClass += 'offline';
}
}
if (store.isLogin) {
vLogin = V.h('a', {
'class': 'item',
'href': '#/',
'onclick': () => socket.sendjson({'subject': 'logout'}, (msg) => {
if (msg.body) {
store.isLogin = false;
store.login = {};
render();
} else {
notify.send({
'header': 'Abmeldung ist fehlgeschlagen',
'type': 'error'
}, 'Logout');
}
})
}, 'Logout');
}
V.patch(this.vStatus, this.vStatus = V.h('div', {'class': statusClass}), this.elStatus);
if (!this.init) {
domlib.setProps(this.el, {'class': 'ui fixed inverted menu'});
const menuContainer = domlib.newAt(this.el, 'div', {'class': 'ui container'});
this.menuRight = domlib.newAt(menuContainer, 'div', {'class': 'menu right'});
this.elStatus.classList.add('item');
this.menuRight.appendChild(this.elStatus);
this.init = true;
}
V.patch(this.vLogin, this.vLogin = vLogin, this.menuRight);
}
}

View File

@ -0,0 +1,94 @@
import * as V from 'picodom';
import View from '../view';
const DELAY_OF_NOTIFY = 15000,
MAX_MESSAGE_SHOW = 5;
class NotifyView extends View {
constructor () {
super();
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': `ui floating message ${msg.type}`
},
V.h('i', {
'class': 'close icon',
'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': '/img/logo.jpg'
});
return;
}
if (this.messages.length > MAX_MESSAGE_SHOW) {
this.removeLast();
}
this.messages.push(msg);
this.render();
}
render () {
V.patch(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};

43
webroot/js/gui.js Normal file
View File

@ -0,0 +1,43 @@
import * as domlib from './domlib';
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 router = new Navigo(null, true, '#'),
elMain = domlib.newEl('div', {'class': 'ui main container'}),
elMenu = new MenuView();
export {router};
let init = false,
currentView = new View();
export function render () {
if (!document.body) {
return;
}
if (!init) {
notify.bind(document.body);
elMenu.bind(document.body);
document.body.appendChild(elMain);
init = true;
}
currentView.render();
notify.render();
elMenu.render();
router.resolve();
}
export function setView (toView) {
currentView.unbind();
currentView = toView;
currentView.bind(elMain);
currentView.render();
}

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

@ -0,0 +1,14 @@
import * as gui from './gui';
import config from './config';
/**
* Self binding with router
*/
/* eslint-disable no-unused-vars */
import home from './view/home';
import login from './view/log';
/* eslint-enable no-unused-vars */
document.title = config.title;
window.onload = () => gui.render();

163
webroot/js/socket.js Normal file
View File

@ -0,0 +1,163 @@
import config from './config';
import {singelton as notify} from './element/notify';
import {render} from './gui';
const RECONNECT_AFTER = 5000,
RETRY_QUERY = 300,
PREFIX_EVENT = true,
query = [],
eventMSGID = {},
eventTo = {};
let socket = null;
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 */
}
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;
console.log('callback bind', obj.id);
}
}
function onmessage (raw) {
const msg = JSON.parse(raw.data),
msgFunc = eventMSGID[msg.id];
let eventFuncs = eventTo[msg.subject];
if (msgFunc) {
msgFunc(msg);
delete eventMSGID[msg.id];
render();
return;
}
if (typeof eventFuncs === 'object' && eventFuncs.length > 0) {
// eslint-disable-next-line guard-for-in
for (const i in eventFuncs) {
const func = eventFuncs[i];
if (func) {
func(msg);
}
}
render();
return;
}
if (PREFIX_EVENT) {
for (const key in eventTo) {
if (msg.subject.indexOf(key) === 0) {
eventFuncs = eventTo[key];
// eslint-disable-next-line guard-for-in
for (const i in eventFuncs) {
const func = eventFuncs[i];
if (func) {
func(msg);
}
}
render();
return;
}
}
}
notify.send('warning', `unable to identify message: ${raw.data}`);
render();
}
function onclose () {
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;
}
window.setInterval(() => {
const queryEntry = query.pop();
if (queryEntry) {
sendjson(queryEntry.obj, queryEntry.callback);
}
console.log('query length: ', query.length);
}, RETRY_QUERY);
export function getStatus () {
if (socket) {
return socket.readyState;
}
return 0;
}
export function setEvent (to, func) {
eventTo[to] = [func];
}
export function addEvent (to, func) {
if (typeof eventTo[to] !== 'object') {
eventTo[to] = [];
}
eventTo[to].push(func);
}
export function delEvent (to, func) {
if (typeof eventTo[to] === 'object' && eventTo[to].length > 1) {
eventTo[to].pop(func);
} else {
eventTo[to] = [];
}
}
connect();

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

@ -0,0 +1,6 @@
const store = {
'bot': [],
'channel': {}
};
export {store};

22
webroot/js/view.js Normal file
View File

@ -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');
}
}

20
webroot/js/view/home.js Normal file
View File

@ -0,0 +1,20 @@
import * as dom from '../domlib';
import * as gui from '../gui';
import View from '../view';
class HomeView extends View {
// eslint-disable-next-line class-methods-use-this
render () {
if (!this.init) {
const h1 = dom.newAt(this.el, 'h1');
h1.innerHTML = 'Home';
this.init = true;
}
}
}
const homeView = new HomeView();
gui.router.on('/', () => {
gui.setView(homeView);
});

57
webroot/js/view/log.js Normal file
View File

@ -0,0 +1,57 @@
import * as domlib from '../domlib';
import * as gui from '../gui';
import * as socket from '../socket';
import View from '../view';
import {store} from '../store';
function levelToColor (lvl) {
return lvl;
}
function addItem (el, msg) {
const div = domlib.newAt(el, 'div', {
'class': levelToColor(msg.Leve)
});
domlib.newAt(div, 'span', null, msg.Data.hostname);
domlib.newAt(div, 'span', null, msg.Message);
}
class LogView extends View {
// eslint-disable-next-line class-methods-use-this
render () {
if (!this.init) {
this.init = true;
}
/*
* Domlib.newAt(this.el, 'h2', {'class': 'ui header'}, 'Log');
* for (const msg in store.channel.ffhb) {
* domlib.newAt(this.el, 'div', null, msg.Data.hostname, msg);
*}
*/
}
constructor () {
super();
socket.addEvent('ws:', (msg) => {
// Length('ws:') = 3
// eslint-disable-next-line no-magic-numbers
const channel = msg.subject.substr(3);
if (!store.channel[channel]) {
store.channel[channel] = [];
}
store.channel[channel].push(msg.body);
addItem(this.el, msg.body);
this.render();
});
}
}
const logView = new LogView();
gui.router.on('/log', () => {
gui.setView(logView);
});

37
webroot/package.json Normal file
View File

@ -0,0 +1,37 @@
{
"name": "logmania",
"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",
"navigo": "^5.3.3",
"picodom": "^1.0.2",
"semantic-ui-less": "^2.2.12",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"watchify": "^3.9.0"
},
"scripts": {
"gulp": "gulp"
},
"babel": {
"presets": [
"es2017"
]
}
}

4555
webroot/yarn.lock Normal file

File diff suppressed because it is too large Load Diff