added stats handler to read stats files

This commit is contained in:
cubixle
2024-04-23 21:28:03 +01:00
parent 2097bb2528
commit 25c7b64fba
+109 -19
View File
@@ -1,12 +1,15 @@
package main package main
import ( import (
"cmp"
"fmt" "fmt"
"html/template"
"io" "io"
"log" "log"
"log/slog" "log/slog"
"math/rand" "math/rand"
"net/http" "net/http"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
@@ -20,24 +23,16 @@ import (
var ( var (
stats = map[string]int{} stats = map[string]int{}
ticker = time.NewTicker(10 * time.Second) ticker = time.NewTicker(10 * time.Minute)
) )
func getEnv(key, fallback string) string {
value := os.Getenv(key)
if value == "" {
return fallback
}
return value
}
func main() { func main() {
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo, Level: slog.LevelDebug,
})) }))
slog.SetDefault(logger) slog.SetDefault(logger)
domain := getEnv("DOMAIN", "0.0.0.0:8070") domain := cmp.Or(os.Getenv("DOMAIN"), "0.0.0.0:8070")
srv := http.NewServeMux() srv := http.NewServeMux()
srv.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) { srv.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
@@ -51,6 +46,8 @@ func main() {
_, _ = w.Write([]byte(``)) _, _ = w.Write([]byte(``))
}) })
srv.HandleFunc("/stats", fileHandler)
srv.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { srv.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// generate links1 to 7 from the names list and populate the template // generate links1 to 7 from the names list and populate the template
// with the links // with the links
@@ -61,7 +58,7 @@ func main() {
"x_forwarded_for", r.Header.Get("X-Forwarded-For"), "x_forwarded_for", r.Header.Get("X-Forwarded-For"),
) )
if !agents.IsCrawler(r.UserAgent()) { if agents.IsCrawler(r.UserAgent()) {
slog.Info("crawler detected", "user_agent", r.UserAgent()) slog.Info("crawler detected", "user_agent", r.UserAgent())
// remove all commas from simpler CSV handling. // remove all commas from simpler CSV handling.
ua := strings.ReplaceAll(r.UserAgent(), ",", "") ua := strings.ReplaceAll(r.UserAgent(), ",", "")
@@ -73,7 +70,7 @@ func main() {
} }
currentName := "Ziggy" currentName := "Ziggy"
// get current name from the subdomai#e8c4c2n // get current name from the subdomain
if strings.Contains(r.Host, ".") { if strings.Contains(r.Host, ".") {
currentName = strings.Split(r.Host, ".")[0] currentName = strings.Split(r.Host, ".")[0]
currentName = strings.ReplaceAll(currentName, "-", " ") currentName = strings.ReplaceAll(currentName, "-", " ")
@@ -83,7 +80,7 @@ func main() {
baseLink := "http://%s." + domain + "/" baseLink := "http://%s." + domain + "/"
content := template content := indexTemplate
content = strings.ReplaceAll(content, "{{img}}", img) content = strings.ReplaceAll(content, "{{img}}", img)
content = strings.ReplaceAll(content, "{{current_name}}", currentName) content = strings.ReplaceAll(content, "{{current_name}}", currentName)
@@ -127,12 +124,91 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
func writeStatsToFile() error { func safeJoin(baseDir, targetDir string) (string, error) {
fileDir := getEnv("LOG_FILE_DIR", "./logs/helprob") // Clean and absolute paths
if fileDir == "" { basePath, err := filepath.Abs(filepath.Clean(baseDir))
return nil if err != nil {
return "", err
} }
targetPath, err := filepath.Abs(filepath.Clean(filepath.Join(basePath, targetDir)))
if err != nil {
return "", err
}
// Prevent directory traversal
if !strings.HasPrefix(targetPath, basePath) {
return "", fmt.Errorf("invalid directory traversal attempt")
}
return targetPath, nil
}
func fileHandler(w http.ResponseWriter, r *http.Request) {
fileDir := cmp.Or(os.Getenv("LOG_FILE_DIR"), "logs/helprob")
requestedDir, err := url.QueryUnescape(r.URL.Query().Get("dir"))
if err != nil {
http.Error(w, "Invalid path.", http.StatusBadRequest)
return
}
path, err := safeJoin(fileDir, requestedDir)
if err != nil {
http.Error(w, "Invalid path.", http.StatusBadRequest)
return
}
slog.Debug("reading path", "path", path)
fileInfo, err := os.Stat(path)
if err != nil {
http.Error(w, "File not found.", http.StatusNotFound)
return
}
slog.Debug("reading path", "path", path, "info", fileInfo)
if fileInfo.IsDir() {
files, err := os.ReadDir(path)
if err != nil {
http.Error(w, "Could not read directory.", http.StatusInternalServerError)
return
}
entries := make([]string, 0, len(files))
for _, file := range files {
entries = append(entries, url.QueryEscape(file.Name()))
}
fileTemplate.Execute(w, struct {
BaseDir string
Path string
Entries []string
}{
BaseDir: fileDir,
Path: requestedDir,
Entries: entries,
})
} else {
// Handle CSV file viewing
if strings.HasSuffix(path, ".csv") {
data, err := os.ReadFile(path)
if err != nil {
http.Error(w, "Could not read file.", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Write(data)
} else {
http.Error(w, "Unsupported file type.", http.StatusUnsupportedMediaType)
}
}
}
func writeStatsToFile() error {
fileDir := cmp.Or(os.Getenv("LOG_FILE_DIR"), "./logs/helprob")
slog.Info("Configured to write stats file", "destination", fileDir) slog.Info("Configured to write stats file", "destination", fileDir)
for range ticker.C { for range ticker.C {
@@ -206,7 +282,21 @@ func writeStatsToFile() error {
return nil return nil
} }
var template = ` var fileTemplate = template.Must(template.New("files").Parse(`
<html>
<head><title>Stats</title></head>
<body>
<h1>Stats</h1>
<ul>
{{- range .Entries}}
<li><a href="/stats?dir={{$.Path}}/{{.}}">{{.}}</a></li>
{{- end}}
</ul>
</body>
</html>
`))
var indexTemplate = `
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>