Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | 37x 37x 84x 84x 84x 84x 720x 720x 720x 720x 720x 720x 720x 720x 720x 720x 720x 720x 720x 128x 592x 592x 224x 224x 218x 218x 218x 84x 84x | import { Outlet, useNavigate } from 'react-router-dom'
import { Page, DataTable, Crud } from '@/layers/L2R'
import type { PageConfig, ListConfig, CrudConfig } from '@/layers/interfaces'
import { Button } from '@/components/ui/button'
import { RunPhaseIcon } from '@/shared/components/RunPhaseIcon'
import { SourceBadge } from '@/shared/components/SourceBadge'
import { abbreviateNumber, formatRelativeTime } from '@/shared/utils/format'
import { StatsCardRow } from './StatsCardRow'
export function DashboardPage() {
const nav = useNavigate()
const pageConfig: PageConfig = { title: 'Dashboard' }
const listConfig: ListConfig = {
entityPath: 'runs',
prefix: 'recent',
entityName: 'run',
fixedParams: { limit: 30 },
columns: [
{
field: 'phase',
label: 'Phase',
render: (row) => (
<RunPhaseIcon phase={((row.phase ?? 'pending') as 'pending' | 'running' | 'succeeded' | 'failed')} />
),
},
{
field: 'project_name',
label: 'Project',
render: (row) => (
<a href={`#/projects/${row.project_id}`} className="text-primary hover:underline">
{row.project_name as string}
</a>
),
},
{
field: 'pipeline_name',
label: 'Pipeline',
render: (row) => (
<a href={`#/pipelines/${row.pipeline_id}`} className="text-primary hover:underline">
{row.pipeline_name as string}
</a>
),
},
{
field: 'source_type',
label: 'Source',
render: (row) => (
<SourceBadge source={((row.source_type ?? 'manual') as 'manual' | 'webhook' | 'api' | 'schedule')} />
),
},
{
field: 'created_at',
label: 'Created',
render: (row) => formatRelativeTime(row.created_at as string | null | undefined),
},
{
field: 'tokens',
label: 'Tokens',
render: (row) => {
const input = row.token_input as number | undefined
const output = row.token_output as number | undefined
const total = (input ?? 0) + (output ?? 0)
return total ? abbreviateNumber(total) : '\u2014'
},
},
{
field: 'duration',
label: 'Duration',
render: (row) => {
const started = row.started_at as string | undefined
const completed = row.completed_at as string | undefined
const phase = row.phase as string | undefined
if (phase === 'running') {
return <span className="text-sm text-blue-500" data-testid="duration-running">running</span>
}
const ms = started && completed ? new Date(completed).getTime() - new Date(started).getTime() : null
if (ms == null) return '\u2014'
const totalSec = Math.floor(ms / 1000)
if (totalSec < 60) return `${totalSec}s`
const m = Math.floor(totalSec / 60)
const s = totalSec % 60
return s > 0 ? `${m}m ${s}s` : `${m}m`
},
},
],
sort: { field: 'created_at', order: 'desc' },
sse: { enabled: true },
rowTarget: '/runs/:id',
}
const crudConfig: CrudConfig = {
entityPath: 'runs',
fieldSet: [
{ field: 'pipeline_id', title: 'Pipeline' },
{ field: 'source_type', title: 'Source Type' },
],
successTarget: '/runs/:id',
cancelTarget: '/',
}
return (
<Page config={pageConfig}>
<StatsCardRow />
<div className="mt-6">
<DataTable
config={listConfig}
extra={
<Button
variant="outline"
size="sm"
type="button"
onClick={() => nav('/runs?runs_sort=created_at&runs_order=desc')}
data-testid="view-all-runs"
>
View All Runs →
</Button>
}
/>
</div>
<Crud config={crudConfig} />
<Outlet />
</Page>
)
}
|