added digital gauge and config for disable/enable it
This commit is contained in:
parent
bf7c924705
commit
15e390b693
142
main.go
142
main.go
|
|
@ -30,7 +30,7 @@ import (
|
||||||
//go:embed static
|
//go:embed static
|
||||||
var staticFiles embed.FS
|
var staticFiles embed.FS
|
||||||
|
|
||||||
const version = "0.8.1"
|
const version = "0.8.2"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Config structs
|
// Config structs
|
||||||
|
|
@ -100,6 +100,7 @@ type ModulesConfig struct {
|
||||||
ShowIntelligence *bool `yaml:"show_intelligence,omitempty"`
|
ShowIntelligence *bool `yaml:"show_intelligence,omitempty"`
|
||||||
ShowAlarmTimeline *bool `yaml:"show_alarm_timeline,omitempty"`
|
ShowAlarmTimeline *bool `yaml:"show_alarm_timeline,omitempty"`
|
||||||
ShowGauges *bool `yaml:"show_gauges,omitempty"`
|
ShowGauges *bool `yaml:"show_gauges,omitempty"`
|
||||||
|
ShowGaugeDigital *bool `yaml:"show_gauge_digital,omitempty"`
|
||||||
ShowTrendChart *bool `yaml:"show_trend_chart,omitempty"`
|
ShowTrendChart *bool `yaml:"show_trend_chart,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,6 +172,7 @@ func defaultConfig() Config {
|
||||||
ShowIntelligence: boolPtr(true),
|
ShowIntelligence: boolPtr(true),
|
||||||
ShowAlarmTimeline: boolPtr(true),
|
ShowAlarmTimeline: boolPtr(true),
|
||||||
ShowGauges: boolPtr(true),
|
ShowGauges: boolPtr(true),
|
||||||
|
ShowGaugeDigital: boolPtr(false),
|
||||||
ShowTrendChart: boolPtr(true),
|
ShowTrendChart: boolPtr(true),
|
||||||
},
|
},
|
||||||
DB: DBConfig{
|
DB: DBConfig{
|
||||||
|
|
@ -272,6 +274,7 @@ func normalizeConfig(cfg *Config) {
|
||||||
setIfNilBool(&cfg.Modules.ShowIntelligence, boolValue(def.Modules.ShowIntelligence, true))
|
setIfNilBool(&cfg.Modules.ShowIntelligence, boolValue(def.Modules.ShowIntelligence, true))
|
||||||
setIfNilBool(&cfg.Modules.ShowAlarmTimeline, boolValue(def.Modules.ShowAlarmTimeline, true))
|
setIfNilBool(&cfg.Modules.ShowAlarmTimeline, boolValue(def.Modules.ShowAlarmTimeline, true))
|
||||||
setIfNilBool(&cfg.Modules.ShowGauges, boolValue(def.Modules.ShowGauges, true))
|
setIfNilBool(&cfg.Modules.ShowGauges, boolValue(def.Modules.ShowGauges, true))
|
||||||
|
setIfNilBool(&cfg.Modules.ShowGaugeDigital, boolValue(def.Modules.ShowGaugeDigital, false))
|
||||||
setIfNilBool(&cfg.Modules.ShowTrendChart, boolValue(def.Modules.ShowTrendChart, true))
|
setIfNilBool(&cfg.Modules.ShowTrendChart, boolValue(def.Modules.ShowTrendChart, true))
|
||||||
|
|
||||||
setIfEmpty(&cfg.DB.Path, def.DB.Path)
|
setIfEmpty(&cfg.DB.Path, def.DB.Path)
|
||||||
|
|
@ -443,6 +446,7 @@ type PageData struct {
|
||||||
ShowIntelligence bool
|
ShowIntelligence bool
|
||||||
ShowAlarmTimeline bool
|
ShowAlarmTimeline bool
|
||||||
ShowGauges bool
|
ShowGauges bool
|
||||||
|
ShowGaugeDigital bool
|
||||||
ShowTrendChart bool
|
ShowTrendChart bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1608,6 +1612,7 @@ func initCachedUI() {
|
||||||
ShowIntelligence: boolValue(cfg.Modules.ShowIntelligence, true),
|
ShowIntelligence: boolValue(cfg.Modules.ShowIntelligence, true),
|
||||||
ShowAlarmTimeline: boolValue(cfg.Modules.ShowAlarmTimeline, true),
|
ShowAlarmTimeline: boolValue(cfg.Modules.ShowAlarmTimeline, true),
|
||||||
ShowGauges: boolValue(cfg.Modules.ShowGauges, true),
|
ShowGauges: boolValue(cfg.Modules.ShowGauges, true),
|
||||||
|
ShowGaugeDigital: boolValue(cfg.Modules.ShowGaugeDigital, false),
|
||||||
ShowTrendChart: boolValue(cfg.Modules.ShowTrendChart, true),
|
ShowTrendChart: boolValue(cfg.Modules.ShowTrendChart, true),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1857,14 +1862,60 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
.soft-glow-yellow { box-shadow: 0 0 0 1px rgba(234,179,8,0.28), 0 0 38px rgba(234,179,8,0.08); }
|
.soft-glow-yellow { box-shadow: 0 0 0 1px rgba(234,179,8,0.28), 0 0 38px rgba(234,179,8,0.08); }
|
||||||
.soft-glow-red { box-shadow: 0 0 0 1px rgba(239,68,68,0.28), 0 0 38px rgba(239,68,68,0.08); }
|
.soft-glow-red { box-shadow: 0 0 0 1px rgba(239,68,68,0.28), 0 0 38px rgba(239,68,68,0.08); }
|
||||||
|
|
||||||
|
.gauge-header-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gauge-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gauge-head.with-digital {
|
||||||
|
justify-content: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gauge-head-copy {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gauge-head-copy.with-digital {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gauge-digital {
|
||||||
|
text-align: right;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.gauge-container {
|
.gauge-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 500px;
|
max-width: 720px;
|
||||||
height: 360px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gauge-container.no-digital {
|
||||||
|
height: clamp(430px, 48vw, 560px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gauge-container.with-digital {
|
||||||
|
height: clamp(360px, 42vw, 500px);
|
||||||
|
}
|
||||||
|
|
||||||
.gauge-canvas {
|
.gauge-canvas {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
@ -2296,46 +2347,66 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
|
|
||||||
{{if .ShowGauges}}
|
{{if .ShowGauges}}
|
||||||
<div class="grid grid-cols-1 2xl:grid-cols-2 gap-8 mb-8">
|
<div class="grid grid-cols-1 2xl:grid-cols-2 gap-8 mb-8">
|
||||||
<div id="card-l" class="glass border border-white/10 rounded-3xl p-6 md:p-8 transition-all duration-300">
|
<div id="card-l" class="glass border border-white/10 rounded-3xl p-5 md:p-6 xl:p-8 transition-all duration-300">
|
||||||
<div class="flex justify-between items-start mb-4 gap-6">
|
{{if .ShowGaugeDigital}}
|
||||||
<div class="flex items-center gap-4">
|
<div class="gauge-header-row">
|
||||||
|
<div class="gauge-head with-digital">
|
||||||
<div id="led-l" class="w-6 h-6 bg-emerald-500 rounded-full shadow-lg shadow-emerald-600/40"></div>
|
<div id="led-l" class="w-6 h-6 bg-emerald-500 rounded-full shadow-lg shadow-emerald-600/40"></div>
|
||||||
<div>
|
<div class="gauge-head-copy with-digital">
|
||||||
<h2 class="text-2xl md:text-3xl xl:text-4xl font-bold tracking-wider">{{.LeftLabel}}</h2>
|
<h2 class="text-2xl md:text-3xl xl:text-4xl font-bold tracking-wider">{{.LeftLabel}}</h2>
|
||||||
<div id="state-l" class="text-sm text-zinc-400 mt-1">NORMAL</div>
|
<div id="state-l" class="text-sm text-zinc-400 mt-1">NORMAL</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="digital-l" class="text-right">
|
<div id="digital-l" class="gauge-digital">
|
||||||
<div class="percent text-5xl md:text-6xl xl:text-7xl font-mono font-bold text-sky-100 leading-none">0.0</div>
|
<div class="percent text-5xl md:text-6xl xl:text-7xl font-mono font-bold text-sky-100 leading-none">0.0</div>
|
||||||
<div class="text-xl text-sky-400 mt-1">{{.UnitPct}}</div>
|
<div class="text-xl text-sky-400 mt-1">{{.UnitPct}}</div>
|
||||||
<div class="kn text-lg text-zinc-300 font-mono mt-3">0.0 {{.UnitForce}}</div>
|
<div class="kn text-lg text-zinc-300 font-mono mt-3">0.0 {{.UnitForce}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="gauge-head">
|
||||||
|
<div id="led-l" class="w-6 h-6 bg-emerald-500 rounded-full shadow-lg shadow-emerald-600/40"></div>
|
||||||
|
<div class="gauge-head-copy">
|
||||||
|
<h2 class="text-2xl md:text-3xl xl:text-4xl font-bold tracking-wider">{{.LeftLabel}}</h2>
|
||||||
|
<div id="state-l" class="text-sm text-zinc-400 mt-1">NORMAL</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<div class="gauge-container">
|
<div class="gauge-container {{if .ShowGaugeDigital}}with-digital{{else}}no-digital{{end}}">
|
||||||
<canvas id="gaugeL" class="gauge-canvas"></canvas>
|
<canvas id="gaugeL" class="gauge-canvas"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="card-r" class="glass border border-white/10 rounded-3xl p-6 md:p-8 transition-all duration-300">
|
<div id="card-r" class="glass border border-white/10 rounded-3xl p-5 md:p-6 xl:p-8 transition-all duration-300">
|
||||||
<div class="flex justify-between items-start mb-4 gap-6">
|
{{if .ShowGaugeDigital}}
|
||||||
<div class="flex items-center gap-4">
|
<div class="gauge-header-row">
|
||||||
|
<div class="gauge-head with-digital">
|
||||||
<div id="led-r" class="w-6 h-6 bg-emerald-500 rounded-full shadow-lg shadow-emerald-600/40"></div>
|
<div id="led-r" class="w-6 h-6 bg-emerald-500 rounded-full shadow-lg shadow-emerald-600/40"></div>
|
||||||
<div>
|
<div class="gauge-head-copy with-digital">
|
||||||
<h2 class="text-2xl md:text-3xl xl:text-4xl font-bold tracking-wider">{{.RightLabel}}</h2>
|
<h2 class="text-2xl md:text-3xl xl:text-4xl font-bold tracking-wider">{{.RightLabel}}</h2>
|
||||||
<div id="state-r" class="text-sm text-zinc-400 mt-1">NORMAL</div>
|
<div id="state-r" class="text-sm text-zinc-400 mt-1">NORMAL</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="digital-r" class="text-right">
|
<div id="digital-r" class="gauge-digital">
|
||||||
<div class="percent text-5xl md:text-6xl xl:text-7xl font-mono font-bold text-violet-100 leading-none">0.0</div>
|
<div class="percent text-5xl md:text-6xl xl:text-7xl font-mono font-bold text-violet-100 leading-none">0.0</div>
|
||||||
<div class="text-xl text-violet-400 mt-1">{{.UnitPct}}</div>
|
<div class="text-xl text-violet-400 mt-1">{{.UnitPct}}</div>
|
||||||
<div class="kn text-lg text-zinc-300 font-mono mt-3">0.0 {{.UnitForce}}</div>
|
<div class="kn text-lg text-zinc-300 font-mono mt-3">0.0 {{.UnitForce}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="gauge-head">
|
||||||
|
<div id="led-r" class="w-6 h-6 bg-emerald-500 rounded-full shadow-lg shadow-emerald-600/40"></div>
|
||||||
|
<div class="gauge-head-copy">
|
||||||
|
<h2 class="text-2xl md:text-3xl xl:text-4xl font-bold tracking-wider">{{.RightLabel}}</h2>
|
||||||
|
<div id="state-r" class="text-sm text-zinc-400 mt-1">NORMAL</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<div class="gauge-container">
|
<div class="gauge-container {{if .ShowGaugeDigital}}with-digital{{else}}no-digital{{end}}">
|
||||||
<canvas id="gaugeR" class="gauge-canvas"></canvas>
|
<canvas id="gaugeR" class="gauge-canvas"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -2394,6 +2465,7 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
const SHOW_INTELLIGENCE = {{if .ShowIntelligence}}true{{else}}false{{end}};
|
const SHOW_INTELLIGENCE = {{if .ShowIntelligence}}true{{else}}false{{end}};
|
||||||
const SHOW_ALARM_TIMELINE = {{if .ShowAlarmTimeline}}true{{else}}false{{end}};
|
const SHOW_ALARM_TIMELINE = {{if .ShowAlarmTimeline}}true{{else}}false{{end}};
|
||||||
const SHOW_GAUGES = {{if .ShowGauges}}true{{else}}false{{end}};
|
const SHOW_GAUGES = {{if .ShowGauges}}true{{else}}false{{end}};
|
||||||
|
const SHOW_GAUGE_DIGITAL = {{if .ShowGaugeDigital}}true{{else}}false{{end}};
|
||||||
const SHOW_TREND_CHART = {{if .ShowTrendChart}}true{{else}}false{{end}};
|
const SHOW_TREND_CHART = {{if .ShowTrendChart}}true{{else}}false{{end}};
|
||||||
|
|
||||||
const START_ANGLE = Math.PI * 0.75;
|
const START_ANGLE = Math.PI * 0.75;
|
||||||
|
|
@ -2429,11 +2501,6 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
if (el) el.textContent = text;
|
if (el) el.textContent = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTextBySelector(selector, text) {
|
|
||||||
const el = document.querySelector(selector);
|
|
||||||
if (el) el.textContent = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
function colorMix(c1, c2, t) {
|
function colorMix(c1, c2, t) {
|
||||||
return {
|
return {
|
||||||
r: Math.round(lerp(c1.r, c2.r, t)),
|
r: Math.round(lerp(c1.r, c2.r, t)),
|
||||||
|
|
@ -2530,18 +2597,18 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
const light = isLightTheme();
|
const light = isLightTheme();
|
||||||
|
|
||||||
const cx = w / 2;
|
const cx = w / 2;
|
||||||
const cy = h * 0.57;
|
const radius = Math.min(w * 0.35, h * 0.42);
|
||||||
const radius = Math.min(w, h) * 0.34;
|
const cy = h * 0.58;
|
||||||
const trackWidth = Math.max(18, radius * 0.16);
|
const trackWidth = Math.max(20, radius * 0.17);
|
||||||
const value = clamp(Number(percentValue) || 0, 0, GAUGE_MAX_PERCENT);
|
const value = clamp(Number(percentValue) || 0, 0, GAUGE_MAX_PERCENT);
|
||||||
const valueAngle = valueToAngle(value);
|
const valueAngle = valueToAngle(value);
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(cx, cy, radius + 22, 0, Math.PI * 2);
|
ctx.arc(cx, cy, radius + 24, 0, Math.PI * 2);
|
||||||
ctx.fillStyle = light ? 'rgba(15,23,42,0.04)' : 'rgba(255,255,255,0.015)';
|
ctx.fillStyle = light ? 'rgba(15,23,42,0.04)' : 'rgba(255,255,255,0.015)';
|
||||||
ctx.shadowColor = light ? 'rgba(15,23,42,0.12)' : 'rgba(0,0,0,0.45)';
|
ctx.shadowColor = light ? 'rgba(15,23,42,0.12)' : 'rgba(0,0,0,0.45)';
|
||||||
ctx.shadowBlur = 30;
|
ctx.shadowBlur = 32;
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
|
||||||
|
|
@ -2632,9 +2699,9 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
const valueText = value.toFixed(1);
|
const valueText = value.toFixed(1);
|
||||||
let valueFontPx = 52;
|
let valueFontPx = 58;
|
||||||
if (value >= 100) valueFontPx = 46;
|
if (value >= 100) valueFontPx = 50;
|
||||||
if (w < 420) valueFontPx -= 4;
|
if (w < 420) valueFontPx -= 6;
|
||||||
|
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
|
|
@ -2644,12 +2711,12 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
ctx.fillText(valueText, cx, cy - 6);
|
ctx.fillText(valueText, cx, cy - 6);
|
||||||
|
|
||||||
ctx.fillStyle = sideAccent;
|
ctx.fillStyle = sideAccent;
|
||||||
ctx.font = '700 18px system-ui, sans-serif';
|
ctx.font = '700 19px system-ui, sans-serif';
|
||||||
ctx.fillText(UNIT_PCT, cx, cy + 28);
|
ctx.fillText(UNIT_PCT, cx, cy + 30);
|
||||||
|
|
||||||
ctx.fillStyle = light ? '#334155' : '#a1a1aa';
|
ctx.fillStyle = light ? '#334155' : '#a1a1aa';
|
||||||
ctx.font = '600 16px system-ui, sans-serif';
|
ctx.font = '600 17px 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 + 58);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getZone(percentValue) {
|
function getZone(percentValue) {
|
||||||
|
|
@ -2664,8 +2731,6 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function setProcessVisualState(connected) {
|
function setProcessVisualState(connected) {
|
||||||
const el = document.getElementById('process-content');
|
const el = document.getElementById('process-content');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
@ -3051,8 +3116,6 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
const connected = !!d.connected;
|
const connected = !!d.connected;
|
||||||
const leftPercent = Number(d.sila_l) || 0;
|
const leftPercent = Number(d.sila_l) || 0;
|
||||||
const rightPercent = Number(d.sila_r) || 0;
|
const rightPercent = Number(d.sila_r) || 0;
|
||||||
const leftKN = Number(d.sila_l_kn) || 0;
|
|
||||||
const rightKN = Number(d.sila_r_kn) || 0;
|
|
||||||
const sumPercent = Number(d.sum_percent) || 0;
|
const sumPercent = Number(d.sum_percent) || 0;
|
||||||
const sumKN = Number(d.sum_kn) || 0;
|
const sumKN = Number(d.sum_kn) || 0;
|
||||||
const imbalance = Number(d.imbalance_percent) || 0;
|
const imbalance = Number(d.imbalance_percent) || 0;
|
||||||
|
|
@ -3069,13 +3132,6 @@ const uiHTML = `<!DOCTYPE html>
|
||||||
setConnectionIndicator(connected, stale);
|
setConnectionIndicator(connected, stale);
|
||||||
setProcessVisualState(connected && !stale);
|
setProcessVisualState(connected && !stale);
|
||||||
|
|
||||||
if (SHOW_GAUGES) {
|
|
||||||
setTextBySelector('#digital-l .percent', leftPercent.toFixed(1));
|
|
||||||
setTextBySelector('#digital-l .kn', leftKN.toFixed(1) + ' ' + UNIT_FORCE);
|
|
||||||
setTextBySelector('#digital-r .percent', rightPercent.toFixed(1));
|
|
||||||
setTextBySelector('#digital-r .kn', rightKN.toFixed(1) + ' ' + UNIT_FORCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SHOW_OVERVIEW) {
|
if (SHOW_OVERVIEW) {
|
||||||
setTextById('sum-percent', sumPercent.toFixed(1));
|
setTextById('sum-percent', sumPercent.toFixed(1));
|
||||||
setTextById('sum-kn', sumKN.toFixed(1));
|
setTextById('sum-kn', sumKN.toFixed(1));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue