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: It receive logs (events) by:
- syslog - syslog
- journald (with service nc) - journald (with service nc)
- logrus (WIP)
- webhook
- git (github,gogs, gitea)
- circleci
- grafana
## output ## output
And forward this logs (events) to multiple different output: And forward this logs (events) to multiple different output:
- xmpp (client and muc) - xmpp (client and muc)
- file - file
- websocket (WIP)
there a multi filter possible there a multi filter possible
- regex - regex
@ -31,3 +25,7 @@ there a multi filter possible
it could replace text by regex expression it could replace text by regex expression
configuration live possible by bot (on input e.g. xmpp) 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 package cmd
import ( import (
"net/http"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"time" "time"
"github.com/NYTimes/gziphandler"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -71,22 +69,6 @@ var serverCmd = &cobra.Command{
log.WithField("defaults", len(db.DefaultNotify)).Info("starting logmania") 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) in = allInput.Init(config.Input, logChannel)
go in.Listen() go in.Listen()

View File

@ -2,10 +2,5 @@ package all
import ( import (
_ "dev.sum7.eu/genofire/logmania/input/journald_json" _ "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/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 // Struct of the configuration
// e.g. under dev.sum7.eu/genofire/logmania/logmania_example.conf // e.g. under dev.sum7.eu/genofire/logmania/logmania_example.conf
type Config struct { type Config struct {
Debug bool `toml:"debug"` Debug bool `toml:"debug"`
DB string `toml:"database"` DB string `toml:"database"`
HTTPAddress string `toml:"http_address"` AlertCheck Duration `toml:"alert_check"`
Webroot string `toml:"webroot"` Output map[string]interface{} `toml:"output"`
AlertCheck Duration `toml:"alert_check"` Input map[string]interface{} `toml:"input"`
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 # have to be mote then a minute
alert_check = "5m" alert_check = "5m"
# webserver
http_address = ":8080"
webroot = "./webroot/"
######### #########
# Input # # Input #
######### #########
@ -20,10 +16,6 @@ address = ":10001"
type = "udp" type = "udp"
address = ":10002" address = ":10002"
# [input.logrus] - WIP
[input.webhook]
########## ##########
# Output # # Output #
########## ##########
@ -37,6 +29,3 @@ jid = "user@example.org"
password = "password" password = "password"
# if boolean is true for muc either user chat # if boolean is true for muc either user chat
default = { "log-raw@conference.example.org" = true, "person@example.org" = false } default = { "log-raw@conference.example.org" = true, "person@example.org" = false }
[output.websocket]
default = "raw"

View File

@ -2,6 +2,5 @@ package all
import ( import (
_ "dev.sum7.eu/genofire/logmania/output/file" _ "dev.sum7.eu/genofire/logmania/output/file"
_ "dev.sum7.eu/genofire/logmania/output/websocket"
_ "dev.sum7.eu/genofire/logmania/output/xmpp" _ "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