added stacitc pages for alarms, dashbord etc
This commit is contained in:
parent
4af3ce0d88
commit
bf435f9abf
407
main.go
407
main.go
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -35,7 +36,7 @@ import (
|
||||||
//go:embed static
|
//go:embed static
|
||||||
var embeddedStaticFiles embed.FS
|
var embeddedStaticFiles embed.FS
|
||||||
|
|
||||||
const version = "1.0.4"
|
const version = "1.0.3"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Config structs
|
// Config structs
|
||||||
|
|
@ -484,6 +485,59 @@ type HistoryResponse struct {
|
||||||
Points []HistoryPoint `json:"points"`
|
Points []HistoryPoint `json:"points"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HistoryPeakPoint struct {
|
||||||
|
Time string `json:"time"`
|
||||||
|
LeftPercent float64 `json:"left_percent"`
|
||||||
|
RightPercent float64 `json:"right_percent"`
|
||||||
|
TotalPercent float64 `json:"total_percent"`
|
||||||
|
TotalKN float64 `json:"total_kn"`
|
||||||
|
ImbalancePercent float64 `json:"imbalance_percent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HistoryAnalyticsResponse struct {
|
||||||
|
Window string `json:"window"`
|
||||||
|
From string `json:"from"`
|
||||||
|
To string `json:"to"`
|
||||||
|
SampleCount int `json:"sample_count"`
|
||||||
|
LeftAvgPct float64 `json:"left_avg_pct"`
|
||||||
|
RightAvgPct float64 `json:"right_avg_pct"`
|
||||||
|
TotalAvgPct float64 `json:"total_avg_pct"`
|
||||||
|
TotalAvgKN float64 `json:"total_avg_kn"`
|
||||||
|
ImbalanceAvgPct float64 `json:"imbalance_avg_pct"`
|
||||||
|
LeftMaxPct float64 `json:"left_max_pct"`
|
||||||
|
RightMaxPct float64 `json:"right_max_pct"`
|
||||||
|
TotalMaxPct float64 `json:"total_max_pct"`
|
||||||
|
TotalMaxKN float64 `json:"total_max_kn"`
|
||||||
|
ImbalanceMaxPct float64 `json:"imbalance_max_pct"`
|
||||||
|
LeftMinPct float64 `json:"left_min_pct"`
|
||||||
|
RightMinPct float64 `json:"right_min_pct"`
|
||||||
|
TotalMinPct float64 `json:"total_min_pct"`
|
||||||
|
ImbalanceMinPct float64 `json:"imbalance_min_pct"`
|
||||||
|
LeftStdPct float64 `json:"left_std_pct"`
|
||||||
|
RightStdPct float64 `json:"right_std_pct"`
|
||||||
|
TotalStdPct float64 `json:"total_std_pct"`
|
||||||
|
ImbalanceStdPct float64 `json:"imbalance_std_pct"`
|
||||||
|
TotalP95Pct float64 `json:"total_p95_pct"`
|
||||||
|
TotalP99Pct float64 `json:"total_p99_pct"`
|
||||||
|
ImbalanceP95Pct float64 `json:"imbalance_p95_pct"`
|
||||||
|
WarningSamples int `json:"warning_samples"`
|
||||||
|
CriticalSamples int `json:"critical_samples"`
|
||||||
|
ImbalanceWarningSamples int `json:"imbalance_warning_samples"`
|
||||||
|
ImbalanceCriticalSamples int `json:"imbalance_critical_samples"`
|
||||||
|
WarningRatePct float64 `json:"warning_rate_pct"`
|
||||||
|
CriticalRatePct float64 `json:"critical_rate_pct"`
|
||||||
|
ImbalanceWarningRatePct float64 `json:"imbalance_warning_rate_pct"`
|
||||||
|
ImbalanceCriticalRatePct float64 `json:"imbalance_critical_rate_pct"`
|
||||||
|
AlarmTransitions int `json:"alarm_transitions"`
|
||||||
|
WarningEvents int `json:"warning_events"`
|
||||||
|
CriticalEvents int `json:"critical_events"`
|
||||||
|
PLCDisconnects int `json:"plc_disconnects"`
|
||||||
|
PreviousWindowDeltaPct float64 `json:"previous_window_delta_pct"`
|
||||||
|
PreviousImbalanceDeltaPct float64 `json:"previous_imbalance_delta_pct"`
|
||||||
|
TopPeaks []HistoryPeakPoint `json:"top_peaks"`
|
||||||
|
WorstImbalances []HistoryPeakPoint `json:"worst_imbalances"`
|
||||||
|
}
|
||||||
|
|
||||||
type TrendResponse struct {
|
type TrendResponse struct {
|
||||||
Window string `json:"window"`
|
Window string `json:"window"`
|
||||||
AvgPeak5m float32 `json:"avg_peak_5m"`
|
AvgPeak5m float32 `json:"avg_peak_5m"`
|
||||||
|
|
@ -552,6 +606,50 @@ func (s NumericStats) StdDev() float64 {
|
||||||
return math.Sqrt(v)
|
return math.Sqrt(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type runningStats struct {
|
||||||
|
sum float64
|
||||||
|
sumSq float64
|
||||||
|
min float64
|
||||||
|
max float64
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *runningStats) Add(v float64) {
|
||||||
|
if r.count == 0 {
|
||||||
|
r.min = v
|
||||||
|
r.max = v
|
||||||
|
} else {
|
||||||
|
if v < r.min {
|
||||||
|
r.min = v
|
||||||
|
}
|
||||||
|
if v > r.max {
|
||||||
|
r.max = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.sum += v
|
||||||
|
r.sumSq += v * v
|
||||||
|
r.count++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r runningStats) Avg() float64 {
|
||||||
|
if r.count == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return r.sum / float64(r.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r runningStats) StdDev() float64 {
|
||||||
|
if r.count <= 1 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
avg := r.Avg()
|
||||||
|
v := (r.sumSq / float64(r.count)) - (avg * avg)
|
||||||
|
if v < 0 {
|
||||||
|
v = 0
|
||||||
|
}
|
||||||
|
return math.Sqrt(v)
|
||||||
|
}
|
||||||
|
|
||||||
type AlarmTracker struct {
|
type AlarmTracker struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
PLCKnown bool
|
PLCKnown bool
|
||||||
|
|
@ -2162,6 +2260,212 @@ func queryAlarmEvents(ctx context.Context, limit int) ([]AlarmEventAPI, error) {
|
||||||
return events, rows.Err()
|
return events, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func percentileFromSorted(vals []float64, p float64) float64 {
|
||||||
|
if len(vals) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if p <= 0 {
|
||||||
|
return vals[0]
|
||||||
|
}
|
||||||
|
if p >= 1 {
|
||||||
|
return vals[len(vals)-1]
|
||||||
|
}
|
||||||
|
idx := p * float64(len(vals)-1)
|
||||||
|
lo := int(math.Floor(idx))
|
||||||
|
hi := int(math.Ceil(idx))
|
||||||
|
if lo == hi {
|
||||||
|
return vals[lo]
|
||||||
|
}
|
||||||
|
frac := idx - float64(lo)
|
||||||
|
return vals[lo] + (vals[hi]-vals[lo])*frac
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertPeakDescending(peaks []HistoryPeakPoint, candidate HistoryPeakPoint, limit int, by func(HistoryPeakPoint) float64) []HistoryPeakPoint {
|
||||||
|
peaks = append(peaks, candidate)
|
||||||
|
sort.Slice(peaks, func(i, j int) bool { return by(peaks[i]) > by(peaks[j]) })
|
||||||
|
if len(peaks) > limit {
|
||||||
|
peaks = peaks[:limit]
|
||||||
|
}
|
||||||
|
return peaks
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryAlarmCount(ctx context.Context, cutoffNs int64, extraWhere string, args ...any) (int, error) {
|
||||||
|
query := `SELECT COUNT(*) FROM alarm_events WHERE ts_unix_ns >= ?`
|
||||||
|
params := []any{cutoffNs}
|
||||||
|
if strings.TrimSpace(extraWhere) != "" {
|
||||||
|
query += " AND " + extraWhere
|
||||||
|
params = append(params, args...)
|
||||||
|
}
|
||||||
|
var count int
|
||||||
|
if err := db.QueryRowContext(ctx, query, params...).Scan(&count); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryHistoryAnalytics(ctx context.Context, window time.Duration, label string) (HistoryAnalyticsResponse, error) {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
windowNs := window.Nanoseconds()
|
||||||
|
startNs := now.UnixNano() - windowNs
|
||||||
|
cfgSnap := getConfigSnapshot()
|
||||||
|
|
||||||
|
rows, err := db.QueryContext(ctx, `
|
||||||
|
SELECT ts_unix_ns, sila_l_pct, sila_r_pct, sum_pct, sum_kn, imbalance_pct
|
||||||
|
FROM samples
|
||||||
|
WHERE ts_unix_ns >= ?
|
||||||
|
ORDER BY ts_unix_ns ASC
|
||||||
|
`, startNs)
|
||||||
|
if err != nil {
|
||||||
|
return HistoryAnalyticsResponse{}, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var leftStats, rightStats, totalStats, totalKNStats, imbalanceStats runningStats
|
||||||
|
totalValues := make([]float64, 0, 2048)
|
||||||
|
imbalanceValues := make([]float64, 0, 2048)
|
||||||
|
topPeaks := make([]HistoryPeakPoint, 0, 10)
|
||||||
|
worstImbalances := make([]HistoryPeakPoint, 0, 10)
|
||||||
|
warningSamples := 0
|
||||||
|
criticalSamples := 0
|
||||||
|
imbWarningSamples := 0
|
||||||
|
imbCriticalSamples := 0
|
||||||
|
firstTS := int64(0)
|
||||||
|
lastTS := int64(0)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var tsUnix int64
|
||||||
|
var leftPct, rightPct, totalPct, totalKN, imbalancePct float64
|
||||||
|
if err := rows.Scan(&tsUnix, &leftPct, &rightPct, &totalPct, &totalKN, &imbalancePct); err != nil {
|
||||||
|
return HistoryAnalyticsResponse{}, err
|
||||||
|
}
|
||||||
|
if firstTS == 0 {
|
||||||
|
firstTS = tsUnix
|
||||||
|
}
|
||||||
|
lastTS = tsUnix
|
||||||
|
leftStats.Add(leftPct)
|
||||||
|
rightStats.Add(rightPct)
|
||||||
|
totalStats.Add(totalPct)
|
||||||
|
totalKNStats.Add(totalKN)
|
||||||
|
imbalanceStats.Add(imbalancePct)
|
||||||
|
totalValues = append(totalValues, totalPct)
|
||||||
|
imbalanceValues = append(imbalanceValues, imbalancePct)
|
||||||
|
if totalPct >= cfgSnap.Thresholds.WarningPercent {
|
||||||
|
warningSamples++
|
||||||
|
}
|
||||||
|
if totalPct >= cfgSnap.Thresholds.CriticalPercent {
|
||||||
|
criticalSamples++
|
||||||
|
}
|
||||||
|
if imbalancePct >= cfgSnap.Thresholds.ImbalanceWarningPercent {
|
||||||
|
imbWarningSamples++
|
||||||
|
}
|
||||||
|
if imbalancePct >= cfgSnap.Thresholds.ImbalanceCriticalPercent {
|
||||||
|
imbCriticalSamples++
|
||||||
|
}
|
||||||
|
peak := HistoryPeakPoint{
|
||||||
|
Time: time.Unix(0, tsUnix).Local().Format("02.01.2006 15:04:05"),
|
||||||
|
LeftPercent: leftPct,
|
||||||
|
RightPercent: rightPct,
|
||||||
|
TotalPercent: totalPct,
|
||||||
|
TotalKN: totalKN,
|
||||||
|
ImbalancePercent: imbalancePct,
|
||||||
|
}
|
||||||
|
topPeaks = insertPeakDescending(topPeaks, peak, 10, func(p HistoryPeakPoint) float64 { return p.TotalPercent })
|
||||||
|
worstImbalances = insertPeakDescending(worstImbalances, peak, 10, func(p HistoryPeakPoint) float64 { return p.ImbalancePercent })
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return HistoryAnalyticsResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Float64s(totalValues)
|
||||||
|
sort.Float64s(imbalanceValues)
|
||||||
|
|
||||||
|
warnEvents, err := queryAlarmCount(ctx, startNs, `severity = ?`, "warning")
|
||||||
|
if err != nil {
|
||||||
|
return HistoryAnalyticsResponse{}, err
|
||||||
|
}
|
||||||
|
criticalEvents, err := queryAlarmCount(ctx, startNs, `severity = ?`, "critical")
|
||||||
|
if err != nil {
|
||||||
|
return HistoryAnalyticsResponse{}, err
|
||||||
|
}
|
||||||
|
alarmTransitions, err := queryAlarmCount(ctx, startNs, ``)
|
||||||
|
if err != nil {
|
||||||
|
return HistoryAnalyticsResponse{}, err
|
||||||
|
}
|
||||||
|
plcDisconnects, err := queryAlarmCount(ctx, startNs, `source = ? AND code = ?`, "plc", "plc_disconnected")
|
||||||
|
if err != nil {
|
||||||
|
return HistoryAnalyticsResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
prevStartNs := startNs - windowNs
|
||||||
|
prevForce, err := queryNumericStats(ctx, "sum_pct", prevStartNs, startNs)
|
||||||
|
if err != nil {
|
||||||
|
return HistoryAnalyticsResponse{}, err
|
||||||
|
}
|
||||||
|
prevImb, err := queryNumericStats(ctx, "imbalance_pct", prevStartNs, startNs)
|
||||||
|
if err != nil {
|
||||||
|
return HistoryAnalyticsResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := HistoryAnalyticsResponse{
|
||||||
|
Window: label,
|
||||||
|
From: time.Unix(0, firstTS).Local().Format(time.RFC3339),
|
||||||
|
To: time.Unix(0, maxInt64(firstTS, lastTS)).Local().Format(time.RFC3339),
|
||||||
|
SampleCount: totalStats.count,
|
||||||
|
LeftAvgPct: leftStats.Avg(),
|
||||||
|
RightAvgPct: rightStats.Avg(),
|
||||||
|
TotalAvgPct: totalStats.Avg(),
|
||||||
|
TotalAvgKN: totalKNStats.Avg(),
|
||||||
|
ImbalanceAvgPct: imbalanceStats.Avg(),
|
||||||
|
LeftMaxPct: leftStats.max,
|
||||||
|
RightMaxPct: rightStats.max,
|
||||||
|
TotalMaxPct: totalStats.max,
|
||||||
|
TotalMaxKN: totalKNStats.max,
|
||||||
|
ImbalanceMaxPct: imbalanceStats.max,
|
||||||
|
LeftMinPct: leftStats.min,
|
||||||
|
RightMinPct: rightStats.min,
|
||||||
|
TotalMinPct: totalStats.min,
|
||||||
|
ImbalanceMinPct: imbalanceStats.min,
|
||||||
|
LeftStdPct: leftStats.StdDev(),
|
||||||
|
RightStdPct: rightStats.StdDev(),
|
||||||
|
TotalStdPct: totalStats.StdDev(),
|
||||||
|
ImbalanceStdPct: imbalanceStats.StdDev(),
|
||||||
|
TotalP95Pct: percentileFromSorted(totalValues, 0.95),
|
||||||
|
TotalP99Pct: percentileFromSorted(totalValues, 0.99),
|
||||||
|
ImbalanceP95Pct: percentileFromSorted(imbalanceValues, 0.95),
|
||||||
|
WarningSamples: warningSamples,
|
||||||
|
CriticalSamples: criticalSamples,
|
||||||
|
ImbalanceWarningSamples: imbWarningSamples,
|
||||||
|
ImbalanceCriticalSamples: imbCriticalSamples,
|
||||||
|
AlarmTransitions: alarmTransitions,
|
||||||
|
WarningEvents: warnEvents,
|
||||||
|
CriticalEvents: criticalEvents,
|
||||||
|
PLCDisconnects: plcDisconnects,
|
||||||
|
PreviousWindowDeltaPct: totalStats.Avg() - prevForce.Avg,
|
||||||
|
PreviousImbalanceDeltaPct: imbalanceStats.Avg() - prevImb.Avg,
|
||||||
|
TopPeaks: topPeaks,
|
||||||
|
WorstImbalances: worstImbalances,
|
||||||
|
}
|
||||||
|
if resp.SampleCount > 0 {
|
||||||
|
den := float64(resp.SampleCount)
|
||||||
|
resp.WarningRatePct = (float64(resp.WarningSamples) / den) * 100
|
||||||
|
resp.CriticalRatePct = (float64(resp.CriticalSamples) / den) * 100
|
||||||
|
resp.ImbalanceWarningRatePct = (float64(resp.ImbalanceWarningSamples) / den) * 100
|
||||||
|
resp.ImbalanceCriticalRatePct = (float64(resp.ImbalanceCriticalSamples) / den) * 100
|
||||||
|
}
|
||||||
|
if resp.SampleCount == 0 {
|
||||||
|
resp.From = time.Unix(0, startNs).Local().Format(time.RFC3339)
|
||||||
|
resp.To = now.Local().Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxInt64(a, b int64) int64 {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// HTTP helpers
|
// HTTP helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -2270,6 +2574,27 @@ func apiHistory(w http.ResponseWriter, r *http.Request) {
|
||||||
writeJSON(w, http.StatusOK, HistoryResponse{Window: label, Points: points})
|
writeJSON(w, http.StatusOK, HistoryResponse{Window: label, Points: points})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func apiHistoryAnalytics(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !allowMethod(w, r, http.MethodGet) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !requireActiveLicense(w, r) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window, label, err := parseWindow(r.URL.Query().Get("window"))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, `{"error":"invalid window"}`, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := queryHistoryAnalytics(r.Context(), window, label)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("history analytics query failed: %v", err)
|
||||||
|
http.Error(w, `{"error":"history analytics query failed"}`, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeJSON(w, http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
func apiTrend(w http.ResponseWriter, r *http.Request) {
|
func apiTrend(w http.ResponseWriter, r *http.Request) {
|
||||||
if !allowMethod(w, r, http.MethodGet) {
|
if !allowMethod(w, r, http.MethodGet) {
|
||||||
return
|
return
|
||||||
|
|
@ -2313,6 +2638,74 @@ func apiAlarms(w http.ResponseWriter, r *http.Request) {
|
||||||
writeJSON(w, http.StatusOK, AlarmResponse{Events: events})
|
writeJSON(w, http.StatusOK, AlarmResponse{Events: events})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serveEmbeddedHTMLPage(w http.ResponseWriter, embeddedPath string) {
|
||||||
|
data, err := embeddedStaticFiles.ReadFile(embeddedPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("embedded page read error (%s): %v", embeddedPath, err)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
_, _ = w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func redirectToCanonicalPath(w http.ResponseWriter, r *http.Request, canonicalPath string) bool {
|
||||||
|
if r.URL.Path == canonicalPath {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r.URL.Path == canonicalPath+"/" {
|
||||||
|
http.Redirect(w, r, canonicalPath, http.StatusMovedPermanently)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveDashboardAlias(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/dashboard" || r.URL.Path == "/dashboard/" {
|
||||||
|
http.Redirect(w, r, "/", http.StatusMovedPermanently)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveAlarmsPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if redirectToCanonicalPath(w, r, "/alarms") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.URL.Path != "/alarms" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serveEmbeddedHTMLPage(w, "static/alarms.html")
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveHistoryPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if redirectToCanonicalPath(w, r, "/history") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.URL.Path != "/history" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serveEmbeddedHTMLPage(w, "static/history.html")
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveLicensePage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/licence" || r.URL.Path == "/licence/" {
|
||||||
|
http.Redirect(w, r, "/license", http.StatusMovedPermanently)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if redirectToCanonicalPath(w, r, "/license") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.URL.Path != "/license" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serveEmbeddedHTMLPage(w, "static/license.html")
|
||||||
|
}
|
||||||
|
|
||||||
func serveUI(w http.ResponseWriter, r *http.Request) {
|
func serveUI(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path == "/" {
|
if r.URL.Path == "/" {
|
||||||
// Check license before serving the UI
|
// Check license before serving the UI
|
||||||
|
|
@ -2329,6 +2722,7 @@ func serveUI(w http.ResponseWriter, r *http.Request) {
|
||||||
<p><strong>Message:</strong> %s</p>
|
<p><strong>Message:</strong> %s</p>
|
||||||
<p><a href="/api/license/status" style="color:#93c5fd">GET /api/license/status</a></p>
|
<p><a href="/api/license/status" style="color:#93c5fd">GET /api/license/status</a></p>
|
||||||
<p><a href="/api/license/request" style="color:#93c5fd">GET /api/license/request</a></p>
|
<p><a href="/api/license/request" style="color:#93c5fd">GET /api/license/request</a></p>
|
||||||
|
<p><a href="/license" style="color:#93c5fd">Open advanced license page</a></p>
|
||||||
<h3>Paste signed license JSON</h3>
|
<h3>Paste signed license JSON</h3>
|
||||||
<textarea id="licenseText" placeholder='{"app":"force_monitor",...}'></textarea>
|
<textarea id="licenseText" placeholder='{"app":"force_monitor",...}'></textarea>
|
||||||
<div style="margin-top:12px"><button onclick="activate()">Activate license</button></div>
|
<div style="margin-top:12px"><button onclick="activate()">Activate license</button></div>
|
||||||
|
|
@ -2749,12 +3143,23 @@ func main() {
|
||||||
}
|
}
|
||||||
fileServer := http.FileServer(http.FS(staticFS))
|
fileServer := http.FileServer(http.FS(staticFS))
|
||||||
mux.Handle("/static/", http.StripPrefix("/static/", fileServer))
|
mux.Handle("/static/", http.StripPrefix("/static/", fileServer))
|
||||||
|
mux.HandleFunc("/dashboard", serveDashboardAlias)
|
||||||
|
mux.HandleFunc("/dashboard/", serveDashboardAlias)
|
||||||
|
mux.HandleFunc("/alarms", serveAlarmsPage)
|
||||||
|
mux.HandleFunc("/alarms/", serveAlarmsPage)
|
||||||
|
mux.HandleFunc("/history", serveHistoryPage)
|
||||||
|
mux.HandleFunc("/history/", serveHistoryPage)
|
||||||
|
mux.HandleFunc("/license", serveLicensePage)
|
||||||
|
mux.HandleFunc("/license/", serveLicensePage)
|
||||||
|
mux.HandleFunc("/licence", serveLicensePage)
|
||||||
|
mux.HandleFunc("/licence/", serveLicensePage)
|
||||||
mux.HandleFunc("/", serveUI)
|
mux.HandleFunc("/", serveUI)
|
||||||
|
|
||||||
mux.HandleFunc("/api/data", apiData)
|
mux.HandleFunc("/api/data", apiData)
|
||||||
mux.HandleFunc("/api/ui-revision", apiUIRevision)
|
mux.HandleFunc("/api/ui-revision", apiUIRevision)
|
||||||
mux.HandleFunc("/api/config/public", apiPublicConfig)
|
mux.HandleFunc("/api/config/public", apiPublicConfig)
|
||||||
mux.HandleFunc("/api/history", apiHistory)
|
mux.HandleFunc("/api/history", apiHistory)
|
||||||
|
mux.HandleFunc("/api/history/analytics", apiHistoryAnalytics)
|
||||||
mux.HandleFunc("/api/trend", apiTrend)
|
mux.HandleFunc("/api/trend", apiTrend)
|
||||||
mux.HandleFunc("/api/alarms", apiAlarms)
|
mux.HandleFunc("/api/alarms", apiAlarms)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue