dashboard mobile fixes
This commit is contained in:
parent
86dacb0a74
commit
caac6877d7
|
|
@ -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(
|
||||
<Layout title="Metrics">
|
||||
<TabBar>
|
||||
<Tab href={appUrl}>App</Tab>
|
||||
<TabActive href={globalUrl}>Global</TabActive>
|
||||
</TabBar>
|
||||
<EmptyState>No apps found</EmptyState>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
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(
|
||||
<Layout title="Metrics - Global">
|
||||
<TabBar>
|
||||
<Tab href={appUrl}>App</Tab>
|
||||
<TabActive href={globalUrl}>Global</TabActive>
|
||||
</TabBar>
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<Th>Name</Th>
|
||||
<Th>State</Th>
|
||||
<ThRight>PID</ThRight>
|
||||
<ThRight>CPU</ThRight>
|
||||
<ThRight>MEM</ThRight>
|
||||
<ThRight>RSS</ThRight>
|
||||
<ThRight>Data</ThRight>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{metrics.map(s => (
|
||||
<Tr>
|
||||
<Td>
|
||||
{s.name}
|
||||
{s.tool && <ToolBadge>[tool]</ToolBadge>}
|
||||
</Td>
|
||||
<Td>
|
||||
<StatusBadge style={`color: ${getStatusColor(s.state)}`}>
|
||||
{s.state}
|
||||
</StatusBadge>
|
||||
</Td>
|
||||
<TdRight>{s.pid ?? '-'}</TdRight>
|
||||
<TdRight>{formatPercent(s.cpu)}</TdRight>
|
||||
<TdRight>{formatPercent(s.memory)}</TdRight>
|
||||
<TdRight>{formatRss(s.rss)}</TdRight>
|
||||
<TdRight>{formatBytes(s.dataSize)}</TdRight>
|
||||
</Tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
<Summary>
|
||||
{running.length} running · {formatPercent(totalCpu)} CPU · {formatRss(totalRss)} RSS · {formatBytes(totalData)} data
|
||||
</Summary>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
const metrics = await getAppMetricsByName(appName)
|
||||
|
||||
if (!metrics) {
|
||||
|
|
@ -499,6 +610,10 @@ app.get('/', async c => {
|
|||
|
||||
return c.html(
|
||||
<Layout title="Metrics">
|
||||
<TabBar>
|
||||
<TabActive href={appUrl}>App</TabActive>
|
||||
<Tab href={globalUrl}>Global</Tab>
|
||||
</TabBar>
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -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 }) {
|
|||
|
||||
<StatusDotsRow>
|
||||
{[...apps.filter(a => !a.tool), ...apps.filter(a => a.tool)].map(app => (
|
||||
<StatusDotLink key={app.name} data-tooltip={app.name} onClick={(e: Event) => {
|
||||
<StatusDotLink
|
||||
key={app.name}
|
||||
data-tooltip={app.name}
|
||||
tooltipVisible={activeTooltip === app.name || undefined}
|
||||
onClick={(e: Event) => {
|
||||
e.preventDefault()
|
||||
if (isNarrow && activeTooltip !== app.name) {
|
||||
activeTooltip = app.name
|
||||
render()
|
||||
return
|
||||
}
|
||||
activeTooltip = null
|
||||
setSelectedApp(app.name)
|
||||
update()
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<StatusDot state={app.state} data-app={app.name} />
|
||||
</StatusDotLink>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ export function LogsSection({ app }: { app: App }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Section style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0, marginBottom: 0 }}>
|
||||
<LogsHeader>
|
||||
<SectionTitle style={{ marginBottom: 0 }}>Logs</SectionTitle>
|
||||
<LogsControls>
|
||||
|
|
|
|||
|
|
@ -127,17 +127,18 @@ function Gauge({ value }: { value: number }) {
|
|||
}
|
||||
|
||||
function VitalsContent() {
|
||||
const narrow = isNarrow || undefined
|
||||
return (
|
||||
<>
|
||||
<VitalCard>
|
||||
<VitalCard narrow={narrow}>
|
||||
<VitalLabel>CPU</VitalLabel>
|
||||
<Gauge value={_metrics.cpu} />
|
||||
</VitalCard>
|
||||
<VitalCard>
|
||||
<VitalCard narrow={narrow}>
|
||||
<VitalLabel>RAM</VitalLabel>
|
||||
<Gauge value={_metrics.ram.percent} />
|
||||
</VitalCard>
|
||||
<VitalCard>
|
||||
<VitalCard narrow={narrow}>
|
||||
<VitalLabel>Disk</VitalLabel>
|
||||
<Gauge value={_metrics.disk.percent} />
|
||||
</VitalCard>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user