346 lines
10 KiB
Go
346 lines
10 KiB
Go
|
|
package ui
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"embed"
|
||
|
|
"html/template"
|
||
|
|
"io/fs"
|
||
|
|
"log"
|
||
|
|
"math"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"path/filepath"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"epigas.gitea.cloud/RiskRancher/core/pkg/auth"
|
||
|
|
"epigas.gitea.cloud/RiskRancher/core/pkg/domain"
|
||
|
|
"epigas.gitea.cloud/RiskRancher/core/pkg/report"
|
||
|
|
)
|
||
|
|
|
||
|
|
//go:embed templates/* templates/components/* static/*
|
||
|
|
var CoreUIFS embed.FS
|
||
|
|
|
||
|
|
var (
|
||
|
|
AppVersion = "dev"
|
||
|
|
AppCommit = "none"
|
||
|
|
)
|
||
|
|
|
||
|
|
var CoreTemplates *template.Template
|
||
|
|
var Pages map[string]*template.Template
|
||
|
|
|
||
|
|
// SetVersionInfo is called by main.go on startup to inject ldflags
|
||
|
|
func SetVersionInfo(version, commit string) {
|
||
|
|
AppVersion = version
|
||
|
|
AppCommit = commit
|
||
|
|
}
|
||
|
|
|
||
|
|
func init() {
|
||
|
|
funcMap := template.FuncMap{"lower": strings.ToLower}
|
||
|
|
Pages = make(map[string]*template.Template)
|
||
|
|
|
||
|
|
var err error
|
||
|
|
|
||
|
|
CoreTemplates, err = template.New("").Funcs(funcMap).ParseFS(CoreUIFS, "templates/*.gohtml", "templates/components/*.gohtml")
|
||
|
|
if err != nil && !strings.Contains(err.Error(), "pattern matches no files") {
|
||
|
|
log.Printf("Warning: Failed to parse master core templates: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
dashTmpl := template.New("").Funcs(funcMap)
|
||
|
|
dashTmpl, err = dashTmpl.ParseFS(CoreUIFS, "templates/base.gohtml", "templates/dashboard.gohtml", "templates/components/*.gohtml")
|
||
|
|
if err != nil {
|
||
|
|
log.Fatalf("FATAL: Failed to parse dashboard shell. Err: %v", err)
|
||
|
|
}
|
||
|
|
Pages["dashboard"] = dashTmpl
|
||
|
|
|
||
|
|
adminTmpl := template.New("").Funcs(funcMap)
|
||
|
|
adminTmpl, err = adminTmpl.ParseFS(CoreUIFS, "templates/base.gohtml", "templates/admin.gohtml", "templates/components/*.gohtml")
|
||
|
|
if err != nil {
|
||
|
|
log.Fatalf("FATAL: Failed to parse admin shell. Err: %v", err)
|
||
|
|
}
|
||
|
|
Pages["admin"] = adminTmpl
|
||
|
|
|
||
|
|
Pages["login"], err = template.New("").Funcs(funcMap).ParseFS(CoreUIFS, "templates/login.gohtml")
|
||
|
|
if err != nil {
|
||
|
|
log.Fatalf("FATAL: Failed to parse login. Err: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
Pages["register"], err = template.New("").Funcs(funcMap).ParseFS(CoreUIFS, "templates/register.gohtml")
|
||
|
|
if err != nil {
|
||
|
|
log.Fatalf("FATAL: Failed to parse register. Err: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
Pages["assets"], err = template.New("").Funcs(funcMap).ParseFS(CoreUIFS, "templates/base.gohtml", "templates/assets.gohtml", "templates/components/*.gohtml")
|
||
|
|
if err != nil {
|
||
|
|
log.Fatalf("FATAL: Failed to parse assets. Err: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
ingestTmpl := template.New("").Funcs(funcMap)
|
||
|
|
ingestTmpl, err = ingestTmpl.ParseFS(CoreUIFS, "templates/base.gohtml", "templates/ingest.gohtml", "templates/components/*.gohtml")
|
||
|
|
if err != nil {
|
||
|
|
log.Fatalf("FATAL: Failed to parse ingest shell. Err: %v", err)
|
||
|
|
}
|
||
|
|
Pages["ingest"] = ingestTmpl
|
||
|
|
|
||
|
|
adapterTmpl := template.New("").Funcs(funcMap)
|
||
|
|
adapterTmpl, err = adapterTmpl.ParseFS(CoreUIFS, "templates/base.gohtml", "templates/adapter_builder.gohtml", "templates/components/*.gohtml")
|
||
|
|
if err != nil {
|
||
|
|
log.Fatalf("FATAL: Failed to parse adapter builder shell. Err: %v", err)
|
||
|
|
}
|
||
|
|
Pages["adapter_builder"] = adapterTmpl
|
||
|
|
|
||
|
|
uploadTmpl := template.New("").Funcs(funcMap)
|
||
|
|
uploadTmpl, err = uploadTmpl.ParseFS(CoreUIFS, "templates/base.gohtml", "templates/report_upload.gohtml", "templates/components/*.gohtml")
|
||
|
|
if err != nil {
|
||
|
|
log.Fatalf("FATAL: Failed to parse report upload template. Err: %v", err)
|
||
|
|
}
|
||
|
|
Pages["report_upload"] = uploadTmpl
|
||
|
|
|
||
|
|
parserTmpl := template.New("").Funcs(funcMap)
|
||
|
|
parserTmpl, err = parserTmpl.ParseFS(CoreUIFS, "templates/base.gohtml", "templates/report_parser.gohtml", "templates/components/*.gohtml")
|
||
|
|
if err != nil {
|
||
|
|
log.Fatalf("FATAL: Failed to parse report parser template. Err: %v", err)
|
||
|
|
}
|
||
|
|
Pages["report_parser"] = parserTmpl
|
||
|
|
}
|
||
|
|
|
||
|
|
func StaticHandler() http.Handler {
|
||
|
|
staticFS, err := fs.Sub(CoreUIFS, "static")
|
||
|
|
if err != nil {
|
||
|
|
log.Fatal("Failed to load embedded static files:", err)
|
||
|
|
}
|
||
|
|
return http.StripPrefix("/static/", http.FileServer(http.FS(staticFS)))
|
||
|
|
}
|
||
|
|
|
||
|
|
type PageData struct {
|
||
|
|
Tickets any
|
||
|
|
CurrentTab string
|
||
|
|
CurrentFilter string
|
||
|
|
CurrentAsset string
|
||
|
|
ReturnedCount int
|
||
|
|
CountCritical int
|
||
|
|
CountOverdue int
|
||
|
|
CountMine int
|
||
|
|
CurrentPage int
|
||
|
|
TotalPages int
|
||
|
|
NextPage int
|
||
|
|
PrevPage int
|
||
|
|
CountVerification int
|
||
|
|
HasNext bool
|
||
|
|
HasPrev bool
|
||
|
|
Version string
|
||
|
|
Commit string
|
||
|
|
}
|
||
|
|
|
||
|
|
func HandleDashboard(store domain.Store) http.HandlerFunc {
|
||
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
userIDVal := r.Context().Value(auth.UserIDKey)
|
||
|
|
if userIDVal == nil {
|
||
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
userID := userIDVal.(int)
|
||
|
|
user, err := store.GetUserByID(r.Context(), userID)
|
||
|
|
if err != nil {
|
||
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if user.GlobalRole == "Sheriff" {
|
||
|
|
http.Redirect(w, r, "/admin", http.StatusSeeOther)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
currentUserEmail := user.Email
|
||
|
|
currentUserRole := user.GlobalRole
|
||
|
|
|
||
|
|
tab := r.URL.Query().Get("tab")
|
||
|
|
if tab == "" {
|
||
|
|
tab = "holding_pen"
|
||
|
|
}
|
||
|
|
|
||
|
|
statusFilter := tab
|
||
|
|
if tab == "holding_pen" {
|
||
|
|
statusFilter = "Waiting to be Triaged"
|
||
|
|
} else if tab == "chute" {
|
||
|
|
statusFilter = "Assigned Out"
|
||
|
|
} else if tab == "verification" {
|
||
|
|
statusFilter = "Pending Verification"
|
||
|
|
}
|
||
|
|
|
||
|
|
filter := r.URL.Query().Get("filter")
|
||
|
|
assetFilter := r.URL.Query().Get("asset")
|
||
|
|
|
||
|
|
pageStr := r.URL.Query().Get("page")
|
||
|
|
page, _ := strconv.Atoi(pageStr)
|
||
|
|
if page < 1 {
|
||
|
|
page = 1
|
||
|
|
}
|
||
|
|
limit := 50
|
||
|
|
offset := (page - 1) * limit
|
||
|
|
|
||
|
|
tickets, totalRecords, metrics, err := store.GetDashboardTickets(
|
||
|
|
r.Context(), statusFilter, filter, assetFilter, currentUserEmail, currentUserRole, limit, offset,
|
||
|
|
)
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
http.Error(w, "Database query error: "+err.Error(), http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
totalPages := int(math.Ceil(float64(totalRecords) / float64(limit)))
|
||
|
|
if totalPages == 0 {
|
||
|
|
totalPages = 1
|
||
|
|
}
|
||
|
|
|
||
|
|
data := PageData{
|
||
|
|
Tickets: tickets,
|
||
|
|
CurrentTab: tab,
|
||
|
|
CurrentFilter: filter,
|
||
|
|
CurrentAsset: assetFilter,
|
||
|
|
ReturnedCount: metrics["returned"],
|
||
|
|
CountCritical: metrics["critical"],
|
||
|
|
CountOverdue: metrics["overdue"],
|
||
|
|
CountMine: metrics["mine"],
|
||
|
|
CountVerification: metrics["verification"],
|
||
|
|
CurrentPage: page,
|
||
|
|
TotalPages: totalPages,
|
||
|
|
NextPage: page + 1,
|
||
|
|
PrevPage: page - 1,
|
||
|
|
HasNext: page < totalPages,
|
||
|
|
HasPrev: page > 1,
|
||
|
|
Version: AppVersion,
|
||
|
|
Commit: AppCommit,
|
||
|
|
}
|
||
|
|
|
||
|
|
var buf bytes.Buffer
|
||
|
|
if err := Pages["dashboard"].ExecuteTemplate(&buf, "base", data); err != nil {
|
||
|
|
http.Error(w, "Template rendering error: "+err.Error(), http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||
|
|
buf.WriteTo(w)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func HandleLoginUI() http.HandlerFunc {
|
||
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||
|
|
if err := Pages["login"].ExecuteTemplate(w, "login", nil); err != nil {
|
||
|
|
http.Error(w, "Template render error: "+err.Error(), http.StatusInternalServerError)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func HandleRegisterUI() http.HandlerFunc {
|
||
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||
|
|
if err := Pages["register"].ExecuteTemplate(w, "register", nil); err != nil {
|
||
|
|
http.Error(w, "Template render error: "+err.Error(), http.StatusInternalServerError)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func HandleAdminDashboard(store domain.Store) http.HandlerFunc {
|
||
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
users, _ := store.GetAllUsers(r.Context())
|
||
|
|
config, _ := store.GetAppConfig(r.Context())
|
||
|
|
slas, _ := store.GetSLAPolicies(r.Context())
|
||
|
|
adapters, _ := store.GetAdapters(r.Context())
|
||
|
|
|
||
|
|
analytics, _ := store.GetSheriffAnalytics(r.Context())
|
||
|
|
activityFeed, _ := store.GetGlobalActivityFeed(r.Context(), 15)
|
||
|
|
syncLogs, _ := store.GetRecentSyncLogs(r.Context(), 10)
|
||
|
|
|
||
|
|
data := map[string]any{
|
||
|
|
"Users": users,
|
||
|
|
"Config": config,
|
||
|
|
"SLAs": slas,
|
||
|
|
"Adapters": adapters,
|
||
|
|
"Analytics": analytics,
|
||
|
|
"Feed": activityFeed,
|
||
|
|
"SyncLogs": syncLogs,
|
||
|
|
"Version": AppVersion,
|
||
|
|
"Commit": AppCommit,
|
||
|
|
}
|
||
|
|
|
||
|
|
var buf bytes.Buffer
|
||
|
|
if err := Pages["admin"].ExecuteTemplate(&buf, "base", data); err != nil {
|
||
|
|
http.Error(w, "Template render error: "+err.Error(), http.StatusInternalServerError)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||
|
|
buf.WriteTo(w)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func HandleIngestUI(store domain.Store) http.HandlerFunc {
|
||
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||
|
|
|
||
|
|
adapters, _ := store.GetAdapters(r.Context())
|
||
|
|
data := map[string]any{
|
||
|
|
"Adapters": adapters,
|
||
|
|
"Version": AppVersion,
|
||
|
|
"Commit": AppCommit,
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := Pages["ingest"].ExecuteTemplate(w, "base", data); err != nil {
|
||
|
|
http.Error(w, "Template render error: "+err.Error(), http.StatusInternalServerError)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func HandleAdapterBuilderUI(store domain.Store) http.HandlerFunc {
|
||
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||
|
|
data := map[string]any{
|
||
|
|
"Filename": r.URL.Query().Get("filename"),
|
||
|
|
"Version": AppVersion,
|
||
|
|
"Commit": AppCommit,
|
||
|
|
}
|
||
|
|
if err := Pages["adapter_builder"].ExecuteTemplate(w, "base", data); err != nil {
|
||
|
|
http.Error(w, "Template render error: "+err.Error(), http.StatusInternalServerError)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func HandleParserUI(store domain.Store) http.HandlerFunc {
|
||
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||
|
|
reportID := r.PathValue("id")
|
||
|
|
|
||
|
|
filePath := filepath.Join(report.UploadDir, reportID)
|
||
|
|
recorder := httptest.NewRecorder()
|
||
|
|
report.ServeDOCXAsHTML(recorder, filePath)
|
||
|
|
safeHTML := template.HTML(recorder.Body.String())
|
||
|
|
|
||
|
|
data := map[string]any{
|
||
|
|
"ReportID": reportID,
|
||
|
|
"RenderedHTML": safeHTML,
|
||
|
|
"Version": AppVersion,
|
||
|
|
"Commit": AppCommit,
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := Pages["report_parser"].ExecuteTemplate(w, "base", data); err != nil {
|
||
|
|
http.Error(w, "Template render error: "+err.Error(), http.StatusInternalServerError)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func HandlePentestUploadUI(store domain.Store) http.HandlerFunc {
|
||
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||
|
|
data := map[string]any{
|
||
|
|
"Version": AppVersion,
|
||
|
|
"Commit": AppCommit,
|
||
|
|
}
|
||
|
|
if err := Pages["report_upload"].ExecuteTemplate(w, "base", data); err != nil {
|
||
|
|
http.Error(w, "Template render error: "+err.Error(), http.StatusInternalServerError)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|