Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
Martin/Geno | eda91728b6 | |
Martin/Geno | bd940a7656 |
|
@ -1,8 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
result="$(gofmt -s -l . | grep -v '^vendor/' )"
|
|
||||||
if [ -n "$result" ]; then
|
|
||||||
echo "Go code is not formatted, run 'gofmt -s -w .'" >&2
|
|
||||||
echo "$result"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
|
@ -1,25 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# checks if every desired package has test files
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
source_re = re.compile(".*\.go")
|
|
||||||
test_re = re.compile(".*_test\.go")
|
|
||||||
missing = False
|
|
||||||
|
|
||||||
for root, dirs, files in os.walk("."):
|
|
||||||
# ignore some paths
|
|
||||||
if root == "." or root.startswith("./vendor") or root.startswith("./."):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# source files but not test files?
|
|
||||||
if len(filter(source_re.match, files)) > 0 and len(filter(test_re.match, files)) == 0:
|
|
||||||
print("no test files for {}".format(root))
|
|
||||||
missing = True
|
|
||||||
|
|
||||||
if missing:
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
print("every package has test files")
|
|
|
@ -1,6 +1,6 @@
|
||||||
workspace:
|
workspace:
|
||||||
base: /go
|
base: /go
|
||||||
path: src/dev.sum7.eu/sum7/logmania
|
path: src/dev.sum7.eu/genofire/logmania
|
||||||
|
|
||||||
pipeline:
|
pipeline:
|
||||||
build:
|
build:
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
image: golang:latest
|
|
||||||
stages:
|
|
||||||
- build
|
|
||||||
- test
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- mkdir -p "/go/src/dev.sum7.eu/$CI_PROJECT_NAMESPACE/"
|
|
||||||
- cp -R "$CI_PROJECT_DIR" "/go/src/dev.sum7.eu/$CI_PROJECT_NAMESPACE/"
|
|
||||||
- cd "/go/src/dev.sum7.eu/$CI_PROJECT_PATH"
|
|
||||||
- go get -d -t ./...
|
|
||||||
|
|
||||||
build-my-project:
|
|
||||||
stage: build
|
|
||||||
script:
|
|
||||||
- mkdir "$CI_PROJECT_DIR/bin/"
|
|
||||||
- go install "dev.sum7.eu/$CI_PROJECT_PATH"
|
|
||||||
- mv "/go/bin/$CI_PROJECT_NAME" "$CI_PROJECT_DIR/bin/$CI_PROJECT_NAME"
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- "bin/$CI_PROJECT_NAME"
|
|
||||||
- config_example.toml
|
|
||||||
|
|
||||||
test-my-project:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- go get github.com/client9/misspell/cmd/misspell
|
|
||||||
- misspell -error .
|
|
||||||
- ./.ci/check-gofmt
|
|
||||||
- ./.ci/check-testfiles
|
|
||||||
- go test $(go list ./... | grep -v /vendor/) -v -coverprofile .testCoverage.txt
|
|
||||||
- go tool cover -func=.testCoverage.txt
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- .testCoverage.txt
|
|
||||||
|
|
||||||
test-race-my-project:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- go test -race ./...
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Issue: https://github.com/mattn/goveralls/issues/20
|
||||||
|
# Source: https://github.com/uber/go-torch/blob/63da5d33a225c195fea84610e2456d5f722f3963/.test-cover.sh
|
||||||
|
CI=$1
|
||||||
|
echo "run for $CI"
|
||||||
|
|
||||||
|
if [ "$CI" == "circle-ci" ]; then
|
||||||
|
cd ${GOPATH}/src/github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "mode: count" > profile.cov
|
||||||
|
FAIL=0
|
||||||
|
|
||||||
|
# Standard go tooling behavior is to ignore dirs with leading underscors
|
||||||
|
for dir in $(find . -maxdepth 10 -not -path './vendor/*' -not -path './.git*' -not -path '*/_*' -type d);
|
||||||
|
do
|
||||||
|
if ls $dir/*.go &> /dev/null; then
|
||||||
|
go test -v -covermode=count -coverprofile=profile.tmp $dir || FAIL=$?
|
||||||
|
if [ -f profile.tmp ]
|
||||||
|
then
|
||||||
|
tail -n +2 < profile.tmp >> profile.cov
|
||||||
|
rm profile.tmp
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Failures have incomplete results, so don't send
|
||||||
|
if [ "$FAIL" -eq 0 ]; then
|
||||||
|
goveralls -v -coverprofile=profile.cov -service=$CI -repotoken=$COVERALLS_REPO_TOKEN
|
||||||
|
bash <(curl -s https://codecov.io/bash) -t $CODECOV_TOKEN -f profile.cov
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit $FAIL
|
56
README.md
56
README.md
|
@ -1,52 +1,6 @@
|
||||||
# logmania
|
# logmania
|
||||||
|
[![DroneCI](https://ci.sum7.eu/api/badges/genofire/logmania/status.svg?branch=master)](https://ci.sum7.eu/genofire/logmania)
|
||||||
[![pipeline status](https://dev.sum7.eu/sum7/logmania/badges/master/pipeline.svg)](https://dev.sum7.eu/genofire/logmania/pipelines)
|
[![CircleCI](https://circleci.com/gh/genofire/logmania/tree/master.svg?style=shield)](https://circleci.com/gh/genofire/logmania/tree/master)
|
||||||
[![coverage report](https://dev.sum7.eu/sum7/logmania/badges/master/coverage.svg)](https://dev.sum7.eu/genofire/logmania/pipelines)
|
[![Coverage Status](https://coveralls.io/repos/github/genofire/logmania/badge.svg?branch=master)](https://coveralls.io/github/genofire/logmania?branch=master)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/dev.sum7.eu/sum7/logmania)](https://goreportcard.com/report/dev.sum7.eu/genofire/logmania)
|
[![Go Report Card](https://goreportcard.com/badge/dev.sum7.eu/genofire/logmania)](https://goreportcard.com/report/dev.sum7.eu/genofire/logmania)
|
||||||
[![GoDoc](https://godoc.org/dev.sum7.eu/sum7/logmania?status.svg)](https://godoc.org/dev.sum7.eu/genofire/logmania)
|
[![GoDoc](https://godoc.org/dev.sum7.eu/genofire/logmania?status.svg)](https://godoc.org/dev.sum7.eu/genofire/logmania)
|
||||||
|
|
||||||
|
|
||||||
This is a little logging server.
|
|
||||||
|
|
||||||
## input
|
|
||||||
It receive logs (events) by:
|
|
||||||
- syslog
|
|
||||||
- journald (with service nc)
|
|
||||||
|
|
||||||
## output
|
|
||||||
And forward this logs (events) to multiple different output:
|
|
||||||
- xmpp (client and muc)
|
|
||||||
- file
|
|
||||||
|
|
||||||
there a multi filter possible
|
|
||||||
- regex
|
|
||||||
- priority
|
|
||||||
|
|
||||||
it could replace text by regex expression
|
|
||||||
|
|
||||||
configuration live possible by bot (on input e.g. xmpp)
|
|
||||||
|
|
||||||
## Get logmania
|
|
||||||
|
|
||||||
#### Download
|
|
||||||
|
|
||||||
Latest Build binary from ci here:
|
|
||||||
|
|
||||||
[Download All](https://dev.sum7.eu/sum7/logmania/-/jobs/artifacts/master/download/?job=build-my-project) (with config example)
|
|
||||||
|
|
||||||
[Download Binary](https://dev.sum7.eu/sum7/logmania/-/jobs/artifacts/master/raw/bin/logmania?inline=false&job=build-my-project)
|
|
||||||
|
|
||||||
#### Build
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go get -u dev.sum7.eu/sum7/logmania
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configure
|
|
||||||
|
|
||||||
see `config_example.toml`
|
|
||||||
|
|
||||||
|
|
||||||
## Related Projects
|
|
||||||
|
|
||||||
- [hook2xmpp](https://dev.sum7.eu/sum7/hook2xmpp) for e.g. grafana, alertmanager(prometheus), gitlab, git, circleci
|
|
||||||
|
|
343
bot/command.go
343
bot/command.go
|
@ -2,45 +2,332 @@ package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
timeago "github.com/ararog/timeago"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type commandFunc func(from string, params []string) string
|
type commandFunc func(func(string), string, []string)
|
||||||
|
|
||||||
type Command struct {
|
// list help
|
||||||
Name string
|
func (b *Bot) help(answer func(string), from string, params []string) {
|
||||||
Description string
|
msg := fmt.Sprintf("Hi %s there are the following commands:\n", from)
|
||||||
Commands []*Command
|
for _, cmd := range b.commands {
|
||||||
Action commandFunc
|
msg = fmt.Sprintf("%s - !%s\n", msg, cmd)
|
||||||
|
}
|
||||||
|
answer(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Command) Run(from string, args []string) string {
|
// add a chat to send log to a chat
|
||||||
if len(args) > 0 {
|
func (b *Bot) addSend(answer func(string), from string, params []string) {
|
||||||
cmdName := args[0]
|
if len(params) < 1 {
|
||||||
if cmdName == "help" {
|
answer("invalid: CMD IPAddress/Hostname\n or\n CMD IPAddress/Hostname to")
|
||||||
return c.Help()
|
return
|
||||||
}
|
}
|
||||||
if len(c.Commands) > 0 {
|
host := params[0]
|
||||||
for _, cmd := range c.Commands {
|
to := from
|
||||||
if cmd.Name == cmdName {
|
if len(params) > 1 {
|
||||||
return cmd.Run(from, args[1:])
|
to = params[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h := b.db.GetHost(host)
|
||||||
|
if h == nil {
|
||||||
|
h = b.db.NewHost(host)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("command %s not found\n%s", cmdName, c.Help())
|
n, ok := b.db.NotifiesByAddress[to]
|
||||||
|
if !ok {
|
||||||
|
n = b.db.NewNotify(to)
|
||||||
}
|
}
|
||||||
}
|
h.AddNotify(n)
|
||||||
if c.Action != nil {
|
|
||||||
return c.Action(from, args)
|
answer(fmt.Sprintf("added %s in list of %s", to, host))
|
||||||
}
|
|
||||||
return c.Help()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Command) Help() string {
|
//add a chat to send log to a chat
|
||||||
if len(c.Commands) > 0 {
|
func (b *Bot) delSend(answer func(string), from string, params []string) {
|
||||||
msg := fmt.Sprintf("%s\n-------------------", c.Description)
|
if len(params) < 1 {
|
||||||
for _, cmd := range c.Commands {
|
answer("invalid: CMD IPAddress/Hostname\n or\n CMD IPAddress/Hostname to")
|
||||||
msg = fmt.Sprintf("%s\n%s: %s", msg, cmd.Name, cmd.Description)
|
return
|
||||||
}
|
}
|
||||||
return msg
|
host := params[0]
|
||||||
|
to := from
|
||||||
|
if len(params) > 1 {
|
||||||
|
to = params[1]
|
||||||
}
|
}
|
||||||
return c.Description
|
|
||||||
|
if h := b.db.GetHost(host); h != nil {
|
||||||
|
h.DeleteNotify(to)
|
||||||
|
answer(fmt.Sprintf("removed %s in list of %s", to, host))
|
||||||
|
} else {
|
||||||
|
answer("not found host")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// list all hostname with the chat where it send to
|
||||||
|
func (b *Bot) listSend(answer func(string), from string, params []string) {
|
||||||
|
msg := "sending:\n"
|
||||||
|
all := false
|
||||||
|
of := from
|
||||||
|
if len(params) > 0 {
|
||||||
|
if params[0] == "all" {
|
||||||
|
all = true
|
||||||
|
} else {
|
||||||
|
of = params[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, host := range b.db.Hosts {
|
||||||
|
toList := ""
|
||||||
|
show := all
|
||||||
|
for _, to := range host.Notifies {
|
||||||
|
if all {
|
||||||
|
toList = fmt.Sprintf("%s , %s", toList, to)
|
||||||
|
} else if to == of {
|
||||||
|
show = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !show {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(toList) > 3 {
|
||||||
|
toList = toList[3:]
|
||||||
|
}
|
||||||
|
if host.Name != "" {
|
||||||
|
msg = fmt.Sprintf("%s%s (%s): %s\n", msg, host.Address, host.Name, toList)
|
||||||
|
} else {
|
||||||
|
msg = fmt.Sprintf("%s%s: %s\n", msg, host.Address, toList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
answer(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add hostname
|
||||||
|
func (b *Bot) addHostname(answer func(string), from string, params []string) {
|
||||||
|
if len(params) < 2 {
|
||||||
|
answer("invalid: CMD IPAddress/Hostname NewHostname")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addr := params[0]
|
||||||
|
name := params[1]
|
||||||
|
|
||||||
|
h := b.db.GetHost(addr)
|
||||||
|
if h == nil {
|
||||||
|
h = b.db.NewHost(addr)
|
||||||
|
}
|
||||||
|
b.db.ChangeHostname(h, name)
|
||||||
|
|
||||||
|
answer(fmt.Sprintf("set for %s the hostname %s", addr, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bot) delHostname(answer func(string), from string, params []string) {
|
||||||
|
if len(params) < 2 {
|
||||||
|
answer("invalid: CMD IPAddress/Hostname")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addr := params[0]
|
||||||
|
h := b.db.GetHost(addr)
|
||||||
|
if h != nil {
|
||||||
|
b.db.DeleteHost(h)
|
||||||
|
if h.Name != "" {
|
||||||
|
answer(fmt.Sprintf("remove host %s with hostname %s", h.Address, h.Name))
|
||||||
|
} else {
|
||||||
|
answer(fmt.Sprintf("remove host %s", h.Address))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
answer("could not found host")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// list all host with his ip
|
||||||
|
func (b *Bot) listHostname(answer func(string), from string, params []string) {
|
||||||
|
msg := "hostnames:\n"
|
||||||
|
for _, host := range b.db.Hosts {
|
||||||
|
if host.Lastseen.Year() > 1 {
|
||||||
|
got, _ := timeago.TimeAgoFromNowWithTime(host.Lastseen)
|
||||||
|
msg = fmt.Sprintf("%s%s - %s (%s)\n", msg, host.Address, host.Name, got)
|
||||||
|
} else {
|
||||||
|
msg = fmt.Sprintf("%s%s - %s\n", msg, host.Address, host.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
answer(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set a filter by max
|
||||||
|
func (b *Bot) listMaxfilter(answer func(string), from string, params []string) {
|
||||||
|
msg := "filters: "
|
||||||
|
if len(params) > 0 && params[0] == "all" {
|
||||||
|
msg = fmt.Sprintf("%s\n", msg)
|
||||||
|
for _, n := range b.db.Notifies {
|
||||||
|
msg = fmt.Sprintf("%s%s - %s\n", msg, n.Address(), n.MaxPrioIn.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
of := from
|
||||||
|
if len(params) > 0 {
|
||||||
|
of = params[0]
|
||||||
|
}
|
||||||
|
if filter, ok := b.db.NotifiesByAddress[of]; ok {
|
||||||
|
msg = fmt.Sprintf("%s of %s is %s", msg, of, filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
answer(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set a filter to a mix
|
||||||
|
func (b *Bot) setMaxfilter(answer func(string), from string, params []string) {
|
||||||
|
if len(params) < 1 {
|
||||||
|
answer("invalid: CMD Priority\n or\n CMD Channel Priority")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
to := from
|
||||||
|
var max log.Level
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if len(params) > 1 {
|
||||||
|
to = params[0]
|
||||||
|
max, err = log.ParseLevel(params[1])
|
||||||
|
} else {
|
||||||
|
max, err = log.ParseLevel(params[0])
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
answer("invalid priority: CMD Priority\n or\n CMD Channel Priority")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, ok := b.db.NotifiesByAddress[to]
|
||||||
|
if !ok {
|
||||||
|
n = b.db.NewNotify(to)
|
||||||
|
}
|
||||||
|
|
||||||
|
n.MaxPrioIn = max
|
||||||
|
|
||||||
|
answer(fmt.Sprintf("set filter for %s to %s", to, max.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// list of regex filter
|
||||||
|
func (b *Bot) listRegex(answer func(string), from string, params []string) {
|
||||||
|
msg := "regexs:\n"
|
||||||
|
if len(params) > 0 && params[0] == "all" {
|
||||||
|
for _, n := range b.db.Notifies {
|
||||||
|
msg = fmt.Sprintf("%s%s\n-------------\n", msg, n.Address())
|
||||||
|
for expression := range n.RegexIn {
|
||||||
|
msg = fmt.Sprintf("%s - %s\n", msg, expression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
of := from
|
||||||
|
if len(params) > 0 {
|
||||||
|
of = params[0]
|
||||||
|
}
|
||||||
|
if n, ok := b.db.NotifiesByAddress[of]; ok {
|
||||||
|
msg = fmt.Sprintf("%s%s\n-------------\n", msg, of)
|
||||||
|
for expression := range n.RegexIn {
|
||||||
|
msg = fmt.Sprintf("%s - %s\n", msg, expression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
answer(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a regex filter
|
||||||
|
func (b *Bot) addRegex(answer func(string), from string, params []string) {
|
||||||
|
if len(params) < 1 {
|
||||||
|
answer("invalid: CMD regex\n or\n CMD channel regex")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
of := from
|
||||||
|
regex := params[0]
|
||||||
|
if len(params) > 1 {
|
||||||
|
of = params[0]
|
||||||
|
regex = params[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
n := b.db.NotifiesByAddress[of]
|
||||||
|
if err := n.AddRegex(regex); err == nil {
|
||||||
|
answer(fmt.Sprintf("add regex for \"%s\" to %s", of, regex))
|
||||||
|
} else {
|
||||||
|
answer(fmt.Sprintf("\"%s\" is no valid regex expression: %s", regex, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// del a regex filter
|
||||||
|
func (b *Bot) delRegex(answer func(string), from string, params []string) {
|
||||||
|
if len(params) < 1 {
|
||||||
|
answer("invalid: CMD regex\n or\n CMD channel regex")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
of := from
|
||||||
|
regex := params[0]
|
||||||
|
if len(params) > 1 {
|
||||||
|
of = params[0]
|
||||||
|
regex = params[1]
|
||||||
|
}
|
||||||
|
n := b.db.NotifiesByAddress[of]
|
||||||
|
delete(n.RegexIn, regex)
|
||||||
|
b.listRegex(answer, of, []string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// list of regex replace
|
||||||
|
func (b *Bot) listRegexReplace(answer func(string), from string, params []string) {
|
||||||
|
msg := "replaces:\n"
|
||||||
|
if len(params) > 0 && params[0] == "all" {
|
||||||
|
for _, n := range b.db.Notifies {
|
||||||
|
msg = fmt.Sprintf("%s%s\n-------------\n", msg, n.Address())
|
||||||
|
for expression, value := range n.RegexReplace {
|
||||||
|
msg = fmt.Sprintf("%s - \"%s\" : \"%s\"\n", msg, expression, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
of := from
|
||||||
|
if len(params) > 0 {
|
||||||
|
of = params[0]
|
||||||
|
}
|
||||||
|
if n, ok := b.db.NotifiesByAddress[of]; ok {
|
||||||
|
msg = fmt.Sprintf("%s%s\n-------------\n", msg, of)
|
||||||
|
for expression, value := range n.RegexReplace {
|
||||||
|
msg = fmt.Sprintf("%s - \"%s\" : \"%s\"\n", msg, expression, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
answer(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a regex replace
|
||||||
|
func (b *Bot) addRegexReplace(answer func(string), from string, params []string) {
|
||||||
|
if len(params) < 1 {
|
||||||
|
answer("invalid: CMD regex replace\n or\n CMD channel regex replace")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
of := from
|
||||||
|
regex := params[0]
|
||||||
|
value := params[1]
|
||||||
|
if len(params) > 2 {
|
||||||
|
of = params[0]
|
||||||
|
regex = params[1]
|
||||||
|
value = params[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
n := b.db.NotifiesByAddress[of]
|
||||||
|
if err := n.AddRegexReplace(regex, value); err == nil {
|
||||||
|
answer(fmt.Sprintf("add replace in \"%s\" for \"%s\" to \"%s\"", of, regex, value))
|
||||||
|
} else {
|
||||||
|
answer(fmt.Sprintf("\"%s\" to \"%s\" is no valid regex replace expression: %s", regex, value, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// del a regex replace
|
||||||
|
func (b *Bot) delRegexReplace(answer func(string), from string, params []string) {
|
||||||
|
if len(params) < 1 {
|
||||||
|
answer("invalid: CMD regex\n or\n CMD channel regex")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
of := from
|
||||||
|
regex := params[0]
|
||||||
|
if len(params) > 1 {
|
||||||
|
of = params[0]
|
||||||
|
regex = params[1]
|
||||||
|
}
|
||||||
|
n := b.db.NotifiesByAddress[of]
|
||||||
|
|
||||||
|
delete(n.RegexReplace, regex)
|
||||||
|
b.listRegexReplace(answer, of, []string{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
package bot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"dev.sum7.eu/sum7/logmania/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewFilter(db *database.DB) *Command {
|
|
||||||
return &Command{
|
|
||||||
Name: "filter",
|
|
||||||
Description: "list and configure regex filter for channel by message content",
|
|
||||||
Commands: []*Command{
|
|
||||||
{
|
|
||||||
Name: "add",
|
|
||||||
Description: "add regex filter for channel: [channel] regex",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
if len(params) < 1 {
|
|
||||||
return "invalid: [channel] regex"
|
|
||||||
}
|
|
||||||
of := from
|
|
||||||
regex := params[0]
|
|
||||||
if len(params) > 1 {
|
|
||||||
of = params[0]
|
|
||||||
regex = params[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
n := db.NotifiesByAddress[of]
|
|
||||||
if err := n.AddRegex(regex); err != nil {
|
|
||||||
return fmt.Sprintf("\"%s\" is no valid regex expression: %s", regex, err)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("add regex for \"%s\" to %s", of, regex)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "del",
|
|
||||||
Description: "del regex filter for channel: [channel] regex",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
if len(params) < 1 {
|
|
||||||
return "invalid: [channel] regex"
|
|
||||||
}
|
|
||||||
of := from
|
|
||||||
regex := params[0]
|
|
||||||
if len(params) > 1 {
|
|
||||||
of = params[0]
|
|
||||||
regex = params[1]
|
|
||||||
}
|
|
||||||
n := db.NotifiesByAddress[of]
|
|
||||||
delete(n.RegexIn, regex)
|
|
||||||
return "deleted"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "all",
|
|
||||||
Description: "list of all channels",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
msg := "filter:\n"
|
|
||||||
for _, n := range db.Notifies {
|
|
||||||
msg = fmt.Sprintf("%s%s\n-------------\n", msg, n.Address())
|
|
||||||
for expression := range n.RegexIn {
|
|
||||||
msg = fmt.Sprintf("%s - %s\n", msg, expression)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "channel",
|
|
||||||
Description: "list of given channel: channel",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
msg := "filter:\n"
|
|
||||||
if len(params) != 1 {
|
|
||||||
return "invalid: no channel given"
|
|
||||||
}
|
|
||||||
of := params[0]
|
|
||||||
if n, ok := db.NotifiesByAddress[of]; ok {
|
|
||||||
msg = fmt.Sprintf("%s%s\n-------------\n", msg, of)
|
|
||||||
for expression := range n.RegexIn {
|
|
||||||
msg = fmt.Sprintf("%s - %s\n", msg, expression)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
msg := "filter:\n"
|
|
||||||
if n, ok := db.NotifiesByAddress[from]; ok {
|
|
||||||
msg = fmt.Sprintf("%s%s\n-------------\n", msg, from)
|
|
||||||
for expression := range n.RegexIn {
|
|
||||||
msg = fmt.Sprintf("%s - %s\n", msg, expression)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
package bot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"dev.sum7.eu/sum7/logmania/database"
|
|
||||||
timeago "github.com/ararog/timeago"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewHostname(db *database.DB) *Command {
|
|
||||||
return &Command{
|
|
||||||
Name: "hostname",
|
|
||||||
Description: "alternative short (host)names for long IP-Addresses or URLs (and time of last received input)",
|
|
||||||
Commands: []*Command{
|
|
||||||
{
|
|
||||||
Name: "set",
|
|
||||||
Description: "set or replace a hostname: IPAddress/Hostname NewHostname",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
if len(params) != 2 {
|
|
||||||
return "invalid: IPAddress/Hostname NewHostname"
|
|
||||||
}
|
|
||||||
addr := params[0]
|
|
||||||
name := params[1]
|
|
||||||
|
|
||||||
h := db.GetHost(addr)
|
|
||||||
if h == nil {
|
|
||||||
h = db.NewHost(addr)
|
|
||||||
}
|
|
||||||
db.ChangeHostname(h, name)
|
|
||||||
|
|
||||||
return fmt.Sprintf("set for %s the hostname %s", addr, name)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "del",
|
|
||||||
Description: "delete a hostname: IPAddress/Hostname",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
if len(params) != 1 {
|
|
||||||
return "invalid: IPAddress/Hostname"
|
|
||||||
}
|
|
||||||
addr := params[0]
|
|
||||||
h := db.GetHost(addr)
|
|
||||||
if h != nil {
|
|
||||||
db.DeleteHost(h)
|
|
||||||
if h.Name != "" {
|
|
||||||
return fmt.Sprintf("remove host %s with hostname %s", h.Address, h.Name)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("remove host %s", h.Address)
|
|
||||||
}
|
|
||||||
return "could not found host"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
msg := "hostnames:\n"
|
|
||||||
for _, host := range db.Hosts {
|
|
||||||
if host.Lastseen.Year() > 1 {
|
|
||||||
got, _ := timeago.TimeAgoFromNowWithTime(host.Lastseen)
|
|
||||||
msg = fmt.Sprintf("%s%s - %s (%s)\n", msg, host.Address, host.Name, got)
|
|
||||||
} else {
|
|
||||||
msg = fmt.Sprintf("%s%s - %s\n", msg, host.Address, host.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
60
bot/main.go
60
bot/main.go
|
@ -1,36 +1,54 @@
|
||||||
package bot
|
package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mattn/go-shellwords"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"dev.sum7.eu/sum7/logmania/database"
|
"dev.sum7.eu/genofire/logmania/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bot struct {
|
type Bot struct {
|
||||||
Command
|
db *database.DB
|
||||||
|
commandsMap map[string]commandFunc
|
||||||
|
commands []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBot(db *database.DB) *Bot {
|
func NewBot(db *database.DB) *Bot {
|
||||||
return &Bot{Command{
|
b := &Bot{
|
||||||
Description: "logmania bot, to configure live all settings",
|
db: db,
|
||||||
Commands: []*Command{
|
}
|
||||||
NewFilter(db),
|
b.commandsMap = map[string]commandFunc{
|
||||||
NewHostname(db),
|
"help": b.help,
|
||||||
NewPriority(db),
|
"send-add": b.addSend,
|
||||||
NewReplace(db),
|
"send-list": b.listSend,
|
||||||
NewSend(db),
|
"send-del": b.delSend,
|
||||||
},
|
"hostname-set": b.addHostname,
|
||||||
}}
|
"hostname-list": b.listHostname,
|
||||||
|
"hostname-del": b.delHostname,
|
||||||
|
"filter-set": b.setMaxfilter,
|
||||||
|
"filter-list": b.listMaxfilter,
|
||||||
|
"regex-add": b.addRegex,
|
||||||
|
"regex-list": b.listRegex,
|
||||||
|
"regex-del": b.delRegex,
|
||||||
|
"replace-add": b.addRegexReplace,
|
||||||
|
"replace-list": b.listRegexReplace,
|
||||||
|
"replace-del": b.delRegexReplace,
|
||||||
|
}
|
||||||
|
for k := range b.commandsMap {
|
||||||
|
b.commands = append(b.commands, k)
|
||||||
|
}
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) Handle(from, msg string) string {
|
func (b *Bot) Handle(answer func(string), from, msg string) {
|
||||||
msgParts, err := shellwords.Parse(msg)
|
msgParts := strings.Split(msg, " ")
|
||||||
if err != nil {
|
if len(msgParts[0]) <= 0 || msgParts[0][0] != '!' {
|
||||||
return ""
|
return
|
||||||
}
|
}
|
||||||
if len(msgParts) <= 0 || msgParts[0][0] != '.' {
|
cmdName := msgParts[0][1:]
|
||||||
return ""
|
if cmd, ok := b.commandsMap[cmdName]; ok {
|
||||||
|
cmd(answer, from, msgParts[1:])
|
||||||
|
} else {
|
||||||
|
answer(fmt.Sprintf("not found command: !%s", cmdName))
|
||||||
}
|
}
|
||||||
msgParts[0] = msgParts[0][1:]
|
|
||||||
return b.Run(from, msgParts)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
package bot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/bdlm/log"
|
|
||||||
logstd "github.com/bdlm/std/logger"
|
|
||||||
|
|
||||||
"dev.sum7.eu/sum7/logmania/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewPriority(db *database.DB) *Command {
|
|
||||||
return &Command{
|
|
||||||
Name: "priority",
|
|
||||||
Description: "list and configure priority in channel",
|
|
||||||
Commands: []*Command{
|
|
||||||
{
|
|
||||||
Name: "set",
|
|
||||||
Description: "set max priority of channel: [channel] Priority",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
if len(params) < 1 {
|
|
||||||
return "invalid: [channel] Priority"
|
|
||||||
}
|
|
||||||
to := from
|
|
||||||
var max logstd.Level
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if len(params) > 1 {
|
|
||||||
to = params[0]
|
|
||||||
max, err = log.ParseLevel(params[1])
|
|
||||||
} else {
|
|
||||||
max, err = log.ParseLevel(params[0])
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return "invalid: [Channel] Priority"
|
|
||||||
}
|
|
||||||
n, ok := db.NotifiesByAddress[to]
|
|
||||||
if !ok {
|
|
||||||
n = db.NewNotify(to)
|
|
||||||
}
|
|
||||||
|
|
||||||
n.MaxPrioIn = max
|
|
||||||
|
|
||||||
return fmt.Sprintf("set filter for %s to %s", to, log.LevelString(max))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "all",
|
|
||||||
Description: "list of all channels",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
msg := "priority: \n"
|
|
||||||
for _, n := range db.Notifies {
|
|
||||||
msg = fmt.Sprintf("%s%s - %s\n", msg, n.Address(), log.LevelString(n.MaxPrioIn))
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "channel",
|
|
||||||
Description: "list of given channel: channel",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
if len(params) != 1 {
|
|
||||||
return "invalid: no channel given"
|
|
||||||
}
|
|
||||||
of := params[0]
|
|
||||||
msg := "priority: \n"
|
|
||||||
if notify, ok := db.NotifiesByAddress[of]; ok {
|
|
||||||
msg = fmt.Sprintf("%s %s is %s", msg, of, log.LevelString(notify.MaxPrioIn))
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
msg := "priority: \n"
|
|
||||||
if notify, ok := db.NotifiesByAddress[from]; ok {
|
|
||||||
msg = fmt.Sprintf("%s %s is %s", msg, from, log.LevelString(notify.MaxPrioIn))
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
101
bot/replace.go
101
bot/replace.go
|
@ -1,101 +0,0 @@
|
||||||
package bot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"dev.sum7.eu/sum7/logmania/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewReplace(db *database.DB) *Command {
|
|
||||||
return &Command{
|
|
||||||
Name: "replace",
|
|
||||||
Description: "list and configure replace content of message for channel",
|
|
||||||
Commands: []*Command{
|
|
||||||
{
|
|
||||||
Name: "add",
|
|
||||||
Description: "add regex replace for channel: [channel] regex replace",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
if len(params) < 1 {
|
|
||||||
return "invalid: [channel] regex replace"
|
|
||||||
}
|
|
||||||
of := from
|
|
||||||
regex := params[0]
|
|
||||||
value := params[1]
|
|
||||||
if len(params) > 2 {
|
|
||||||
of = params[0]
|
|
||||||
regex = params[1]
|
|
||||||
value = params[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
n := db.NotifiesByAddress[of]
|
|
||||||
if err := n.AddRegexReplace(regex, value); err != nil {
|
|
||||||
return fmt.Sprintf("\"%s\" to \"%s\" is no valid regex replace expression: %s", regex, value, err)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("add replace in \"%s\" for \"%s\" to \"%s\"", of, regex, value)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "del",
|
|
||||||
Description: "del regex replace for channel: [channel] regex replace",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
if len(params) < 1 {
|
|
||||||
return "invalid: [channel] regex replace"
|
|
||||||
}
|
|
||||||
of := from
|
|
||||||
regex := params[0]
|
|
||||||
if len(params) > 1 {
|
|
||||||
of = params[0]
|
|
||||||
regex = params[1]
|
|
||||||
}
|
|
||||||
n := db.NotifiesByAddress[of]
|
|
||||||
|
|
||||||
delete(n.RegexReplace, regex)
|
|
||||||
return "deleted"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
Name: "all",
|
|
||||||
Description: "list of all channels",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
msg := "replaces:\n"
|
|
||||||
for _, n := range db.Notifies {
|
|
||||||
msg = fmt.Sprintf("%s%s\n-------------\n", msg, n.Address())
|
|
||||||
for expression, value := range n.RegexReplace {
|
|
||||||
msg = fmt.Sprintf("%s - \"%s\" : \"%s\"\n", msg, expression, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "channel",
|
|
||||||
Description: "list of given channel: channel",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
if len(params) != 1 {
|
|
||||||
return "invalid: no channel given"
|
|
||||||
}
|
|
||||||
of := params[0]
|
|
||||||
msg := "replaces:\n"
|
|
||||||
if n, ok := db.NotifiesByAddress[of]; ok {
|
|
||||||
msg = fmt.Sprintf("%s%s\n-------------\n", msg, of)
|
|
||||||
for expression, value := range n.RegexReplace {
|
|
||||||
msg = fmt.Sprintf("%s - \"%s\" : \"%s\"\n", msg, expression, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
msg := "replaces:\n"
|
|
||||||
if n, ok := db.NotifiesByAddress[from]; ok {
|
|
||||||
msg = fmt.Sprintf("%s%s\n-------------\n", msg, from)
|
|
||||||
for expression, value := range n.RegexReplace {
|
|
||||||
msg = fmt.Sprintf("%s - \"%s\" : \"%s\"\n", msg, expression, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
142
bot/send.go
142
bot/send.go
|
@ -1,142 +0,0 @@
|
||||||
package bot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"dev.sum7.eu/sum7/logmania/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewSend(db *database.DB) *Command {
|
|
||||||
return &Command{
|
|
||||||
Name: "send",
|
|
||||||
Description: "list and configure destination for hostnames",
|
|
||||||
Commands: []*Command{
|
|
||||||
{
|
|
||||||
Name: "add",
|
|
||||||
Description: "add a destination for host with: IPAddress/Hostname [to]",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
if len(params) < 1 {
|
|
||||||
return "invalid: IPAddress/Hostname [to]"
|
|
||||||
}
|
|
||||||
host := params[0]
|
|
||||||
to := from
|
|
||||||
if len(params) > 1 {
|
|
||||||
to = params[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
h := db.GetHost(host)
|
|
||||||
if h == nil {
|
|
||||||
h = db.NewHost(host)
|
|
||||||
}
|
|
||||||
if h == nil {
|
|
||||||
return fmt.Sprintf("could not create host %s", host)
|
|
||||||
}
|
|
||||||
n, ok := db.NotifiesByAddress[to]
|
|
||||||
if !ok {
|
|
||||||
n = db.NewNotify(to)
|
|
||||||
}
|
|
||||||
if n == nil {
|
|
||||||
return fmt.Sprintf("could not create notify %s in list of %s", to, host)
|
|
||||||
}
|
|
||||||
h.AddNotify(n)
|
|
||||||
|
|
||||||
return fmt.Sprintf("added %s in list of %s", to, host)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "del",
|
|
||||||
Description: "del a destination for host with: IPAddress/Hostname [to]",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
if len(params) < 1 {
|
|
||||||
return "invalid: IPAddress/Hostname [to]"
|
|
||||||
}
|
|
||||||
host := params[0]
|
|
||||||
to := from
|
|
||||||
if len(params) > 1 {
|
|
||||||
to = params[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if h := db.GetHost(host); h != nil {
|
|
||||||
h.DeleteNotify(to)
|
|
||||||
return fmt.Sprintf("removed %s in list of %s", to, host)
|
|
||||||
}
|
|
||||||
return "not found host"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "all",
|
|
||||||
Description: "list of all hosts with there channels",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
msg := "sending:\n"
|
|
||||||
for _, host := range db.Hosts {
|
|
||||||
toList := ""
|
|
||||||
for _, to := range host.Notifies {
|
|
||||||
toList = fmt.Sprintf("%s , %s", toList, to)
|
|
||||||
}
|
|
||||||
if len(toList) > 3 {
|
|
||||||
toList = toList[3:]
|
|
||||||
}
|
|
||||||
if host.Name != "" {
|
|
||||||
msg = fmt.Sprintf("%s%s (%s): %s\n", msg, host.Address, host.Name, toList)
|
|
||||||
} else {
|
|
||||||
msg = fmt.Sprintf("%s%s: %s\n", msg, host.Address, toList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "channel",
|
|
||||||
Description: "list all host of given channel: channel",
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
if len(params) != 1 {
|
|
||||||
return "invalid: no channel given"
|
|
||||||
}
|
|
||||||
|
|
||||||
of := params[0]
|
|
||||||
msg := "sending:\n"
|
|
||||||
|
|
||||||
for _, host := range db.Hosts {
|
|
||||||
show := false
|
|
||||||
for _, to := range host.Notifies {
|
|
||||||
if to == of {
|
|
||||||
show = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !show {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if host.Name != "" {
|
|
||||||
msg = fmt.Sprintf("%s%s (%s)\n", msg, host.Address, host.Name)
|
|
||||||
} else {
|
|
||||||
msg = fmt.Sprintf("%s%s\n", msg, host.Address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(from string, params []string) string {
|
|
||||||
msg := "sending:\n"
|
|
||||||
for _, host := range db.Hosts {
|
|
||||||
show := false
|
|
||||||
for _, to := range host.Notifies {
|
|
||||||
if to == from {
|
|
||||||
show = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !show {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if host.Name != "" {
|
|
||||||
msg = fmt.Sprintf("%s%s (%s)\n", msg, host.Address, host.Name)
|
|
||||||
} else {
|
|
||||||
msg = fmt.Sprintf("%s%s\n", msg, host.Address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package bot
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
version: 2
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:latest
|
||||||
|
working_directory: /go/src/dev.sum7.eu/genofire/logmania
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: go get -t -d -v ./...
|
||||||
|
- run: go install dev.sum7.eu/genofire/logmania
|
||||||
|
- store_artifacts:
|
||||||
|
path: /go/bin/
|
||||||
|
destination: logmania
|
||||||
|
test:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:latest
|
||||||
|
working_directory: /go/src/dev.sum7.eu/genofire/logmania
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: go get -t -d -v ./...
|
||||||
|
- run: go get github.com/mattn/goveralls
|
||||||
|
- run: go get golang.org/x/tools/cmd/cover
|
||||||
|
- run: ./.test-coverage circle-ci
|
||||||
|
- store_test_results:
|
||||||
|
path: ./
|
||||||
|
destination: profile.cov
|
||||||
|
test_race:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:latest
|
||||||
|
working_directory: /go/src/dev.sum7.eu/genofire/logmania
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: go get -t -d -v ./...
|
||||||
|
- run: go test -race ./...
|
||||||
|
workflows:
|
||||||
|
version: 2
|
||||||
|
build_and_tests:
|
||||||
|
jobs:
|
||||||
|
- build
|
||||||
|
- test
|
||||||
|
- test_race
|
|
@ -1,7 +1,7 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bdlm/log"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
package cmd
|
|
|
@ -6,18 +6,18 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bdlm/log"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"dev.sum7.eu/genofire/golang-lib/file"
|
"dev.sum7.eu/genofire/golang-lib/file"
|
||||||
"dev.sum7.eu/genofire/golang-lib/worker"
|
"dev.sum7.eu/genofire/golang-lib/worker"
|
||||||
"dev.sum7.eu/sum7/logmania/bot"
|
"dev.sum7.eu/genofire/logmania/bot"
|
||||||
"dev.sum7.eu/sum7/logmania/database"
|
"dev.sum7.eu/genofire/logmania/database"
|
||||||
"dev.sum7.eu/sum7/logmania/input"
|
"dev.sum7.eu/genofire/logmania/lib"
|
||||||
allInput "dev.sum7.eu/sum7/logmania/input/all"
|
"dev.sum7.eu/genofire/logmania/notify"
|
||||||
"dev.sum7.eu/sum7/logmania/lib"
|
allNotify "dev.sum7.eu/genofire/logmania/notify/all"
|
||||||
"dev.sum7.eu/sum7/logmania/output"
|
"dev.sum7.eu/genofire/logmania/receive"
|
||||||
allOutput "dev.sum7.eu/sum7/logmania/output/all"
|
allReceiver "dev.sum7.eu/genofire/logmania/receive/all"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -25,8 +25,8 @@ var (
|
||||||
config *lib.Config
|
config *lib.Config
|
||||||
db *database.DB
|
db *database.DB
|
||||||
dbSaveWorker *worker.Worker
|
dbSaveWorker *worker.Worker
|
||||||
out output.Output
|
notifier notify.Notifier
|
||||||
in input.Input
|
receiver receive.Receiver
|
||||||
logChannel chan *log.Entry
|
logChannel chan *log.Entry
|
||||||
logmaniaBot *bot.Bot
|
logmaniaBot *bot.Bot
|
||||||
)
|
)
|
||||||
|
@ -45,33 +45,30 @@ var serverCmd = &cobra.Command{
|
||||||
if config == nil || err != nil {
|
if config == nil || err != nil {
|
||||||
log.Panicf("Could not load '%s' for configuration.", configPath)
|
log.Panicf("Could not load '%s' for configuration.", configPath)
|
||||||
}
|
}
|
||||||
if config.Debug {
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
db = database.ReadDBFile(config.DB)
|
db = database.ReadDBFile(config.DB)
|
||||||
go func() { dbSaveWorker = file.NewSaveJSONWorker(time.Minute, config.DB, db) }()
|
go func() { dbSaveWorker = file.NewSaveJSONWorker(time.Minute, config.DB, db) }()
|
||||||
|
|
||||||
logmaniaBot = bot.NewBot(db)
|
logmaniaBot = bot.NewBot(db)
|
||||||
|
|
||||||
out = allOutput.Init(config.Output, db, logmaniaBot)
|
notifier = allNotify.Init(&config.Notify, db, logmaniaBot)
|
||||||
logChannel = make(chan *log.Entry)
|
logChannel = make(chan *log.Entry)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for a := range logChannel {
|
for a := range logChannel {
|
||||||
out.Send(a, nil)
|
notifier.Send(a, nil)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if config.AlertCheck.Duration > time.Duration(time.Second) {
|
if config.Notify.AlertCheck.Duration > time.Duration(time.Second) {
|
||||||
go db.Alert(config.AlertCheck.Duration, out.Send)
|
go db.Alert(config.Notify.AlertCheck.Duration, notifier.Send)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithField("defaults", len(db.DefaultNotify)).Info("starting logmania")
|
log.Info("starting logmania")
|
||||||
|
|
||||||
in = allInput.Init(config.Input, logChannel)
|
receiver = allReceiver.Init(&config.Receive, logChannel)
|
||||||
|
|
||||||
go in.Listen()
|
go receiver.Listen()
|
||||||
|
|
||||||
// Wait for system signal
|
// Wait for system signal
|
||||||
sigchan := make(chan os.Signal, 1)
|
sigchan := make(chan os.Signal, 1)
|
||||||
|
@ -95,8 +92,8 @@ var serverCmd = &cobra.Command{
|
||||||
func quit() {
|
func quit() {
|
||||||
dbSaveWorker.Close()
|
dbSaveWorker.Close()
|
||||||
file.SaveJSON(config.DB, db)
|
file.SaveJSON(config.DB, db)
|
||||||
in.Close()
|
receiver.Close()
|
||||||
out.Close()
|
notifier.Close()
|
||||||
log.Info("quit of logmania")
|
log.Info("quit of logmania")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
@ -109,17 +106,12 @@ func reload() {
|
||||||
log.Errorf("reload: could not load '%s' for new configuration. Skip reload.", configPath)
|
log.Errorf("reload: could not load '%s' for new configuration. Skip reload.", configPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if config.Debug {
|
receiver.Close()
|
||||||
log.SetLevel(log.DebugLevel)
|
receiver = allReceiver.Init(&config.Receive, logChannel)
|
||||||
} else {
|
go receiver.Listen()
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
}
|
|
||||||
in.Close()
|
|
||||||
in = allInput.Init(config.Input, logChannel)
|
|
||||||
go in.Listen()
|
|
||||||
|
|
||||||
out.Close()
|
notifier.Close()
|
||||||
out = allOutput.Init(config.Output, db, logmaniaBot)
|
notifier = allNotify.Init(&config.Notify, db, logmaniaBot)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
debug = false
|
|
||||||
database = "/tmp/logmania.state.json"
|
|
||||||
|
|
||||||
# have to be mote then a minute
|
|
||||||
alert_check = "5m"
|
|
||||||
|
|
||||||
#########
|
|
||||||
# Input #
|
|
||||||
#########
|
|
||||||
|
|
||||||
[input.syslog]
|
|
||||||
type = "udp"
|
|
||||||
address = ":10001"
|
|
||||||
|
|
||||||
[input.journald_json]
|
|
||||||
type = "udp"
|
|
||||||
address = ":10002"
|
|
||||||
|
|
||||||
##########
|
|
||||||
# Output #
|
|
||||||
##########
|
|
||||||
|
|
||||||
[output.file]
|
|
||||||
directory = "/tmp/"
|
|
||||||
default = "raw"
|
|
||||||
|
|
||||||
[output.xmpp]
|
|
||||||
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 }
|
|
|
@ -89,7 +89,6 @@ func (db *DB) NewHost(addr string) *Host {
|
||||||
h := &Host{
|
h := &Host{
|
||||||
Address: addr,
|
Address: addr,
|
||||||
NotifiesByAddress: make(map[string]*Notify),
|
NotifiesByAddress: make(map[string]*Notify),
|
||||||
Lastseen: time.Now(),
|
|
||||||
}
|
}
|
||||||
db.AddHost(h)
|
db.AddHost(h)
|
||||||
return h
|
return h
|
||||||
|
|
|
@ -4,17 +4,15 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bdlm/log"
|
log "github.com/sirupsen/logrus"
|
||||||
logstd "github.com/bdlm/std/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const AlertMsg = "alert service from logmania, device did not send new message for a while"
|
const AlertMsg = "alert service from logmania, device did not send new message for a while"
|
||||||
|
|
||||||
type DB struct {
|
type DB struct {
|
||||||
// depraced Format -> transformation to new format by db.update()
|
|
||||||
Hostname map[string]string `json:"hostname,omitempty"`
|
Hostname map[string]string `json:"hostname,omitempty"`
|
||||||
HostTo map[string]map[string]bool `json:"host_to,omitempty"`
|
HostTo map[string]map[string]bool `json:"host_to,omitempty"`
|
||||||
MaxPrioIn map[string]logstd.Level `json:"maxLevel,omitempty"`
|
MaxPrioIn map[string]log.Level `json:"maxLevel,omitempty"`
|
||||||
RegexIn map[string]map[string]*regexp.Regexp `json:"regexIn,omitempty"`
|
RegexIn map[string]map[string]*regexp.Regexp `json:"regexIn,omitempty"`
|
||||||
Lastseen map[string]time.Time `json:"lastseen,omitempty"`
|
Lastseen map[string]time.Time `json:"lastseen,omitempty"`
|
||||||
LastseenNotify map[string]time.Time `json:"-"`
|
LastseenNotify map[string]time.Time `json:"-"`
|
||||||
|
@ -24,7 +22,6 @@ type DB struct {
|
||||||
HostsByName map[string]*Host `json:"-"`
|
HostsByName map[string]*Host `json:"-"`
|
||||||
Notifies []*Notify `json:"notifies"`
|
Notifies []*Notify `json:"notifies"`
|
||||||
NotifiesByAddress map[string]*Notify `json:"-"`
|
NotifiesByAddress map[string]*Notify `json:"-"`
|
||||||
DefaultNotify []*Notify `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) SendTo(e *log.Entry) (*log.Entry, *Host, []*Notify) {
|
func (db *DB) SendTo(e *log.Entry) (*log.Entry, *Host, []*Notify) {
|
||||||
|
@ -38,31 +35,33 @@ func (db *DB) SendTo(e *log.Entry) (*log.Entry, *Host, []*Notify) {
|
||||||
host.Lastseen = time.Now()
|
host.Lastseen = time.Now()
|
||||||
}
|
}
|
||||||
var toList []*Notify
|
var toList []*Notify
|
||||||
entry := e
|
for _, notify := range host.NotifiesByAddress {
|
||||||
|
if lvl := notify.MaxPrioIn; e.Level >= lvl {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stopForTo := false
|
||||||
|
for _, expr := range notify.RegexIn {
|
||||||
|
if expr.MatchString(e.Message) {
|
||||||
|
stopForTo = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if stopForTo {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
toList = append(toList, notify)
|
||||||
|
}
|
||||||
if host.Name != "" {
|
if host.Name != "" {
|
||||||
entry = entry.WithField("hostname", host.Name)
|
entry := e.WithField("hostname", host.Name)
|
||||||
entry.Level = e.Level
|
entry.Level = e.Level
|
||||||
entry.Message = e.Message
|
entry.Message = e.Message
|
||||||
}
|
|
||||||
// return default notify list
|
|
||||||
if host.Notifies == nil || len(host.Notifies) == 0 {
|
|
||||||
for _, notify := range db.DefaultNotify {
|
|
||||||
if notify.Send(e) {
|
|
||||||
toList = append(toList, notify)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// return with host specify list
|
|
||||||
for _, notify := range host.NotifiesByAddress {
|
|
||||||
if notify.Send(e) {
|
|
||||||
toList = append(toList, notify)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entry, host, toList
|
return entry, host, toList
|
||||||
}
|
}
|
||||||
|
return e, host, toList
|
||||||
|
} else {
|
||||||
host = db.NewHost(addr)
|
host = db.NewHost(addr)
|
||||||
return e, host, db.DefaultNotify
|
}
|
||||||
|
return e, host, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) Alert(expired time.Duration, send func(e *log.Entry, n *Notify) bool) {
|
func (db *DB) Alert(expired time.Duration, send func(e *log.Entry, n *Notify) bool) {
|
||||||
|
@ -71,10 +70,10 @@ func (db *DB) Alert(expired time.Duration, send func(e *log.Entry, n *Notify) bo
|
||||||
for range c {
|
for range c {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
for _, h := range db.Hosts {
|
for _, h := range db.Hosts {
|
||||||
if h.Lastseen.Before(now.Add(expired * -1)) {
|
if !h.Lastseen.Before(now.Add(expired * -2)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if h.Lastseen.After(h.LastseenNotify) {
|
if h.LastseenNotify.Year() <= 1 && h.Lastseen.Before(h.LastseenNotify) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
h.LastseenNotify = now
|
h.LastseenNotify = now
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
package database
|
|
|
@ -4,8 +4,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bdlm/log"
|
log "github.com/sirupsen/logrus"
|
||||||
logstd "github.com/bdlm/std/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Notify struct {
|
type Notify struct {
|
||||||
|
@ -13,7 +12,7 @@ type Notify struct {
|
||||||
To string `json:"to"`
|
To string `json:"to"`
|
||||||
RegexIn map[string]*regexp.Regexp `json:"regexIn"`
|
RegexIn map[string]*regexp.Regexp `json:"regexIn"`
|
||||||
RegexReplace map[string]string `json:"regexReplace"`
|
RegexReplace map[string]string `json:"regexReplace"`
|
||||||
MaxPrioIn logstd.Level `json:"maxLevel"`
|
MaxPrioIn log.Level `json:"maxLevel"`
|
||||||
regexReplaceExpression map[string]*regexp.Regexp
|
regexReplaceExpression map[string]*regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,18 +67,6 @@ func (n *Notify) Address() string {
|
||||||
return n.Protocol + ":" + n.To
|
return n.Protocol + ":" + n.To
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notify) Send(e *log.Entry) bool {
|
|
||||||
if lvl := n.MaxPrioIn; e.Level >= lvl {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, expr := range n.RegexIn {
|
|
||||||
if expr.MatchString(e.Message) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- global notify
|
// -- global notify
|
||||||
|
|
||||||
func (db *DB) InitNotify() {
|
func (db *DB) InitNotify() {
|
||||||
|
@ -91,6 +78,7 @@ func (db *DB) InitNotify() {
|
||||||
db.NotifiesByAddress[n.Address()] = n
|
db.NotifiesByAddress[n.Address()] = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) AddNotify(n *Notify) {
|
func (db *DB) AddNotify(n *Notify) {
|
||||||
db.Notifies = append(db.Notifies, n)
|
db.Notifies = append(db.Notifies, n)
|
||||||
db.NotifiesByAddress[n.Address()] = n
|
db.NotifiesByAddress[n.Address()] = n
|
||||||
|
@ -98,14 +86,10 @@ func (db *DB) AddNotify(n *Notify) {
|
||||||
|
|
||||||
func (db *DB) NewNotify(to string) *Notify {
|
func (db *DB) NewNotify(to string) *Notify {
|
||||||
addr := strings.Split(to, ":")
|
addr := strings.Split(to, ":")
|
||||||
if len(addr) != 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
n := &Notify{
|
n := &Notify{
|
||||||
Protocol: addr[0],
|
Protocol: addr[0],
|
||||||
To: addr[1],
|
To: addr[1],
|
||||||
RegexIn: make(map[string]*regexp.Regexp),
|
RegexIn: make(map[string]*regexp.Regexp),
|
||||||
MaxPrioIn: log.DebugLevel,
|
|
||||||
}
|
}
|
||||||
db.AddNotify(n)
|
db.AddNotify(n)
|
||||||
return n
|
return n
|
||||||
|
|
|
@ -2,19 +2,19 @@ package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"dev.sum7.eu/genofire/golang-lib/file"
|
"dev.sum7.eu/genofire/golang-lib/file"
|
||||||
"github.com/bdlm/log"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ReadDBFile(path string) *DB {
|
func ReadDBFile(path string) *DB {
|
||||||
var db DB
|
var db DB
|
||||||
|
|
||||||
if err := file.ReadJSON(path, &db); err == nil {
|
if err := file.ReadJSON(path, &db); err == nil {
|
||||||
|
log.Infof("loaded %d hosts", len(db.HostTo))
|
||||||
|
|
||||||
db.InitNotify()
|
db.InitNotify()
|
||||||
db.InitHost()
|
db.InitHost()
|
||||||
// import
|
// import
|
||||||
db.update()
|
db.update()
|
||||||
log.Infof("loaded %d hosts and %d notifies", len(db.Hosts), len(db.Notifies))
|
|
||||||
return &db
|
return &db
|
||||||
} else {
|
} else {
|
||||||
log.Error("failed to open db file: ", path, ":", err)
|
log.Error("failed to open db file: ", path, ":", err)
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
package all
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bdlm/log"
|
|
||||||
|
|
||||||
"dev.sum7.eu/sum7/logmania/input"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Input struct {
|
|
||||||
input.Input
|
|
||||||
list []input.Input
|
|
||||||
}
|
|
||||||
|
|
||||||
func Init(configInterface interface{}, exportChannel chan *log.Entry) input.Input {
|
|
||||||
config := configInterface.(map[string]interface{})
|
|
||||||
|
|
||||||
var list []input.Input
|
|
||||||
for inputType, init := range input.Register {
|
|
||||||
configForItem := config[inputType]
|
|
||||||
if configForItem == nil {
|
|
||||||
log.Warnf("the input type '%s' has no configuration", inputType)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
in := init(configForItem, exportChannel)
|
|
||||||
|
|
||||||
if in == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
list = append(list, in)
|
|
||||||
}
|
|
||||||
return &Input{
|
|
||||||
list: list,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (in *Input) Listen() {
|
|
||||||
for _, item := range in.list {
|
|
||||||
go item.Listen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (in *Input) Close() {
|
|
||||||
for _, item := range in.list {
|
|
||||||
item.Close()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package all
|
|
|
@ -1,6 +0,0 @@
|
||||||
package all
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "dev.sum7.eu/sum7/logmania/input/journald_json"
|
|
||||||
_ "dev.sum7.eu/sum7/logmania/input/syslog"
|
|
||||||
)
|
|
|
@ -1 +0,0 @@
|
||||||
package journald_json
|
|
|
@ -1,77 +0,0 @@
|
||||||
package journald_json
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/bdlm/log"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
|
|
||||||
"dev.sum7.eu/sum7/logmania/input"
|
|
||||||
)
|
|
||||||
|
|
||||||
const inputType = "journald_json"
|
|
||||||
|
|
||||||
var logger = log.WithField("input", inputType)
|
|
||||||
|
|
||||||
type Input struct {
|
|
||||||
input.Input
|
|
||||||
exportChannel chan *log.Entry
|
|
||||||
serverSocket *net.UDPConn
|
|
||||||
}
|
|
||||||
|
|
||||||
type InputConfig struct {
|
|
||||||
Type string `mapstructure:"type"`
|
|
||||||
Address string `mapstructure:"address"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Init(configInterface interface{}, exportChannel chan *log.Entry) input.Input {
|
|
||||||
var config InputConfig
|
|
||||||
if err := mapstructure.Decode(configInterface, &config); err != nil {
|
|
||||||
logger.Warnf("not able to decode data: %s", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
addr, err := net.ResolveUDPAddr(config.Type, config.Address)
|
|
||||||
ln, err := net.ListenUDP(config.Type, addr)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("init ", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
in := &Input{
|
|
||||||
serverSocket: ln,
|
|
||||||
exportChannel: exportChannel,
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("init")
|
|
||||||
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxDataGramSize = 8192
|
|
||||||
|
|
||||||
func (in *Input) Listen() {
|
|
||||||
logger.Info("listen")
|
|
||||||
for {
|
|
||||||
buf := make([]byte, maxDataGramSize)
|
|
||||||
n, src, err := in.serverSocket.ReadFromUDP(buf)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn("failed to accept connection", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
raw := make([]byte, n)
|
|
||||||
copy(raw, buf)
|
|
||||||
entry := toLogEntry(raw, src.IP.String())
|
|
||||||
if entry != nil {
|
|
||||||
in.exportChannel <- entry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (in *Input) Close() {
|
|
||||||
in.serverSocket.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
input.Add(inputType, Init)
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package input
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bdlm/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Register = make(map[string]Init)
|
|
||||||
|
|
||||||
type Input interface {
|
|
||||||
Listen()
|
|
||||||
Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Init func(interface{}, chan *log.Entry) Input
|
|
||||||
|
|
||||||
func Add(name string, init Init) {
|
|
||||||
Register[name] = init
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package input
|
|
|
@ -1,77 +0,0 @@
|
||||||
package syslog
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/bdlm/log"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
|
|
||||||
"dev.sum7.eu/sum7/logmania/input"
|
|
||||||
)
|
|
||||||
|
|
||||||
const inputType = "syslog"
|
|
||||||
|
|
||||||
var logger = log.WithField("input", inputType)
|
|
||||||
|
|
||||||
type Input struct {
|
|
||||||
input.Input
|
|
||||||
exportChannel chan *log.Entry
|
|
||||||
serverSocket *net.UDPConn
|
|
||||||
}
|
|
||||||
|
|
||||||
type InputConfig struct {
|
|
||||||
Type string `mapstructure:"type"`
|
|
||||||
Address string `mapstructure:"address"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Init(configInterface interface{}, exportChannel chan *log.Entry) input.Input {
|
|
||||||
var config InputConfig
|
|
||||||
if err := mapstructure.Decode(configInterface, &config); err != nil {
|
|
||||||
logger.Warnf("not able to decode data: %s", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
addr, err := net.ResolveUDPAddr(config.Type, config.Address)
|
|
||||||
ln, err := net.ListenUDP(config.Type, addr)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("init ", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
in := &Input{
|
|
||||||
serverSocket: ln,
|
|
||||||
exportChannel: exportChannel,
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("init")
|
|
||||||
|
|
||||||
return in
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxDataGramSize = 8192
|
|
||||||
|
|
||||||
func (in *Input) Listen() {
|
|
||||||
logger.Info("listen")
|
|
||||||
for {
|
|
||||||
buf := make([]byte, maxDataGramSize)
|
|
||||||
n, src, err := in.serverSocket.ReadFromUDP(buf)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn("failed to accept connection", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
raw := make([]byte, n)
|
|
||||||
copy(raw, buf)
|
|
||||||
entry := toLogEntry(raw, src.IP.String())
|
|
||||||
if entry != nil {
|
|
||||||
in.exportChannel <- entry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (in *Input) Close() {
|
|
||||||
in.serverSocket.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
input.Add(inputType, Init)
|
|
||||||
}
|
|
|
@ -1,11 +1,33 @@
|
||||||
package lib
|
package lib
|
||||||
|
|
||||||
// Struct of the configuration
|
// Struct of the configuration
|
||||||
// e.g. under dev.sum7.eu/sum7/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"`
|
Notify NotifyConfig `toml:"notify"`
|
||||||
|
Receive ReceiveConfig `toml:"receive"`
|
||||||
DB string `toml:"database"`
|
DB string `toml:"database"`
|
||||||
AlertCheck Duration `toml:"alert_check"`
|
}
|
||||||
Output map[string]interface{} `toml:"output"`
|
|
||||||
Input map[string]interface{} `toml:"input"`
|
type NotifyConfig struct {
|
||||||
|
AlertCheck Duration `toml:"alert_check"`
|
||||||
|
Console bool `toml:"debug"`
|
||||||
|
XMPP struct {
|
||||||
|
JID string `toml:"jid"`
|
||||||
|
Password string `toml:"password"`
|
||||||
|
} `toml:"xmpp"`
|
||||||
|
Websocket struct {
|
||||||
|
Address string `toml:"address"`
|
||||||
|
Webroot string `toml:"webroot"`
|
||||||
|
} `toml:"websocket"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReceiveConfig struct {
|
||||||
|
Syslog struct {
|
||||||
|
Type string `toml:"type"`
|
||||||
|
Address string `toml:"address"`
|
||||||
|
} `toml:"syslog"`
|
||||||
|
JournaldJSON struct {
|
||||||
|
Type string `toml:"type"`
|
||||||
|
Address string `toml:"address"`
|
||||||
|
} `toml:"journald_json"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
package syslog
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
[receive.syslog]
|
||||||
|
type = "udp"
|
||||||
|
address = ":10001"
|
||||||
|
|
||||||
|
[receive.journald_json]
|
||||||
|
type = "udp"
|
||||||
|
address = ":10002"
|
||||||
|
|
||||||
|
[notify]
|
||||||
|
state_file = "/tmp/logmania.state.json"
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[notify.xmpp]
|
||||||
|
jid = "user@example.org"
|
||||||
|
password = "password"
|
||||||
|
|
||||||
|
[notify.websocket]
|
||||||
|
address = ":8080"
|
||||||
|
webroot = "./webroot/"
|
2
main.go
2
main.go
|
@ -1,6 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "dev.sum7.eu/sum7/logmania/cmd"
|
import "dev.sum7.eu/genofire/logmania/cmd"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cmd.Execute()
|
cmd.Execute()
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package all
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"dev.sum7.eu/genofire/logmania/bot"
|
||||||
|
"dev.sum7.eu/genofire/logmania/database"
|
||||||
|
"dev.sum7.eu/genofire/logmania/lib"
|
||||||
|
"dev.sum7.eu/genofire/logmania/notify"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger = log.WithField("notify", "all")
|
||||||
|
|
||||||
|
type Notifier struct {
|
||||||
|
notify.Notifier
|
||||||
|
list []notify.Notifier
|
||||||
|
db *database.DB
|
||||||
|
channelNotify chan *log.Entry
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(config *lib.NotifyConfig, db *database.DB, bot *bot.Bot) notify.Notifier {
|
||||||
|
var list []notify.Notifier
|
||||||
|
for _, init := range notify.NotifyRegister {
|
||||||
|
notify := init(config, db, bot)
|
||||||
|
|
||||||
|
if notify == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
list = append(list, notify)
|
||||||
|
}
|
||||||
|
|
||||||
|
n := &Notifier{
|
||||||
|
db: db,
|
||||||
|
list: list,
|
||||||
|
channelNotify: make(chan *log.Entry),
|
||||||
|
}
|
||||||
|
go n.sender()
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Notifier) sender() {
|
||||||
|
for c := range n.channelNotify {
|
||||||
|
e, _, tos := n.db.SendTo(c)
|
||||||
|
for _, to := range tos {
|
||||||
|
send := false
|
||||||
|
for _, item := range n.list {
|
||||||
|
send = item.Send(e, to)
|
||||||
|
if send {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !send {
|
||||||
|
logger.Warn("notify not send to anybody: [%s] %s", c.Level.String(), c.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Notifier) Send(e *log.Entry, to *database.Notify) bool {
|
||||||
|
n.channelNotify <- e
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Notifier) Close() {
|
||||||
|
for _, item := range n.list {
|
||||||
|
item.Close()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package all
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "dev.sum7.eu/genofire/logmania/notify/websocket"
|
||||||
|
_ "dev.sum7.eu/genofire/logmania/notify/xmpp"
|
||||||
|
)
|
|
@ -0,0 +1,22 @@
|
||||||
|
package notify
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"dev.sum7.eu/genofire/logmania/bot"
|
||||||
|
"dev.sum7.eu/genofire/logmania/database"
|
||||||
|
"dev.sum7.eu/genofire/logmania/lib"
|
||||||
|
)
|
||||||
|
|
||||||
|
var NotifyRegister []NotifyInit
|
||||||
|
|
||||||
|
type Notifier interface {
|
||||||
|
Send(entry *log.Entry, to *database.Notify) bool
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotifyInit func(*lib.NotifyConfig, *database.DB, *bot.Bot) Notifier
|
||||||
|
|
||||||
|
func AddNotifier(n NotifyInit) {
|
||||||
|
NotifyRegister = append(NotifyRegister, n)
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"dev.sum7.eu/genofire/golang-lib/websocket"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"dev.sum7.eu/genofire/logmania/bot"
|
||||||
|
"dev.sum7.eu/genofire/logmania/database"
|
||||||
|
"dev.sum7.eu/genofire/logmania/lib"
|
||||||
|
"dev.sum7.eu/genofire/logmania/notify"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
proto = "ws"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger = log.WithField("notify", proto)
|
||||||
|
|
||||||
|
type Notifier struct {
|
||||||
|
notify.Notifier
|
||||||
|
ws *websocket.Server
|
||||||
|
formatter log.Formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(config *lib.NotifyConfig, db *database.DB, bot *bot.Bot) notify.Notifier {
|
||||||
|
inputMSG := make(chan *websocket.Message)
|
||||||
|
ws := websocket.NewServer(inputMSG, nil)
|
||||||
|
|
||||||
|
http.HandleFunc("/ws", ws.Handler)
|
||||||
|
http.Handle("/", http.FileServer(http.Dir(config.Websocket.Webroot)))
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for msg := range inputMSG {
|
||||||
|
if msg.Subject != "bot" {
|
||||||
|
logger.Warnf("receive unknown websocket message: %s", msg.Subject)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bot.Handle(func(answer string) {
|
||||||
|
msg.Answer("bot", answer)
|
||||||
|
}, "", msg.Body.(string))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: config.Websocket.Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := srv.ListenAndServe(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger.Info("startup")
|
||||||
|
return &Notifier{
|
||||||
|
ws: ws,
|
||||||
|
formatter: &log.TextFormatter{
|
||||||
|
DisableTimestamp: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Notifier) Send(e *log.Entry, to *database.Notify) bool {
|
||||||
|
if to.Protocol != proto {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
n.ws.SendAll(&websocket.Message{
|
||||||
|
Subject: to.Address(),
|
||||||
|
Body: e,
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Notifier) Close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
notify.AddNotifier(Init)
|
||||||
|
}
|
|
@ -0,0 +1,202 @@
|
||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
xmpp_client "dev.sum7.eu/genofire/yaja/client"
|
||||||
|
xmpp "dev.sum7.eu/genofire/yaja/xmpp"
|
||||||
|
"dev.sum7.eu/genofire/yaja/xmpp/base"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"dev.sum7.eu/genofire/logmania/bot"
|
||||||
|
"dev.sum7.eu/genofire/logmania/database"
|
||||||
|
"dev.sum7.eu/genofire/logmania/lib"
|
||||||
|
"dev.sum7.eu/genofire/logmania/notify"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
proto = "xmpp"
|
||||||
|
protoGroup = "xmpp-muc"
|
||||||
|
nickname = "logmania"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger = log.WithField("notify", proto)
|
||||||
|
|
||||||
|
type Notifier struct {
|
||||||
|
notify.Notifier
|
||||||
|
client *xmpp_client.Client
|
||||||
|
channels map[string]bool
|
||||||
|
formatter log.Formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(config *lib.NotifyConfig, db *database.DB, bot *bot.Bot) notify.Notifier {
|
||||||
|
channels := make(map[string]bool)
|
||||||
|
|
||||||
|
client, err := xmpp_client.NewClient(xmppbase.NewJID(config.XMPP.JID), config.XMPP.Password)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
if err := client.Start(); err != nil {
|
||||||
|
log.Warn("close connection, try reconnect")
|
||||||
|
client.Connect(config.XMPP.Password)
|
||||||
|
} else {
|
||||||
|
log.Warn("closed connection")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
element, more := client.Recv()
|
||||||
|
if !more {
|
||||||
|
log.Warn("could not receive new message, try later")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch element.(type) {
|
||||||
|
case *xmpp.PresenceClient:
|
||||||
|
pres := element.(*xmpp.PresenceClient)
|
||||||
|
sender := pres.From
|
||||||
|
logPres := logger.WithField("from", sender.Full())
|
||||||
|
switch pres.Type {
|
||||||
|
case xmpp.PresenceTypeSubscribe:
|
||||||
|
logPres.Debugf("recv presence subscribe")
|
||||||
|
pres.Type = xmpp.PresenceTypeSubscribed
|
||||||
|
pres.To = sender
|
||||||
|
pres.From = nil
|
||||||
|
client.Send(pres)
|
||||||
|
logPres.Debugf("accept new subscribe")
|
||||||
|
|
||||||
|
pres.Type = xmpp.PresenceTypeSubscribe
|
||||||
|
pres.ID = ""
|
||||||
|
client.Send(pres)
|
||||||
|
logPres.Info("request also subscribe")
|
||||||
|
case xmpp.PresenceTypeSubscribed:
|
||||||
|
logPres.Info("recv presence accepted subscribe")
|
||||||
|
case xmpp.PresenceTypeUnsubscribe:
|
||||||
|
logPres.Info("recv presence remove subscribe")
|
||||||
|
case xmpp.PresenceTypeUnsubscribed:
|
||||||
|
logPres.Info("recv presence removed subscribe")
|
||||||
|
case xmpp.PresenceTypeUnavailable:
|
||||||
|
logPres.Debug("recv presence unavailable")
|
||||||
|
case "":
|
||||||
|
logPres.Debug("recv empty presence, maybe from joining muc")
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
logPres.Warnf("recv presence unsupported: %s -> %s", pres.Type, xmpp.XMLChildrenString(pres))
|
||||||
|
}
|
||||||
|
case *xmpp.MessageClient:
|
||||||
|
msg := element.(*xmpp.MessageClient)
|
||||||
|
from := msg.From.Bare().String()
|
||||||
|
if msg.Type == xmpp.MessageTypeGroupchat {
|
||||||
|
from = protoGroup + ":" + from
|
||||||
|
} else {
|
||||||
|
from = proto + ":" + from
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.Handle(func(answer string) {
|
||||||
|
to := msg.From
|
||||||
|
if msg.Type == xmpp.MessageTypeGroupchat && !to.IsBare() {
|
||||||
|
to = to.Bare()
|
||||||
|
}
|
||||||
|
err := client.Send(&xmpp.MessageClient{
|
||||||
|
Type: msg.Type,
|
||||||
|
To: to,
|
||||||
|
Body: answer,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("xmpp to ", msg.From.String(), " error:", err)
|
||||||
|
}
|
||||||
|
}, from, msg.Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for toAddr, toAddresses := range db.NotifiesByAddress {
|
||||||
|
if toAddresses.Protocol == protoGroup {
|
||||||
|
toJID := xmppbase.NewJID(toAddresses.To)
|
||||||
|
toJID.Resource = nickname
|
||||||
|
err := client.Send(&xmpp.PresenceClient{
|
||||||
|
To: toJID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("xmpp could not join ", toJID.String(), " error:", err)
|
||||||
|
} else {
|
||||||
|
channels[toAddr] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Info("startup")
|
||||||
|
return &Notifier{
|
||||||
|
channels: channels,
|
||||||
|
client: client,
|
||||||
|
formatter: &log.TextFormatter{
|
||||||
|
DisableTimestamp: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Notifier) Send(e *log.Entry, to *database.Notify) bool {
|
||||||
|
textByte, err := n.formatter.Format(e)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("during format notify", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
text := strings.TrimRight(to.RegexReplace(string(textByte)), "\n")
|
||||||
|
if to.Protocol == protoGroup {
|
||||||
|
if _, ok := n.channels[to.To]; ok {
|
||||||
|
toJID := xmppbase.NewJID(to.To)
|
||||||
|
toJID.Resource = nickname
|
||||||
|
err := n.client.Send(&xmpp.PresenceClient{
|
||||||
|
To: toJID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("xmpp could not join ", toJID.String(), " error:", err)
|
||||||
|
} else {
|
||||||
|
n.channels[to.To] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := n.client.Send(&xmpp.MessageClient{
|
||||||
|
Type: xmpp.MessageTypeGroupchat,
|
||||||
|
To: xmppbase.NewJID(to.To),
|
||||||
|
Body: text,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("xmpp to ", to.To, " error:", err)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if to.Protocol == proto {
|
||||||
|
err := n.client.Send(&xmpp.MessageClient{
|
||||||
|
Type: xmpp.MessageTypeChat,
|
||||||
|
To: xmppbase.NewJID(to.To),
|
||||||
|
Body: text,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("xmpp to ", to, " error:", err)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Notifier) Close() {
|
||||||
|
for jid := range n.channels {
|
||||||
|
toJID := xmppbase.NewJID(jid)
|
||||||
|
toJID.Resource = nickname
|
||||||
|
err := n.client.Send(&xmpp.PresenceClient{
|
||||||
|
To: toJID,
|
||||||
|
Type: xmpp.PresenceTypeUnavailable,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("xmpp could not leave ", toJID.String(), " error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.client.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
notify.AddNotifier(Init)
|
||||||
|
}
|
|
@ -1,93 +0,0 @@
|
||||||
package all
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bdlm/log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"dev.sum7.eu/sum7/logmania/bot"
|
|
||||||
"dev.sum7.eu/sum7/logmania/database"
|
|
||||||
"dev.sum7.eu/sum7/logmania/output"
|
|
||||||
)
|
|
||||||
|
|
||||||
var logger = log.WithField("notify", "all")
|
|
||||||
|
|
||||||
type Output struct {
|
|
||||||
output.Output
|
|
||||||
list []output.Output
|
|
||||||
db *database.DB
|
|
||||||
channelNotify chan *log.Entry
|
|
||||||
}
|
|
||||||
|
|
||||||
func Init(configInterface interface{}, db *database.DB, bot *bot.Bot) output.Output {
|
|
||||||
config := configInterface.(map[string]interface{})
|
|
||||||
|
|
||||||
var list []output.Output
|
|
||||||
|
|
||||||
for outputType, init := range output.Register {
|
|
||||||
configForItem := config[outputType]
|
|
||||||
if configForItem == nil {
|
|
||||||
log.Warnf("the input type '%s' has no configuration\n", outputType)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
notify := init(configForItem, db, bot)
|
|
||||||
|
|
||||||
if notify == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
list = append(list, notify)
|
|
||||||
def := notify.Default()
|
|
||||||
if def == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
db.DefaultNotify = append(db.DefaultNotify, def...)
|
|
||||||
}
|
|
||||||
|
|
||||||
out := &Output{
|
|
||||||
db: db,
|
|
||||||
list: list,
|
|
||||||
channelNotify: make(chan *log.Entry),
|
|
||||||
}
|
|
||||||
go out.sender()
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (out *Output) sender() {
|
|
||||||
for c := range out.channelNotify {
|
|
||||||
e, _, tos := out.db.SendTo(c)
|
|
||||||
for _, to := range tos {
|
|
||||||
send := false
|
|
||||||
for _, item := range out.list {
|
|
||||||
send = item.Send(e, to)
|
|
||||||
if send {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !send {
|
|
||||||
logger.Warnf("notify not send to %s: [%d] %s", to.Address(), c.Level, c.Message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (out *Output) Send(e *log.Entry, to *database.Notify) bool {
|
|
||||||
before := time.Now()
|
|
||||||
|
|
||||||
logger := log.WithFields(e.Data)
|
|
||||||
logger = logger.WithField("msg", e.Message)
|
|
||||||
|
|
||||||
logger.Debugf("starting forward message")
|
|
||||||
|
|
||||||
out.channelNotify <- e
|
|
||||||
|
|
||||||
after := time.Now()
|
|
||||||
delta := after.Sub(before)
|
|
||||||
logger.WithField("ms", float64(delta)/float64(time.Millisecond)).Debugf("end forward message")
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (out *Output) Close() {
|
|
||||||
for _, item := range out.list {
|
|
||||||
item.Close()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package all
|
|
|
@ -1,6 +0,0 @@
|
||||||
package all
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "dev.sum7.eu/sum7/logmania/output/file"
|
|
||||||
_ "dev.sum7.eu/sum7/logmania/output/xmpp"
|
|
||||||
)
|
|
|
@ -1,107 +0,0 @@
|
||||||
package file
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/bdlm/log"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
|
|
||||||
"dev.sum7.eu/sum7/logmania/bot"
|
|
||||||
"dev.sum7.eu/sum7/logmania/database"
|
|
||||||
"dev.sum7.eu/sum7/logmania/output"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
proto = "file"
|
|
||||||
)
|
|
||||||
|
|
||||||
var logger = log.WithField("output", proto)
|
|
||||||
|
|
||||||
type Output struct {
|
|
||||||
output.Output
|
|
||||||
defaults []*database.Notify
|
|
||||||
files map[string]*os.File
|
|
||||||
formatter log.Formatter
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutputConfig struct {
|
|
||||||
Directory string `mapstructure:"directory"`
|
|
||||||
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
|
|
||||||
}
|
|
||||||
if config.Directory == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
logger.WithField("directory", config.Directory).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,
|
|
||||||
files: make(map[string]*os.File),
|
|
||||||
formatter: &log.JSONFormatter{
|
|
||||||
DisableCaller: true,
|
|
||||||
},
|
|
||||||
path: config.Directory,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (out *Output) Default() []*database.Notify {
|
|
||||||
return out.defaults
|
|
||||||
}
|
|
||||||
|
|
||||||
func (out *Output) getFile(name string) *os.File {
|
|
||||||
if file, ok := out.files[name]; ok {
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
if m, err := regexp.MatchString(`^[0-9A-Za-z_-]*$`, name); err != nil || !m {
|
|
||||||
logger.Errorf("not allowed to use '%s:%s'", proto, name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
filename := path.Join(out.path, name+".json")
|
|
||||||
file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("could not open file: %s", err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out.files[name] = file
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
|
|
||||||
func (out *Output) Send(e *log.Entry, to *database.Notify) bool {
|
|
||||||
if to.Protocol != proto {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
byteText, err := out.formatter.Format(e)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
text := to.RunReplace(string(byteText))
|
|
||||||
file := out.getFile(to.To)
|
|
||||||
if file == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_, err = file.WriteString(text)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
output.Add(proto, Init)
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package file
|
|
|
@ -1,22 +0,0 @@
|
||||||
package output
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bdlm/log"
|
|
||||||
|
|
||||||
"dev.sum7.eu/sum7/logmania/bot"
|
|
||||||
"dev.sum7.eu/sum7/logmania/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Register = make(map[string]Init)
|
|
||||||
|
|
||||||
type Output interface {
|
|
||||||
Default() []*database.Notify
|
|
||||||
Send(entry *log.Entry, to *database.Notify) bool
|
|
||||||
Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Init func(interface{}, *database.DB, *bot.Bot) Output
|
|
||||||
|
|
||||||
func Add(name string, init Init) {
|
|
||||||
Register[name] = init
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package output
|
|
|
@ -1,126 +0,0 @@
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/bdlm/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var tempLog = template.Must(template.New("log").Parse(
|
|
||||||
"{{$color := .Color}}<span>" +
|
|
||||||
// Hostname
|
|
||||||
"{{ if .Hostname }}<span style=\"color: {{ $color.Hostname }};\">{{ .Hostname}}</span>{{ end }}" +
|
|
||||||
// Level
|
|
||||||
"<span style=\"font-weight: bold; color: {{$color.Level}};\">{{printf \" %5s\" .Level}}</span>" +
|
|
||||||
// Message
|
|
||||||
"{{printf \" %s\" .Message}}" +
|
|
||||||
// Data
|
|
||||||
"{{if .Data}}{{range $k, $v := .Data}}" +
|
|
||||||
"<span style=\"color: {{$color.DataLabel}};\">{{printf \" %s\" $k}}</span>" +
|
|
||||||
"=" +
|
|
||||||
"<span style=\"color: {{$color.DataValue}};\">{{$v}}</span>" +
|
|
||||||
"{{end}}{{end}}" +
|
|
||||||
"</span>",
|
|
||||||
))
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DEFAULTColor is the default html 'level' color.
|
|
||||||
DEFAULTColor = "#00ff00"
|
|
||||||
// ERRORColor is the html 'level' color for error messages.
|
|
||||||
ERRORColor = "#ff8700"
|
|
||||||
// FATALColor is the html 'level' color for fatal messages.
|
|
||||||
FATALColor = "#af0000"
|
|
||||||
// PANICColor is the html 'level' color for panic messages.
|
|
||||||
PANICColor = "#ff0000"
|
|
||||||
// WARNColor is the html 'level' color for warning messages.
|
|
||||||
WARNColor = "#ffff00"
|
|
||||||
// DEBUGColor is the html 'level' color for debug messages.
|
|
||||||
DEBUGColor = "#8a8a8a"
|
|
||||||
|
|
||||||
// DataLabelColor is the html data label color.
|
|
||||||
DataLabelColor = "#87afff"
|
|
||||||
// DataValueColor is the html data value color.
|
|
||||||
DataValueColor = "#d7af87"
|
|
||||||
// HostnameColor is the html hostname color.
|
|
||||||
HostnameColor = "#00afff"
|
|
||||||
// TimestampColor is the html timestamp color.
|
|
||||||
TimestampColor = "#5faf87"
|
|
||||||
)
|
|
||||||
|
|
||||||
type logData struct {
|
|
||||||
Color colors `json:"-"`
|
|
||||||
Data map[string]interface{} `json:"data,omitempty"`
|
|
||||||
Hostname string `json:"host,omitempty"`
|
|
||||||
Level string `json:"level,omitempty"`
|
|
||||||
Message string `json:"msg,omitempty"`
|
|
||||||
Timestamp string `json:"time,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type colors struct {
|
|
||||||
DataLabel string
|
|
||||||
DataValue string
|
|
||||||
Hostname string
|
|
||||||
Level string
|
|
||||||
Reset string
|
|
||||||
Timestamp string
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatLog(entry *log.Entry) (string, string) {
|
|
||||||
var levelColor string
|
|
||||||
|
|
||||||
var logLine *bytes.Buffer
|
|
||||||
if entry.Buffer != nil {
|
|
||||||
logLine = entry.Buffer
|
|
||||||
} else {
|
|
||||||
logLine = &bytes.Buffer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
data := &logData{
|
|
||||||
Data: make(map[string]interface{}),
|
|
||||||
Level: log.LevelString(entry.Level),
|
|
||||||
Message: entry.Message,
|
|
||||||
Timestamp: entry.Time.Format(log.RFC3339Milli),
|
|
||||||
}
|
|
||||||
switch entry.Level {
|
|
||||||
case log.DebugLevel:
|
|
||||||
levelColor = DEBUGColor
|
|
||||||
case log.WarnLevel:
|
|
||||||
levelColor = WARNColor
|
|
||||||
case log.ErrorLevel:
|
|
||||||
levelColor = ERRORColor
|
|
||||||
case log.FatalLevel:
|
|
||||||
levelColor = FATALColor
|
|
||||||
case log.PanicLevel:
|
|
||||||
levelColor = PANICColor
|
|
||||||
default:
|
|
||||||
levelColor = DEFAULTColor
|
|
||||||
}
|
|
||||||
data.Color = colors{
|
|
||||||
DataLabel: DataLabelColor,
|
|
||||||
DataValue: DataValueColor,
|
|
||||||
Hostname: HostnameColor,
|
|
||||||
Level: levelColor,
|
|
||||||
Timestamp: TimestampColor,
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
if k == "hostname" {
|
|
||||||
if data.Hostname == "" {
|
|
||||||
data.Hostname = v.(string)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if str, ok := v.(string); ok {
|
|
||||||
data.Data[k] = "'" + str + "'"
|
|
||||||
} else {
|
|
||||||
data.Data[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tempLog.Execute(logLine, data); err != nil {
|
|
||||||
return "formating error", "formating error"
|
|
||||||
}
|
|
||||||
return logLine.String(), fmt.Sprintf("[%s] %s > %s", data.Hostname, log.LevelString(entry.Level), entry.Message)
|
|
||||||
}
|
|
|
@ -1,121 +0,0 @@
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/bdlm/log"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"gosrc.io/xmpp"
|
|
||||||
"gosrc.io/xmpp/stanza"
|
|
||||||
|
|
||||||
"dev.sum7.eu/sum7/logmania/bot"
|
|
||||||
"dev.sum7.eu/sum7/logmania/database"
|
|
||||||
"dev.sum7.eu/sum7/logmania/output"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
proto = "xmpp"
|
|
||||||
protoGroup = "xmpp-muc"
|
|
||||||
nickname = "logmania"
|
|
||||||
)
|
|
||||||
|
|
||||||
var logger = log.WithField("output", proto)
|
|
||||||
|
|
||||||
type Output struct {
|
|
||||||
output.Output
|
|
||||||
defaults []*database.Notify
|
|
||||||
channels map[string]bool
|
|
||||||
bot *bot.Bot
|
|
||||||
client xmpp.Sender
|
|
||||||
botOut chan interface{}
|
|
||||||
logOut chan interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutputConfig struct {
|
|
||||||
Address string `mapstructure:"address"`
|
|
||||||
JID string `mapstructure:"jid"`
|
|
||||||
Password string `mapstructure:"password"`
|
|
||||||
Defaults map[string]bool `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
|
|
||||||
}
|
|
||||||
|
|
||||||
out := &Output{
|
|
||||||
channels: make(map[string]bool),
|
|
||||||
bot: bot,
|
|
||||||
}
|
|
||||||
|
|
||||||
router := xmpp.NewRouter()
|
|
||||||
router.HandleFunc("message", out.recvMessage)
|
|
||||||
router.HandleFunc("presence", out.recvPresence)
|
|
||||||
|
|
||||||
client, err := xmpp.NewClient(xmpp.Config{
|
|
||||||
Address: config.Address,
|
|
||||||
Jid: config.JID,
|
|
||||||
Password: config.Password,
|
|
||||||
}, router)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cm := xmpp.NewStreamManager(client, func(c xmpp.Sender) {
|
|
||||||
out.client = c
|
|
||||||
|
|
||||||
for to, muc := range config.Defaults {
|
|
||||||
def := &database.Notify{
|
|
||||||
Protocol: proto,
|
|
||||||
To: to,
|
|
||||||
RegexIn: make(map[string]*regexp.Regexp),
|
|
||||||
MaxPrioIn: log.DebugLevel,
|
|
||||||
}
|
|
||||||
if muc {
|
|
||||||
def.Protocol = protoGroup
|
|
||||||
out.Join(to)
|
|
||||||
}
|
|
||||||
out.defaults = append(out.defaults, def)
|
|
||||||
}
|
|
||||||
for _, toAddresses := range db.NotifiesByAddress {
|
|
||||||
if toAddresses.Protocol == protoGroup {
|
|
||||||
out.Join(toAddresses.To)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.Info("join muc after connect")
|
|
||||||
})
|
|
||||||
go func() {
|
|
||||||
cm.Run()
|
|
||||||
log.Panic("closed connection")
|
|
||||||
}()
|
|
||||||
|
|
||||||
logger.WithField("jid", config.JID).Info("startup")
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (out *Output) Default() []*database.Notify {
|
|
||||||
return out.defaults
|
|
||||||
}
|
|
||||||
|
|
||||||
func (out *Output) Close() {
|
|
||||||
for jid := range out.channels {
|
|
||||||
toJID, err := xmpp.NewJid(jid)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("xmpp could generate jid to leave ", jid, " error:", err)
|
|
||||||
}
|
|
||||||
toJID.Resource = nickname
|
|
||||||
if err = out.client.Send(stanza.Presence{Attrs: stanza.Attrs{
|
|
||||||
To: toJID.Full(),
|
|
||||||
Type: stanza.PresenceTypeUnavailable,
|
|
||||||
}}); err != nil {
|
|
||||||
logger.Error("xmpp could not leave ", toJID.Full(), " error:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
output.Add(proto, Init)
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package xmpp
|
|
|
@ -1,109 +0,0 @@
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gosrc.io/xmpp"
|
|
||||||
"gosrc.io/xmpp/stanza"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (out *Output) recvMessage(s xmpp.Sender, p stanza.Packet) {
|
|
||||||
before := time.Now()
|
|
||||||
|
|
||||||
msg, ok := p.(stanza.Message)
|
|
||||||
if !ok {
|
|
||||||
logger.Errorf("blame gosrc.io/xmpp for routing: %s", p)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.WithFields(map[string]interface{}{
|
|
||||||
"sender": msg.From,
|
|
||||||
"request": msg.Body,
|
|
||||||
}).Debug("handling bot message")
|
|
||||||
|
|
||||||
from, err := xmpp.NewJid(msg.From)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("blame gosrc.io/xmpp for jid encoding: %s", msg.From)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fromBare := from.Bare()
|
|
||||||
fromLogmania := ""
|
|
||||||
if msg.Type == stanza.MessageTypeGroupchat {
|
|
||||||
fromLogmania = protoGroup + ":" + fromBare
|
|
||||||
} else {
|
|
||||||
fromLogmania = proto + ":" + fromBare
|
|
||||||
}
|
|
||||||
|
|
||||||
answer := out.bot.Handle(fromLogmania, msg.Body)
|
|
||||||
if answer == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := s.Send(stanza.Message{Attrs: stanza.Attrs{To: fromBare, Type: msg.Type}, Body: answer}); err != nil {
|
|
||||||
logger.WithFields(map[string]interface{}{
|
|
||||||
"sender": fromLogmania,
|
|
||||||
"request": msg.Body,
|
|
||||||
"answer": answer,
|
|
||||||
}).Errorf("unable to send bot answer: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
after := time.Now()
|
|
||||||
delta := after.Sub(before)
|
|
||||||
|
|
||||||
logger.WithFields(map[string]interface{}{
|
|
||||||
"sender": fromLogmania,
|
|
||||||
"request": msg.Body,
|
|
||||||
"answer": answer,
|
|
||||||
"ms": float64(delta) / float64(time.Millisecond),
|
|
||||||
}).Debug("handled xmpp bot message")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (out *Output) recvPresence(s xmpp.Sender, p stanza.Packet) {
|
|
||||||
pres, ok := p.(stanza.Presence)
|
|
||||||
if !ok {
|
|
||||||
logger.Errorf("blame gosrc.io/xmpp for routing: %s", p)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
from, err := xmpp.NewJid(pres.From)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("blame gosrc.io/xmpp for jid encoding: %s", pres.From)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fromBare := from.Bare()
|
|
||||||
logPres := logger.WithField("from", from)
|
|
||||||
|
|
||||||
switch pres.Type {
|
|
||||||
case stanza.PresenceTypeSubscribe:
|
|
||||||
logPres.Debugf("recv presence subscribe")
|
|
||||||
if err := s.Send(stanza.Presence{Attrs: stanza.Attrs{
|
|
||||||
Type: stanza.PresenceTypeSubscribed,
|
|
||||||
To: fromBare,
|
|
||||||
Id: pres.Id,
|
|
||||||
}}); err != nil {
|
|
||||||
logPres.WithField("user", pres.From).Errorf("answer of subscribe not send: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logPres.Debugf("accept new subscribe")
|
|
||||||
|
|
||||||
if err := s.Send(stanza.Presence{Attrs: stanza.Attrs{
|
|
||||||
Type: stanza.PresenceTypeSubscribe,
|
|
||||||
To: fromBare,
|
|
||||||
}}); err != nil {
|
|
||||||
logPres.WithField("user", pres.From).Errorf("request of subscribe not send: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logPres.Info("request also subscribe")
|
|
||||||
case stanza.PresenceTypeSubscribed:
|
|
||||||
logPres.Info("recv presence accepted subscribe")
|
|
||||||
case stanza.PresenceTypeUnsubscribe:
|
|
||||||
logPres.Info("recv presence remove subscribe")
|
|
||||||
case stanza.PresenceTypeUnsubscribed:
|
|
||||||
logPres.Info("recv presence removed subscribe")
|
|
||||||
case stanza.PresenceTypeUnavailable:
|
|
||||||
logPres.Debug("recv presence unavailable")
|
|
||||||
case "":
|
|
||||||
logPres.Debug("recv empty presence, maybe from joining muc")
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
logPres.Warnf("recv presence unsupported: %s -> %v", pres.Type, pres)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/bdlm/log"
|
|
||||||
"gosrc.io/xmpp"
|
|
||||||
"gosrc.io/xmpp/stanza"
|
|
||||||
|
|
||||||
"dev.sum7.eu/sum7/logmania/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (out *Output) Join(to string) {
|
|
||||||
toJID, err := xmpp.NewJid(to)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("jid not generate to join muc %s : %s", to, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
toJID.Resource = nickname
|
|
||||||
|
|
||||||
if err = out.client.Send(stanza.Presence{Attrs: stanza.Attrs{To: toJID.Full()},
|
|
||||||
Extensions: []stanza.PresExtension{
|
|
||||||
stanza.MucPresence{
|
|
||||||
History: stanza.History{MaxStanzas: stanza.NewNullableInt(0)},
|
|
||||||
}},
|
|
||||||
}); err != nil {
|
|
||||||
logger.Errorf("muc not join %s : %s", toJID.Full(), err)
|
|
||||||
} else {
|
|
||||||
out.channels[to] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (out *Output) Send(e *log.Entry, to *database.Notify) bool {
|
|
||||||
if out.client == nil {
|
|
||||||
logger.Error("xmpp not connected (yet)")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
html, text := formatLog(e)
|
|
||||||
if html == "" || text == "" {
|
|
||||||
logger.Error("during format notify")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
html = strings.TrimRight(to.RunReplace(html), "\n")
|
|
||||||
text = strings.TrimRight(to.RunReplace(text), "\n")
|
|
||||||
|
|
||||||
msg := stanza.Message{
|
|
||||||
Attrs: stanza.Attrs{
|
|
||||||
To: to.To,
|
|
||||||
},
|
|
||||||
Body: text,
|
|
||||||
Extensions: []stanza.MsgExtension{
|
|
||||||
stanza.HTML{Body: stanza.HTMLBody{InnerXML: html}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if to.Protocol == protoGroup {
|
|
||||||
if _, ok := out.channels[to.To]; ok {
|
|
||||||
out.Join(to.To)
|
|
||||||
}
|
|
||||||
msg.Type = stanza.MessageTypeGroupchat
|
|
||||||
if err := out.client.Send(msg); err != nil {
|
|
||||||
logger.WithFields(map[string]interface{}{
|
|
||||||
"muc": to.To,
|
|
||||||
"text": text,
|
|
||||||
}).Errorf("log message not forwarded: %s", err)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if to.Protocol == proto {
|
|
||||||
msg.Type = stanza.MessageTypeChat
|
|
||||||
if err := out.client.Send(msg); err != nil {
|
|
||||||
logger.WithFields(map[string]interface{}{
|
|
||||||
"user": to.To,
|
|
||||||
"text": text,
|
|
||||||
}).Errorf("log message not forwarded: %s", err)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package all
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"dev.sum7.eu/genofire/logmania/lib"
|
||||||
|
"dev.sum7.eu/genofire/logmania/receive"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Receiver struct {
|
||||||
|
receive.Receiver
|
||||||
|
list []receive.Receiver
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(config *lib.ReceiveConfig, exportChannel chan *log.Entry) receive.Receiver {
|
||||||
|
var list []receive.Receiver
|
||||||
|
for _, init := range receive.Register {
|
||||||
|
receiver := init(config, exportChannel)
|
||||||
|
|
||||||
|
if receiver == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
list = append(list, receiver)
|
||||||
|
}
|
||||||
|
return &Receiver{
|
||||||
|
list: list,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Receiver) Listen() {
|
||||||
|
for _, item := range r.list {
|
||||||
|
go item.Listen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Receiver) Close() {
|
||||||
|
for _, item := range r.list {
|
||||||
|
item.Close()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package all
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "dev.sum7.eu/genofire/logmania/receive/journald_json"
|
||||||
|
_ "dev.sum7.eu/genofire/logmania/receive/logrus"
|
||||||
|
_ "dev.sum7.eu/genofire/logmania/receive/syslog"
|
||||||
|
)
|
|
@ -4,8 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/bdlm/log"
|
log "github.com/sirupsen/logrus"
|
||||||
logstd "github.com/bdlm/std/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type JournalMessage struct {
|
type JournalMessage struct {
|
||||||
|
@ -35,7 +34,7 @@ type JournalMessage struct {
|
||||||
Message string `json:"MESSAGE"`
|
Message string `json:"MESSAGE"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var PriorityMap = map[int]logstd.Level{
|
var PriorityMap = map[int]log.Level{
|
||||||
0: log.PanicLevel, // emerg
|
0: log.PanicLevel, // emerg
|
||||||
1: log.PanicLevel, // alert
|
1: log.PanicLevel, // alert
|
||||||
2: log.PanicLevel, // crit
|
2: log.PanicLevel, // crit
|
|
@ -0,0 +1,65 @@
|
||||||
|
package journald_json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"dev.sum7.eu/genofire/logmania/lib"
|
||||||
|
"dev.sum7.eu/genofire/logmania/receive"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger = log.WithField("receive", "journald_json")
|
||||||
|
|
||||||
|
type Receiver struct {
|
||||||
|
receive.Receiver
|
||||||
|
exportChannel chan *log.Entry
|
||||||
|
serverSocket *net.UDPConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(config *lib.ReceiveConfig, exportChannel chan *log.Entry) receive.Receiver {
|
||||||
|
addr, err := net.ResolveUDPAddr(config.JournaldJSON.Type, config.JournaldJSON.Address)
|
||||||
|
ln, err := net.ListenUDP(config.JournaldJSON.Type, addr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("init ", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
recv := &Receiver{
|
||||||
|
serverSocket: ln,
|
||||||
|
exportChannel: exportChannel,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("init")
|
||||||
|
|
||||||
|
return recv
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxDataGramSize = 8192
|
||||||
|
|
||||||
|
func (rc *Receiver) Listen() {
|
||||||
|
logger.Info("listen")
|
||||||
|
for {
|
||||||
|
buf := make([]byte, maxDataGramSize)
|
||||||
|
n, src, err := rc.serverSocket.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("failed to accept connection", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := make([]byte, n)
|
||||||
|
copy(raw, buf)
|
||||||
|
entry := toLogEntry(raw, src.IP.String())
|
||||||
|
if entry != nil {
|
||||||
|
rc.exportChannel <- entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *Receiver) Close() {
|
||||||
|
rc.serverSocket.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
receive.AddReceiver("journald_json", Init)
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
websocketLib "dev.sum7.eu/genofire/golang-lib/websocket"
|
||||||
|
"dev.sum7.eu/genofire/logmania/receive/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)
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package client
|
|
@ -0,0 +1,55 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"dev.sum7.eu/genofire/golang-lib/websocket"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"dev.sum7.eu/genofire/logmania/lib"
|
||||||
|
"dev.sum7.eu/genofire/logmania/receive"
|
||||||
|
)
|
||||||
|
|
||||||
|
const WS_LOG_ENTRY = "log"
|
||||||
|
|
||||||
|
var logger = log.WithField("receive", "logrus")
|
||||||
|
|
||||||
|
type Receiver struct {
|
||||||
|
receive.Receiver
|
||||||
|
input chan *websocket.Message
|
||||||
|
exportChannel chan *log.Entry
|
||||||
|
serverSocket *websocket.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(config *lib.ReceiveConfig, exportChannel chan *log.Entry) receive.Receiver {
|
||||||
|
inputMsg := make(chan *websocket.Message)
|
||||||
|
ws := websocket.NewServer(inputMsg, websocket.NewSessionManager())
|
||||||
|
|
||||||
|
http.HandleFunc("/receiver", ws.Handler)
|
||||||
|
|
||||||
|
recv := &Receiver{
|
||||||
|
input: inputMsg,
|
||||||
|
serverSocket: ws,
|
||||||
|
exportChannel: exportChannel,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("init")
|
||||||
|
|
||||||
|
return recv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *Receiver) Listen() {
|
||||||
|
logger.Info("listen")
|
||||||
|
for msg := range rc.input {
|
||||||
|
if event, ok := msg.Body.(log.Entry); ok {
|
||||||
|
rc.exportChannel <- &event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *Receiver) Close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
receive.AddReceiver("websocket", Init)
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package receive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dev.sum7.eu/genofire/logmania/lib"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Register = make(map[string]ReceiverInit)
|
||||||
|
|
||||||
|
type Receiver interface {
|
||||||
|
Listen()
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReceiverInit func(*lib.ReceiveConfig, chan *log.Entry) Receiver
|
||||||
|
|
||||||
|
func AddReceiver(name string, n ReceiverInit) {
|
||||||
|
Register[name] = n
|
||||||
|
}
|
|
@ -1,13 +1,12 @@
|
||||||
package syslog
|
package syslog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bdlm/log"
|
log "github.com/sirupsen/logrus"
|
||||||
logstd "github.com/bdlm/std/logger"
|
|
||||||
|
|
||||||
libSyslog "dev.sum7.eu/sum7/logmania/lib/syslog"
|
libSyslog "dev.sum7.eu/genofire/logmania/lib/syslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
var SyslogPriorityMap = map[int]logstd.Level{
|
var SyslogPriorityMap = map[int]log.Level{
|
||||||
0: log.PanicLevel,
|
0: log.PanicLevel,
|
||||||
1: log.PanicLevel,
|
1: log.PanicLevel,
|
||||||
2: log.PanicLevel,
|
2: log.PanicLevel,
|
|
@ -5,7 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/bdlm/log"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestToEntry(t *testing.T) {
|
func TestToEntry(t *testing.T) {
|
|
@ -0,0 +1,65 @@
|
||||||
|
package syslog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"dev.sum7.eu/genofire/logmania/lib"
|
||||||
|
"dev.sum7.eu/genofire/logmania/receive"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger = log.WithField("receive", "syslog")
|
||||||
|
|
||||||
|
type Receiver struct {
|
||||||
|
receive.Receiver
|
||||||
|
exportChannel chan *log.Entry
|
||||||
|
serverSocket *net.UDPConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init(config *lib.ReceiveConfig, exportChannel chan *log.Entry) receive.Receiver {
|
||||||
|
addr, err := net.ResolveUDPAddr(config.Syslog.Type, config.Syslog.Address)
|
||||||
|
ln, err := net.ListenUDP(config.Syslog.Type, addr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("init ", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
recv := &Receiver{
|
||||||
|
serverSocket: ln,
|
||||||
|
exportChannel: exportChannel,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("init")
|
||||||
|
|
||||||
|
return recv
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxDataGramSize = 8192
|
||||||
|
|
||||||
|
func (rc *Receiver) Listen() {
|
||||||
|
logger.Info("listen")
|
||||||
|
for {
|
||||||
|
buf := make([]byte, maxDataGramSize)
|
||||||
|
n, src, err := rc.serverSocket.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("failed to accept connection", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := make([]byte, n)
|
||||||
|
copy(raw, buf)
|
||||||
|
entry := toLogEntry(raw, src.IP.String())
|
||||||
|
if entry != nil {
|
||||||
|
rc.exportChannel <- entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *Receiver) Close() {
|
||||||
|
rc.serverSocket.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
receive.AddReceiver("syslog", Init)
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"presets": ["env"]
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
extends: eslint:all,
|
||||||
|
rules: {
|
||||||
|
no-tabs: [off],
|
||||||
|
indent: [error, tab],
|
||||||
|
quotes: [error, single],
|
||||||
|
padded-blocks: [error, { blocks: never }],
|
||||||
|
no-console: [error, { allow: [log, warn, error] }],
|
||||||
|
func-style: [error, declaration],
|
||||||
|
object-curly-newline: off,
|
||||||
|
wrap-iife: [error, inside],
|
||||||
|
object-shorthand: ["error", "always", { "avoidQuotes": true }],
|
||||||
|
require-jsdoc: [off],
|
||||||
|
max-statements: [off],
|
||||||
|
no-magic-numbers: ["error", { "ignore": [0,1,-1] }],
|
||||||
|
sort-vars: [off],
|
||||||
|
max-len: [off],
|
||||||
|
id-length: [error, { exceptions: ["i"] }],
|
||||||
|
no-ternary: [off]
|
||||||
|
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: module
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
es6: true,
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
document: true,
|
||||||
|
window: true,
|
||||||
|
console: true,
|
||||||
|
localStorage: true,
|
||||||
|
location: true,
|
||||||
|
navigator: true,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
.notifications {
|
||||||
|
position: fixed;
|
||||||
|
top: 16px;
|
||||||
|
right: 72px;
|
||||||
|
z-index: 105;
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
.status {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background-color: #80FF00;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: #000 0 -1px 6px 1px, inset #460 0 -1px 8px, #80FF00 0 3px 11px;
|
||||||
|
|
||||||
|
&.connecting,
|
||||||
|
&.running {
|
||||||
|
background-color: #FF0;
|
||||||
|
box-shadow: #000 0 -1px 6px 1px, inset #660 0 -1px 8px, #FF0 0 3px 11px;
|
||||||
|
animation: blinkDot 2s infinite;
|
||||||
|
}
|
||||||
|
&.offline,
|
||||||
|
&.failed {
|
||||||
|
background-color: #F00;
|
||||||
|
box-shadow: #000 0 -1px 6px 1px, inset #600 0 -1px 8px, #F00 0 3px 11px;
|
||||||
|
animation: blinkDot 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes blinkDot {
|
||||||
|
50% {
|
||||||
|
background-color: rgba(255, 255, 255, 0.25);
|
||||||
|
box-shadow: #000 0 -1px 6px 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
@import "../node_modules/semantic-ui-less/semantic.less";
|
||||||
|
|
||||||
|
@import "_status.less";
|
||||||
|
@import "_notify.less";
|
||||||
|
|
||||||
|
body {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main.container {
|
||||||
|
margin-top: 7em;
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
import browserSync from 'browser-sync';
|
||||||
|
import browserify from 'browserify';
|
||||||
|
import buffer from 'vinyl-buffer';
|
||||||
|
import gulp from 'gulp';
|
||||||
|
import gulpLoadPlugins from 'gulp-load-plugins';
|
||||||
|
import source from 'vinyl-source-stream';
|
||||||
|
import sourcemaps from 'gulp-sourcemaps';
|
||||||
|
import watchify from 'watchify';
|
||||||
|
|
||||||
|
const gulpPlugins = gulpLoadPlugins();
|
||||||
|
|
||||||
|
function bundle (watching = false) {
|
||||||
|
const browserifyConf = {
|
||||||
|
'debug': true,
|
||||||
|
'entries': ['js/index.js'],
|
||||||
|
'transform': ['babelify']};
|
||||||
|
|
||||||
|
if (watching) {
|
||||||
|
browserifyConf.plugin = [watchify];
|
||||||
|
}
|
||||||
|
|
||||||
|
const browser = browserify(browserifyConf);
|
||||||
|
|
||||||
|
function bundler () {
|
||||||
|
return browser.bundle().
|
||||||
|
on('error', (err) => {
|
||||||
|
console.log(err.message);
|
||||||
|
}).
|
||||||
|
pipe(source('app.js')).
|
||||||
|
pipe(buffer()).
|
||||||
|
pipe(sourcemaps.init({'loadMaps': true})).
|
||||||
|
pipe(gulpPlugins.uglify()).
|
||||||
|
pipe(sourcemaps.write('./')).
|
||||||
|
pipe(gulp.dest('./'));
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.on('update', () => {
|
||||||
|
bundler();
|
||||||
|
console.log('scripts rebuild');
|
||||||
|
});
|
||||||
|
|
||||||
|
return bundler();
|
||||||
|
}
|
||||||
|
|
||||||
|
gulp.task('scripts', () => {
|
||||||
|
bundle();
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('styles', () => {
|
||||||
|
gulp.src('css/styles.less').
|
||||||
|
pipe(gulpPlugins.plumber()).
|
||||||
|
pipe(sourcemaps.init()).
|
||||||
|
pipe(gulpPlugins.less({
|
||||||
|
'includePaths': ['.']
|
||||||
|
})).
|
||||||
|
pipe(gulpPlugins.autoprefixer()).
|
||||||
|
pipe(sourcemaps.write('./')).
|
||||||
|
pipe(gulp.dest('./'));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('build', [
|
||||||
|
'scripts',
|
||||||
|
'styles'
|
||||||
|
]);
|
||||||
|
|
||||||
|
gulp.task('watch', () => {
|
||||||
|
bundle(true);
|
||||||
|
gulp.watch('css/**/*.less', ['styles']);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('serve', ['watch'], () => {
|
||||||
|
browserSync({
|
||||||
|
'notify': false,
|
||||||
|
'port': 9000,
|
||||||
|
// Proxy: 'example.com',
|
||||||
|
'server': {
|
||||||
|
'baseDir': '.'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.watch([
|
||||||
|
'**/*.html',
|
||||||
|
'**/*.php',
|
||||||
|
'styles.css',
|
||||||
|
'app.js'
|
||||||
|
]).on('change', browserSync.reload);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('default', [
|
||||||
|
'build',
|
||||||
|
'serve'
|
||||||
|
]);
|
|
@ -0,0 +1,15 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||||
|
<title>logmania</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<script src="app.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>JavaScript required</strong>
|
||||||
|
</noscript>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,9 @@
|
||||||
|
/* eslint no-magic-numbers: "off"*/
|
||||||
|
/* eslint sort-keys: "off"*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
'title': 'Logmania',
|
||||||
|
'backend': 'wss://cetus.h.sum7.eu/logmania/ws'
|
||||||
|
|
||||||
|
// 'backend': `ws${location.protocol === 'https:' ? 's' :''}://${location.host}${location.pathname.replace(/\/$/, '')}/ws`
|
||||||
|
};
|
|
@ -0,0 +1,54 @@
|
||||||
|
export function setProps (el, props) {
|
||||||
|
if (props) {
|
||||||
|
if (props.class) {
|
||||||
|
let classList = props.class;
|
||||||
|
if (typeof props.class === 'string') {
|
||||||
|
classList = classList.split(' ');
|
||||||
|
}
|
||||||
|
el.classList.add(...classList);
|
||||||
|
delete props.class;
|
||||||
|
}
|
||||||
|
Object.keys(props).map((key) => {
|
||||||
|
if (key.indexOf('on') === 0 && typeof props[key] === 'function') {
|
||||||
|
// eslint-disable-next-line no-magic-numbers
|
||||||
|
return el.addEventListener(key.slice(2), props[key]);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
Object.keys(props).map((key) => el.setAttribute(key, props[key]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function newEl (eltype, props, content) {
|
||||||
|
const el = document.createElement(eltype);
|
||||||
|
setProps(el, props);
|
||||||
|
if (content) {
|
||||||
|
el.innerHTML = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function appendChild (el, child) {
|
||||||
|
if (child && !child.parentNode) {
|
||||||
|
el.appendChild(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeChild (el) {
|
||||||
|
if (el && el.parentNode) {
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-params
|
||||||
|
export function newAt (at, eltype, props, content) {
|
||||||
|
const el = document.createElement(eltype);
|
||||||
|
setProps(el, props);
|
||||||
|
if (content) {
|
||||||
|
el.innerHTML = content;
|
||||||
|
}
|
||||||
|
at.appendChild(el);
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
import * as V from 'picodom';
|
||||||
|
import * as domlib from '../domlib';
|
||||||
|
import * as socket from '../socket';
|
||||||
|
import * as store from '../store';
|
||||||
|
import View from '../view';
|
||||||
|
import {singelton as notify} from './notify';
|
||||||
|
import {render} from '../gui';
|
||||||
|
|
||||||
|
export class MenuView extends View {
|
||||||
|
constructor () {
|
||||||
|
super();
|
||||||
|
this.elStatus = document.createElement('div');
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const socketStatus = socket.getStatus();
|
||||||
|
let statusClass = 'status ',
|
||||||
|
vLogin = V.h('a', {
|
||||||
|
'class': 'item',
|
||||||
|
'href': '#/log'
|
||||||
|
}, 'Logs');
|
||||||
|
if (socketStatus !== 1) {
|
||||||
|
// eslint-disable-next-line no-magic-numbers
|
||||||
|
if (socketStatus === 0 || socketStatus === 2) {
|
||||||
|
statusClass += 'connecting';
|
||||||
|
} else {
|
||||||
|
statusClass += 'offline';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (store.isLogin) {
|
||||||
|
vLogin = V.h('a', {
|
||||||
|
'class': 'item',
|
||||||
|
'href': '#/',
|
||||||
|
'onclick': () => socket.sendjson({'subject': 'logout'}, (msg) => {
|
||||||
|
if (msg.body) {
|
||||||
|
store.isLogin = false;
|
||||||
|
store.login = {};
|
||||||
|
render();
|
||||||
|
} else {
|
||||||
|
notify.send({
|
||||||
|
'header': 'Abmeldung ist fehlgeschlagen',
|
||||||
|
'type': 'error'
|
||||||
|
}, 'Logout');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 'Logout');
|
||||||
|
}
|
||||||
|
|
||||||
|
V.patch(this.vStatus, this.vStatus = V.h('div', {'class': statusClass}), this.elStatus);
|
||||||
|
|
||||||
|
|
||||||
|
if (!this.init) {
|
||||||
|
domlib.setProps(this.el, {'class': 'ui fixed inverted menu'});
|
||||||
|
const menuContainer = domlib.newAt(this.el, 'div', {'class': 'ui container'});
|
||||||
|
this.menuRight = domlib.newAt(menuContainer, 'div', {'class': 'menu right'});
|
||||||
|
this.elStatus.classList.add('item');
|
||||||
|
this.menuRight.appendChild(this.elStatus);
|
||||||
|
this.init = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
V.patch(this.vLogin, this.vLogin = vLogin, this.menuRight);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
import * as V from 'picodom';
|
||||||
|
import View from '../view';
|
||||||
|
|
||||||
|
|
||||||
|
const DELAY_OF_NOTIFY = 15000,
|
||||||
|
MAX_MESSAGE_SHOW = 5;
|
||||||
|
|
||||||
|
class NotifyView extends View {
|
||||||
|
constructor () {
|
||||||
|
super();
|
||||||
|
if ('Notification' in window) {
|
||||||
|
window.Notification.requestPermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messages = [];
|
||||||
|
window.setInterval(this.removeLast.bind(this), DELAY_OF_NOTIFY);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeLast () {
|
||||||
|
this.messages.splice(0, 1);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMSG (msg) {
|
||||||
|
const {messages} = this,
|
||||||
|
content = [msg.content];
|
||||||
|
|
||||||
|
let {render} = this;
|
||||||
|
render = render.bind(this);
|
||||||
|
|
||||||
|
if (msg.header) {
|
||||||
|
content.unshift(V.h('div', {'class': 'header'}, msg.header));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return V.h(
|
||||||
|
'div', {
|
||||||
|
'class': `ui floating message ${msg.type}`
|
||||||
|
},
|
||||||
|
V.h('i', {
|
||||||
|
'class': 'close icon',
|
||||||
|
'onclick': () => {
|
||||||
|
const index = messages.indexOf(msg);
|
||||||
|
if (index !== -1) {
|
||||||
|
messages.splice(index, 1);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}), V.h('div', {'class': 'content'}, content)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
send (props, content) {
|
||||||
|
let msg = props;
|
||||||
|
if (typeof props === 'object') {
|
||||||
|
msg.content = content;
|
||||||
|
} else {
|
||||||
|
msg = {
|
||||||
|
'content': content,
|
||||||
|
'type': props
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ('Notification' in window &&
|
||||||
|
window.Notification.permission === 'granted') {
|
||||||
|
let body = msg.type,
|
||||||
|
title = content;
|
||||||
|
if (msg.header) {
|
||||||
|
title = msg.header;
|
||||||
|
body = msg.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-new
|
||||||
|
new window.Notification(title, {
|
||||||
|
'body': body,
|
||||||
|
'icon': '/img/logo.jpg'
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.messages.length > MAX_MESSAGE_SHOW) {
|
||||||
|
this.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messages.push(msg);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
V.patch(this.vel, this.vel = V.h('div', {'class': 'notifications'}, this.messages.map(this.renderMSG.bind(this))), this.el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line one-var
|
||||||
|
const singelton = new NotifyView();
|
||||||
|
export {singelton, NotifyView};
|
|
@ -0,0 +1,43 @@
|
||||||
|
import * as domlib from './domlib';
|
||||||
|
import {MenuView} from './element/menu';
|
||||||
|
import Navigo from '../node_modules/navigo/lib/navigo';
|
||||||
|
import View from './view';
|
||||||
|
import {singelton as notify} from './element/notify';
|
||||||
|
|
||||||
|
const router = new Navigo(null, true, '#'),
|
||||||
|
elMain = domlib.newEl('div', {'class': 'ui main container'}),
|
||||||
|
elMenu = new MenuView();
|
||||||
|
|
||||||
|
export {router};
|
||||||
|
|
||||||
|
let init = false,
|
||||||
|
currentView = new View();
|
||||||
|
|
||||||
|
export function render () {
|
||||||
|
if (!document.body) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!init) {
|
||||||
|
notify.bind(document.body);
|
||||||
|
elMenu.bind(document.body);
|
||||||
|
|
||||||
|
document.body.appendChild(elMain);
|
||||||
|
|
||||||
|
init = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentView.render();
|
||||||
|
|
||||||
|
notify.render();
|
||||||
|
elMenu.render();
|
||||||
|
|
||||||
|
router.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setView (toView) {
|
||||||
|
currentView.unbind();
|
||||||
|
currentView = toView;
|
||||||
|
currentView.bind(elMain);
|
||||||
|
currentView.render();
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import * as gui from './gui';
|
||||||
|
import config from './config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Self binding with router
|
||||||
|
*/
|
||||||
|
/* eslint-disable no-unused-vars */
|
||||||
|
import home from './view/home';
|
||||||
|
import login from './view/log';
|
||||||
|
/* eslint-enable no-unused-vars */
|
||||||
|
|
||||||
|
|
||||||
|
document.title = config.title;
|
||||||
|
window.onload = () => gui.render();
|
|
@ -0,0 +1,163 @@
|
||||||
|
import config from './config';
|
||||||
|
import {singelton as notify} from './element/notify';
|
||||||
|
import {render} from './gui';
|
||||||
|
|
||||||
|
const RECONNECT_AFTER = 5000,
|
||||||
|
RETRY_QUERY = 300,
|
||||||
|
PREFIX_EVENT = true,
|
||||||
|
query = [],
|
||||||
|
eventMSGID = {},
|
||||||
|
eventTo = {};
|
||||||
|
|
||||||
|
let socket = null;
|
||||||
|
|
||||||
|
function newUUID () {
|
||||||
|
/* eslint-disable */
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||||
|
const r = Math.random() * 16 | 0,
|
||||||
|
v = c === 'x' ? r : r & 0x3 | 0x8;
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
/* eslint-enable */
|
||||||
|
}
|
||||||
|
|
||||||
|
function correctMSG (obj) {
|
||||||
|
if (!obj.id) {
|
||||||
|
obj.id = newUUID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onerror (err) {
|
||||||
|
console.warn(err);
|
||||||
|
// eslint-disable-next-line no-magic-numbers
|
||||||
|
if (socket.readyState !== 3) {
|
||||||
|
notify.send({
|
||||||
|
'header': 'Verbindung',
|
||||||
|
'type': 'error'
|
||||||
|
}, 'Verbindung zum Server unterbrochen!');
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onopen () {
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function sendjson (obj, callback) {
|
||||||
|
if (socket.readyState !== 1) {
|
||||||
|
query.push({
|
||||||
|
'callback': callback,
|
||||||
|
'obj': obj
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
correctMSG(obj);
|
||||||
|
const socketMSG = JSON.stringify(obj);
|
||||||
|
socket.send(socketMSG);
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
eventMSGID[obj.id] = callback;
|
||||||
|
console.log('callback bind', obj.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmessage (raw) {
|
||||||
|
const msg = JSON.parse(raw.data),
|
||||||
|
msgFunc = eventMSGID[msg.id];
|
||||||
|
let eventFuncs = eventTo[msg.subject];
|
||||||
|
|
||||||
|
if (msgFunc) {
|
||||||
|
msgFunc(msg);
|
||||||
|
delete eventMSGID[msg.id];
|
||||||
|
render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof eventFuncs === 'object' && eventFuncs.length > 0) {
|
||||||
|
// eslint-disable-next-line guard-for-in
|
||||||
|
for (const i in eventFuncs) {
|
||||||
|
const func = eventFuncs[i];
|
||||||
|
if (func) {
|
||||||
|
func(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (PREFIX_EVENT) {
|
||||||
|
for (const key in eventTo) {
|
||||||
|
if (msg.subject.indexOf(key) === 0) {
|
||||||
|
eventFuncs = eventTo[key];
|
||||||
|
// eslint-disable-next-line guard-for-in
|
||||||
|
for (const i in eventFuncs) {
|
||||||
|
const func = eventFuncs[i];
|
||||||
|
if (func) {
|
||||||
|
func(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notify.send('warning', `unable to identify message: ${raw.data}`);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onclose () {
|
||||||
|
console.log('socket closed by server');
|
||||||
|
notify.send({
|
||||||
|
'header': 'Verbindung',
|
||||||
|
'type': 'warning'
|
||||||
|
}, 'Verbindung zum Server beendet!');
|
||||||
|
render();
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
window.setTimeout(connect, RECONNECT_AFTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect () {
|
||||||
|
socket = new window.WebSocket(config.backend);
|
||||||
|
socket.onopen = onopen;
|
||||||
|
socket.onerror = onerror;
|
||||||
|
socket.onmessage = onmessage;
|
||||||
|
socket.onclose = onclose;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setInterval(() => {
|
||||||
|
const queryEntry = query.pop();
|
||||||
|
if (queryEntry) {
|
||||||
|
sendjson(queryEntry.obj, queryEntry.callback);
|
||||||
|
}
|
||||||
|
console.log('query length: ', query.length);
|
||||||
|
}, RETRY_QUERY);
|
||||||
|
|
||||||
|
|
||||||
|
export function getStatus () {
|
||||||
|
if (socket) {
|
||||||
|
return socket.readyState;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setEvent (to, func) {
|
||||||
|
eventTo[to] = [func];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addEvent (to, func) {
|
||||||
|
if (typeof eventTo[to] !== 'object') {
|
||||||
|
eventTo[to] = [];
|
||||||
|
}
|
||||||
|
eventTo[to].push(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function delEvent (to, func) {
|
||||||
|
if (typeof eventTo[to] === 'object' && eventTo[to].length > 1) {
|
||||||
|
eventTo[to].pop(func);
|
||||||
|
} else {
|
||||||
|
eventTo[to] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connect();
|
|
@ -0,0 +1,6 @@
|
||||||
|
const store = {
|
||||||
|
'bot': [],
|
||||||
|
'channel': {}
|
||||||
|
};
|
||||||
|
|
||||||
|
export {store};
|
|
@ -0,0 +1,22 @@
|
||||||
|
export default class View {
|
||||||
|
constructor () {
|
||||||
|
this.el = document.createElement('div');
|
||||||
|
}
|
||||||
|
|
||||||
|
unbind () {
|
||||||
|
if (this.el && this.el.parentNode) {
|
||||||
|
this.el.parentNode.removeChild(this.el);
|
||||||
|
} else {
|
||||||
|
console.warn('unbind view not possible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bind (el) {
|
||||||
|
el.appendChild(this.el);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
render () {
|
||||||
|
console.log('abstract view');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import * as dom from '../domlib';
|
||||||
|
import * as gui from '../gui';
|
||||||
|
import View from '../view';
|
||||||
|
|
||||||
|
class HomeView extends View {
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
render () {
|
||||||
|
if (!this.init) {
|
||||||
|
const h1 = dom.newAt(this.el, 'h1');
|
||||||
|
h1.innerHTML = 'Home';
|
||||||
|
this.init = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const homeView = new HomeView();
|
||||||
|
|
||||||
|
gui.router.on('/', () => {
|
||||||
|
gui.setView(homeView);
|
||||||
|
});
|
|
@ -0,0 +1,57 @@
|
||||||
|
import * as domlib from '../domlib';
|
||||||
|
import * as gui from '../gui';
|
||||||
|
import * as socket from '../socket';
|
||||||
|
import View from '../view';
|
||||||
|
import {store} from '../store';
|
||||||
|
|
||||||
|
|
||||||
|
function levelToColor (lvl) {
|
||||||
|
return lvl;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addItem (el, msg) {
|
||||||
|
const div = domlib.newAt(el, 'div', {
|
||||||
|
'class': levelToColor(msg.Leve)
|
||||||
|
});
|
||||||
|
domlib.newAt(div, 'span', null, msg.Data.hostname);
|
||||||
|
domlib.newAt(div, 'span', null, msg.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogView extends View {
|
||||||
|
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
render () {
|
||||||
|
if (!this.init) {
|
||||||
|
this.init = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Domlib.newAt(this.el, 'h2', {'class': 'ui header'}, 'Log');
|
||||||
|
* for (const msg in store.channel.ffhb) {
|
||||||
|
* domlib.newAt(this.el, 'div', null, msg.Data.hostname, msg);
|
||||||
|
*}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
super();
|
||||||
|
socket.addEvent('ws:', (msg) => {
|
||||||
|
// Length('ws:') = 3
|
||||||
|
// eslint-disable-next-line no-magic-numbers
|
||||||
|
const channel = msg.subject.substr(3);
|
||||||
|
if (!store.channel[channel]) {
|
||||||
|
store.channel[channel] = [];
|
||||||
|
}
|
||||||
|
store.channel[channel].push(msg.body);
|
||||||
|
addItem(this.el, msg.body);
|
||||||
|
this.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const logView = new LogView();
|
||||||
|
|
||||||
|
gui.router.on('/log', () => {
|
||||||
|
gui.setView(logView);
|
||||||
|
});
|
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"name": "logmania",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"dependencies": {
|
||||||
|
"babel-core": "^6.26.0",
|
||||||
|
"babel-preset-env": "^1.6.1",
|
||||||
|
"babel-preset-es2017": "^6.24.1",
|
||||||
|
"babel-register": "^6.26.0",
|
||||||
|
"babelify": "^8.0.0",
|
||||||
|
"browser-sync": "^2.18.13",
|
||||||
|
"browserify": "^14.5.0",
|
||||||
|
"gulp": "3.9.0",
|
||||||
|
"gulp-autoprefixer": "^4.0.0",
|
||||||
|
"gulp-cli": "^1.4.0",
|
||||||
|
"gulp-less": "^3.3.2",
|
||||||
|
"gulp-load-plugins": "^1.5.0",
|
||||||
|
"gulp-plumber": "^1.1.0",
|
||||||
|
"gulp-sourcemaps": "^2.6.1",
|
||||||
|
"gulp-uglify": "^3.0.0",
|
||||||
|
"navigo": "^5.3.3",
|
||||||
|
"picodom": "^1.0.2",
|
||||||
|
"semantic-ui-less": "^2.2.12",
|
||||||
|
"vinyl-buffer": "^1.0.0",
|
||||||
|
"vinyl-source-stream": "^1.1.0",
|
||||||
|
"watchify": "^3.9.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"gulp": "gulp"
|
||||||
|
},
|
||||||
|
"babel": {
|
||||||
|
"presets": [
|
||||||
|
"es2017"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue