Initial commit of dashboard
Static status dashboard served via nginx, with the switch TLS proxy section relabeled from WWTP to Extreme.
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
FROM nginx:alpine
|
||||||
|
COPY index.html /usr/share/nginx/html/index.html
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# dashboard — links page for services on this box
|
||||||
|
|
||||||
|
Static launcher page linking out to every service's own web UI (NetBox,
|
||||||
|
NocoDB, camera-list, go2rtc, LLDP mapper, switch-config-manager, RV50x
|
||||||
|
manager, and the stunnel-proxied WWTP switches). No health polling, no
|
||||||
|
live status — just direct links, same as typing the URL by hand.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Open `http://<vm-ip>:8090/`.
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
`index.html` is a single static file — edit it directly (add/remove a
|
||||||
|
card, fix a port) and run `docker compose up -d --build` to pick up the
|
||||||
|
change. There's no generator script; unlike camera-list or lldp-mapper
|
||||||
|
this doesn't pull from NocoDB, since it's just a fixed list of the
|
||||||
|
services running on this host.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
services:
|
||||||
|
|
||||||
|
dashboard:
|
||||||
|
build: .
|
||||||
|
container_name: dashboard
|
||||||
|
ports:
|
||||||
|
- "8090:80"
|
||||||
|
restart: unless-stopped
|
||||||
+258
@@ -0,0 +1,258 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Services</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #f5f6f8;
|
||||||
|
--card-bg: #ffffff;
|
||||||
|
--border: #e2e4e9;
|
||||||
|
--text: #1b1e24;
|
||||||
|
--muted: #6b7280;
|
||||||
|
--accent: #3b5bfd;
|
||||||
|
--tag-bg: #eef0ff;
|
||||||
|
--tag-text: #3b5bfd;
|
||||||
|
--shadow: 0 1px 2px rgba(16,24,40,0.06), 0 1px 3px rgba(16,24,40,0.08);
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--bg: #14161a;
|
||||||
|
--card-bg: #1d2027;
|
||||||
|
--border: #2c2f38;
|
||||||
|
--text: #e7e9ee;
|
||||||
|
--muted: #9aa1af;
|
||||||
|
--accent: #7f96ff;
|
||||||
|
--tag-bg: #262b45;
|
||||||
|
--tag-text: #b7c1ff;
|
||||||
|
--shadow: 0 1px 2px rgba(0,0,0,0.3), 0 1px 3px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:root[data-theme="dark"] {
|
||||||
|
--bg: #14161a; --card-bg: #1d2027; --border: #2c2f38; --text: #e7e9ee;
|
||||||
|
--muted: #9aa1af; --accent: #7f96ff; --tag-bg: #262b45; --tag-text: #b7c1ff;
|
||||||
|
--shadow: 0 1px 2px rgba(0,0,0,0.3), 0 1px 3px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
:root[data-theme="light"] {
|
||||||
|
--bg: #f5f6f8; --card-bg: #ffffff; --border: #e2e4e9; --text: #1b1e24;
|
||||||
|
--muted: #6b7280; --accent: #3b5bfd; --tag-bg: #eef0ff; --tag-text: #3b5bfd;
|
||||||
|
--shadow: 0 1px 2px rgba(16,24,40,0.06), 0 1px 3px rgba(16,24,40,0.08);
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
padding: 2.5rem 1.5rem 4rem;
|
||||||
|
}
|
||||||
|
.wrap { max-width: 980px; margin: 0 auto; }
|
||||||
|
h1 { font-size: 1.5rem; margin: 0 0 .25rem; }
|
||||||
|
.sub { color: var(--muted); margin: 0 0 2rem; font-size: .95rem; }
|
||||||
|
h2.section {
|
||||||
|
font-size: .8rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .06em;
|
||||||
|
color: var(--muted);
|
||||||
|
margin: 2.25rem 0 .75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
a.card {
|
||||||
|
display: block;
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.1rem 1.2rem;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
transition: transform .12s ease, border-color .12s ease;
|
||||||
|
}
|
||||||
|
a.card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
.card-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .5rem;
|
||||||
|
margin-bottom: .4rem;
|
||||||
|
}
|
||||||
|
.card-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.02rem;
|
||||||
|
}
|
||||||
|
.port {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
font-size: .72rem;
|
||||||
|
color: var(--tag-text);
|
||||||
|
background: var(--tag-bg);
|
||||||
|
padding: .15rem .45rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.card-desc {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: .86rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.offline-note {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: .5rem;
|
||||||
|
font-size: .74rem;
|
||||||
|
color: #b45309;
|
||||||
|
background: rgba(180,83,9,0.12);
|
||||||
|
padding: .1rem .45rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
details.subgroup {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.1rem 1.2rem;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
details.subgroup summary {
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.02rem;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
details.subgroup summary::-webkit-details-marker { display: none; }
|
||||||
|
details.subgroup summary::after {
|
||||||
|
content: "▸";
|
||||||
|
float: right;
|
||||||
|
color: var(--muted);
|
||||||
|
transition: transform .12s ease;
|
||||||
|
}
|
||||||
|
details.subgroup[open] summary::after { transform: rotate(90deg); }
|
||||||
|
.subgroup-desc {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: .86rem;
|
||||||
|
margin: .4rem 0 .9rem;
|
||||||
|
}
|
||||||
|
.sublinks {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
|
||||||
|
gap: .5rem;
|
||||||
|
}
|
||||||
|
.sublinks a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .5rem;
|
||||||
|
padding: .45rem .6rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
font-size: .85rem;
|
||||||
|
}
|
||||||
|
.sublinks a:hover { border-color: var(--accent); }
|
||||||
|
.sublinks .port { font-size: .68rem; }
|
||||||
|
.tool-note {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: .86rem;
|
||||||
|
padding: .6rem 0 0;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
background: var(--tag-bg);
|
||||||
|
color: var(--tag-text);
|
||||||
|
padding: .05rem .35rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: .85em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="wrap">
|
||||||
|
<h1>Services</h1>
|
||||||
|
<p class="sub">Links to every web UI running on this box (192.168.16.137). No live status — click through to check a service directly.</p>
|
||||||
|
|
||||||
|
<h2 class="section">Network & Infrastructure</h2>
|
||||||
|
<div class="grid">
|
||||||
|
<a class="card" href="http://192.168.16.137:8000/" target="_blank" rel="noopener">
|
||||||
|
<div class="card-top"><span class="card-name">NetBox</span><span class="port">:8000</span></div>
|
||||||
|
<div class="card-desc">IPAM / DCIM — source of truth for network inventory.</div>
|
||||||
|
</a>
|
||||||
|
<a class="card" href="http://192.168.16.137:5000/" target="_blank" rel="noopener">
|
||||||
|
<div class="card-top"><span class="card-name">LLDP Mapper</span><span class="port">:5000</span></div>
|
||||||
|
<div class="card-desc">SSH-based topology discovery for FS switches, visualized with Cytoscape.js.</div>
|
||||||
|
</a>
|
||||||
|
<a class="card" href="http://192.168.16.137:8003/" target="_blank" rel="noopener">
|
||||||
|
<div class="card-top"><span class="card-name">Switch Config Manager</span><span class="port">:8003</span></div>
|
||||||
|
<div class="card-desc">Write, save, and push switch configs over SSH.</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="section">Cameras</h2>
|
||||||
|
<div class="grid">
|
||||||
|
<a class="card" href="http://192.168.16.137:8081/" target="_blank" rel="noopener">
|
||||||
|
<div class="card-top"><span class="card-name">Camera List</span><span class="port">:8081</span></div>
|
||||||
|
<div class="card-desc">Links to every camera's own web UI, plus an ONVIF discovery scan vs. NocoDB.</div>
|
||||||
|
</a>
|
||||||
|
<a class="card" href="http://192.168.16.137:1984/" target="_blank" rel="noopener">
|
||||||
|
<div class="card-top"><span class="card-name">go2rtc</span><span class="port">:1984</span></div>
|
||||||
|
<div class="card-desc">Live camera viewer — click a camera name to view its stream.</div>
|
||||||
|
<span class="offline-note">not currently running — start with docker compose in /opt/go2rtc</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="section">Cellular & Data</h2>
|
||||||
|
<div class="grid">
|
||||||
|
<a class="card" href="http://192.168.16.137:8002/" target="_blank" rel="noopener">
|
||||||
|
<div class="card-top"><span class="card-name">RV50x Manager</span><span class="port">:8002</span></div>
|
||||||
|
<div class="card-desc">Manage Sierra Wireless AirLink RV50x cellular modems — configs, SSH/AT terminal.</div>
|
||||||
|
</a>
|
||||||
|
<a class="card" href="http://192.168.16.137:8080/" target="_blank" rel="noopener">
|
||||||
|
<div class="card-top"><span class="card-name">NocoDB</span><span class="port">:8080</span></div>
|
||||||
|
<div class="card-desc">Database backend behind the camera, switch, and modem inventories.</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="section">Extreme Switch TLS Proxy (stunnel)</h2>
|
||||||
|
<details class="subgroup">
|
||||||
|
<summary>20 switches via stunnel — TLS 1.0 legacy switches, proxied through 192.168.16.137</summary>
|
||||||
|
<p class="subgroup-desc">Browser warns about a self-signed/expired cert on each — click through, this is expected. Container is currently stopped; start with <code>docker compose up -d</code> in <code>/opt/stunnel</code>.</p>
|
||||||
|
<div class="sublinks">
|
||||||
|
<a href="https://192.168.16.137:4500/" target="_blank" rel="noopener"><span>wwtp-offc-sw01</span><span class="port">:4500</span></a>
|
||||||
|
<a href="https://192.168.16.137:4501/" target="_blank" rel="noopener"><span>wwtp-srvr-sw01</span><span class="port">:4501</span></a>
|
||||||
|
<a href="https://192.168.16.137:4502/" target="_blank" rel="noopener"><span>wwtp-ecb1-sw01</span><span class="port">:4502</span></a>
|
||||||
|
<a href="https://192.168.16.137:4503/" target="_blank" rel="noopener"><span>wwtp-head-sw01</span><span class="port">:4503</span></a>
|
||||||
|
<a href="https://192.168.16.137:4504/" target="_blank" rel="noopener"><span>wwtp-ecb2-sw01</span><span class="port">:4504</span></a>
|
||||||
|
<a href="https://192.168.16.137:4505/" target="_blank" rel="noopener"><span>wwtp-ags1-sw01</span><span class="port">:4505</span></a>
|
||||||
|
<a href="https://192.168.16.137:4506/" target="_blank" rel="noopener"><span>wwtp-ags2-sw01</span><span class="port">:4506</span></a>
|
||||||
|
<a href="https://192.168.16.137:4507/" target="_blank" rel="noopener"><span>wwtp-ags3-sw01</span><span class="port">:4507</span></a>
|
||||||
|
<a href="https://192.168.16.137:4508/" target="_blank" rel="noopener"><span>wwtp-coag-sw01</span><span class="port">:4508</span></a>
|
||||||
|
<a href="https://192.168.16.137:4509/" target="_blank" rel="noopener"><span>wwtp-reuse-sw01</span><span class="port">:4509</span></a>
|
||||||
|
<a href="https://192.168.16.137:4510/" target="_blank" rel="noopener"><span>wwtp-ecb3-sw01</span><span class="port">:4510</span></a>
|
||||||
|
<a href="https://192.168.16.137:4511/" target="_blank" rel="noopener"><span>wwtp-yard-sw01</span><span class="port">:4511</span></a>
|
||||||
|
<a href="https://192.168.16.137:4512/" target="_blank" rel="noopener"><span>wwtp-gate-sw01</span><span class="port">:4512</span></a>
|
||||||
|
<a href="https://192.168.16.137:4513/" target="_blank" rel="noopener"><span>wwtp-ecb1-sw02</span><span class="port">:4513</span></a>
|
||||||
|
<a href="https://192.168.16.137:4514/" target="_blank" rel="noopener"><span>wwtp-ecb2-sw02</span><span class="port">:4514</span></a>
|
||||||
|
<a href="https://192.168.16.137:4515/" target="_blank" rel="noopener"><span>wwtp-uv-sw01</span><span class="port">:4515</span></a>
|
||||||
|
<a href="https://192.168.16.137:4516/" target="_blank" rel="noopener"><span>wwtp-fitr-sw01</span><span class="port">:4516</span></a>
|
||||||
|
<a href="https://192.168.16.137:4517/" target="_blank" rel="noopener"><span>wwtp-crn-sw01</span><span class="port">:4517</span></a>
|
||||||
|
<a href="https://192.168.16.137:4518/" target="_blank" rel="noopener"><span>wwtp-bst-sw01</span><span class="port">:4518</span></a>
|
||||||
|
<a href="https://192.168.16.137:4519/" target="_blank" rel="noopener"><span>wwtp-ecb3-sw02</span><span class="port">:4519</span></a>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<h2 class="section">On-demand tools</h2>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="card" style="cursor:default;">
|
||||||
|
<div class="card-top"><span class="card-name">onvif-discover</span></div>
|
||||||
|
<div class="card-desc">One-off ONVIF camera sweep vs. NocoDB — not a running service, no link. Run from <code>/opt/onvif-discover</code>.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user