added static page
This commit is contained in:
parent
45b565a672
commit
c7057d7853
37
main.go
37
main.go
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -21,6 +22,9 @@ import (
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed static
|
||||||
|
var staticFiles embed.FS
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Server ServerConfig `yaml:"server"`
|
Server ServerConfig `yaml:"server"`
|
||||||
PLC PLCConfig `yaml:"plc"`
|
PLC PLCConfig `yaml:"plc"`
|
||||||
|
|
@ -849,6 +853,8 @@ func main() {
|
||||||
go startDBCleanup(db)
|
go startDBCleanup(db)
|
||||||
go startPLCPoller()
|
go startPLCPoller()
|
||||||
|
|
||||||
|
// Serve embedded static assets (tailwind.min.js, chart.umd.min.js)
|
||||||
|
http.Handle("/static/", http.FileServer(http.FS(staticFiles)))
|
||||||
http.HandleFunc("/", serveUI)
|
http.HandleFunc("/", serveUI)
|
||||||
http.HandleFunc("/api/data", apiData)
|
http.HandleFunc("/api/data", apiData)
|
||||||
http.HandleFunc("/api/history", apiHistory)
|
http.HandleFunc("/api/history", apiHistory)
|
||||||
|
|
@ -865,11 +871,9 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{{.Title}}</title>
|
<title>{{.Title}}</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="/static/tailwind.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
<script src="/static/chart.umd.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Space+Grotesk:wght@500;600;700&display=swap');
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--bg1: #050816;
|
--bg1: #050816;
|
||||||
--bg2: #0b1224;
|
--bg2: #0b1224;
|
||||||
|
|
@ -879,7 +883,7 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Inter', system-ui, sans-serif;
|
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at 10% 10%, rgba(34,211,238,0.12), transparent 18%),
|
radial-gradient(circle at 10% 10%, rgba(34,211,238,0.12), transparent 18%),
|
||||||
radial-gradient(circle at 90% 10%, rgba(168,85,247,0.14), transparent 18%),
|
radial-gradient(circle at 90% 10%, rgba(168,85,247,0.14), transparent 18%),
|
||||||
|
|
@ -887,8 +891,6 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
color: #f4f4f5;
|
color: #f4f4f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title { font-family: 'Space Grotesk', sans-serif; }
|
|
||||||
|
|
||||||
.glass {
|
.glass {
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
backdrop-filter: blur(14px);
|
backdrop-filter: blur(14px);
|
||||||
|
|
@ -993,14 +995,14 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
<div class="w-[92vw] max-w-[1800px] mx-auto p-4 md:p-8 min-h-screen">
|
<div class="w-[92vw] max-w-[1800px] mx-auto p-4 md:p-8 min-h-screen">
|
||||||
<div id="alarm-banner" class="hidden mb-6 bg-red-600/90 border border-red-500 text-white px-8 py-4 rounded-2xl flex items-center justify-between text-lg font-medium">
|
<div id="alarm-banner" class="hidden mb-6 bg-red-600/90 border border-red-500 text-white px-8 py-4 rounded-2xl flex items-center justify-between text-lg font-medium">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="text-2xl">⚠️</span>
|
<span class="text-2xl">⚠️</span>
|
||||||
<span id="alarm-text">CRITICAL ALARM ACTIVE</span>
|
<span id="alarm-text">CRITICAL ALARM ACTIVE</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-6 xl:flex-row xl:items-center xl:justify-between mb-8">
|
<div class="flex flex-col gap-6 xl:flex-row xl:items-center xl:justify-between mb-8">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="title text-4xl md:text-5xl xl:text-6xl font-semibold tracking-tighter bg-gradient-to-r from-sky-300 to-violet-300 bg-clip-text text-transparent">{{.Title}}</h1>
|
<h1 class="text-4xl md:text-5xl xl:text-6xl font-semibold tracking-tighter bg-gradient-to-r from-sky-300 to-violet-300 bg-clip-text text-transparent">{{.Title}}</h1>
|
||||||
<p class="text-zinc-400 mt-2 text-base md:text-lg">{{.Subtitle}}</p>
|
<p class="text-zinc-400 mt-2 text-base md:text-lg">{{.Subtitle}}</p>
|
||||||
<p class="text-zinc-500 mt-1 text-sm font-mono">MAX_TONNAGE = {{printf "%.1f" .MaxTonnage}} {{.UnitForce}}</p>
|
<p class="text-zinc-500 mt-1 text-sm font-mono">MAX_TONNAGE = {{printf "%.1f" .MaxTonnage}} {{.UnitForce}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1195,7 +1197,8 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function colorToCss(c, a = 1) {
|
function colorToCss(c, a) {
|
||||||
|
a = a === undefined ? 1 : a;
|
||||||
return 'rgba(' + c.r + ',' + c.g + ',' + c.b + ',' + a + ')';
|
return 'rgba(' + c.r + ',' + c.g + ',' + c.b + ',' + a + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1241,7 +1244,8 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
return colorMix(yellow, red, t);
|
return colorMix(yellow, red, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawArc(ctx, cx, cy, r, a1, a2, stroke, width, shadowBlur = 0) {
|
function drawArc(ctx, cx, cy, r, a1, a2, stroke, width, shadowBlur) {
|
||||||
|
shadowBlur = shadowBlur || 0;
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(cx, cy, r, a1, a2, false);
|
ctx.arc(cx, cy, r, a1, a2, false);
|
||||||
|
|
@ -1292,7 +1296,6 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
|
|
||||||
drawArc(ctx, cx, cy, radius, START_ANGLE, END_ANGLE, 'rgba(255,255,255,0.06)', trackWidth + 10, 0);
|
drawArc(ctx, cx, cy, radius, START_ANGLE, END_ANGLE, 'rgba(255,255,255,0.06)', trackWidth + 10, 0);
|
||||||
drawColoredBand(ctx, cx, cy, radius, trackWidth);
|
drawColoredBand(ctx, cx, cy, radius, trackWidth);
|
||||||
|
|
||||||
drawArc(ctx, cx, cy, radius, valueAngle, END_ANGLE, 'rgba(9,9,11,0.60)', trackWidth - 1, 0);
|
drawArc(ctx, cx, cy, radius, valueAngle, END_ANGLE, 'rgba(9,9,11,0.60)', trackWidth - 1, 0);
|
||||||
drawArc(ctx, cx, cy, radius, START_ANGLE, valueAngle, 'rgba(255,255,255,0.04)', trackWidth - 1, 10);
|
drawArc(ctx, cx, cy, radius, START_ANGLE, valueAngle, 'rgba(255,255,255,0.04)', trackWidth - 1, 10);
|
||||||
|
|
||||||
|
|
@ -1328,7 +1331,7 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.fillStyle = 'rgba(244,244,245,0.96)';
|
ctx.fillStyle = 'rgba(244,244,245,0.96)';
|
||||||
ctx.font = '700 18px Inter, sans-serif';
|
ctx.font = '700 18px system-ui, sans-serif';
|
||||||
|
|
||||||
for (const v of labels) {
|
for (const v of labels) {
|
||||||
const a = valueToAngle(v);
|
const a = valueToAngle(v);
|
||||||
|
|
@ -1386,15 +1389,15 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
|
|
||||||
ctx.fillStyle = '#ffffff';
|
ctx.fillStyle = '#ffffff';
|
||||||
ctx.font = '700 ' + valueFontPx + 'px Space Grotesk, Inter, sans-serif';
|
ctx.font = '700 ' + valueFontPx + 'px system-ui, sans-serif';
|
||||||
ctx.fillText(valueText, cx, cy - 6);
|
ctx.fillText(valueText, cx, cy - 6);
|
||||||
|
|
||||||
ctx.fillStyle = sideAccent;
|
ctx.fillStyle = sideAccent;
|
||||||
ctx.font = '700 18px Inter, sans-serif';
|
ctx.font = '700 18px system-ui, sans-serif';
|
||||||
ctx.fillText(UNIT_PCT, cx, cy + 28);
|
ctx.fillText(UNIT_PCT, cx, cy + 28);
|
||||||
|
|
||||||
ctx.fillStyle = '#a1a1aa';
|
ctx.fillStyle = '#a1a1aa';
|
||||||
ctx.font = '600 16px Inter, sans-serif';
|
ctx.font = '600 16px system-ui, sans-serif';
|
||||||
ctx.fillText((Number(knValue) || 0).toFixed(1) + ' ' + UNIT_FORCE, cx, cy + 54);
|
ctx.fillText((Number(knValue) || 0).toFixed(1) + ' ' + UNIT_FORCE, cx, cy + 54);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1529,7 +1532,7 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
parts.push('IMBALANCE');
|
parts.push('IMBALANCE');
|
||||||
}
|
}
|
||||||
|
|
||||||
text.textContent = 'CRITICAL ALARM ACTIVE • ' + parts.join(' • ');
|
text.textContent = 'CRITICAL ALARM ACTIVE \u2022 ' + parts.join(' \u2022 ');
|
||||||
banner.classList.remove('hidden');
|
banner.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue