dashboard: fix gauge charts (install gauge.js, render after polls), force dark theme with white text and red/green accents, add icons
This commit is contained in:
@@ -82,7 +82,8 @@ SUMMARY_PHP_FILE = "news_summary.php"
|
|||||||
# Repo files copied into the out dir at daemon start (git pull + restart
|
# Repo files copied into the out dir at daemon start (git pull + restart
|
||||||
# updates them). The PHP endpoint reads news.json in place, so it lives
|
# updates them). The PHP endpoint reads news.json in place, so it lives
|
||||||
# next to the page.
|
# next to the page.
|
||||||
STATIC_FILES = [PAGE_FILE, "dashboard.css", "dashboard.js", SUMMARY_PHP_FILE]
|
STATIC_FILES = [PAGE_FILE, "dashboard.css", "dashboard.js", "gauge.js",
|
||||||
|
SUMMARY_PHP_FILE]
|
||||||
|
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
+41
-2
@@ -8,12 +8,43 @@
|
|||||||
:root {
|
:root {
|
||||||
--xau-font-sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
|
--xau-font-sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||||
--xau-font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Consolas, Menlo, monospace;
|
--xau-font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Consolas, Menlo, monospace;
|
||||||
|
--xau-gold: #f5b301;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forced dark theme (owner decision 2026-08-16): dark background, pure
|
||||||
|
* white text (no gray), vivid red/green for data accents. Loaded after
|
||||||
|
* Bootstrap so these overrides win. */
|
||||||
|
[data-bs-theme="dark"] {
|
||||||
|
--bs-body-bg: #0a0e14;
|
||||||
|
--bs-secondary-bg: #111823;
|
||||||
|
--bs-tertiary-bg: #0e141d;
|
||||||
|
--bs-body-color: #ffffff;
|
||||||
|
--bs-emphasis-color: #ffffff;
|
||||||
|
--bs-heading-color: #ffffff;
|
||||||
|
--bs-secondary-color: rgba(255, 255, 255, 0.92);
|
||||||
|
--bs-tertiary-color: rgba(255, 255, 255, 0.8);
|
||||||
|
--bs-border-color: rgba(255, 255, 255, 0.14);
|
||||||
|
--bs-card-bg: #0e141d;
|
||||||
|
--bs-card-border-color: rgba(255, 255, 255, 0.12);
|
||||||
|
--bs-modal-bg: #0e141d;
|
||||||
|
--bs-success: #22c55e;
|
||||||
|
--bs-success-rgb: 34, 197, 94;
|
||||||
|
--bs-success-text-emphasis: #4ade80;
|
||||||
|
--bs-success-bg-subtle: rgba(34, 197, 94, 0.16);
|
||||||
|
--bs-success-border-subtle: rgba(34, 197, 94, 0.45);
|
||||||
|
--bs-danger: #ef4444;
|
||||||
|
--bs-danger-rgb: 239, 68, 68;
|
||||||
|
--bs-danger-text-emphasis: #f87171;
|
||||||
|
--bs-danger-bg-subtle: rgba(239, 68, 68, 0.16);
|
||||||
|
--bs-danger-border-subtle: rgba(239, 68, 68, 0.45);
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
font-family: var(--xau-font-sans);
|
font-family: var(--xau-font-sans);
|
||||||
letter-spacing: -0.005em;
|
letter-spacing: -0.005em;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
background-color: var(--bs-body-bg);
|
||||||
|
color: var(--bs-body-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.num, .xau-price, .xau-symbol {
|
.num, .xau-price, .xau-symbol {
|
||||||
@@ -25,12 +56,12 @@ html, body {
|
|||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
color: var(--bs-primary);
|
color: var(--xau-gold);
|
||||||
}
|
}
|
||||||
|
|
||||||
.xau-price {
|
.xau-price {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
color: var(--bs-primary-text-emphasis);
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.live-dot {
|
.live-dot {
|
||||||
@@ -100,6 +131,14 @@ html, body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Chart.js gauge canvas host (see gauge.js) — thin bar in the row */
|
||||||
|
.scorebar .gauge-container {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 60px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
.scorebar .track::after {
|
.scorebar .track::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
+10
-14
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Gold Desk — XAU/USD</title>
|
<title>Gold Desk — XAU/USD</title>
|
||||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='7' fill='%230d6efd'/%3E%3C/svg%3E">
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='7' fill='%23f5b301'/%3E%3C/svg%3E">
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" crossorigin="anonymous">
|
||||||
@@ -12,13 +12,6 @@
|
|||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap">
|
||||||
<link rel="stylesheet" href="dashboard.css">
|
<link rel="stylesheet" href="dashboard.css">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
var t = localStorage.getItem("xau-theme");
|
|
||||||
if (!t && matchMedia("(prefers-color-scheme: dark)").matches) t = "dark";
|
|
||||||
if (t) document.documentElement.setAttribute("data-bs-theme", t);
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -27,16 +20,15 @@
|
|||||||
<header class="card mb-3" id="hero">
|
<header class="card mb-3" id="hero">
|
||||||
<div class="card-body d-flex align-items-center flex-wrap gap-3">
|
<div class="card-body d-flex align-items-center flex-wrap gap-3">
|
||||||
<div>
|
<div>
|
||||||
<div class="xau-symbol">XAU/USD</div>
|
<div class="xau-symbol"><i class="bi bi-coin me-1"></i>XAU/USD</div>
|
||||||
<div class="display-5 fw-semibold xau-price" id="hero-price">—</div>
|
<div class="display-5 fw-semibold xau-price" id="hero-price">—</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-wrap gap-2" id="hero-rail" aria-label="Signal by timeframe"></div>
|
<div class="d-flex flex-wrap gap-2" id="hero-rail" aria-label="Signal by timeframe"></div>
|
||||||
<div class="ms-auto text-end">
|
<div class="ms-auto text-end">
|
||||||
<div id="hero-status"><span class="live-dot"></span>…</div>
|
<div id="hero-status"><span class="live-dot"></span>…</div>
|
||||||
<div class="small text-secondary">M5 close in <span class="num" id="hero-cd">--:--</span></div>
|
<div class="small text-secondary"><i class="bi bi-hourglass-split me-1"></i>M5 close in <span class="num" id="hero-cd">--:--</span></div>
|
||||||
<div class="small text-secondary" id="hero-updated">updated —</div>
|
<div class="small text-secondary" id="hero-updated">updated —</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="theme-toggle" class="btn btn-outline-secondary" type="button" aria-label="Toggle dark mode"><i class="bi bi-moon-fill"></i></button>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -53,18 +45,21 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<h6 class="text-uppercase text-secondary small fw-bold mb-3 d-flex align-items-center gap-2">
|
<h6 class="text-uppercase text-secondary small fw-bold mb-3 d-flex align-items-center gap-2">
|
||||||
|
<i class="bi bi-clock-history text-primary"></i>
|
||||||
Timeframes
|
Timeframes
|
||||||
<span class="fw-normal text-body-secondary" style="text-transform:none">— select one for full detail</span>
|
<span class="fw-normal text-body-secondary" style="text-transform:none">— select one for full detail</span>
|
||||||
</h6>
|
</h6>
|
||||||
<div class="row g-3" id="tf-grid"></div>
|
<div class="row g-3" id="tf-grid"></div>
|
||||||
|
|
||||||
<h6 class="text-uppercase text-secondary small fw-bold mb-3 mt-4">Fundamentals</h6>
|
<h6 class="text-uppercase text-secondary small fw-bold mb-3 mt-4 d-flex align-items-center gap-2">
|
||||||
|
<i class="bi bi-globe2 text-primary"></i>Fundamentals
|
||||||
|
</h6>
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex align-items-center gap-3 mb-3 flex-wrap">
|
<div class="d-flex align-items-center gap-3 mb-3 flex-wrap">
|
||||||
<h5 class="card-title mb-0">Economic calendar</h5>
|
<h5 class="card-title mb-0"><i class="bi bi-calendar-event me-2 text-primary"></i>Economic calendar</h5>
|
||||||
<span class="text-secondary small">times local · USD events</span>
|
<span class="text-secondary small">times local · USD events</span>
|
||||||
<div class="form-check form-switch ms-auto">
|
<div class="form-check form-switch ms-auto">
|
||||||
<input class="form-check-input" type="checkbox" id="hi-only">
|
<input class="form-check-input" type="checkbox" id="hi-only">
|
||||||
@@ -94,7 +89,7 @@
|
|||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex align-items-center gap-3 mb-3 flex-wrap">
|
<div class="d-flex align-items-center gap-3 mb-3 flex-wrap">
|
||||||
<h5 class="card-title mb-0">Newswire</h5>
|
<h5 class="card-title mb-0"><i class="bi bi-newspaper me-2 text-primary"></i>Newswire</h5>
|
||||||
<span class="text-secondary small" id="wire-note"></span>
|
<span class="text-secondary small" id="wire-note"></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="wire-total" class="d-none"></div>
|
<div id="wire-total" class="d-none"></div>
|
||||||
@@ -105,6 +100,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="mt-4 pt-3 border-top text-secondary small">
|
<footer class="mt-4 pt-3 border-top text-secondary small">
|
||||||
|
<i class="bi bi-info-circle me-1"></i>
|
||||||
Signals are generated on closed candles only and refresh at candle boundaries
|
Signals are generated on closed candles only and refresh at candle boundaries
|
||||||
(M5/M15/H1/H4). Not financial advice — educational and research use only.
|
(M5/M15/H1/H4). Not financial advice — educational and research use only.
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
+32
-35
@@ -80,32 +80,9 @@ const badgeCls = s => s === "buy" ? "bg-success-subtle text-success-emphasis bor
|
|||||||
: "bg-secondary-subtle text-secondary-emphasis border-secondary-subtle";
|
: "bg-secondary-subtle text-secondary-emphasis border-secondary-subtle";
|
||||||
|
|
||||||
/* ── theme ────────────────────────────────────────────────────── */
|
/* ── theme ────────────────────────────────────────────────────── */
|
||||||
(function () {
|
// Dark theme is forced site-wide (owner decision 2026-08-16): dark
|
||||||
const KEY = "xau-theme";
|
// background, pure-white text, red/green data accents.
|
||||||
const sysDark = matchMedia("(prefers-color-scheme: dark)");
|
document.documentElement.setAttribute("data-bs-theme", "dark");
|
||||||
const stored = () => localStorage.getItem(KEY);
|
|
||||||
const apply = theme => {
|
|
||||||
document.documentElement.setAttribute("data-bs-theme", theme);
|
|
||||||
const btn = $("theme-toggle");
|
|
||||||
if (!btn) return;
|
|
||||||
const icon = btn.querySelector("i");
|
|
||||||
if (theme === "dark") {
|
|
||||||
btn.title = "Switch to light mode";
|
|
||||||
if (icon) icon.className = "bi bi-sun-fill";
|
|
||||||
} else {
|
|
||||||
btn.title = "Switch to dark mode";
|
|
||||||
if (icon) icon.className = "bi bi-moon-fill";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
apply(stored() || (sysDark.matches ? "dark" : "light"));
|
|
||||||
sysDark.addEventListener("change", e => { if (!stored()) apply(e.matches ? "dark" : "light"); });
|
|
||||||
const btn = $("theme-toggle");
|
|
||||||
if (btn) btn.addEventListener("click", () => {
|
|
||||||
const next = document.documentElement.getAttribute("data-bs-theme") === "dark" ? "light" : "dark";
|
|
||||||
localStorage.setItem(KEY, next);
|
|
||||||
apply(next);
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
/* ── hero ─────────────────────────────────────────────────────── */
|
/* ── hero ─────────────────────────────────────────────────────── */
|
||||||
function renderHero() {
|
function renderHero() {
|
||||||
@@ -176,29 +153,30 @@ function scoreBar(label, score) {
|
|||||||
const v = Math.max(-100, Math.min(100, score ?? 0));
|
const v = Math.max(-100, Math.min(100, score ?? 0));
|
||||||
return `<div class="scorebar">
|
return `<div class="scorebar">
|
||||||
<span class="lbl">${label}</span>
|
<span class="lbl">${label}</span>
|
||||||
<div class="gauge-container" style="height: 160px;" data-label="${label}" data-score="${v}"></div>
|
<div class="gauge-container" data-label="${label}" data-score="${v}"></div>
|
||||||
<span class="val num">${v >= 0 ? "+" : ""}${v.toFixed(1)}</span>
|
<span class="val num ${v > 0 ? "text-success" : v < 0 ? "text-danger" : ""}">${v >= 0 ? "+" : ""}${v.toFixed(1)}</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupCard(ts) {
|
function setupCard(ts) {
|
||||||
if (!ts) return "";
|
if (!ts) return "";
|
||||||
const dir = ts.direction === "BUY" ? "buy" : "sell";
|
const dir = ts.direction === "BUY" ? "buy" : "sell";
|
||||||
|
const dirIcon = ts.direction === "BUY" ? "bi-arrow-up-circle-fill" : "bi-arrow-down-circle-fill";
|
||||||
const trail = (ts.trail_arm_r != null && ts.trail_dist_r != null)
|
const trail = (ts.trail_arm_r != null && ts.trail_dist_r != null)
|
||||||
? `arm ${ts.trail_arm_r}R · dist ${ts.trail_dist_r}R` : "platform default";
|
? `arm ${ts.trail_arm_r}R · dist ${ts.trail_dist_r}R` : "platform default";
|
||||||
return `<div class="card border-signal sig-${dir} bg-body-tertiary">
|
return `<div class="card border-signal sig-${dir} bg-body-tertiary">
|
||||||
<div class="card-body py-2 px-3">
|
<div class="card-body py-2 px-3">
|
||||||
<div class="d-flex align-items-baseline gap-2 mb-2">
|
<div class="d-flex align-items-baseline gap-2 mb-2">
|
||||||
<span class="fw-bold text-signal sig-${dir}">${ts.direction}</span>
|
<span class="fw-bold text-signal sig-${dir}"><i class="bi ${dirIcon} me-1"></i>${ts.direction}</span>
|
||||||
<span class="small text-secondary font-monospace">${esc(ts.strategy || "")}</span>
|
<span class="small text-secondary font-monospace">${esc(ts.strategy || "")}</span>
|
||||||
<span class="small text-secondary ms-auto font-monospace">R:R ${(+ts.risk_reward).toFixed(1)}</span>
|
<span class="small text-secondary ms-auto font-monospace">R:R ${(+ts.risk_reward).toFixed(1)}</span>
|
||||||
</div>
|
</div>
|
||||||
<table class="table table-sm table-borderless mb-0">
|
<table class="table table-sm table-borderless mb-0">
|
||||||
<tr><td class="text-secondary">Entry</td><td class="num">${fmt$(ts.entry)}</td>
|
<tr><td class="text-secondary"><i class="bi bi-box-arrow-in-right me-1"></i>Entry</td><td class="num">${fmt$(ts.entry)}</td>
|
||||||
<td class="text-secondary ps-4">TP1</td><td class="num">${fmt$(ts.take_profit_1)}</td></tr>
|
<td class="text-secondary ps-4"><i class="bi bi-flag-fill me-1 text-success"></i>TP1</td><td class="num text-success">${fmt$(ts.take_profit_1)}</td></tr>
|
||||||
<tr><td class="text-secondary">Stop</td><td class="num">${fmt$(ts.stop_loss)}</td>
|
<tr><td class="text-secondary"><i class="bi bi-shield-x me-1 text-danger"></i>Stop</td><td class="num text-danger">${fmt$(ts.stop_loss)}</td>
|
||||||
<td class="text-secondary ps-4">TP2</td><td class="num">${fmt$(ts.take_profit_2)}</td></tr>
|
<td class="text-secondary ps-4"><i class="bi bi-flag-fill me-1 text-success"></i>TP2</td><td class="num text-success">${fmt$(ts.take_profit_2)}</td></tr>
|
||||||
<tr><td class="text-secondary">Trail</td><td class="num" colspan="3">${esc(trail)}</td></tr>
|
<tr><td class="text-secondary"><i class="bi bi-signpost-split me-1"></i>Trail</td><td class="num" colspan="3">${esc(trail)}</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -252,7 +230,25 @@ const tile = (k, v, cls = "") =>
|
|||||||
<span class="v ${cls}">${v}</span>
|
<span class="v ${cls}">${v}</span>
|
||||||
</div></div></div>`;
|
</div></div></div>`;
|
||||||
|
|
||||||
const sec = (title, html) => html ? `<section class="mb-4"><h6 class="text-uppercase text-secondary small fw-bold mb-2 d-flex align-items-center gap-2"><span>${esc(title)}</span><span class="flex-fill border-bottom"></span></h6>${html}</section>` : "";
|
const SEC_ICON = {
|
||||||
|
"At a glance": "bi-speedometer2",
|
||||||
|
"Trade setup": "bi-bullseye",
|
||||||
|
"Why this signal": "bi-lightbulb",
|
||||||
|
"Multi-timeframe confirmation": "bi-layers",
|
||||||
|
"News context": "bi-newspaper",
|
||||||
|
"AI panel (advisory)": "bi-robot",
|
||||||
|
"Scan detail": "bi-clock-history",
|
||||||
|
"Score": "bi-graph-up",
|
||||||
|
"From the source": "bi-quote",
|
||||||
|
"AI gold-impact summary": "bi-stars",
|
||||||
|
"Timing": "bi-alarm",
|
||||||
|
"Numbers": "bi-calculator"
|
||||||
|
};
|
||||||
|
const sec = (title, html) => {
|
||||||
|
if (!html) return "";
|
||||||
|
const icon = SEC_ICON[title];
|
||||||
|
return `<section class="mb-4"><h6 class="text-uppercase text-secondary small fw-bold mb-2 d-flex align-items-center gap-2">${icon ? `<i class="bi ${icon} text-primary"></i>` : ""}<span>${esc(title)}</span><span class="flex-fill border-bottom"></span></h6>${html}</section>`;
|
||||||
|
};
|
||||||
|
|
||||||
function indTable(src) {
|
function indTable(src) {
|
||||||
const rows = IND_ORDER.map(name => {
|
const rows = IND_ORDER.map(name => {
|
||||||
@@ -748,6 +744,7 @@ async function poll() {
|
|||||||
if (snapChanged) {
|
if (snapChanged) {
|
||||||
renderHero();
|
renderHero();
|
||||||
renderCards();
|
renderCards();
|
||||||
|
if (window.renderGauges) window.renderGauges();
|
||||||
renderDetail();
|
renderDetail();
|
||||||
}
|
}
|
||||||
if (newsChanged) {
|
if (newsChanged) {
|
||||||
|
|||||||
+48
-37
@@ -1,44 +1,50 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
/* Gold Desk — score gauges.
|
||||||
// Draw horizontal bar charts for each score using Chart.js
|
*
|
||||||
function drawCharts() {
|
* Draws a horizontal diverging Chart.js bar (−100…+100, zero-centered)
|
||||||
const gaugeContainers = document.querySelectorAll('.gauge-container');
|
* into every `.gauge-container` element. The containers are created
|
||||||
gaugeContainers.forEach(container => {
|
* dynamically by dashboard.js after each snapshot poll, so this module
|
||||||
const label = container.dataset.label;
|
* exposes window.renderGauges() which dashboard.js calls after every
|
||||||
const score = parseFloat(container.dataset.score);
|
* card render (and on resize). Re-running it destroys and redraws each
|
||||||
// Clear any previous content
|
* chart, so it is safe to call repeatedly.
|
||||||
container.innerHTML = '';
|
*/
|
||||||
// Create canvas element
|
(function () {
|
||||||
const canvas = document.createElement('canvas');
|
"use strict";
|
||||||
// Set a fixed height for thin bar
|
|
||||||
canvas.height = 30;
|
function renderGauges() {
|
||||||
container.appendChild(canvas);
|
if (typeof Chart === "undefined") return;
|
||||||
const ctx = canvas.getContext('2d');
|
document.querySelectorAll(".gauge-container").forEach(function (container) {
|
||||||
// Determine bar colour based on sign
|
var score = parseFloat(container.dataset.score);
|
||||||
const bgColor = score >= 0 ? 'rgba(40,167,69,0.8)' : 'rgba(220,53,69,0.8)'; // success/red
|
if (!isFinite(score)) return;
|
||||||
new Chart(ctx, {
|
|
||||||
type: 'bar',
|
var canvas = container.querySelector("canvas");
|
||||||
|
if (!canvas) {
|
||||||
|
container.innerHTML = "";
|
||||||
|
canvas = document.createElement("canvas");
|
||||||
|
container.appendChild(canvas);
|
||||||
|
} else {
|
||||||
|
var existing = Chart.getChart(canvas);
|
||||||
|
if (existing) existing.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
var pos = score >= 0;
|
||||||
|
new Chart(canvas.getContext("2d"), {
|
||||||
|
type: "bar",
|
||||||
data: {
|
data: {
|
||||||
labels: [label],
|
labels: [container.dataset.label || ""],
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: label,
|
|
||||||
data: [score],
|
data: [score],
|
||||||
backgroundColor: bgColor,
|
backgroundColor: pos ? "rgba(25,135,84,0.85)" : "rgba(220,53,69,0.85)",
|
||||||
borderColor: 'rgba(0,0,0,0.1)',
|
borderColor: pos ? "#198754" : "#dc3545",
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
barThickness: 12,
|
barThickness: 10,
|
||||||
maxBarThickness: 12,
|
maxBarThickness: 10,
|
||||||
|
borderRadius: 3
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
indexAxis: 'y',
|
indexAxis: "y",
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: { min: -100, max: 100, display: false, grid: { display: false }, ticks: { display: false } },
|
||||||
min: -100,
|
|
||||||
max: 100,
|
|
||||||
display: false,
|
|
||||||
grid: { display: false },
|
|
||||||
ticks: { display: false }
|
|
||||||
},
|
|
||||||
y: { display: false, grid: { display: false } }
|
y: { display: false, grid: { display: false } }
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
@@ -48,12 +54,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
},
|
},
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
responsive: true,
|
responsive: true,
|
||||||
animation: { duration: 500, easing: 'easeOutQuad' }
|
animation: { duration: 400, easing: "easeOutQuad" }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
drawCharts();
|
window.renderGauges = renderGauges;
|
||||||
window.addEventListener('resize', drawCharts);
|
|
||||||
});
|
var resizeTimer = null;
|
||||||
|
window.addEventListener("resize", function () {
|
||||||
|
clearTimeout(resizeTimer);
|
||||||
|
resizeTimer = setTimeout(renderGauges, 150);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|||||||
+1
-1
@@ -5670,7 +5670,7 @@ def _():
|
|||||||
pub = dpub.DashboardPublisher(out_dir=tmp)
|
pub = dpub.DashboardPublisher(out_dir=tmp)
|
||||||
pub.install_static()
|
pub.install_static()
|
||||||
assert sorted(os.listdir(tmp)) == \
|
assert sorted(os.listdir(tmp)) == \
|
||||||
["dashboard.css", "dashboard.html", "dashboard.js", "news_summary.php"] # all installed, no .tmp
|
["dashboard.css", "dashboard.html", "dashboard.js", "gauge.js", "news_summary.php"] # all installed, no .tmp
|
||||||
|
|
||||||
|
|
||||||
@test("dashboard: publish is atomic and leaves no temp file")
|
@test("dashboard: publish is atomic and leaves no temp file")
|
||||||
|
|||||||
Reference in New Issue
Block a user