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 128 129 130 131 132 133 134 135 136 137 138 | 47x 47x | import { useNavigate } from 'react-router-dom'
import { useRepositories, useCheckRepositoryHealth } from './repositoriesApi'
import { DataTable, type Column } from '@/shared/components/DataTable'
import { Button } from '@/components/ui/button'
import { toast } from 'sonner'
function statusDot(status: string): string {
switch (status) {
case 'connected': return '\u25CF'
case 'disconnected': return '\u25CF'
default: return '\u25CB'
}
}
function statusColor(status: string): string {
switch (status) {
case 'connected': return 'text-green-500'
case 'disconnected': return 'text-red-500'
default: return 'text-muted-foreground'
}
}
function relativeTime(dateStr: string | undefined | null): string {
if (!dateStr) return '\u2014'
const diff = Date.now() - new Date(dateStr).getTime()
const mins = Math.floor(diff / 60000)
if (mins < 1) return 'now'
if (mins < 60) return `${mins} min ago`
const hours = Math.floor(mins / 60)
if (hours < 24) return `${hours}h ago`
const days = Math.floor(hours / 24)
if (days < 7) return `${days}d ago`
return new Date(dateStr).toLocaleDateString()
}
export function RepositoriesPage() {
const navigate = useNavigate()
const { data, isLoading, error, refetch } = useRepositories()
const checkHealth = useCheckRepositoryHealth()
const repos = data?.data ?? []
const columns: Column[] = [
{
key: 'status',
label: '',
render: (item) => (
<span className={`${statusColor(item.status as string)} text-lg`}>
{statusDot(item.status as string)}
</span>
),
},
{ key: 'name', label: 'Name' },
{ key: 'url', label: 'URL' },
{
key: 'description',
label: 'Description',
render: (item) => (
<span className="text-sm text-muted-foreground">
{(item.description as string) ?? '\u2014'}
</span>
),
},
{
key: 'status',
label: 'Status',
render: (item) => (
<span className={statusColor(item.status as string)}>
{(item.status as string).charAt(0).toUpperCase() + (item.status as string).slice(1)}
</span>
),
},
{
key: 'last_checked_at',
label: 'Last Checked',
render: (item) => (
<span className="text-sm text-muted-foreground">
{relativeTime(item.last_checked_at as string | undefined | null)}
</span>
),
},
{
key: 'actions',
label: 'Actions',
render: (item) => (
<div className="flex gap-1">
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation()
checkHealth.mutate(item.id as string, {
onSuccess: (result) => {
toast.success(`Status: ${result.status} (${result.latency_ms}ms)`)
refetch()
},
onError: () => toast.error('Health check failed'),
})
}}
>
Check
</Button>
<Button
variant="ghost"
size="sm"
onClick={(e) => {
e.stopPropagation()
navigate(`/repositories/${item.id as string}/edit`)
}}
>
Edit
</Button>
</div>
),
},
]
return (
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold">Repositories</h1>
<Button onClick={() => navigate('/repositories/new')}>
New Repository
</Button>
</div>
<DataTable
columns={columns}
data={repos as unknown as Record<string, unknown>[]}
isLoading={isLoading}
error={error ?? null}
onRetry={() => refetch()}
emptyMessage="No repositories configured"
onRowClick={(item) => navigate(`/repositories/${item.id as string}/edit`)}
/>
</div>
)
}
|