added stats page and password reset
This commit is contained in:
parent
621cc2a83e
commit
cabc3e94b4
55 changed files with 8094 additions and 4362 deletions
|
|
@ -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
14
pkg/cmd/helper.go
Normal 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
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ const (
|
|||
SearchURI = "/search"
|
||||
LogsURI = "/logs"
|
||||
TagsURI = "/tags"
|
||||
StatsURI = "/stats"
|
||||
UserURI = "/user"
|
||||
|
||||
RawPrefix = "/raw"
|
||||
|
|
|
|||
|
|
@ -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:"-"`
|
||||
|
|
|
|||
16
pkg/db/db.go
16
pkg/db/db.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ var reserved = []string{
|
|||
core.SearchURI,
|
||||
core.LogsURI,
|
||||
core.TagsURI,
|
||||
core.StatsURI,
|
||||
core.UserURI,
|
||||
core.RawPrefix,
|
||||
core.IndexPrefix,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
68
pkg/server/helper.go
Normal 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
|
||||
}
|
||||
|
|
@ -143,6 +143,13 @@ var routes = append(static, []route{
|
|||
tagsHandler)),
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
core.StatsURI,
|
||||
jwtHandler(
|
||||
authHandler(
|
||||
statsHandler)),
|
||||
[]string{"GET"},
|
||||
},
|
||||
{
|
||||
"/tags/new",
|
||||
jwtHandler(
|
||||
|
|
|
|||
|
|
@ -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:<="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:<="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)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue