Switch worker to the Ookla CLI (jitter, packet loss, server, ISP, shareable result URL, bytes). Migration 028 enriches speedtest_results + adds a generic app_settings store. New /speedtest page: KPIs, throughput + latency charts, window stats, configurable schedule (reschedulable cron) & low-speed alert threshold, history table. SV card gains ping/jitter + a link through to the page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
63 lines
2.5 KiB
JavaScript
63 lines
2.5 KiB
JavaScript
// Hash-based router. Routes:
|
|
// #/ home
|
|
// #/space/:id space view
|
|
// #/project/:id project view
|
|
// #/page/:id page view
|
|
// #/ref/:id reference detail
|
|
// #/resource/:id resource detail
|
|
// #/search?q= search results
|
|
// #/inbox pending changes
|
|
// #/sacred-valley dashboard placeholder
|
|
// #/sentinel Yerin security view
|
|
// #/little-blue Little Blue caretaker view
|
|
// #/timelapse Timelapse iframe embed
|
|
// #/ai-usage AI Usage iframe embed
|
|
// Anything unrecognized falls through to the home handler.
|
|
|
|
const ROUTES = [
|
|
{ name: 'space', re: /^\/space\/([^/]+)$/, keys: ['id'] },
|
|
{ name: 'project', re: /^\/project\/([^/]+)$/, keys: ['id'] },
|
|
{ name: 'page', re: /^\/page\/([^/]+)$/, keys: ['id'] },
|
|
{ name: 'ref', re: /^\/ref\/([^/]+)$/, keys: ['id'] },
|
|
{ name: 'resource', re: /^\/resource\/([^/]+)$/, keys: ['id'] },
|
|
{ name: 'search', re: /^\/search$/, keys: [] },
|
|
{ name: 'inbox', re: /^\/inbox$/, keys: [] },
|
|
{ name: 'sacred-valley', re: /^\/sacred-valley$/, keys: [] },
|
|
{ name: 'yerin', re: /^\/(yerin|sentinel)$/, keys: [] },
|
|
{ name: 'little-blue', re: /^\/little-blue$/, keys: [] },
|
|
{ name: 'terminal', re: /^\/terminal$/, keys: [] },
|
|
{ name: 'timelapse', re: /^\/timelapse$/, keys: [] },
|
|
{ name: 'ai-usage', re: /^\/ai-usage$/, keys: [] },
|
|
{ name: 'obd2', re: /^\/obd2$/, keys: [] },
|
|
{ name: 'links', re: /^\/links$/, keys: [] },
|
|
{ name: 'mirror', re: /^\/mirror$/, keys: [] },
|
|
{ name: 'settings', re: /^\/settings$/, keys: [] },
|
|
{ name: 'jobs', re: /^\/jobs$/, keys: [] },
|
|
{ name: 'speedtest', re: /^\/speedtest$/, keys: [] },
|
|
{ name: 'home', re: /^\/?$/, keys: [] }
|
|
];
|
|
|
|
export function current() {
|
|
const raw = (location.hash || '#/').slice(1);
|
|
const [path, queryString = ''] = raw.split('?');
|
|
const query = Object.fromEntries(new URLSearchParams(queryString));
|
|
for (const r of ROUTES) {
|
|
const m = path.match(r.re);
|
|
if (m) {
|
|
const params = {};
|
|
r.keys.forEach((k, i) => { params[k] = m[i + 1]; });
|
|
return { name: r.name, params, query, hash: raw };
|
|
}
|
|
}
|
|
return { name: 'home', params: {}, query: {}, hash: raw };
|
|
}
|
|
|
|
export function navigate(hash) {
|
|
location.hash = hash.startsWith('#') ? hash : '#' + hash;
|
|
}
|
|
|
|
export function route(handler) {
|
|
window.addEventListener('hashchange', () => handler(current()));
|
|
handler(current());
|
|
}
|