drop websocket and http support for hook2xmpp
This commit is contained in:
parent
89b426f3a9
commit
a5b0b06b3b
10
README.md
10
README.md
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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"
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package client
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
|
||||||
|
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"presets": ["env"]
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
.notifications {
|
|
||||||
position: fixed;
|
|
||||||
top: 16px;
|
|
||||||
right: 72px;
|
|
||||||
z-index: 105;
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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'
|
|
||||||
]);
|
|
|
@ -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>
|
|
|
@ -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`
|
|
||||||
};
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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};
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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();
|
|
|
@ -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();
|
|
|
@ -1,6 +0,0 @@
|
||||||
const store = {
|
|
||||||
'bot': [],
|
|
||||||
'channel': {}
|
|
||||||
};
|
|
||||||
|
|
||||||
export {store};
|
|
|
@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
});
|
|
|
@ -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);
|
|
||||||
});
|
|
|
@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
4555
webroot/yarn.lock
4555
webroot/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue