From caac6877d710af72417aa0f71381291a1f073b14 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Mon, 16 Feb 2026 09:22:26 -0800 Subject: [PATCH] dashboard mobile fixes --- apps/metrics/20260130-000000/index.tsx | 115 +++++++++++++++++++++ src/client/components/DashboardLanding.tsx | 23 ++++- src/client/components/LogsSection.tsx | 2 +- src/client/components/Vitals.tsx | 7 +- src/client/styles/dashboard.ts | 16 ++- src/client/styles/layout.ts | 8 +- src/client/styles/logs.tsx | 3 +- src/client/styles/misc.ts | 14 ++- 8 files changed, 171 insertions(+), 17 deletions(-) diff --git a/apps/metrics/20260130-000000/index.tsx b/apps/metrics/20260130-000000/index.tsx index 5bb33b2..4850500 100644 --- a/apps/metrics/20260130-000000/index.tsx +++ b/apps/metrics/20260130-000000/index.tsx @@ -339,6 +339,41 @@ const EmptyState = define('EmptyState', { color: theme('colors-textMuted'), }) +const Tab = define('Tab', { + base: 'a', + padding: '8px 16px', + fontSize: '13px', + fontFamily: theme('fonts-sans'), + color: theme('colors-textMuted'), + textDecoration: 'none', + borderBottom: '2px solid transparent', + cursor: 'pointer', + states: { + ':hover': { + color: theme('colors-text'), + }, + }, +}) + +const TabActive = define('TabActive', { + base: 'a', + padding: '8px 16px', + fontSize: '13px', + fontFamily: theme('fonts-sans'), + color: theme('colors-text'), + textDecoration: 'none', + borderBottom: `2px solid ${theme('colors-primary')}`, + fontWeight: 'bold', + cursor: 'default', +}) + +const TabBar = define('TabBar', { + display: 'flex', + gap: '4px', + borderBottom: `1px solid ${theme('colors-border')}`, + marginBottom: '15px', +}) + const ChartsContainer = define('ChartsContainer', { marginTop: '24px', display: 'grid', @@ -487,6 +522,82 @@ app.get('/', async c => { // Single app view if (appName) { + const tab = c.req.query('tab') === 'global' ? 'global' : 'app' + const appUrl = `/?app=${appName}` + const globalUrl = `/?app=${appName}&tab=global` + + if (tab === 'global') { + const metrics = await getAppMetrics() + + metrics.sort((a, b) => { + if (a.state === 'running' && b.state !== 'running') return -1 + if (a.state !== 'running' && b.state === 'running') return 1 + return a.name.localeCompare(b.name) + }) + + if (metrics.length === 0) { + return c.html( + + + App + Global + + No apps found + + ) + } + + const running = metrics.filter(s => s.state === 'running') + const totalCpu = running.reduce((sum, s) => sum + (s.cpu ?? 0), 0) + const totalRss = running.reduce((sum, s) => sum + (s.rss ?? 0), 0) + const totalData = metrics.reduce((sum, s) => sum + (s.dataSize ?? 0), 0) + + return c.html( + + + App + Global + + + + + + + PID + CPU + MEM + RSS + Data + + + + {metrics.map(s => ( + + + + {s.pid ?? '-'} + {formatPercent(s.cpu)} + {formatPercent(s.memory)} + {formatRss(s.rss)} + {formatBytes(s.dataSize)} + + ))} + +
NameState
+ {s.name} + {s.tool && [tool]} + + + {s.state} + +
+ + {running.length} running · {formatPercent(totalCpu)} CPU · {formatRss(totalRss)} RSS · {formatBytes(totalData)} data + +
+ ) + } + const metrics = await getAppMetricsByName(appName) if (!metrics) { @@ -499,6 +610,10 @@ app.get('/', async c => { return c.html( + + App + Global + diff --git a/src/client/components/DashboardLanding.tsx b/src/client/components/DashboardLanding.tsx index 36ca676..a771f83 100644 --- a/src/client/components/DashboardLanding.tsx +++ b/src/client/components/DashboardLanding.tsx @@ -14,6 +14,8 @@ import { update } from '../update' import { UnifiedLogs, initUnifiedLogs } from './UnifiedLogs' import { Vitals, initVitals } from './Vitals' +let activeTooltip: string | null = null + export function DashboardLanding({ render }: { render: () => void }) { useEffect(() => { initUnifiedLogs() @@ -38,11 +40,22 @@ export function DashboardLanding({ render }: { render: () => void }) { {[...apps.filter(a => !a.tool), ...apps.filter(a => a.tool)].map(app => ( - { - e.preventDefault() - setSelectedApp(app.name) - update() - }}> + { + e.preventDefault() + if (isNarrow && activeTooltip !== app.name) { + activeTooltip = app.name + render() + return + } + activeTooltip = null + setSelectedApp(app.name) + update() + }} + > ))} diff --git a/src/client/components/LogsSection.tsx b/src/client/components/LogsSection.tsx index 1b3f9f5..e7ffda4 100644 --- a/src/client/components/LogsSection.tsx +++ b/src/client/components/LogsSection.tsx @@ -206,7 +206,7 @@ export function LogsSection({ app }: { app: App }) { } return ( -
+
Logs diff --git a/src/client/components/Vitals.tsx b/src/client/components/Vitals.tsx index 014cfa3..5e01ffc 100644 --- a/src/client/components/Vitals.tsx +++ b/src/client/components/Vitals.tsx @@ -127,17 +127,18 @@ function Gauge({ value }: { value: number }) { } function VitalsContent() { + const narrow = isNarrow || undefined return ( <> - + CPU - + RAM - + Disk diff --git a/src/client/styles/dashboard.ts b/src/client/styles/dashboard.ts index 2b77e63..c144429 100644 --- a/src/client/styles/dashboard.ts +++ b/src/client/styles/dashboard.ts @@ -10,7 +10,7 @@ export const VitalsSection = define('VitalsSection', { maxWidth: 800, variants: { narrow: { - gridTemplateColumns: '1fr', + gap: 12, }, }, }) @@ -24,6 +24,12 @@ export const VitalCard = define('VitalCard', { flexDirection: 'column', alignItems: 'center', gap: 16, + variants: { + narrow: { + padding: 12, + gap: 8, + }, + }, }) export const VitalLabel = define('VitalLabel', { @@ -60,6 +66,10 @@ export const GaugeValue = define('GaugeValue', { export const LogsSection = define('LogsSection', { width: '100%', maxWidth: 800, + flex: 1, + minHeight: 0, + display: 'flex', + flexDirection: 'column', background: theme('colors-bgElement'), border: `1px solid ${theme('colors-border')}`, borderRadius: theme('radius-md'), @@ -72,6 +82,7 @@ export const LogsHeader = define('LogsHeader', { alignItems: 'center', padding: '12px 16px', borderBottom: `1px solid ${theme('colors-border')}`, + flexShrink: 0, }) export const LogsTitle = define('LogsTitle', { @@ -133,7 +144,8 @@ export const LogsTab = define('LogsTab', { }) export const LogsBody = define('LogsBody', { - height: 200, + flex: 1, + minHeight: 0, overflow: 'auto', fontFamily: theme('fonts-mono'), fontSize: 12, diff --git a/src/client/styles/layout.ts b/src/client/styles/layout.ts index 13b69a6..6492cde 100644 --- a/src/client/styles/layout.ts +++ b/src/client/styles/layout.ts @@ -192,8 +192,10 @@ export const HeaderActions = define('HeaderActions', { export const MainContent = define('MainContent', { flex: 1, + display: 'flex', + flexDirection: 'column', padding: '10px 24px', - overflow: 'auto', + overflow: 'hidden', }) export const DashboardContainer = define('DashboardContainer', { @@ -201,14 +203,12 @@ export const DashboardContainer = define('DashboardContainer', { display: 'flex', flexDirection: 'column', alignItems: 'center', - justifyContent: 'center', padding: 40, - paddingTop: 0, gap: 40, + overflow: 'hidden', variants: { narrow: { padding: 20, - paddingTop: 0, gap: 24, }, }, diff --git a/src/client/styles/logs.tsx b/src/client/styles/logs.tsx index 3166ccb..f322915 100644 --- a/src/client/styles/logs.tsx +++ b/src/client/styles/logs.tsx @@ -8,7 +8,8 @@ export const LogsContainer = define('LogsContainer', { fontFamily: theme('fonts-mono'), fontSize: 12, color: theme('colors-textMuted'), - maxHeight: 200, + flex: 1, + minHeight: 0, overflow: 'auto', variants: { narrow: { diff --git a/src/client/styles/misc.ts b/src/client/styles/misc.ts index 180c11f..1b85faf 100644 --- a/src/client/styles/misc.ts +++ b/src/client/styles/misc.ts @@ -31,6 +31,15 @@ export const StatusDotLink = define('StatusDotLink', { opacity: 1, }, }, + variants: { + tooltipVisible: { + selectors: { + '&::after': { + opacity: 1, + }, + }, + }, + }, }) export const StatusDotsRow = define('StatusDotsRow', { @@ -143,10 +152,13 @@ export const Tab = define('Tab', { export const TabContent = define('TabContent', { display: 'none', + minHeight: 0, variants: { active: { - display: 'block' + display: 'flex', + flexDirection: 'column', + flex: 1, } } })