You've already forked nyx
mirror of
https://github.com/rls-moe/nyx
synced 2025-08-19 06:18:38 +00:00
Improved stability, improved spam algorithm, fixed some bugs
This commit is contained in:
@@ -1,21 +1,139 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/tidwall/buntdb"
|
||||
"go.rls.moe/nyx/http/errw"
|
||||
"go.rls.moe/nyx/http/middle"
|
||||
"go.rls.moe/nyx/resources"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func handleCleanup(w http.ResponseWriter, r *http.Request) {
|
||||
sess := middle.GetSession(r)
|
||||
if sess == nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized"))
|
||||
return
|
||||
}
|
||||
if sess.CAttr("mode") != "admin" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Beginning cleanup...")
|
||||
db := middle.GetDB(r)
|
||||
err := db.Update(func(tx *buntdb.Tx) error {
|
||||
var delKeys = []string{}
|
||||
err := db.View(func(tx *buntdb.Tx) error {
|
||||
var err error
|
||||
tx.AscendKeys("*", func(key, value string) bool {
|
||||
keyType := detectType(key)
|
||||
if keyType == "thread" {
|
||||
var host string
|
||||
host, err = resources.GetHostnameFromKey(key)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %s", err)
|
||||
return false
|
||||
}
|
||||
var thread = &resources.Thread{}
|
||||
err = json.Unmarshal([]byte(value), thread)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %s", err)
|
||||
return false
|
||||
}
|
||||
threadTime := resources.DateFromId(thread.ID)
|
||||
dur := threadTime.Sub(time.Now())
|
||||
if dur > time.Hour*24*7 {
|
||||
fmt.Printf("Sched %s for deletion: expired\n", key)
|
||||
delKeys = append(delKeys, key)
|
||||
return true
|
||||
}
|
||||
err = resources.FillReplies(tx, host, thread)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %s", err)
|
||||
return false
|
||||
}
|
||||
if len(thread.GetReplies()) == 0 {
|
||||
fmt.Printf("Sched %s for deletion: empty\n", key)
|
||||
delKeys = append(delKeys, key)
|
||||
return true
|
||||
}
|
||||
if _, err := resources.GetReply(tx, host, thread.Board, thread.ID, thread.StartReply); err == buntdb.ErrNotFound {
|
||||
fmt.Printf("Sched %s for delection: main reply dead\n", key)
|
||||
delKeys = append(delKeys, key)
|
||||
return true
|
||||
}
|
||||
} else if keyType == "reply" {
|
||||
var host string
|
||||
host, err = resources.GetHostnameFromKey(key)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %s", err)
|
||||
return false
|
||||
}
|
||||
var reply = &resources.Reply{}
|
||||
err = json.Unmarshal([]byte(value), reply)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %s", err)
|
||||
return false
|
||||
}
|
||||
replyTime := resources.DateFromId(reply.ID)
|
||||
dur := replyTime.Sub(time.Now())
|
||||
if dur > time.Hour*24*7 {
|
||||
fmt.Printf("Sched %s for deletion: expired\n", key)
|
||||
delKeys = append(delKeys, key)
|
||||
return true
|
||||
}
|
||||
if val, ok := reply.Metadata["deleted"]; ok && val == "yes" {
|
||||
fmt.Printf("Sched %s for deletion: deleted\n", key)
|
||||
delKeys = append(delKeys, key)
|
||||
return true
|
||||
}
|
||||
if err := resources.TestThread(tx, host, reply.Board, reply.Thread); err == buntdb.ErrNotFound {
|
||||
fmt.Printf("Sched %s for deletion: missing parent %d: %s\n", key, reply.Thread, err)
|
||||
delKeys = append(delKeys, key)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
/* Insert cleanup codes here */
|
||||
return err
|
||||
})
|
||||
fmt.Println("Removing sched' entries")
|
||||
db.Update(func(tx *buntdb.Tx) error {
|
||||
for _, v := range delKeys {
|
||||
fmt.Printf("Deleting %s\n", v)
|
||||
tx.Delete(v)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
fmt.Println("Shrinking DB")
|
||||
err = db.Shrink()
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
fmt.Println("Finished Cleanup")
|
||||
|
||||
http.Redirect(w, r, "/admin/panel.html", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func detectType(key string) string {
|
||||
if strings.Contains(key, "/jack/") {
|
||||
if strings.HasSuffix(key, "/board-data") {
|
||||
return "board"
|
||||
}
|
||||
if strings.HasSuffix(key, "/thread") {
|
||||
return "thread"
|
||||
}
|
||||
if strings.HasSuffix(key, "/reply-data") {
|
||||
return "reply"
|
||||
}
|
||||
return "system"
|
||||
}
|
||||
return "none"
|
||||
}
|
||||
|
74
http/admin/delpost.go
Normal file
74
http/admin/delpost.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"go.rls.moe/nyx/http/errw"
|
||||
"go.rls.moe/nyx/http/middle"
|
||||
"strconv"
|
||||
"go.rls.moe/nyx/resources"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"github.com/tidwall/buntdb"
|
||||
)
|
||||
|
||||
func handleDelPost(w http.ResponseWriter, r *http.Request) {
|
||||
sess := middle.GetSession(r)
|
||||
if sess == nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized"))
|
||||
return
|
||||
}
|
||||
if sess.CAttr("mode") != "admin" && sess.CAttr("mode") != "mod" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
rid, err := strconv.Atoi(r.FormValue("reply_id"))
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
trid, err := strconv.Atoi(r.FormValue("thread_id"))
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
board := r.FormValue("board")
|
||||
|
||||
if sess.CAttr("mode") == "mod" && sess.CAttr("board") != board {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Not on this board"))
|
||||
return
|
||||
}
|
||||
|
||||
db := middle.GetDB(r)
|
||||
|
||||
err = db.Update(func(tx *buntdb.Tx) error {
|
||||
reply, err := resources.GetReply(tx, r.Host, board, trid, rid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reply.Text = "[deleted]"
|
||||
reply.Metadata["deleted"] = "yes"
|
||||
reply.Image = nil
|
||||
reply.Thumbnail = nil
|
||||
err = resources.UpdateReply(tx, r.Host, board, reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/%s/%d/thread.html", board, trid), http.StatusSeeOther)
|
||||
}
|
@@ -2,17 +2,12 @@ package admin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/GeertJohan/go.rice"
|
||||
"github.com/icza/session"
|
||||
"github.com/pressly/chi"
|
||||
"github.com/tidwall/buntdb"
|
||||
"go.rls.moe/nyx/http/errw"
|
||||
"go.rls.moe/nyx/http/middle"
|
||||
"go.rls.moe/nyx/resources"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -27,8 +22,9 @@ var riceConf = rice.Config{
|
||||
var box = riceConf.MustFindBox("http/admin/res/")
|
||||
|
||||
var (
|
||||
panelTmpl = template.New("admin/panel")
|
||||
loginTmpl = template.New("admin/login")
|
||||
panelTmpl = template.New("admin/panel")
|
||||
loginTmpl = template.New("admin/login")
|
||||
statusTmpl = template.New("admin/status")
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -41,6 +37,10 @@ func init() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
statusTmpl, err = statusTmpl.Parse(box.MustString("status.html"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Router sets up the Administration Panel
|
||||
@@ -50,8 +50,12 @@ func AdminRouter(r chi.Router) {
|
||||
r.Get("/index.html", serveLogin)
|
||||
r.Get("/panel.html", servePanel)
|
||||
r.Post("/new_board.sh", handleNewBoard)
|
||||
r.Post("/cleanup.sh", handleCleanup)
|
||||
r.Post("/login.sh", handleLogin)
|
||||
r.Post("/logout.sh", handleLogout)
|
||||
r.Post("/new_admin.sh", handleNewAdmin)
|
||||
r.Post("/del_admin.sh", handleDelAdmin)
|
||||
r.Get("/status.sh", serveStatus)
|
||||
}
|
||||
|
||||
// Router sets up moderation functions
|
||||
@@ -60,69 +64,6 @@ func ModRouter(r chi.Router) {
|
||||
r.Post("/del_reply.sh", handleDelPost)
|
||||
}
|
||||
|
||||
func handleDelPost(w http.ResponseWriter, r *http.Request) {
|
||||
sess := middle.GetSession(r)
|
||||
if sess == nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized"))
|
||||
return
|
||||
}
|
||||
if sess.CAttr("mode") != "admin" && sess.CAttr("mode") != "mod" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
rid, err := strconv.Atoi(r.FormValue("reply_id"))
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
trid, err := strconv.Atoi(r.FormValue("thread_id"))
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
board := r.FormValue("board")
|
||||
|
||||
if sess.CAttr("mode") == "mod" && sess.CAttr("board") != board {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Not on this board"))
|
||||
return
|
||||
}
|
||||
|
||||
db := middle.GetDB(r)
|
||||
|
||||
err = db.Update(func(tx *buntdb.Tx) error {
|
||||
reply, err := resources.GetReply(tx, r.Host, board, trid, rid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reply.Text = "[deleted]"
|
||||
reply.Metadata["deleted"] = "yes"
|
||||
reply.Image = nil
|
||||
reply.Thumbnail = nil
|
||||
err = resources.UpdateReply(tx, r.Host, board, reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/%s/%d/thread.html", board, trid), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func serveLogin(w http.ResponseWriter, r *http.Request) {
|
||||
dat := bytes.NewBuffer([]byte{})
|
||||
err := loginTmpl.Execute(dat, middle.GetBaseCtx(r))
|
||||
@@ -150,41 +91,3 @@ func servePanel(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeContent(w, r, "panel.html", time.Now(),
|
||||
bytes.NewReader(dat.Bytes()))
|
||||
}
|
||||
|
||||
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
sess := middle.GetSession(r)
|
||||
if sess == nil {
|
||||
http.Redirect(w, r, "/admin/index.html", http.StatusSeeOther)
|
||||
}
|
||||
session.Remove(sess, w)
|
||||
http.Redirect(w, r, "/admin/index.html", http.StatusSeeOther)
|
||||
}
|
||||
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
}
|
||||
db := middle.GetDB(r)
|
||||
|
||||
var admin = &resources.AdminPass{}
|
||||
err = db.View(func(tx *buntdb.Tx) error {
|
||||
var err error
|
||||
admin, err = resources.GetAdmin(tx, r.FormValue("id"))
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
err = errw.MakeErrorWithTitle("Access Denied", "User or Password Invalid")
|
||||
errw.ErrorWriter(err, w, r)
|
||||
}
|
||||
err = admin.VerifyLogin(r.FormValue("pass"))
|
||||
if err != nil {
|
||||
err = errw.MakeErrorWithTitle("Access Denied", "User or Password Invalid")
|
||||
errw.ErrorWriter(err, w, r)
|
||||
}
|
||||
sess := session.NewSessionOptions(&session.SessOptions{
|
||||
CAttrs: map[string]interface{}{"mode": "admin"},
|
||||
})
|
||||
session.Add(sess, w)
|
||||
|
||||
http.Redirect(w, r, "/admin/panel.html", http.StatusSeeOther)
|
||||
}
|
||||
|
50
http/admin/login.go
Normal file
50
http/admin/login.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"github.com/icza/session"
|
||||
"github.com/tidwall/buntdb"
|
||||
"go.rls.moe/nyx/http/errw"
|
||||
"go.rls.moe/nyx/http/middle"
|
||||
"go.rls.moe/nyx/resources"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
sess := middle.GetSession(r)
|
||||
if sess == nil {
|
||||
http.Redirect(w, r, "/admin/index.html", http.StatusSeeOther)
|
||||
}
|
||||
session.Remove(sess, w)
|
||||
http.Redirect(w, r, "/admin/index.html", http.StatusSeeOther)
|
||||
}
|
||||
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
}
|
||||
db := middle.GetDB(r)
|
||||
|
||||
var admin = &resources.AdminPass{}
|
||||
err = db.View(func(tx *buntdb.Tx) error {
|
||||
var err error
|
||||
admin, err = resources.GetAdmin(tx, r.FormValue("id"))
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
err = errw.MakeErrorWithTitle("Access Denied", "User or Password Invalid")
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
err = admin.VerifyLogin(r.FormValue("pass"))
|
||||
if err != nil {
|
||||
err = errw.MakeErrorWithTitle("Access Denied", "User or Password Invalid")
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
sess := session.NewSessionOptions(&session.SessOptions{
|
||||
CAttrs: map[string]interface{}{"mode": "admin"},
|
||||
})
|
||||
session.Add(sess, w)
|
||||
|
||||
http.Redirect(w, r, "/admin/panel.html", http.StatusSeeOther)
|
||||
}
|
92
http/admin/newadmin.go
Normal file
92
http/admin/newadmin.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"github.com/tidwall/buntdb"
|
||||
"go.rls.moe/nyx/http/errw"
|
||||
"go.rls.moe/nyx/http/middle"
|
||||
"go.rls.moe/nyx/resources"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func handleDelAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
sess := middle.GetSession(r)
|
||||
if !middle.IsAdminSession(sess) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
db := middle.GetDB(r)
|
||||
|
||||
adminID := r.FormValue("adminid")
|
||||
if len(adminID) > 255 {
|
||||
errw.ErrorWriter(errw.MakeErrorWithTitle("Too long", "The ID of the administrator is too long"), w, r)
|
||||
return
|
||||
}
|
||||
if len(adminID) < 4 {
|
||||
errw.ErrorWriter(errw.MakeErrorWithTitle("Too short", "The ID of the administrator is too short"), w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if err = db.Update(func(tx *buntdb.Tx) error {
|
||||
return resources.DelAdmin(tx, adminID)
|
||||
}); err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/admin/panel.html", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func handleNewAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
sess := middle.GetSession(r)
|
||||
if !middle.IsAdminSession(sess) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
db := middle.GetDB(r)
|
||||
|
||||
var admin = &resources.AdminPass{}
|
||||
|
||||
admin.ID = r.FormValue("adminid")
|
||||
if len(admin.ID) > 255 {
|
||||
errw.ErrorWriter(errw.MakeErrorWithTitle("Too long", "The ID of the administrator is too long"), w, r)
|
||||
return
|
||||
}
|
||||
if len(admin.ID) < 4 {
|
||||
errw.ErrorWriter(errw.MakeErrorWithTitle("Too short", "The ID of the administrator is too short"), w, r)
|
||||
return
|
||||
}
|
||||
if len(r.FormValue("adminpass")) > 255 {
|
||||
errw.ErrorWriter(errw.MakeErrorWithTitle("Too long", "The Password of the administrator is too long"), w, r)
|
||||
return
|
||||
}
|
||||
if len(r.FormValue("adminpass")) < 12 {
|
||||
errw.ErrorWriter(errw.MakeErrorWithTitle("Too short", "The Password of the administrator is too short"), w, r)
|
||||
return
|
||||
}
|
||||
admin.HashLogin(r.FormValue("adminpass"))
|
||||
|
||||
if err = db.Update(func(tx *buntdb.Tx) error {
|
||||
return resources.NewAdmin(tx, admin)
|
||||
}); err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/admin/panel.html", http.StatusSeeOther)
|
||||
}
|
@@ -11,12 +11,7 @@ import (
|
||||
|
||||
func handleNewBoard(w http.ResponseWriter, r *http.Request) {
|
||||
sess := middle.GetSession(r)
|
||||
if sess == nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized"))
|
||||
return
|
||||
}
|
||||
if sess.CAttr("mode") != "admin" {
|
||||
if !middle.IsAdminSession(sess) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized"))
|
||||
return
|
||||
|
@@ -19,20 +19,53 @@
|
||||
<input type="submit" value="Logout" />
|
||||
</form>
|
||||
</div>
|
||||
<div class="panel boardmgr">
|
||||
<form method="POST" action="/admin/new_board.sh">
|
||||
<input
|
||||
type="hidden"
|
||||
name="csrf_token"
|
||||
value="{{ .CSRFToken }}" />
|
||||
<input type="text" placeholder="shortname" name="shortname"/>
|
||||
<input type="text" placeholder="longname" name="longname"/>
|
||||
<input type="submit" value="Create Board" />
|
||||
<input type="reset" value="Reset" />
|
||||
<br clear="left" /><hr />
|
||||
<div class="postarea">
|
||||
<form id="postform1" action="/admin/new_board.sh" method="POST">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Action
|
||||
</td>
|
||||
<td>
|
||||
New Board
|
||||
<input
|
||||
type="hidden"
|
||||
name="csrf_token"
|
||||
value="{{ .CSRFToken }}" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Short Name
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" placeholder="shortname" name="shortname"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Long Name
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" placeholder="longname" name="longname"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock"></td>
|
||||
<td>
|
||||
<input type="submit" value="Create Board" />
|
||||
<input type="reset" value="Reset" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
<br clear="left" /><hr />
|
||||
<div class="postarea">
|
||||
<form id="postform1" action="/admin/new_admin.sh" method="POST">
|
||||
<form id="postform2" action="/admin/new_admin.sh" method="POST">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
@@ -53,8 +86,10 @@
|
||||
</td>
|
||||
<td>
|
||||
<input type="text"
|
||||
minlength="8"
|
||||
name="id" />
|
||||
minlength="4"
|
||||
maxlength="255"
|
||||
name="adminid"
|
||||
required />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -65,7 +100,83 @@
|
||||
<input type="password"
|
||||
minlength="12"
|
||||
maxlength="255"
|
||||
name="id" />
|
||||
name="adminpass"
|
||||
required />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock"></td>
|
||||
<td>
|
||||
<input type="submit" value="Create Admin ID" />
|
||||
<input type="reset" value="Reset" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
<br clear="left" /><hr />
|
||||
<div class="postarea">
|
||||
<form id="postform3" action="/admin/del_admin.sh" method="POST">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Action
|
||||
</td>
|
||||
<td>
|
||||
Delete Administrator
|
||||
<input
|
||||
type="hidden"
|
||||
name="csrf_token"
|
||||
value="{{ .CSRFToken }}" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
ID
|
||||
</td>
|
||||
<td>
|
||||
<input type="text"
|
||||
minlength="4"
|
||||
maxlength="255"
|
||||
name="adminid"
|
||||
required />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock"></td>
|
||||
<td>
|
||||
<input type="submit" value="Delete Admin ID" />
|
||||
<input type="reset" value="Reset" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
<br clear="left" /><hr />
|
||||
<div class="postarea">
|
||||
<form id="postform4" action="/admin/cleanup.sh" method="POST">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Action
|
||||
</td>
|
||||
<td>
|
||||
Cleanup Database
|
||||
<input
|
||||
type="hidden"
|
||||
name="csrf_token"
|
||||
value="{{ .CSRFToken }}" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock"></td>
|
||||
<td>
|
||||
<input type="submit" value="Start Cleanup" />
|
||||
<input type="reset" value="Reset" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
125
http/admin/res/status.html
Normal file
125
http/admin/res/status.html
Normal file
@@ -0,0 +1,125 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.Config.Site.Title}} Status</title>
|
||||
<link rel="stylesheet" href="/@/style.css">
|
||||
<link rel="stylesheet" href="/@/custom.css">
|
||||
<link rel="stylesheet" href="/@/admin.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="banner logo">
|
||||
<h2>Runtime Statistics</h2>
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
<div class="postarea">
|
||||
<form id="postform1" action="/admin/new_board.sh" method="POST">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><td class="postblock">
|
||||
Start Time
|
||||
</td><td>
|
||||
{{.Uptime.human}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
Uptime (Precise)
|
||||
</td><td>
|
||||
{{ .Uptime.hours | printf "%.4f"}} hours<br/>
|
||||
{{ .Uptime.seconds | printf "%.4f"}} seconds
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
Num. CPU
|
||||
</td><td>
|
||||
{{.GC.numcpu}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
Num. GoRoutines
|
||||
</td><td>
|
||||
{{.GC.numgor}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
Go Version<br/>
|
||||
Arch/OS/Compiler
|
||||
</td><td>
|
||||
{{.GC.version}}<br/>
|
||||
{{.GC.arch}} / {{.GC.os}} / {{.GC.compiler}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
Current Allocs
|
||||
</td><td>
|
||||
{{.GC.memory.alloc}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
Cumulative Allocs
|
||||
</td><td>
|
||||
{{.GC.memory.calloc}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
Used Sys Memory
|
||||
</td><td>
|
||||
{{.GC.memory.sysmem}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
Pointer Lookups
|
||||
</td><td>
|
||||
{{.GC.memory.lookups}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
MAllocs
|
||||
</td><td>
|
||||
{{.GC.memory.mallocs}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
MFrees
|
||||
</td><td>
|
||||
{{.GC.memory.frees}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
Live Objects
|
||||
</td><td>
|
||||
{{.GC.memory.liveobj}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
Heap Allocated
|
||||
</td><td>
|
||||
{{.GC.memory.heapalloc}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
Heap Released
|
||||
</td><td>
|
||||
{{.GC.memory.heaprelease}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
GC Metadata
|
||||
</td><td>
|
||||
{{.GC.memory.gcmeta}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
GC Pause
|
||||
</td><td>
|
||||
{{.GC.memory.pause}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
GC Invokations
|
||||
</td><td>
|
||||
{{.GC.memory.gctimes}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
GC Forced
|
||||
</td><td>
|
||||
{{.GC.memory.fgctimes}}
|
||||
</td></tr>
|
||||
<tr><td class="postblock">
|
||||
GC CPU Usage
|
||||
</td><td>
|
||||
{{.GC.memory.cpufrac}}
|
||||
</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
<br clear="left" /><hr />
|
||||
</body>
|
||||
</html>
|
87
http/admin/status.go
Normal file
87
http/admin/status.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/dustin/go-humanize"
|
||||
"go.rls.moe/nyx/http/errw"
|
||||
"go.rls.moe/nyx/http/middle"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var memStat = map[string]interface{}{}
|
||||
var memStatLock = new(sync.RWMutex)
|
||||
|
||||
var startTime = time.Now().UTC()
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
update()
|
||||
ticker := time.Tick(time.Second * 10)
|
||||
for _ = range ticker {
|
||||
update()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func update() {
|
||||
memStatLock.Lock()
|
||||
defer memStatLock.Unlock()
|
||||
memStat["Uptime"] = uptime()
|
||||
memStat["GC"] = gcStat()
|
||||
}
|
||||
func uptime() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"human": humanize.Time(startTime),
|
||||
"hours": time.Now().Sub(startTime).Hours(),
|
||||
"seconds": time.Now().Sub(startTime).Seconds(),
|
||||
}
|
||||
}
|
||||
|
||||
func gcStat() map[string]interface{} {
|
||||
m := &runtime.MemStats{}
|
||||
runtime.ReadMemStats(m)
|
||||
|
||||
mem := map[string]interface{}{}
|
||||
mem["alloc"] = fmt.Sprintf("%.5f GiB", float64(m.Alloc)/1024/1024/1024)
|
||||
mem["calloc"] = fmt.Sprintf("%.5f GiB", float64(m.TotalAlloc)/1024/1024/1024)
|
||||
mem["sysmem"] = fmt.Sprintf("%.5f MiB", float64(m.Sys)/1024/1024)
|
||||
mem["lookups"] = fmt.Sprintf("× %d", m.Lookups)
|
||||
mem["mallocs"] = fmt.Sprintf("× %d", m.Mallocs)
|
||||
mem["frees"] = fmt.Sprintf("× %d", m.Frees)
|
||||
mem["liveobj"] = fmt.Sprintf("× %d", m.Mallocs-m.Frees)
|
||||
mem["heapalloc"] = fmt.Sprintf("%.5f MiB", float64(m.HeapSys)/1024/1024)
|
||||
mem["heaprelease"] = fmt.Sprintf("%.5f MiB", float64(m.HeapReleased)/1024/1024)
|
||||
mem["gcmeta"] = fmt.Sprintf("%.5f MiB", float64(m.GCSys)/1024/1024)
|
||||
mem["pause"] = fmt.Sprintf("%.5f min", float64(m.PauseTotalNs)/1000/1000/1000/60)
|
||||
mem["gctimes"] = fmt.Sprintf("× %d", m.NumGC)
|
||||
mem["fgctimes"] = fmt.Sprintf("× %d", m.NumForcedGC)
|
||||
mem["cpufrac"] = fmt.Sprintf("%.5f %%", m.GCCPUFraction*100)
|
||||
return map[string]interface{}{
|
||||
"numcpu": runtime.NumCPU(),
|
||||
"numgor": runtime.NumGoroutine(),
|
||||
"version": runtime.Version(),
|
||||
"arch": runtime.GOARCH,
|
||||
"os": runtime.GOOS,
|
||||
"compiler": runtime.Compiler,
|
||||
"memory": mem,
|
||||
}
|
||||
}
|
||||
func serveStatus(w http.ResponseWriter, r *http.Request) {
|
||||
memStatLock.RLock()
|
||||
defer memStatLock.RUnlock()
|
||||
ctx := middle.GetBaseCtx(r)
|
||||
ctx["Uptime"] = memStat["Uptime"]
|
||||
ctx["GC"] = memStat["GC"]
|
||||
dat := bytes.NewBuffer([]byte{})
|
||||
err := statusTmpl.Execute(dat, ctx)
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
http.ServeContent(w, r, "panel.html", time.Now(),
|
||||
bytes.NewReader(dat.Bytes()))
|
||||
}
|
Reference in New Issue
Block a user