fix: rebuild station-b from station.html base, only canvas background differs
This commit is contained in:
parent
70c983f1e7
commit
f726b4b9af
|
|
@ -8,15 +8,15 @@
|
||||||
<title>posimai-station B</title>
|
<title>posimai-station B</title>
|
||||||
<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 href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@300;400;500&display=swap" rel="stylesheet">
|
||||||
<script src="https://unpkg.com/lucide@0.344.0/dist/umd/lucide.min.js"></script>
|
<script src="https://unpkg.com/lucide@0.344.0/dist/umd/lucide.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
:root {
|
:root {
|
||||||
--bg: #070D18;
|
--bg: #0C1221;
|
||||||
--surface: rgba(10,18,35,0.72);
|
--surface: rgba(15,22,40,0.70);
|
||||||
--surface2: rgba(18,28,50,0.82);
|
--surface2: rgba(26,34,53,0.80);
|
||||||
--border: rgba(255,255,255,0.07);
|
--border: rgba(255,255,255,0.08);
|
||||||
--border2: rgba(255,255,255,0.04);
|
--border2: rgba(255,255,255,0.04);
|
||||||
--text: #F1F5F9;
|
--text: #F1F5F9;
|
||||||
--text2: #94A3B8;
|
--text2: #94A3B8;
|
||||||
|
|
@ -28,15 +28,12 @@
|
||||||
--crit: #F87171;
|
--crit: #F87171;
|
||||||
}
|
}
|
||||||
html, body { height: 100%; background: var(--bg); color: var(--text); font-family: 'Inter', sans-serif; overflow: hidden; user-select: none; }
|
html, body { height: 100%; background: var(--bg); color: var(--text); font-family: 'Inter', sans-serif; overflow: hidden; user-select: none; }
|
||||||
|
|
||||||
/* ── Binary curtain canvas ── */
|
|
||||||
#binary-canvas { position:fixed;inset:0;z-index:0;pointer-events:none; }
|
#binary-canvas { position:fixed;inset:0;z-index:0;pointer-events:none; }
|
||||||
|
|
||||||
/* ── Layout ── */
|
|
||||||
.panel {
|
.panel {
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
backdrop-filter: blur(28px) saturate(180%);
|
backdrop-filter: blur(24px) saturate(160%);
|
||||||
-webkit-backdrop-filter: blur(28px) saturate(180%);
|
-webkit-backdrop-filter: blur(24px) saturate(160%);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 16px; padding: 18px;
|
border-radius: 16px; padding: 18px;
|
||||||
display: flex; flex-direction: column; gap: 14px;
|
display: flex; flex-direction: column; gap: 14px;
|
||||||
|
|
@ -44,98 +41,104 @@
|
||||||
}
|
}
|
||||||
.panel::before {
|
.panel::before {
|
||||||
content:''; position:absolute; inset:0; border-radius:16px;
|
content:''; position:absolute; inset:0; border-radius:16px;
|
||||||
background: linear-gradient(135deg, rgba(34,211,238,0.04) 0%, transparent 60%);
|
background: linear-gradient(145deg,rgba(255,255,255,0.035) 0%,transparent 55%);
|
||||||
pointer-events:none;
|
pointer-events:none;
|
||||||
}
|
}
|
||||||
.panel-title { font-size:11px;font-weight:600;color:var(--text3);letter-spacing:0.12em;text-transform:uppercase;display:flex;align-items:center;gap:6px;flex-shrink:0; }
|
.panel-title { font-size:11px;font-weight:600;color:var(--text3);letter-spacing:0.12em;text-transform:uppercase;display:flex;align-items:center;gap:6px;flex-shrink:0; }
|
||||||
.panel-title svg { width:12px;height:12px; }
|
.panel-title svg { width:13px;height:13px; }
|
||||||
|
|
||||||
#alert-bar { display:none;position:relative;z-index:2;align-items:center;gap:10px;padding:7px 14px;border-radius:10px;font-size:12px;font-weight:500;margin-bottom:10px; }
|
#alert-bar { display:none;position:relative;z-index:2;align-items:center;gap:10px;padding:7px 14px;border-radius:10px;font-size:12px;font-weight:500;margin-bottom:10px; }
|
||||||
#alert-bar.visible { display:flex; }
|
#alert-bar.visible { display:flex; }
|
||||||
#alert-bar.warn { background:rgba(251,146,60,0.10);border:1px solid rgba(251,146,60,0.28);color:var(--warn); }
|
#alert-bar.warn { background:rgba(251,146,60,0.10);border:1px solid rgba(251,146,60,0.28);color:var(--warn); }
|
||||||
#alert-bar.crit { background:rgba(248,113,113,0.10);border:1px solid rgba(248,113,113,0.3);color:var(--crit);animation:alert-pulse 2s ease-in-out infinite; }
|
#alert-bar.crit { background:rgba(248,113,113,0.10);border:1px solid rgba(248,113,113,0.3);color:var(--crit);animation:alert-pulse 2s ease-in-out infinite; }
|
||||||
@keyframes alert-pulse { 0%,100%{border-color:rgba(248,113,113,0.3)} 50%{border-color:rgba(248,113,113,0.65)} }
|
@keyframes alert-pulse { 0%,100%{border-color:rgba(248,113,113,0.3)} 50%{border-color:rgba(248,113,113,0.65)} }
|
||||||
|
#alert-bar svg { width:13px;height:13px;flex-shrink:0; }
|
||||||
|
|
||||||
#app { position:relative;z-index:1;height:100vh;display:grid;grid-template-rows:auto auto 1fr auto;padding:20px;gap:0; }
|
#app { position:relative;z-index:1;height:100vh;display:grid;grid-template-rows:auto auto 1fr auto;padding:20px;gap:0; }
|
||||||
|
|
||||||
#top { display:flex;align-items:flex-end;justify-content:space-between;padding:10px 16px 14px;flex-shrink:0;background:rgba(8,14,26,0.72);backdrop-filter:blur(16px);border-radius:14px;margin-bottom:4px; }
|
#top { display:grid;grid-template-columns:1fr auto 1fr;align-items:center;padding-bottom:14px; }
|
||||||
#hostname-area { display:flex;align-items:center;gap:8px; }
|
#hostname-area { display:flex;align-items:center;gap:8px; }
|
||||||
#status-dot { width:8px;height:8px;border-radius:50%;background:var(--text3); }
|
#hostname { font-size:12px;font-weight:500;color:var(--text3);letter-spacing:0.08em;text-transform:uppercase; }
|
||||||
#status-dot.ok { background:var(--ok); box-shadow:0 0 8px var(--ok); }
|
#status-dot { width:7px;height:7px;border-radius:50%;background:var(--text3);flex-shrink:0;transition:background 0.4s; }
|
||||||
#status-dot.warn { background:var(--warn); box-shadow:0 0 7px var(--warn);animation:pdot 1.5s ease-in-out infinite; }
|
#status-dot.ok { background:var(--ok); box-shadow:0 0 7px var(--ok); animation:pdot 2.4s ease-in-out infinite; }
|
||||||
|
#status-dot.warn { background:var(--warn); box-shadow:0 0 7px var(--warn); }
|
||||||
#status-dot.crit { background:var(--crit); box-shadow:0 0 7px var(--crit);animation:pdot 1s ease-in-out infinite; }
|
#status-dot.crit { background:var(--crit); box-shadow:0 0 7px var(--crit);animation:pdot 1s ease-in-out infinite; }
|
||||||
|
#status-dot.off { background:var(--text3);box-shadow:none; }
|
||||||
@keyframes pdot { 0%,100%{opacity:1} 50%{opacity:0.35} }
|
@keyframes pdot { 0%,100%{opacity:1} 50%{opacity:0.35} }
|
||||||
#hostname { font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--text2); }
|
|
||||||
#clock-area { text-align:center; }
|
#clock-area { text-align:center; }
|
||||||
#clock { font-family:'JetBrains Mono',monospace;font-size:clamp(48px,5.5vw,84px);font-weight:400;letter-spacing:-0.03em;line-height:1;color:var(--text); }
|
#clock { font-family:'JetBrains Mono',monospace;font-size:clamp(48px,5.5vw,84px);font-weight:300;letter-spacing:-0.03em;line-height:1; }
|
||||||
#date { font-size:12px;color:var(--text3);margin-top:4px;text-align:center; }
|
#date { font-size:12px;color:var(--text3);margin-top:3px;letter-spacing:0.06em; }
|
||||||
#last-checked { font-size:11px;color:var(--text3); }
|
#last-checked { text-align:right;font-size:11px;color:var(--text3); }
|
||||||
|
|
||||||
#middle { display:grid;grid-template-columns:270px 280px 1fr 196px;gap:12px;min-height:0; }
|
#middle { display:grid;grid-template-columns:270px 280px 1fr 196px;gap:12px;min-height:0; }
|
||||||
|
|
||||||
/* metric panel */
|
.metric-item { display:flex;flex-direction:column;gap:5px;flex-shrink:0; }
|
||||||
.metric-item { display:flex;flex-direction:column;gap:5px; }
|
.metric-header-row { display:flex;justify-content:space-between;align-items:baseline; }
|
||||||
.metric-header-row { display:flex;align-items:center;justify-content:space-between; }
|
.metric-label { font-size:12px;color:var(--text2); }
|
||||||
.metric-label { font-size:10px;color:var(--text3);font-weight:500;letter-spacing:0.06em;text-transform:uppercase; }
|
.metric-val { font-family:'JetBrains Mono',monospace;font-size:12px;font-weight:500; }
|
||||||
.metric-val { font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:500;color:var(--text2); }
|
.bin-bar { font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:0.04em;display:flex;gap:0px;margin-top:2px; }
|
||||||
.bin-bar { display:flex;gap:2px;font-family:'JetBrains Mono',monospace;font-size:9px;line-height:1; }
|
.bin-bar .b1 { color:var(--accent);transition:color 0.5s; }
|
||||||
.bin-bar .b1 { color:var(--accent); }
|
.bin-bar .b0 { color:rgba(255,255,255,0.1); }
|
||||||
.bin-bar .b0 { color:rgba(100,180,220,0.32); }
|
|
||||||
.bin-bar.warn .b1 { color:var(--warn); }
|
.bin-bar.warn .b1 { color:var(--warn); }
|
||||||
.bin-bar.crit .b1 { color:var(--crit); }
|
.bin-bar.crit .b1 { color:var(--crit); }
|
||||||
.load-row { display:flex;gap:6px; }
|
|
||||||
.load-chip { flex:1;background:var(--surface2);border:1px solid var(--border2);border-radius:8px;padding:6px 8px;display:flex;flex-direction:column;gap:3px;align-items:center; }
|
.load-row { display:flex;gap:6px;flex-shrink:0; }
|
||||||
.load-chip-label { font-size:9px;color:var(--text3);font-weight:600;letter-spacing:0.06em;text-transform:uppercase; }
|
.load-chip { flex:1;background:var(--surface2);border-radius:7px;padding:7px;text-align:center; }
|
||||||
.load-chip-val { font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:500;color:var(--text); }
|
.load-chip-label { font-size:10px;color:var(--text3);margin-bottom:2px; }
|
||||||
|
.load-chip-val { font-family:'JetBrains Mono',monospace;font-size:14px;font-weight:500; }
|
||||||
.load-chip-val.warn { color:var(--warn); }
|
.load-chip-val.warn { color:var(--warn); }
|
||||||
.load-chip-val.crit { color:var(--crit); }
|
.load-chip-val.crit { color:var(--crit); }
|
||||||
.stat-grid { display:grid;grid-template-columns:1fr 1fr;gap:6px; }
|
|
||||||
.stat-card { background:var(--surface2);border:1px solid var(--border2);border-radius:10px;padding:8px 10px; }
|
.stat-grid { display:grid;grid-template-columns:1fr 1fr;gap:7px;flex-shrink:0; }
|
||||||
.stat-label { font-size:9px;color:var(--text3);font-weight:600;text-transform:uppercase;letter-spacing:0.06em; }
|
.stat-card { background:var(--surface2);border-radius:9px;padding:9px 11px; }
|
||||||
.stat-val { font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:500;margin-top:3px; }
|
.stat-label { font-size:11px;color:var(--text3);margin-bottom:2px; }
|
||||||
|
.stat-val { font-family:'JetBrains Mono',monospace;font-size:15px;font-weight:500;letter-spacing:-0.02em; }
|
||||||
|
|
||||||
.open-btn { display:flex;align-items:center;justify-content:center;gap:6px;padding:8px;background:rgba(34,211,238,0.07);border:1px solid rgba(34,211,238,0.18);border-radius:9px;color:var(--accent);text-decoration:none;font-size:12px;font-weight:500;transition:background 0.2s,border-color 0.2s;margin-top:auto;flex-shrink:0; }
|
.open-btn { display:flex;align-items:center;justify-content:center;gap:6px;padding:8px;background:rgba(34,211,238,0.07);border:1px solid rgba(34,211,238,0.18);border-radius:9px;color:var(--accent);text-decoration:none;font-size:12px;font-weight:500;transition:background 0.2s,border-color 0.2s;margin-top:auto;flex-shrink:0; }
|
||||||
.open-btn:hover { background:rgba(34,211,238,0.13);border-color:rgba(34,211,238,0.35); }
|
.open-btn:hover { background:rgba(34,211,238,0.13);border-color:rgba(34,211,238,0.35); }
|
||||||
.open-btn svg { width:13px;height:13px; }
|
.open-btn svg { width:13px;height:13px; }
|
||||||
|
|
||||||
/* rings panel */
|
/* rings panel */
|
||||||
.rings-panel { align-items:center; }
|
.rings-panel { display:flex;flex-direction:column;gap:12px; }
|
||||||
.ring-track { fill:none;stroke:rgba(255,255,255,0.05);stroke-width:8; }
|
.ring-group { display:flex;flex-direction:column;align-items:center;gap:3px; }
|
||||||
|
.ring-track { fill:none;stroke:rgba(255,255,255,0.06);stroke-width:8;stroke-linecap:round; }
|
||||||
.ring-fill { fill:none;stroke-width:8;stroke-linecap:round;transition:stroke-dashoffset 1s cubic-bezier(0.4,0,0.2,1),stroke 0.4s; }
|
.ring-fill { fill:none;stroke-width:8;stroke-linecap:round;transition:stroke-dashoffset 1s cubic-bezier(0.4,0,0.2,1),stroke 0.4s; }
|
||||||
.ring-center { font-family:'JetBrains Mono',monospace;font-size:14px;font-weight:500;fill:var(--text);text-anchor:middle; }
|
.ring-center { font-family:'JetBrains Mono',monospace;font-size:15px;font-weight:500;fill:var(--text);dominant-baseline:middle;text-anchor:middle; }
|
||||||
.ring-sublabel { font-size:9px;fill:var(--text3);text-anchor:middle;font-weight:600;letter-spacing:0.1em;text-transform:uppercase; }
|
.ring-sublabel { font-size:9px;fill:var(--text3);dominant-baseline:middle;text-anchor:middle; }
|
||||||
.ring-group { display:flex;flex-direction:column;align-items:center;gap:4px; }
|
.ring-label-text { font-size:9px;color:var(--text3);letter-spacing:0.06em;text-transform:uppercase; }
|
||||||
.ring-label-text { font-size:9px;color:var(--text3);font-weight:600;letter-spacing:0.08em;text-transform:uppercase; }
|
.small-rings { display:flex;gap:8px;justify-content:center; }
|
||||||
.small-rings { display:flex;gap:16px;justify-content:center; }
|
|
||||||
.spark-section { width:100%;display:flex;flex-direction:column;gap:6px;flex:1;min-height:0; }
|
/* sparkline */
|
||||||
.spark-header { display:flex;align-items:center;justify-content:space-between; }
|
.spark-section { flex:1;display:flex;flex-direction:column;gap:6px;min-height:0; }
|
||||||
|
.spark-header { display:flex;justify-content:space-between;align-items:center; }
|
||||||
.spark-legend { display:flex;gap:8px; }
|
.spark-legend { display:flex;gap:8px; }
|
||||||
.spark-legend-item { display:flex;align-items:center;gap:3px;font-size:9px;color:var(--text3); }
|
.spark-legend-item { display:flex;align-items:center;gap:3px;font-size:9px;color:var(--text3); }
|
||||||
.spark-legend-dot { width:6px;height:6px;border-radius:50%; }
|
.spark-legend-dot { width:6px;height:6px;border-radius:50%; }
|
||||||
.spark-wrap { flex:1;min-height:40px; }
|
.spark-wrap { flex:1;background:var(--surface2);border-radius:8px;padding:8px;min-height:55px;position:relative; }
|
||||||
.spark-svg { width:100%;height:100%;overflow:visible; }
|
.spark-svg { width:100%;height:100%;overflow:visible; }
|
||||||
|
|
||||||
/* services */
|
/* services */
|
||||||
.service-grid { display:grid;grid-template-columns:repeat(auto-fill,minmax(168px,1fr));gap:8px;overflow-y:auto;flex:1; }
|
.service-grid { display:grid;grid-template-columns:repeat(auto-fill,minmax(168px,1fr));gap:8px;overflow-y:auto;flex:1; }
|
||||||
.service-card { background:var(--surface2);border:1px solid var(--border2);border-radius:10px;padding:9px 11px;display:flex;flex-direction:column;gap:5px; }
|
.service-card { background:var(--surface2);border:1px solid var(--border2);border-radius:11px;padding:12px;display:flex;flex-direction:column;gap:6px; }
|
||||||
.service-card-top { display:flex;align-items:center;justify-content:space-between; }
|
.service-card-top { display:flex;align-items:center;justify-content:space-between; }
|
||||||
.service-name { font-size:12px;font-weight:600; }
|
.service-name { font-size:13px;font-weight:500; }
|
||||||
.service-desc { font-size:10px;color:var(--text3); }
|
.service-badge { font-size:10px;font-weight:600;letter-spacing:0.06em;padding:2px 7px;border-radius:20px; }
|
||||||
.service-badge { font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:600;padding:2px 6px;border-radius:5px; }
|
|
||||||
.service-badge.checking { background:rgba(100,116,139,0.12);color:var(--text3); }
|
|
||||||
.service-badge.ok { background:rgba(74,222,128,0.14); color:var(--ok); }
|
.service-badge.ok { background:rgba(74,222,128,0.14); color:var(--ok); }
|
||||||
.service-badge.crit { background:rgba(248,113,113,0.14);color:var(--crit); }
|
.service-badge.crit { background:rgba(248,113,113,0.14);color:var(--crit); }
|
||||||
.svc-spark-wrap { height:24px; }
|
.service-badge.checking { background:rgba(34,211,238,0.09); color:var(--accent); }
|
||||||
.svc-spark { width:100%;height:100%;overflow:visible; }
|
.service-desc { font-size:11px;color:var(--text3);line-height:1.4; }
|
||||||
.service-footer { display:flex;align-items:center;justify-content:space-between;margin-top:2px; }
|
.service-footer { display:flex;align-items:center;justify-content:space-between;margin-top:2px; }
|
||||||
|
.service-latency { font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text3); }
|
||||||
.service-dots { display:flex;gap:3px; }
|
.service-dots { display:flex;gap:3px; }
|
||||||
.svc-dot { width:7px;height:7px;border-radius:50%;background:rgba(255,255,255,0.06); }
|
.svc-dot { width:5px;height:5px;border-radius:50%;background:var(--text3);opacity:0.25; }
|
||||||
.svc-dot.ok { background:var(--ok); }
|
.svc-dot.ok { background:var(--ok); opacity:0.85; }
|
||||||
.svc-dot.crit { background:var(--crit); }
|
.svc-dot.crit { background:var(--crit); opacity:0.85; }
|
||||||
.service-uptime { font-family:'JetBrains Mono',monospace;font-size:9px;font-weight:600; }
|
.service-uptime { font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:600; }
|
||||||
.service-uptime.full { color:var(--ok); }
|
.service-uptime.full { color:var(--ok); }
|
||||||
.service-uptime.partial { color:var(--warn); }
|
.service-uptime.partial { color:#FB923C; }
|
||||||
.service-uptime.down { color:var(--crit); }
|
.service-uptime.down { color:var(--crit); }
|
||||||
.service-latency { font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--text3); }
|
.svc-spark-wrap { height:28px;margin:2px 0; }
|
||||||
|
.svc-spark { width:100%;height:100%;overflow:visible; }
|
||||||
|
|
||||||
/* stream */
|
/* stream */
|
||||||
#stream-feed { flex:1;overflow:hidden;display:flex;flex-direction:column;gap:0; }
|
#stream-feed { flex:1;overflow:hidden;display:flex;flex-direction:column;gap:0; }
|
||||||
|
|
@ -155,7 +158,6 @@
|
||||||
#stream-ticker-inner { display:inline-block;animation:ticker 60s linear infinite; }
|
#stream-ticker-inner { display:inline-block;animation:ticker 60s linear infinite; }
|
||||||
@keyframes ticker { from{transform:translateX(0)} to{transform:translateX(-50%)} }
|
@keyframes ticker { from{transform:translateX(0)} to{transform:translateX(-50%)} }
|
||||||
|
|
||||||
/* bottom */
|
|
||||||
#bottom { display:flex;align-items:center;justify-content:space-between;padding-top:12px;border-top:1px solid var(--border); }
|
#bottom { display:flex;align-items:center;justify-content:space-between;padding-top:12px;border-top:1px solid var(--border); }
|
||||||
.bottom-brand { font-size:12px;color:var(--text3);font-weight:500;letter-spacing:0.04em; }
|
.bottom-brand { font-size:12px;color:var(--text3);font-weight:500;letter-spacing:0.04em; }
|
||||||
.bottom-brand span { color:var(--accent); }
|
.bottom-brand span { color:var(--accent); }
|
||||||
|
|
@ -167,9 +169,7 @@
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Binary curtain aurora canvas -->
|
|
||||||
<canvas id="binary-canvas"></canvas>
|
<canvas id="binary-canvas"></canvas>
|
||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div id="top">
|
<div id="top">
|
||||||
<div id="hostname-area">
|
<div id="hostname-area">
|
||||||
|
|
@ -220,7 +220,9 @@
|
||||||
<div class="panel-title"><i data-lucide="activity"></i>Vitals</div>
|
<div class="panel-title"><i data-lucide="activity"></i>Vitals</div>
|
||||||
<div class="ring-group">
|
<div class="ring-group">
|
||||||
<svg viewBox="0 0 120 120" style="width:120px;height:120px;overflow:visible">
|
<svg viewBox="0 0 120 120" style="width:120px;height:120px;overflow:visible">
|
||||||
<defs><filter id="glow-c"><feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs>
|
<defs>
|
||||||
|
<filter id="glow-c"><feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
|
||||||
|
</defs>
|
||||||
<circle class="ring-track" cx="60" cy="60" r="48"/>
|
<circle class="ring-track" cx="60" cy="60" r="48"/>
|
||||||
<circle class="ring-fill" cx="60" cy="60" r="48" id="ring-cpu-fill"
|
<circle class="ring-fill" cx="60" cy="60" r="48" id="ring-cpu-fill"
|
||||||
stroke="#22D3EE" filter="url(#glow-c)"
|
stroke="#22D3EE" filter="url(#glow-c)"
|
||||||
|
|
@ -293,127 +295,13 @@
|
||||||
<a class="bottom-link" href="/station" rel="noopener"><i data-lucide="monitor"></i>Design A</a>
|
<a class="bottom-link" href="/station" rel="noopener"><i data-lucide="monitor"></i>Design A</a>
|
||||||
<a class="bottom-link" href="/" target="_blank" rel="noopener"><i data-lucide="terminal"></i>dev</a>
|
<a class="bottom-link" href="/" target="_blank" rel="noopener"><i data-lucide="terminal"></i>dev</a>
|
||||||
<a class="bottom-link" href="https://posimai-atlas.vercel.app" target="_blank" rel="noopener"><i data-lucide="network"></i>atlas</a>
|
<a class="bottom-link" href="https://posimai-atlas.vercel.app" target="_blank" rel="noopener"><i data-lucide="network"></i>atlas</a>
|
||||||
|
<a class="bottom-link" href="https://posimai.soar-enrich.com" target="_blank" rel="noopener"><i data-lucide="layout-dashboard"></i>dashboard</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="refresh-countdown">次の更新まで <span id="countdown">30</span>s</div>
|
<div id="refresh-countdown">次の更新まで <span id="countdown">30</span>s</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ── Binary curtain aurora ────────────────────────────────────────────────────
|
|
||||||
(function initBinaryCurtain(){
|
|
||||||
const canvas = document.getElementById('binary-canvas');
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
// Column state
|
|
||||||
const FONT_SIZE = 14;
|
|
||||||
const COLS_PER_BAND = 1; // one char column per pixel column slot
|
|
||||||
let cols = [];
|
|
||||||
|
|
||||||
// Aurora color bands: each band has a hue center, width, and phase
|
|
||||||
// We generate 4–6 soft color regions that shift slowly
|
|
||||||
const BANDS = [
|
|
||||||
{ hue: 185, sat: 90, x: 0.15, speed: 0.00018, phase: 0 }, // cyan
|
|
||||||
{ hue: 265, sat: 80, x: 0.38, speed: 0.00013, phase: 1.5 }, // violet
|
|
||||||
{ hue: 185, sat: 85, x: 0.62, speed: 0.00020, phase: 3.0 }, // cyan-2
|
|
||||||
{ hue: 150, sat: 70, x: 0.80, speed: 0.00015, phase: 4.2 }, // green
|
|
||||||
];
|
|
||||||
|
|
||||||
function resize(){
|
|
||||||
canvas.width = window.innerWidth;
|
|
||||||
canvas.height = window.innerHeight;
|
|
||||||
const numCols = Math.ceil(canvas.width / FONT_SIZE);
|
|
||||||
// preserve existing cols, extend if needed
|
|
||||||
if(cols.length < numCols){
|
|
||||||
for(let i = cols.length; i < numCols; i++){
|
|
||||||
cols.push({
|
|
||||||
y: Math.random() * canvas.height, // current head y position
|
|
||||||
speed: 1.2 + Math.random() * 3.5, // fall speed px/frame
|
|
||||||
len: 8 + Math.floor(Math.random() * 20), // trail length in chars
|
|
||||||
chars: [], // char values
|
|
||||||
nextChange: 0, // when to mutate a char
|
|
||||||
opacity: 0.3 + Math.random() * 0.5,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cols.length = numCols;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resize();
|
|
||||||
window.addEventListener('resize', resize);
|
|
||||||
|
|
||||||
let t = 0;
|
|
||||||
|
|
||||||
function getBandColor(x, t){
|
|
||||||
// find dominant band at this x fraction
|
|
||||||
const xf = x / canvas.width;
|
|
||||||
let best = BANDS[0], bestDist = Infinity;
|
|
||||||
BANDS.forEach(b => {
|
|
||||||
const bx = b.x + Math.sin(t * b.speed + b.phase) * 0.12;
|
|
||||||
const dist = Math.abs(xf - bx);
|
|
||||||
if(dist < bestDist){ bestDist = dist; best = b; }
|
|
||||||
});
|
|
||||||
// brightness falloff from band center
|
|
||||||
const bx = best.x + Math.sin(t * best.speed + best.phase) * 0.12;
|
|
||||||
const dist = Math.abs(xf - bx);
|
|
||||||
const alpha = Math.max(0, 1 - dist / 0.22);
|
|
||||||
return { hue: best.hue, sat: best.sat, alpha };
|
|
||||||
}
|
|
||||||
|
|
||||||
function draw(){
|
|
||||||
t++;
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
ctx.font = `${FONT_SIZE}px 'JetBrains Mono', monospace`;
|
|
||||||
|
|
||||||
cols.forEach((col, i) => {
|
|
||||||
const x = i * FONT_SIZE;
|
|
||||||
const band = getBandColor(x, t);
|
|
||||||
|
|
||||||
// draw trail
|
|
||||||
for(let j = 0; j < col.len; j++){
|
|
||||||
const cy = col.y - j * FONT_SIZE;
|
|
||||||
if(cy < -FONT_SIZE || cy > canvas.height + FONT_SIZE) continue;
|
|
||||||
|
|
||||||
// ensure char exists
|
|
||||||
if(!col.chars[j] || (t % 8 === 0 && Math.random() < 0.05)){
|
|
||||||
col.chars[j] = Math.random() < 0.5 ? '1' : '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
const ch = col.chars[j];
|
|
||||||
// fade trail: head is brightest
|
|
||||||
const trailAlpha = (1 - j / col.len) * col.opacity;
|
|
||||||
const finalAlpha = trailAlpha * (band.alpha * 0.7 + 0.15);
|
|
||||||
|
|
||||||
if(j === 0){
|
|
||||||
// head char — bright white/cyan
|
|
||||||
ctx.fillStyle = `hsla(${band.hue},${band.sat}%,92%,${Math.min(1, finalAlpha * 2.2)})`;
|
|
||||||
} else if(ch === '1'){
|
|
||||||
ctx.fillStyle = `hsla(${band.hue},${band.sat}%,65%,${finalAlpha})`;
|
|
||||||
} else {
|
|
||||||
// '0' — slightly different hue for cyber feel
|
|
||||||
ctx.fillStyle = `hsla(${(band.hue + 30) % 360},${Math.round(band.sat * 0.6)}%,45%,${finalAlpha * 0.55})`;
|
|
||||||
}
|
|
||||||
ctx.fillText(ch, x, cy);
|
|
||||||
}
|
|
||||||
|
|
||||||
// advance column
|
|
||||||
col.y += col.speed;
|
|
||||||
if(col.y - col.len * FONT_SIZE > canvas.height){
|
|
||||||
col.y = -FONT_SIZE * 2;
|
|
||||||
col.speed = 1.2 + Math.random() * 3.5;
|
|
||||||
col.len = 8 + Math.floor(Math.random() * 20);
|
|
||||||
col.chars = [];
|
|
||||||
col.opacity = 0.3 + Math.random() * 0.5;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
requestAnimationFrame(draw);
|
|
||||||
}
|
|
||||||
draw();
|
|
||||||
})();
|
|
||||||
|
|
||||||
// ── Shared logic (same as station.html) ────────────────────────────────────
|
|
||||||
const HEALTH_URL = '/api/health';
|
const HEALTH_URL = '/api/health';
|
||||||
const REFRESH_SEC = 30;
|
const REFRESH_SEC = 30;
|
||||||
const HISTORY_MAX = 20;
|
const HISTORY_MAX = 20;
|
||||||
|
|
@ -429,6 +317,7 @@ const hist = {cpu:[], load:[]};
|
||||||
const svcHist = {};
|
const svcHist = {};
|
||||||
const svcLatHist = {};
|
const svcLatHist = {};
|
||||||
SERVICES.forEach(s => { svcHist[s.id] = []; svcLatHist[s.id] = []; });
|
SERVICES.forEach(s => { svcHist[s.id] = []; svcLatHist[s.id] = []; });
|
||||||
|
// メトリクスのグローバルキャッシュ(updateStream の setInterval から参照)
|
||||||
let _metrics = { cpuPct:0, memPct:0, diskPct:0, loadAvg:[0,0,0], uptimeS:0, sessions:0, hostname:'ubuntu-pc', nodeVer:'' };
|
let _metrics = { cpuPct:0, memPct:0, diskPct:0, loadAvg:[0,0,0], uptimeS:0, sessions:0, hostname:'ubuntu-pc', nodeVer:'' };
|
||||||
let streamData = null;
|
let streamData = null;
|
||||||
|
|
||||||
|
|
@ -495,6 +384,7 @@ async function fetchHealth(){
|
||||||
const cpuCount=data.cpu_count||1;
|
const cpuCount=data.cpu_count||1;
|
||||||
const loadAvg=data.load_avg||[0,0,0];
|
const loadAvg=data.load_avg||[0,0,0];
|
||||||
window._cpuCount=cpuCount;
|
window._cpuCount=cpuCount;
|
||||||
|
|
||||||
document.getElementById('cpu-val').textContent=`${cpuPct}%`;
|
document.getElementById('cpu-val').textContent=`${cpuPct}%`;
|
||||||
renderBinBar('cpu-bar',cpuPct,60,80);
|
renderBinBar('cpu-bar',cpuPct,60,80);
|
||||||
document.getElementById('mem-val').textContent=`${data.mem_used_mb}/${data.mem_total_mb}MB (${memPct}%)`;
|
document.getElementById('mem-val').textContent=`${data.mem_used_mb}/${data.mem_total_mb}MB (${memPct}%)`;
|
||||||
|
|
@ -514,12 +404,15 @@ async function fetchHealth(){
|
||||||
document.getElementById('node-val').textContent=(data.node_version||'—').replace('v','');
|
document.getElementById('node-val').textContent=(data.node_version||'—').replace('v','');
|
||||||
document.getElementById('platform-val').textContent=data.platform||'—';
|
document.getElementById('platform-val').textContent=data.platform||'—';
|
||||||
document.getElementById('hostname').textContent=data.hostname||'ubuntu-pc';
|
document.getElementById('hostname').textContent=data.hostname||'ubuntu-pc';
|
||||||
|
|
||||||
updateRing('ring-cpu-fill','ring-cpu-val', cpuPct, 301.6,'#22D3EE','#FB923C','#F87171');
|
updateRing('ring-cpu-fill','ring-cpu-val', cpuPct, 301.6,'#22D3EE','#FB923C','#F87171');
|
||||||
updateRing('ring-mem-fill','ring-mem-val', memPct, 188.5,'#A78BFA','#FB923C','#F87171');
|
updateRing('ring-mem-fill','ring-mem-val', memPct, 188.5,'#A78BFA','#FB923C','#F87171');
|
||||||
updateRing('ring-disk-fill','ring-disk-val',diskPct,188.5,'#4ADE80','#FB923C','#F87171');
|
updateRing('ring-disk-fill','ring-disk-val',diskPct,188.5,'#4ADE80','#FB923C','#F87171');
|
||||||
|
|
||||||
hist.cpu.push(cpuPct); if(hist.cpu.length>HISTORY_MAX)hist.cpu.shift();
|
hist.cpu.push(cpuPct); if(hist.cpu.length>HISTORY_MAX)hist.cpu.shift();
|
||||||
hist.load.push(loadAvg[0]||0); if(hist.load.length>HISTORY_MAX)hist.load.shift();
|
hist.load.push(loadAvg[0]||0); if(hist.load.length>HISTORY_MAX)hist.load.shift();
|
||||||
renderSparklines();
|
renderSparklines();
|
||||||
|
|
||||||
const alerts=[];
|
const alerts=[];
|
||||||
if(cpuPct>80)alerts.push({level:'crit',msg:`CPU ${cpuPct}% — 高負荷`});
|
if(cpuPct>80)alerts.push({level:'crit',msg:`CPU ${cpuPct}% — 高負荷`});
|
||||||
else if(cpuPct>60)alerts.push({level:'warn',msg:`CPU ${cpuPct}%`});
|
else if(cpuPct>60)alerts.push({level:'warn',msg:`CPU ${cpuPct}%`});
|
||||||
|
|
@ -532,6 +425,7 @@ async function fetchHealth(){
|
||||||
setAlerts(alerts);
|
setAlerts(alerts);
|
||||||
const hasCrit=alerts.some(a=>a.level==='crit'),hasWarn=alerts.some(a=>a.level==='warn');
|
const hasCrit=alerts.some(a=>a.level==='crit'),hasWarn=alerts.some(a=>a.level==='warn');
|
||||||
document.getElementById('status-dot').className=hasCrit?'crit':hasWarn?'warn':'ok';
|
document.getElementById('status-dot').className=hasCrit?'crit':hasWarn?'warn':'ok';
|
||||||
|
|
||||||
updateStream(data);
|
updateStream(data);
|
||||||
return true;
|
return true;
|
||||||
}catch(e){
|
}catch(e){
|
||||||
|
|
@ -583,7 +477,13 @@ function updateSparkline(id, ms, ok){
|
||||||
return `${x.toFixed(1)},${y.toFixed(1)}`;
|
return `${x.toFixed(1)},${y.toFixed(1)}`;
|
||||||
});
|
});
|
||||||
area.push(`${W},${H}`, `0,${H}`);
|
area.push(`${W},${H}`, `0,${H}`);
|
||||||
svg.innerHTML = `<defs><linearGradient id="sg-${id}" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="${color}" stop-opacity="0.25"/><stop offset="100%" stop-color="${color}" stop-opacity="0.03"/></linearGradient></defs><polygon points="${area.join(' ')}" fill="url(#sg-${id})"/><polyline points="${pts}" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`;
|
svg.innerHTML = `
|
||||||
|
<defs><linearGradient id="sg-${id}" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0%" stop-color="${color}" stop-opacity="0.25"/>
|
||||||
|
<stop offset="100%" stop-color="${color}" stop-opacity="0.03"/>
|
||||||
|
</linearGradient></defs>
|
||||||
|
<polygon points="${area.join(' ')}" fill="url(#sg-${id})"/>
|
||||||
|
<polyline points="${pts}" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushSvcHistory(id,ok){
|
function pushSvcHistory(id,ok){
|
||||||
|
|
@ -594,6 +494,7 @@ function pushSvcHistory(id,ok){
|
||||||
const idx=i-(5-h.length); if(idx<0){dot.className='svc-dot';continue;}
|
const idx=i-(5-h.length); if(idx<0){dot.className='svc-dot';continue;}
|
||||||
dot.className='svc-dot '+(h[idx]?'ok':'crit');
|
dot.className='svc-dot '+(h[idx]?'ok':'crit');
|
||||||
}
|
}
|
||||||
|
// 稼働率表示
|
||||||
const uptEl=document.getElementById(`upt-${id}`);
|
const uptEl=document.getElementById(`upt-${id}`);
|
||||||
if(uptEl&&h.length>0){
|
if(uptEl&&h.length>0){
|
||||||
const pct=Math.round(h.filter(Boolean).length/h.length*100);
|
const pct=Math.round(h.filter(Boolean).length/h.length*100);
|
||||||
|
|
@ -676,9 +577,12 @@ function updateStream(data){
|
||||||
const ip='100.77.11.43';
|
const ip='100.77.11.43';
|
||||||
const b8 = n => n.toString(2).padStart(8,'0');
|
const b8 = n => n.toString(2).padStart(8,'0');
|
||||||
const segments = [
|
const segments = [
|
||||||
`CPU:${b8(cpuPct??0)}`,`MEM:${b8(memPct??0)}`,`DISK:${b8(diskPct??0)}`,
|
`CPU:${b8(cpuPct??0)}`,
|
||||||
|
`MEM:${b8(memPct??0)}`,
|
||||||
|
`DISK:${b8(diskPct??0)}`,
|
||||||
`LOAD:${b8(Math.min(255,Math.round(((data.load_avg&&data.load_avg[0])||0)*64)))}`,
|
`LOAD:${b8(Math.min(255,Math.round(((data.load_avg&&data.load_avg[0])||0)*64)))}`,
|
||||||
`UP:${(data.uptime_s||0).toString(2)}`,`SESSION:${b8(data.active_sessions||0)}`,
|
`UP:${(data.uptime_s||0).toString(2)}`,
|
||||||
|
`SESSION:${b8(data.active_sessions||0)}`,
|
||||||
`TIME:${(Math.floor(Date.now()/1000)).toString(2).slice(-20)}`,
|
`TIME:${(Math.floor(Date.now()/1000)).toString(2).slice(-20)}`,
|
||||||
`IP:${ip.split('.').map(o=>b8(parseInt(o))).join('.')}`,
|
`IP:${ip.split('.').map(o=>b8(parseInt(o))).join('.')}`,
|
||||||
`HOST:${(data.hostname||'ubuntu-pc').split('').map(c=>b8(c.charCodeAt(0))).join(' ')}`,
|
`HOST:${(data.hostname||'ubuntu-pc').split('').map(c=>b8(c.charCodeAt(0))).join(' ')}`,
|
||||||
|
|
@ -713,6 +617,66 @@ buildServiceCards();
|
||||||
if(window.lucide)lucide.createIcons();
|
if(window.lucide)lucide.createIcons();
|
||||||
runRefresh();
|
runRefresh();
|
||||||
window.addEventListener('resize',renderSparklines);
|
window.addEventListener('resize',renderSparklines);
|
||||||
|
|
||||||
|
// ── Binary curtain aurora canvas ─────────────────────────────────────────────
|
||||||
|
(function initBinaryCurtain(){
|
||||||
|
const canvas = document.getElementById('binary-canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const FONT_SIZE = 14;
|
||||||
|
let cols = [];
|
||||||
|
const BANDS = [
|
||||||
|
{ hue: 185, sat: 90, x: 0.15, speed: 0.00018, phase: 0 },
|
||||||
|
{ hue: 265, sat: 80, x: 0.38, speed: 0.00013, phase: 1.5 },
|
||||||
|
{ hue: 185, sat: 85, x: 0.62, speed: 0.00020, phase: 3.0 },
|
||||||
|
{ hue: 150, sat: 70, x: 0.80, speed: 0.00015, phase: 4.2 },
|
||||||
|
];
|
||||||
|
function resize(){
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
const numCols = Math.ceil(canvas.width / FONT_SIZE);
|
||||||
|
if(cols.length < numCols){
|
||||||
|
for(let i = cols.length; i < numCols; i++){
|
||||||
|
cols.push({ y: Math.random()*canvas.height, speed:1.2+Math.random()*3.5, len:8+Math.floor(Math.random()*20), chars:[], opacity:0.3+Math.random()*0.5 });
|
||||||
|
}
|
||||||
|
} else { cols.length = numCols; }
|
||||||
|
}
|
||||||
|
resize();
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
let t = 0;
|
||||||
|
function getBandColor(x, t){
|
||||||
|
const xf = x / canvas.width;
|
||||||
|
let best = BANDS[0], bestDist = Infinity;
|
||||||
|
BANDS.forEach(b => { const bx=b.x+Math.sin(t*b.speed+b.phase)*0.12; const dist=Math.abs(xf-bx); if(dist<bestDist){bestDist=dist;best=b;} });
|
||||||
|
const bx = best.x + Math.sin(t*best.speed+best.phase)*0.12;
|
||||||
|
const alpha = Math.max(0, 1 - Math.abs(xf-bx)/0.22);
|
||||||
|
return { hue: best.hue, sat: best.sat, alpha };
|
||||||
|
}
|
||||||
|
function draw(){
|
||||||
|
t++;
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.font = `${FONT_SIZE}px 'JetBrains Mono', monospace`;
|
||||||
|
cols.forEach((col, i) => {
|
||||||
|
const x = i * FONT_SIZE;
|
||||||
|
const band = getBandColor(x, t);
|
||||||
|
for(let j = 0; j < col.len; j++){
|
||||||
|
const cy = col.y - j * FONT_SIZE;
|
||||||
|
if(cy < -FONT_SIZE || cy > canvas.height + FONT_SIZE) continue;
|
||||||
|
if(!col.chars[j] || (t%8===0 && Math.random()<0.05)) col.chars[j] = Math.random()<0.5?'1':'0';
|
||||||
|
const ch = col.chars[j];
|
||||||
|
const trailAlpha = (1 - j/col.len) * col.opacity;
|
||||||
|
const finalAlpha = trailAlpha * (band.alpha*0.7+0.15);
|
||||||
|
if(j===0){ ctx.fillStyle=`hsla(${band.hue},${band.sat}%,92%,${Math.min(1,finalAlpha*2.2)})`; }
|
||||||
|
else if(ch==='1'){ ctx.fillStyle=`hsla(${band.hue},${band.sat}%,65%,${finalAlpha})`; }
|
||||||
|
else { ctx.fillStyle=`hsla(${(band.hue+30)%360},${Math.round(band.sat*0.6)}%,45%,${finalAlpha*0.55})`; }
|
||||||
|
ctx.fillText(ch, x, cy);
|
||||||
|
}
|
||||||
|
col.y += col.speed;
|
||||||
|
if(col.y - col.len*FONT_SIZE > canvas.height){ col.y=-FONT_SIZE*2; col.speed=1.2+Math.random()*3.5; col.len=8+Math.floor(Math.random()*20); col.chars=[]; col.opacity=0.3+Math.random()*0.5; }
|
||||||
|
});
|
||||||
|
requestAnimationFrame(draw);
|
||||||
|
}
|
||||||
|
draw();
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue