added stats page and password reset

This commit is contained in:
ston1th 2019-11-24 12:25:19 +01:00
commit cabc3e94b4
55 changed files with 8094 additions and 4362 deletions

View file

@ -5,6 +5,7 @@ package cmd
import (
"encoding/json"
"errors"
"fmt"
"git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/docstore/pkg/db"
"git.giftfish.de/ston1th/docstore/pkg/log"
@ -174,9 +175,43 @@ func Run(version string) {
return nil
},
},
{
Name: "reset",
Usage: "reset the users password and TOTP token (docstore must be stopped)",
Flags: defaultFlags(nil),
Action: func(c *cli.Context) error {
if err := loadConfig(); err != nil {
stdlog.Fatal("reset: ", err)
}
DB, err := db.NewPlain(config)
if err != nil {
stdlog.Fatal("reset: ", err)
}
u, err := DB.GetUser()
if err != nil {
stdlog.Fatal("reset: ", err)
}
fmt.Println("username:", u.Username)
pw, err := password()
if err != nil {
stdlog.Fatal("reset: ", err)
}
if err = DB.UpdateUserPassword(pw); err != nil {
stdlog.Fatal("reset: ", err)
}
fmt.Println("password:", pw)
if err = DB.UpdateUserSecret(""); err != nil {
stdlog.Fatal("reset: ", err)
}
if err = DB.Close(); err != nil {
stdlog.Fatal("reset: ", err)
}
return nil
},
},
{
Name: "dump",
Usage: "create a json dump of all users (docstore needs to be stopped)",
Usage: "create a json dump of all users (docstore must be stopped)",
Flags: defaultFlags(dumpRestoreFlags()),
Action: func(c *cli.Context) error {
var (
@ -192,7 +227,7 @@ func Run(version string) {
stdlog.Fatal("dump: ", err)
}
}
DB, err := db.New(config)
DB, err := db.NewPlain(config)
if err != nil {
stdlog.Fatal("dump: ", err)
}
@ -209,7 +244,7 @@ func Run(version string) {
},
{
Name: "restore",
Usage: "restore a json dump (docstore needs to be stopped)",
Usage: "restore a json dump (docstore must be stopped)",
Flags: defaultFlags(dumpRestoreFlags()),
Action: func(c *cli.Context) error {
var (
@ -225,7 +260,7 @@ func Run(version string) {
stdlog.Fatal("restore: ", err)
}
}
DB, err := db.New(config)
DB, err := db.NewPlain(config)
if err != nil {
stdlog.Fatal("restore: ", err)
}

14
pkg/cmd/helper.go Normal file
View file

@ -0,0 +1,14 @@
// Copyright (C) 2019 Marius Schellenberger
package cmd
import (
"crypto/rand"
"encoding/hex"
)
func password() (string, error) {
b := make([]byte, 16)
_, err := rand.Read(b)
return hex.EncodeToString(b), err
}

View file

@ -10,6 +10,7 @@ const (
SearchURI = "/search"
LogsURI = "/logs"
TagsURI = "/tags"
StatsURI = "/stats"
UserURI = "/user"
RawPrefix = "/raw"

View file

@ -64,6 +64,14 @@ func (p Paths) Len() int { return len(p) }
func (p Paths) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p Paths) Less(i, j int) bool { return p[i].Name < p[j].Name }
type Stats struct {
Goroutines int
Alloc string
Sys string
Docs uint64
GoVersion string
}
type Config struct {
RunDir string `json:"run_dir,omitempty"`
DataDir string `json:"-"`

View file

@ -25,9 +25,7 @@ type DB struct {
}
func New(cfg core.Config) (db *DB, err error) {
db = new(DB)
dbFile := filepath.Join(cfg.RunDir, storeFile)
db.store, err = store.NewBoltStore(dbFile, nil)
db, err = NewPlain(cfg)
if err != nil {
return
}
@ -50,6 +48,13 @@ func New(cfg core.Config) (db *DB, err error) {
return
}
func NewPlain(cfg core.Config) (db *DB, err error) {
db = new(DB)
dbFile := filepath.Join(cfg.RunDir, storeFile)
db.store, err = store.NewBoltStore(dbFile, nil)
return
}
func (db *DB) Dump(w io.Writer) error {
return db.store.Dump(w)
}
@ -63,5 +68,8 @@ func (db *DB) Close() (err error) {
if err != nil {
return
}
return db.Index.Close()
if db.Index != nil {
return db.Index.Close()
}
return nil
}

View file

@ -27,6 +27,7 @@ var reserved = []string{
core.SearchURI,
core.LogsURI,
core.TagsURI,
core.StatsURI,
core.UserURI,
core.RawPrefix,
core.IndexPrefix,

View file

@ -66,6 +66,11 @@ func (i *Index) Close() error {
return i.i.Close()
}
func (i *Index) DocCount() (c uint64) {
c, _ = i.i.DocCount()
return
}
func (i *Index) Add(text string, tags []string) (id string, err error) {
id = newID()
for {

View file

@ -174,51 +174,19 @@ func rawHandler(ctx *Context) {
http.ServeContent(ctx.Response, ctx.Request, fi.Name(), fi.ModTime(), f)
}
func scanFile(srv *HTTPServer, path string) (err error) {
err = srv.FS.AddScan(path)
if err != nil {
return
}
go func() {
defer srv.FS.RemoveScan(path)
l := srv.Log
file, txt, err := srv.Scanner.Scan(path)
if err != nil {
l.Printf("scanner: %s: %s", path, err)
return
}
tags, err := srv.DB.GetAllRTags()
if err != nil {
l.Printf("getTags: %s: %s", path, err)
}
found := tags.Match(txt)
id, err := srv.DB.Index.Add(txt, found)
if err != nil {
l.Printf("index: %s: %s", path, err)
return
}
err = srv.DB.NewFile(id, file, found)
if err != nil {
l.Printf("newFile: %s: %s", path, err)
}
}()
return
}
func indexHandler(ctx *Context) {
switch ctx.Method() {
case "GET":
var (
err error
ok bool
fpath string
)
path := ctx.Path()
if strings.HasPrefix(path, core.IndexPrefix) {
fpath = strings.TrimPrefix(path, core.IndexPrefix)
if fpath, ok = hasTrimPrefix(path, core.IndexPrefix); ok {
err = scanFile(ctx.Srv, fpath)
}
if strings.HasPrefix(path, core.RetagPrefix) {
fpath = strings.TrimPrefix(path, core.RetagPrefix)
if fpath, ok = hasTrimPrefix(path, core.RetagPrefix); ok {
if fpath == "" {
go func() {
err := ctx.Srv.DB.RetagAll()
@ -859,6 +827,19 @@ func tagsDelHandler(ctx *Context) {
}
}
func statsHandler(ctx *Context) {
ctx.Template("statsHandler")
ctx.Data = webData{
Title: "Stats",
BodyTitle: "Stats",
}
switch ctx.Method() {
case "GET":
ctx.Data.Data = getStats(ctx.Srv.DB.Index)
ctx.Exec()
}
}
func logoutHandler(ctx *Context) {
user := ctx.User()
if user == "" {

68
pkg/server/helper.go Normal file
View file

@ -0,0 +1,68 @@
// Copyright (C) 2019 Marius Schellenberger
package server
import (
"runtime"
"strconv"
"git.giftfish.de/ston1th/docstore/pkg/core"
bleve "git.giftfish.de/ston1th/docstore/pkg/index"
)
const div = 1048576
func fmtMem(i uint64) string {
return strconv.FormatFloat(float64(i)/div, 'f', 2, 64)
}
func getStats(i *bleve.Index) *core.Stats {
mem := new(runtime.MemStats)
runtime.ReadMemStats(mem)
return &core.Stats{
Goroutines: runtime.NumGoroutine(),
Alloc: fmtMem(mem.Alloc),
Sys: fmtMem(mem.Sys),
Docs: i.DocCount(),
GoVersion: runtime.Version(),
}
}
func scanFile(srv *HTTPServer, path string) (err error) {
err = srv.FS.AddScan(path)
if err != nil {
return
}
go func() {
defer srv.FS.RemoveScan(path)
l := srv.Log
file, txt, err := srv.Scanner.Scan(path)
if err != nil {
l.Printf("scanner: %s: %s", path, err)
return
}
tags, err := srv.DB.GetAllRTags()
if err != nil {
l.Printf("getTags: %s: %s", path, err)
}
found := tags.Match(txt)
id, err := srv.DB.Index.Add(txt, found)
if err != nil {
l.Printf("index: %s: %s", path, err)
return
}
err = srv.DB.NewFile(id, file, found)
if err != nil {
l.Printf("newFile: %s: %s", path, err)
}
}()
return
}
// Parts taken from strings.TrimPrefix
func hasTrimPrefix(s, prefix string) (string, bool) {
if len(s) >= len(prefix) && s[0:len(prefix)] == prefix {
return s[len(prefix):], true
}
return s, false
}

View file

@ -143,6 +143,13 @@ var routes = append(static, []route{
tagsHandler)),
[]string{"GET"},
},
{
core.StatsURI,
jwtHandler(
authHandler(
statsHandler)),
[]string{"GET"},
},
{
"/tags/new",
jwtHandler(

View file

@ -325,6 +325,7 @@ const (
{{end}}
<ul class="nav navbar-nav ml-auto">
{{if .Login}}
<li class="nav-item"><a class="nav-link" href="/stats">Stats</a></li>
<li class="nav-item"><a class="nav-link" href="/user/edit">Profile</a></li>
<li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li>
{{else}}
@ -501,6 +502,39 @@ created:&lt;="2016-09-21"</code></pre>
</div>
{{end}}
</div>
{{end}}`
stats = `{{define "body"}}
<div class="page-header">
<div class="row">
<h2>{{.BodyTitle}}</h2>
</div>
</div>
<div class="row">
<table class="table table-stripped table-hover">
<tbody>
<tr>
<td><b>Goroutines</b></td>
<td>{{.Data.Goroutines}}</td>
</tr>
<tr>
<td><b>Memory usage</b></td>
<td>{{.Data.Alloc}} MiB</td>
</tr>
<tr>
<td><b>Memory allocated</b></td>
<td>{{.Data.Sys}} MiB</td>
</tr>
<tr>
<td><b>Documents</b></td>
<td>{{.Data.Docs}}</td>
</tr>
<tr>
<td><b>Go Version</b></td>
<td>{{.Data.GoVersion}}</td>
</tr>
</tbody>
</table>
</div>
{{end}}`
tagsDel = `{{define "body"}}
<div class="row">
@ -662,7 +696,7 @@ created:&lt;="2016-09-21"</code></pre>
<form class="form-horizontal" action="/user/edit" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<div class="form-group">
<label class="col-form-label" for="password">Password</label>
<label class="col-form-label" for="password">New Password</label>
<input class="form-control input-sm" type="password" id="password" name="password" autofocus>
</div>
<div class="form-group">
@ -946,6 +980,8 @@ func (s *HTTPServer) loadTemplates() {
s.templ["tagsNewHandler"] = parse(index, menu, tagsNew)
s.templ["tagsEditHandler"] = parse(index, menu, tagsEdit)
s.templ["tagsDelHandler"] = parse(index, menu, tagsDel)
// stats
s.templ["statsHandler"] = parse(index, menu, stats)
// logs
s.templ["logsHandler"] = parse(index, menu, logs)