drop websocket and http support for hook2xmpp

This commit is contained in:
Martin/Geno 2019-06-20 09:25:02 +02:00
parent 89b426f3a9
commit a5b0b06b3b
No known key found for this signature in database
GPG Key ID: 9D7D3C6BFF600C6A
36 changed files with 9 additions and 5975 deletions

View File

@ -12,17 +12,11 @@ This is a little logging server.
It receive logs (events) by:
- syslog
- journald (with service nc)
- logrus (WIP)
- webhook
- git (github,gogs, gitea)
- circleci
- grafana
## output
And forward this logs (events) to multiple different output:
- xmpp (client and muc)
- file
- websocket (WIP)
there a multi filter possible
- regex
@ -31,3 +25,7 @@ there a multi filter possible
it could replace text by regex expression
configuration live possible by bot (on input e.g. xmpp)
## Related Projects
- [hook2xmpp](https://dev.sum7.eu/genofire/hook2xmpp) for e.g. grafana, alertmanager(prometheus), gitlab, git, circleci

View File

@ -1,13 +1,11 @@
package cmd
import (
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/NYTimes/gziphandler"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@ -71,22 +69,6 @@ var serverCmd = &cobra.Command{
log.WithField("defaults", len(db.DefaultNotify)).Info("starting logmania")
if config.HTTPAddress != "" {
if config.Webroot != "" {
http.Handle("/", gziphandler.GzipHandler(http.FileServer(http.Dir(config.Webroot))))
}
srv := &http.Server{
Addr: config.HTTPAddress,
}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Panic(err)
}
}()
}
in = allInput.Init(config.Input, logChannel)
go in.Listen()

View File

@ -2,10 +2,5 @@ package all
import (
_ "dev.sum7.eu/genofire/logmania/input/journald_json"
_ "dev.sum7.eu/genofire/logmania/input/logrus"
_ "dev.sum7.eu/genofire/logmania/input/syslog"
_ "dev.sum7.eu/genofire/logmania/input/webhook"
_ "dev.sum7.eu/genofire/logmania/input/webhook/circleci"
_ "dev.sum7.eu/genofire/logmania/input/webhook/git"
_ "dev.sum7.eu/genofire/logmania/input/webhook/grafana"
)

View File

@ -1,85 +0,0 @@
package client
import (
"io"
websocketLib "dev.sum7.eu/genofire/golang-lib/websocket"
"dev.sum7.eu/genofire/logmania/input/logrus"
"github.com/google/uuid"
"github.com/gorilla/websocket"
log "github.com/sirupsen/logrus"
)
// client logger
type Logmania struct {
URL string
Token uuid.UUID
Levels []log.Level
quere chan *log.Entry
conn *websocket.Conn
}
func NewClient(url string, token uuid.UUID, lvls ...log.Level) *Logmania {
logger := &Logmania{
URL: url,
Token: token,
Levels: lvls,
quere: make(chan *log.Entry),
}
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Error("[logmania] error on connect: ", err)
return nil
}
logger.conn = conn
go logger.Start()
return logger
}
// Listen if logmania server want to close the connection
func (l *Logmania) listen() {
for {
var msg websocketLib.Message
err := websocket.ReadJSON(l.conn, &msg)
if err == io.EOF {
l.Close()
log.Warn("[logmania] close listener:", err)
} else if err != nil {
log.Println(err)
} else {
if msg.Subject == websocketLib.SessionMessageInit {
l.conn.WriteJSON(&websocketLib.Message{
Subject: websocketLib.SessionMessageInit,
ID: l.Token,
})
}
}
}
}
func (l *Logmania) writer() {
for e := range l.quere {
err := l.conn.WriteJSON(&websocketLib.Message{
Subject: logrus.WS_LOG_ENTRY,
Body: e,
})
if err != nil {
log.Error("[logmania] could not send log entry:", err)
}
}
}
func (l *Logmania) Start() {
go l.listen()
l.writer()
}
func (l *Logmania) Fire(e *log.Entry) {
l.quere <- e
}
// close connection to logger
func (l *Logmania) Close() {
l.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
close(l.quere)
}

View File

@ -1 +0,0 @@
package client

View File

@ -1,55 +0,0 @@
package logrus
import (
"net/http"
"dev.sum7.eu/genofire/golang-lib/websocket"
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/logmania/input"
)
const inputType = "logrus"
const WS_LOG_ENTRY = "log"
var logger = log.WithField("input", inputType)
type Input struct {
input.Input
input chan *websocket.Message
exportChannel chan *log.Entry
serverSocket *websocket.Server
}
func Init(config interface{}, exportChannel chan *log.Entry) input.Input {
inputMsg := make(chan *websocket.Message)
ws := websocket.NewServer(inputMsg, websocket.NewSessionManager())
http.HandleFunc("/input/"+inputType, ws.Handler)
in := &Input{
input: inputMsg,
serverSocket: ws,
exportChannel: exportChannel,
}
logger.Info("init")
return in
}
func (in *Input) Listen() {
logger.Info("listen")
for msg := range in.input {
if event, ok := msg.Body.(log.Entry); ok {
in.exportChannel <- &event
}
}
}
func (in *Input) Close() {
}
func init() {
input.Add(inputType, Init)
}

View File

@ -1,58 +0,0 @@
package circleci
import (
"fmt"
"net/http"
"time"
"github.com/mitchellh/mapstructure"
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/logmania/input/webhook"
)
type requestBody struct {
Payload struct {
VCSURL string `mapstructure:"vcs_url"`
Status string `mapstructure:"status"`
BuildNum float64 `mapstructure:"build_num"`
BuildURL string `mapstructure:"build_url"`
BuildTime float64 `mapstructure:"build_time_millis"`
Subject string `mapstructure:"subject"`
} `mapstructure:"payload"`
}
const webhookType = "circleci"
var HookstatusMap = map[string]log.Level{
"failed": log.ErrorLevel,
"success": log.InfoLevel,
}
var logger = log.WithField("input", webhook.InputType).WithField("hook", webhookType)
func handler(_ http.Header, body interface{}) *log.Entry {
var request requestBody
if err := mapstructure.Decode(body, &request); err != nil {
logger.Warnf("not able to decode data: %s", err)
return nil
}
if request.Payload.VCSURL == "" {
return nil
}
entry := log.NewEntry(nil)
entry = entry.WithFields(map[string]interface{}{
"hostname": request.Payload.VCSURL,
"url": request.Payload.BuildURL,
})
entry.Time = time.Now()
entry.Level = HookstatusMap[request.Payload.Status]
entry.Message = fmt.Sprintf("#%0.f (%0.fs): %s", request.Payload.BuildNum, request.Payload.BuildTime/1000, request.Payload.Subject)
return entry
}
func init() {
webhook.AddHandler(webhookType, handler)
}

View File

@ -1,81 +0,0 @@
package git
import (
"net/http"
"time"
"github.com/mitchellh/mapstructure"
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/logmania/input/webhook"
)
type requestBody struct {
Repository struct {
HTMLURL string `mapstructure:"html_url"`
FullName string `mapstructure:"full_name"`
} `mapstructure:"repository"`
//push
Pusher struct {
Name string `mapstructure:"name"`
} `mapstructure:"pusher"`
Commits []struct {
Added []interface{} `mapstructure:"added"`
Removed []interface{} `mapstructure:"removed"`
Modified []interface{} `mapstructure:"modified"`
} `mapstructure:"commits"`
Compare string `mapstructure:"compare"`
Ref string `mapstructure:"ref"`
// issue + fallback
Sender struct {
Login string `mapstructure:"login"`
} `mapstructure:"sender"`
// issue
Action string `mapstructure:"action"`
Issue struct {
HTMLURL string `mapstructure:"html_url"`
Number float64 `mapstructure:"number"`
Title string `mapstructure:"title"`
} `mapstructure:"issue"`
}
const webhookType = "git"
var eventHeader = []string{"X-GitHub-Event", "X-Gogs-Event"}
var logger = log.WithField("input", webhook.InputType).WithField("hook", webhookType)
func handler(header http.Header, body interface{}) *log.Entry {
event := ""
for _, head := range eventHeader {
event = header.Get(head)
if event != "" {
break
}
}
if event == "status" {
return nil
}
var request requestBody
if err := mapstructure.Decode(body, &request); err != nil {
logger.Warnf("not able to decode data: %s", err)
return nil
}
if request.Repository.HTMLURL == "" {
return nil
}
entry := log.NewEntry(nil)
entry = entry.WithField("hostname", request.Repository.HTMLURL)
entry.Time = time.Now()
entry.Level = log.InfoLevel
entry.Message = RequestToString(event, request)
return entry
}
func init() {
webhook.AddHandler(webhookType, handler)
}

View File

@ -1,75 +0,0 @@
package git
import (
"fmt"
"strings"
)
var eventMsg = map[string]string{
"commit_comment_created": "Commit comment",
"status_error": "Commit status: error",
"status_failure": "Commit status: failure",
"status_pending": "Commit status: pending",
"status_success": "Commit status: success",
"create_branch": "Create branch",
"create_tag": "Create tag",
"delete_branch": "Delete branch",
"delete_tag": "Delete tag",
"issue_comment_created": "Issue comment",
"issue_comment_deleted": "Issue comment: deleted",
"issue_comment_edited": "Issue comment: edited",
"issue_assigned": "Issue: assigned",
"issue_closed": "Issue: closed",
"issue_edited": "Issue: edited",
"issue_labeled": "Issue: labeled",
"issue_opened": "Issue: opened",
"issue_reopened": "Issue: reopened",
"issue_unassigned": "Issue: unassigned",
"issue_unlabeled": "Issue: unlabeled",
"pr_review_created": "Pull request review comment",
"pr_review_deleted": "Pull request review comment: deleted",
"pr_review_edited": "Pull request review comment: edited",
"pr_assigned": "Pull request: assigned",
"pr_closed": "Pull request: closed",
"pr_edited": "Pull request: edited",
"pr_labeled": "Pull request: labeled",
"pr_opened": "Pull request: opened",
"pr_reopened": "Pull request: reopened",
"pr_synchronize": "Pull request: synchronize",
"pr_unassigned": "Pull request: unassigned",
"pr_unlabeled": "Pull request: unlabeled",
"push": "Push",
"release_published": "Release published",
"member_added": "Repo: added collaborator",
"team_add": "Repo: added to a team",
"fork": "Repo: forked",
"public": "Repo: made public",
"watch_started": "Repo: starred",
"gollum_created": "Wiki: created page",
"gollum_edited": "Wiki: edited page",
}
func RequestToString(event string, request requestBody) string {
msg := fmt.Sprintf("[%s]", request.Repository.FullName)
if event == "push" {
added := 0
removed := 0
modified := 0
for _, commit := range request.Commits {
added += len(commit.Added)
removed += len(commit.Removed)
modified += len(commit.Modified)
}
msg = fmt.Sprintf("%s %s - pushed %d commit(s) to %s [+%d/-%d/\u00B1%d]: %s", msg, request.Pusher.Name, len(request.Commits), strings.TrimLeft(request.Ref, "refs/heads/"), added, removed, modified, request.Compare)
} else if event == "issues" || event == "issue_comment" {
msg = fmt.Sprintf("%s %s - %s action #%.0f: %s - %s", msg, request.Sender.Login, request.Action, request.Issue.Number, request.Issue.Title, request.Issue.HTMLURL)
} else {
text := eventMsg[event]
if text == "" {
text = event
}
msg = fmt.Sprintf("%s %s - %s", msg, request.Sender.Login, text)
}
return msg
}

View File

@ -1,73 +0,0 @@
package grafana
import (
"fmt"
"net/http"
"time"
"github.com/mitchellh/mapstructure"
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/logmania/input/webhook"
)
type evalMatch struct {
Tags map[string]string `mapstructure:"tags,omitempty"`
Metric string `mapstructure:"metric"`
Value float64 `mapstructure:"value"`
}
type requestBody struct {
Title string `mapstructure:"title"`
State string `mapstructure:"state"`
RuleID int64 `mapstructure:"ruleId"`
RuleName string `mapstructure:"ruleName"`
RuleURL string `mapstructure:"ruleUrl"`
EvalMatches []evalMatch `mapstructure:"evalMatches"`
ImageURL string `mapstructure:"imageUrl"`
Message string `mapstructure:"message"`
}
const webhookType = "grafana"
var HookstateMap = map[string]log.Level{
"no_data": log.ErrorLevel,
"paused": log.InfoLevel,
"alerting": log.ErrorLevel,
"ok": log.InfoLevel,
"pending": log.WarnLevel,
}
var logger = log.WithField("input", webhook.InputType).WithField("hook", webhookType)
func handler(_ http.Header, body interface{}) *log.Entry {
var request requestBody
if err := mapstructure.Decode(body, &request); err != nil {
logger.Warnf("not able to decode data: %s", err)
return nil
}
if request.RuleURL == "" {
return nil
}
entry := log.NewEntry(nil)
entry = entry.WithFields(map[string]interface{}{
"hostname": request.RuleURL,
"ruleid": request.RuleID,
"url": request.RuleURL,
})
if request.ImageURL != "" {
entry = entry.WithField("imageurl", request.ImageURL)
}
entry.Time = time.Now()
entry.Level = HookstateMap[request.State]
for _, e := range request.EvalMatches {
entry = entry.WithField(e.Metric, e.Value)
}
entry.Message = fmt.Sprintf("%s: %s", request.Title, request.Message)
return entry
}
func init() {
webhook.AddHandler(webhookType, handler)
}

View File

@ -1,15 +0,0 @@
package webhook
import (
"net/http"
log "github.com/sirupsen/logrus"
)
type WebhookHandler func(http.Header, interface{}) *log.Entry
var handlers = make(map[string]WebhookHandler)
func AddHandler(name string, f WebhookHandler) {
handlers[name] = f
}

View File

@ -1,56 +0,0 @@
package webhook
import (
"fmt"
"net/http"
libHTTP "github.com/genofire/golang-lib/http"
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/logmania/input"
)
const InputType = "webhook"
var logger = log.WithField("input", InputType)
type Input struct {
input.Input
exportChannel chan *log.Entry
}
func Init(config interface{}, exportChannel chan *log.Entry) input.Input {
logger.Info("init")
return &Input{
exportChannel: exportChannel,
}
}
func (in *Input) getHTTPHandler(name string, h WebhookHandler) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var body interface{}
libHTTP.Read(r, &body)
e := h(r.Header, body)
if e == nil {
http.Error(w, fmt.Sprintf("no able to generate log for handler-request %s", name), http.StatusInternalServerError)
return
}
in.exportChannel <- e
http.Error(w, fmt.Sprintf("handler-request %s - ok", name), http.StatusOK)
}
}
func (in *Input) Listen() {
for name, h := range handlers {
http.HandleFunc("/input/"+InputType+"/"+name, in.getHTTPHandler(name, h))
}
}
func (in *Input) Close() {
}
func init() {
input.Add(InputType, Init)
}

View File

@ -3,11 +3,9 @@ package lib
// Struct of the configuration
// e.g. under dev.sum7.eu/genofire/logmania/logmania_example.conf
type Config struct {
Debug bool `toml:"debug"`
DB string `toml:"database"`
HTTPAddress string `toml:"http_address"`
Webroot string `toml:"webroot"`
AlertCheck Duration `toml:"alert_check"`
Output map[string]interface{} `toml:"output"`
Input map[string]interface{} `toml:"input"`
Debug bool `toml:"debug"`
DB string `toml:"database"`
AlertCheck Duration `toml:"alert_check"`
Output map[string]interface{} `toml:"output"`
Input map[string]interface{} `toml:"input"`
}

View File

@ -4,10 +4,6 @@ database = "/tmp/logmania.state.json"
# have to be mote then a minute
alert_check = "5m"
# webserver
http_address = ":8080"
webroot = "./webroot/"
#########
# Input #
#########
@ -20,10 +16,6 @@ address = ":10001"
type = "udp"
address = ":10002"
# [input.logrus] - WIP
[input.webhook]
##########
# Output #
##########
@ -37,6 +29,3 @@ jid = "user@example.org"
password = "password"
# if boolean is true for muc either user chat
default = { "log-raw@conference.example.org" = true, "person@example.org" = false }
[output.websocket]
default = "raw"

View File

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

View File

@ -1,105 +0,0 @@
package xmpp
import (
"net/http"
"regexp"
"dev.sum7.eu/genofire/golang-lib/websocket"
"github.com/mitchellh/mapstructure"
log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/logmania/bot"
"dev.sum7.eu/genofire/logmania/database"
"dev.sum7.eu/genofire/logmania/output"
)
const (
proto = "ws"
)
var logger = log.WithField("output", proto)
type Output struct {
output.Output
defaults []*database.Notify
ws *websocket.Server
formatter log.Formatter
}
type OutputConfig struct {
Default string `mapstructure:"default"`
}
func Init(configInterface interface{}, db *database.DB, bot *bot.Bot) output.Output {
var config OutputConfig
if err := mapstructure.Decode(configInterface, &config); err != nil {
logger.Warnf("not able to decode data: %s", err)
return nil
}
inputMSG := make(chan *websocket.Message)
ws := websocket.NewServer(inputMSG, nil)
http.HandleFunc("/output/ws", ws.Handler)
go func() {
for msg := range inputMSG {
if msg.Subject != "bot" {
logger.Warnf("receive unknown websocket message: %s", msg.Subject)
continue
}
answer := bot.Handle("", msg.Body.(string))
if answer != "" {
msg.Answer("bot", answer)
}
}
}()
logger.Info("startup")
var defaults []*database.Notify
if config.Default != "" {
defaults = append(defaults, &database.Notify{
Protocol: proto,
To: config.Default,
RegexIn: make(map[string]*regexp.Regexp),
MaxPrioIn: log.DebugLevel,
})
}
return &Output{
defaults: defaults,
ws: ws,
formatter: &log.TextFormatter{
DisableTimestamp: true,
},
}
}
func (out *Output) Default() []*database.Notify {
return out.defaults
}
func (out *Output) Send(e *log.Entry, to *database.Notify) bool {
if to.Protocol != proto {
return false
}
out.ws.SendAll(&websocket.Message{
Subject: to.Address(),
Body: &log.Entry{
Buffer: e.Buffer,
Data: e.Data,
Level: e.Level,
Logger: e.Logger,
Message: to.RunReplace(e.Message),
Time: e.Time,
},
})
return true
}
func (out *Output) Close() {
}
func init() {
output.Add("websocket", Init)
}

View File

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

View File

@ -1,36 +0,0 @@
{
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,
}
}

View File

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

View File

@ -1,29 +0,0 @@
.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;
}
}
}

View File

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

View File

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

View File

@ -1,15 +0,0 @@
<!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>

View File

@ -1,9 +0,0 @@
/* 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`
};

View File

@ -1,54 +0,0 @@
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

@ -1,63 +0,0 @@
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

@ -1,94 +0,0 @@
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};

View File

@ -1,43 +0,0 @@
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();
}

View File

@ -1,14 +0,0 @@
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();

View File

@ -1,163 +0,0 @@
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();

View File

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

View File

@ -1,22 +0,0 @@
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');
}
}

View File

@ -1,20 +0,0 @@
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);
});

View File

@ -1,50 +0,0 @@
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.Level)
});
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;
}
}
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);
});

View File

@ -1,37 +0,0 @@
{
"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"
]
}
}

File diff suppressed because it is too large Load Diff