Widgets tab links the external demo when config.json names one (demo_url)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
65a9888784
commit
62605d57eb
@@ -15,3 +15,6 @@ Anyone who can load the page can read the token, so serve it only where that's a
|
|||||||
(it can read sensors and nothing else). Deployed on `core` as the `hamlab-demo` stack —
|
(it can read sensors and nothing else). Deployed on `core` as the `hamlab-demo` stack —
|
||||||
see Hamlet `docs/docker.md`. Update it with
|
see Hamlet `docs/docker.md`. Update it with
|
||||||
`cp examples/external-demo/index.html /home/core/apps/hamlab-demo/site/`.
|
`cp examples/external-demo/index.html /home/core/apps/hamlab-demo/site/`.
|
||||||
|
|
||||||
|
To link it from the Widgets tab, set `"demo_url": "http://…"` in the hub's `config.json`
|
||||||
|
and restart Ham Lab.
|
||||||
|
|||||||
@@ -736,10 +736,12 @@ func (s *Server) me(w http.ResponseWriter, _ *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, rebootErr := os.Stat("/run/reboot-required")
|
_, rebootErr := os.Stat("/run/reboot-required")
|
||||||
|
var demoURL string
|
||||||
|
s.cfg.View(func(c *config.Config) { demoURL = c.DemoURL })
|
||||||
jsonOK(w, 200, map[string]any{
|
jsonOK(w, 200, map[string]any{
|
||||||
"host": s.host, "user": s.user, "version": s.version,
|
"host": s.host, "user": s.user, "version": s.version,
|
||||||
"load": load, "uptime": uptime, "service_started": s.started,
|
"load": load, "uptime": uptime, "service_started": s.started,
|
||||||
"reboot_needed": rebootErr == nil, "fingerprint": s.fingerprint, "arch": runtime.GOARCH,
|
"reboot_needed": rebootErr == nil, "fingerprint": s.fingerprint, "arch": runtime.GOARCH, "demo_url": demoURL,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ type Config struct {
|
|||||||
TLS string `json:"tls,omitempty"` // "auto" → self-signed cert in the data dir; "" → plain HTTP
|
TLS string `json:"tls,omitempty"` // "auto" → self-signed cert in the data dir; "" → plain HTTP
|
||||||
Tokens []Token `json:"tokens,omitempty"`
|
Tokens []Token `json:"tokens,omitempty"`
|
||||||
Nodes []Node `json:"nodes,omitempty"`
|
Nodes []Node `json:"nodes,omitempty"`
|
||||||
|
// DemoURL, when set, is linked from the Widgets tab: a page outside Ham Lab that
|
||||||
|
// shows the sensor API being used (examples/external-demo).
|
||||||
|
DemoURL string `json:"demo_url,omitempty"`
|
||||||
|
|
||||||
path string
|
path string
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|||||||
+6
-2
@@ -31,7 +31,7 @@ function Tile({ spec, data, note }: { spec: WidgetSpec; data: WidgetData; note:
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Gallery() {
|
export default function Gallery({ demoUrl }: { demoUrl?: string }) {
|
||||||
const [cat, setCat] = useState<Sensor[] | null>(null)
|
const [cat, setCat] = useState<Sensor[] | null>(null)
|
||||||
const [err, setErr] = useState('')
|
const [err, setErr] = useState('')
|
||||||
const rate = useRefreshMs()
|
const rate = useRefreshMs()
|
||||||
@@ -54,7 +54,11 @@ export default function Gallery() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dash gallery">
|
<div className="dash gallery">
|
||||||
<div className="dash-bar"><span className="hint">{sensors.length} sensors on this machine · live · tap a tile to copy its ID</span></div>
|
<div className="dash-bar">
|
||||||
|
<span className="hint">{sensors.length} sensors on this machine · live · tap a tile to copy its ID</span>
|
||||||
|
<span className="spacer" />
|
||||||
|
{demoUrl && <a className="btn" href={demoUrl} target="_blank" rel="noreferrer" title="a page outside Ham Lab, built on the same sensor API">External demo ↗</a>}
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2 className="section">Widget types</h2>
|
<h2 className="section">Widget types</h2>
|
||||||
<div className="kpis">
|
<div className="kpis">
|
||||||
|
|||||||
+1
-1
@@ -179,7 +179,7 @@ export default function Shell({ me: hubMe, onLoggedOut }: { me: Me; onLoggedOut:
|
|||||||
{view === 'settings' && <Settings me={hubMe} />}
|
{view === 'settings' && <Settings me={hubMe} />}
|
||||||
{view === 'machines' && <Machines hub={hubMe} onChanged={loadMachines} onOpen={(n) => { switchMachine(n); setView('dashboard') }} />}
|
{view === 'machines' && <Machines hub={hubMe} onChanged={loadMachines} onOpen={(n) => { switchMachine(n); setView('dashboard') }} />}
|
||||||
{view === 'dashboard' && <Dashboard />}
|
{view === 'dashboard' && <Dashboard />}
|
||||||
{view === 'widgets' && <Gallery />}
|
{view === 'widgets' && <Gallery demoUrl={hubMe.demo_url} />}
|
||||||
{view === 'procs' && <Procs />}
|
{view === 'procs' && <Procs />}
|
||||||
{view === 'services' && <Services />}
|
{view === 'services' && <Services />}
|
||||||
{view === 'cron' && <Cron />}
|
{view === 'cron' && <Cron />}
|
||||||
|
|||||||
+1
-1
@@ -51,7 +51,7 @@ async function req<T>(method: string, path: string, body?: unknown, local = fals
|
|||||||
return r.json()
|
return r.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Me { host: string; user: string; version: string; load: string; uptime: number; reboot_needed?: boolean; fingerprint?: string; arch?: string }
|
export interface Me { demo_url?: string; host: string; user: string; version: string; load: string; uptime: number; reboot_needed?: boolean; fingerprint?: string; arch?: string }
|
||||||
export interface PushResult { name: string; ok: boolean; version?: string; error?: string }
|
export interface PushResult { name: string; ok: boolean; version?: string; error?: string }
|
||||||
export interface MachineStatus { name: string; url: string; fingerprint?: string; added: string; online: boolean; host?: string; version?: string; load?: string; uptime?: number; reboot_needed?: boolean; last_seen?: string; error?: string }
|
export interface MachineStatus { name: string; url: string; fingerprint?: string; added: string; online: boolean; host?: string; version?: string; load?: string; uptime?: number; reboot_needed?: boolean; last_seen?: string; error?: string }
|
||||||
export interface Term { id: string; name: string; running: boolean; exit_code: number; created: string; clients: number }
|
export interface Term { id: string; name: string; running: boolean; exit_code: number; created: string; clients: number }
|
||||||
|
|||||||
@@ -427,3 +427,4 @@ h2.section { margin: 0; font-size: 13px; font-weight: 600; color: var(--muted);
|
|||||||
.gallery-tile .value { font-size: 18px; overflow-wrap: anywhere; }
|
.gallery-tile .value { font-size: 18px; overflow-wrap: anywhere; }
|
||||||
.gallery-id { margin-top: auto; padding-top: 8px; font-size: 10.5px; color: var(--muted); overflow-wrap: anywhere; }
|
.gallery-id { margin-top: auto; padding-top: 8px; font-size: 10.5px; color: var(--muted); overflow-wrap: anywhere; }
|
||||||
@media (max-width: 800px) { .gallery .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
@media (max-width: 800px) { .gallery .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||||
|
a.btn { text-decoration: none; display: inline-flex; align-items: center; }
|
||||||
|
|||||||
Reference in New Issue
Block a user