wrappd.sh/internal/web/utils.go

196 lines
4.1 KiB
Go

package web
import (
"bufio"
"encoding/json"
"fmt"
"html/template"
"io"
"log"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"github.com/go-echarts/go-echarts/v2/charts"
"github.com/go-echarts/go-echarts/v2/opts"
)
func loadTemplates(name string) (*template.Template, error) {
funcs := template.FuncMap{
"safeHTML": func(s string) interface{} { return template.HTML(s) },
}
templates, err := loadTemplatesFromDir("internal/web/templates")
if err != nil {
log.Printf("Error loading templates: %v", err)
return nil, err
}
return template.New(name).Funcs(funcs).ParseFiles(templates...)
}
func ConvertAndSort[K comparable](data map[K]int, limit int) []CommandStat {
var result []CommandStat
for key, count := range data {
result = append(result, CommandStat{
Command: fmt.Sprintf("%v", key),
Count: count,
})
}
sort.Slice(result, func(i, j int) bool {
return result[i].Count > result[j].Count
})
if len(result) > limit {
result = result[:limit]
}
return result
}
func parseHistoryFile(file http.File) (map[string]int, error) {
commandCounts := make(map[string]int)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
command := strings.Fields(line)[0]
commandCounts[command]++
}
if err := scanner.Err(); err != nil {
return nil, err
}
return commandCounts, nil
}
func generateTopCommands(commandCounts map[string]int, topN int) []CommandStat {
stats := make([]CommandStat, 0, len(commandCounts))
for cmd, count := range commandCounts {
stats = append(stats, CommandStat{Command: cmd, Count: count})
}
sort.Slice(stats, func(i, j int) bool {
return stats[i].Count > stats[j].Count
})
if topN < len(stats) {
stats = stats[:topN]
}
return stats
}
func createBarChart(stats []CommandStat) *charts.Bar {
bar := charts.NewBar()
bar.SetGlobalOptions(
charts.WithTitleOpts(opts.Title{
Title: "Top Commands",
Subtitle: "Most used commands",
}),
charts.WithXAxisOpts(opts.XAxis{
AxisLabel: &opts.AxisLabel{
Show: opts.Bool(true),
Interval: "0",
Rotate: 45,
},
}),
charts.WithLegendOpts(opts.Legend{
TextStyle: &opts.TextStyle{
Color: "#888",
},
}),
)
commands := make([]string, len(stats))
counts := make([]int, len(stats))
for i, stat := range stats {
commands[i] = stat.Command
counts[i] = stat.Count
}
bar.SetXAxis(commands).
AddSeries("Frequency", generateBarItems(counts))
return bar
}
func generateBarItems(data []int) []opts.BarData {
items := make([]opts.BarData, len(data))
for i, v := range data {
items[i] = opts.BarData{Value: v}
}
return items
}
func saveResults(uniqueID string, stats map[string]interface{}) {
filename := "results/" + uniqueID + ".json"
file, err := os.Create(filename)
if err != nil {
log.Fatal(err)
}
defer file.Close()
json.NewEncoder(file).Encode(stats)
}
func loadResults(uniqueID string) (map[string]interface{}, error) {
filename := "results/" + uniqueID + ".json"
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
var stats map[string]interface{}
err = json.NewDecoder(file).Decode(&stats)
return stats, err
}
func loadTemplatesFromDir(dir string) ([]string, error) {
var templates []string
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && filepath.Ext(path) == ".html" {
templates = append(templates, path)
}
return nil
})
return templates, err
}
func isTextFileAndSizeOk(file multipart.File, size int64) (bool, error) {
if size > 1*1024*1024 {
return false, fmt.Errorf("File size exceeds 1MB")
}
buffer := make([]byte, 512)
_, err := file.Read(buffer)
if err != nil && err != io.EOF {
return false, fmt.Errorf("Error reading file: %v", err)
}
mimeType := http.DetectContentType(buffer)
if !strings.HasPrefix(mimeType, "text/") {
return false, fmt.Errorf("File is not a text file (mimetype: %s)", mimeType)
}
_, err = file.Seek(0, 0)
if err != nil {
return false, fmt.Errorf("Error seeking file: %v", err)
}
return true, nil
}