feat: share status layout and prioritize operational groups
Publish Docker image (Multi-arch) / Build & push (amd64) (push) Failing after 1m0s
Release (Linux, macOS, Windows) / Linux Release (push) Failing after 32m33s
Publish Docker image (Multi-arch) / Build & push (arm64) (push) Canceled after 0s
Release (Linux, macOS, Windows) / macOS Release (push) Canceled after 0s
Release (Linux, macOS, Windows) / Windows Release (push) Canceled after 0s
Publish Docker image (Multi-arch) / Create multi-arch manifests (push) Canceled after 0s

This commit is contained in:
cneicy committed 2026-09-26 13:35:14 +08:00
1 parent a5f7b8e586
commit a2d17be0c4
9 files changed
+131 -35

No files matched your search

@@ -13,9 +13,75 @@ import { api } from '@/lib/api'
import { useAuthStore } from '@/stores/auth-store'
import { FingerprintRetest } from '../fingerprint-panel'
import { MonitorCard } from '../index'
import { GroupStatus, MonitorCard } from '../index'
vi.mock('@/components/layout', () => ({ PublicLayout: () => null }))
vi.mock('@/components/layout', () => ({
PublicLayout: ({ children }: { children: React.ReactNode }) => children,
}))
it('uses the shared rows while preserving main-site names, artworks and metrics', async () => {
const get = vi.spyOn(api, 'get').mockResolvedValue({
data: {
success: true,
data: [
{
categoryName: 'Groups',
monitors: [
{
name: '#alias',
group: 'real-astra',
status: 1,
uptime: 1,
models: ['gpt-6-astra'],
metrics: {
avg_ttft_ms: 10730,
avg_latency_ms: 22250,
avg_tps: 26.6,
success_rate: 100,
},
pelican: {
channels: 1,
normal: 1,
degraded: 0,
unknown: 0,
failed: 0,
running: 0,
history: [
{
id: 'hidden-uptime-work',
at: 1,
status: 'succeeded',
quality: 'normal',
user_initiated: false,
has_html: true,
},
],
},
},
],
},
],
},
})
const client = new QueryClient({
defaultOptions: { queries: { retry: false } },
})
render(
<QueryClientProvider client={client}>
<GroupStatus />
</QueryClientProvider>
)
await screen.findByRole('heading', { name: 'real-astra' })
expect(get).toHaveBeenCalledWith('/api/group-status')
expect(
screen.queryByRole('heading', { name: '#alias' })
).not.toBeInTheDocument()
expect(
screen.getByTitle(t('Pelican sample {{number}}', { number: 1 }))
).toHaveAttribute('src', '/api/group-status/artwork/hidden-uptime-work')
fireEvent.click(screen.getByRole('button', { name: t('Detection details') }))
expect(await screen.findByText('10.7 s')).toBeInTheDocument()
})
afterEach(() => {
cleanup()
@@ -194,8 +194,9 @@ it('keeps non-Astra groups compact and puts Astra groups first', async () => {
data: [
{
categoryName: 'Groups',
monitors: [
{ name: 'Luna only', status: 1, uptime: 1, models: ['gpt-6-luna'] },
monitors: [
{ name: 'Failed Astra', status: 0, uptime: 0, models: ['gpt-6-astra'] },
{ name: 'Luna only', status: 1, uptime: 1, models: ['gpt-6-luna'] },
{
name: 'Astra group',
status: 1,
@@ -213,12 +214,12 @@ it('keeps non-Astra groups compact and puts Astra groups first', async () => {
screen
.getAllByRole('heading', { level: 2 })
.map((heading) => heading.textContent)
).toEqual(['Astra group', 'Luna only'])
expect(screen.getAllByText(t('Recent pelican works'))).toHaveLength(1)
expect(screen.getAllByText(t('Model fingerprint'))).toHaveLength(1)
).toEqual(['Astra group', 'Luna only', 'Failed Astra'])
expect(screen.getAllByText(t('Recent pelican works'))).toHaveLength(2)
expect(screen.getAllByText(t('Model fingerprint'))).toHaveLength(2)
expect(
screen.getAllByRole('region', { name: t('Availability history') })
).toHaveLength(2)
).toHaveLength(3)
})
it('keeps the main-site group status link on the original internal route', () => {
+16 -8
View File
@@ -27,12 +27,14 @@ import { DetectionActions, PelicanPanel } from './detection-panel'
import { FingerprintPanel } from './fingerprint-panel'
import {
cacheHitRates,
compareStatusMonitors,
formatCacheHitRate,
getOverallStatus,
statusKind,
type GroupStatusKind,
} from './lib/status'
import { ProbeHistory } from './probe-history'
import { StatusGroupRow } from './status-page'
import { ResetGroupDetections } from './status-page-settings'
type StatusKind = GroupStatusKind
@@ -191,9 +193,9 @@ export function GroupStatus() {
})
const monitors = useMemo<UptimeMonitor[]>(
() =>
(query.data?.data || []).flatMap(
(group: UptimeGroupResult) => group.monitors || []
),
(query.data?.data || [])
.flatMap((group: UptimeGroupResult) => group.monitors || [])
.sort(compareStatusMonitors),
[query.data?.data]
)
const overall = getOverallStatus(monitors.map((monitor) => monitor.status))
@@ -205,7 +207,7 @@ export function GroupStatus() {
)
if (query.isLoading) {
content = (
<div className='mt-7 grid gap-5 md:grid-cols-2 xl:grid-cols-3'>
<div className='mt-7 space-y-5'>
{[0, 1, 2].map((item) => (
<Skeleton key={item} className='h-[400px] rounded-lg' />
))}
@@ -219,13 +221,19 @@ export function GroupStatus() {
)
} else if (monitors.length) {
content = (
<div className='mt-7 grid gap-5 md:grid-cols-2 xl:grid-cols-3'>
<div className='mt-7 space-y-5'>
{monitors.map((monitor) => (
<MonitorCard
<StatusGroupRow
key={`${monitor.group}-${monitor.name}`}
monitor={monitor}
monitor={{ ...monitor, name: monitor.group || monitor.name }}
updatedAt={query.dataUpdatedAt}
refreshing={query.isFetching}
artworkBase='/api/group-status/artwork/'
actions={
!!monitor.fingerprint?.channels &&
!!monitor.group && <DetectionActions group={monitor.group} />
}
detailsContent={<MonitorCard monitor={monitor} />}
/>
))}
</div>
@@ -234,7 +242,7 @@ export function GroupStatus() {
return (
<PublicLayout showMainContainer={false}>
<main className='mx-auto w-full max-w-7xl min-w-0 px-4 pt-24 pb-12 md:px-6'>
<main className='mx-auto w-full max-w-[1680px] min-w-0 px-5 pt-24 pb-12 md:px-8'>
<div className='flex flex-wrap items-end justify-between gap-4'>
<div>
<h1 className='text-3xl font-bold'>{t('Group Status')}</h1>
@@ -2,6 +2,14 @@ import type { UptimeMonitor, UptimeSample } from '@/features/dashboard/types'
export type GroupStatusKind = 'operational' | 'degraded' | 'error' | 'unknown'
export function supportsAstra(monitor: UptimeMonitor): boolean {
return monitor.models?.includes('gpt-6-astra') ?? false
}
export function compareStatusMonitors(a: UptimeMonitor, b: UptimeMonitor): number {
return Number(b.status === 1) - Number(a.status === 1) || Number(supportsAstra(b)) - Number(supportsAstra(a))
}
export function statusKind(status: number): GroupStatusKind {
if (status < 0) return 'unknown'
if (status === 1) return 'operational'
+25 -12
View File
@@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query'
import { Activity, ArrowUpRight, RefreshCw, Settings2 } from 'lucide-react'
import { useEffect, useRef, useState } from 'react'
import { useEffect, useRef, useState, type ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import { Dialog } from '@/components/dialog'
@@ -19,7 +19,12 @@ import { cn } from '@/lib/utils'
import { useAuthStore } from '@/stores/auth-store'
import { FingerprintPanel } from './fingerprint-panel'
import { getOverallStatus, statusKind } from './lib/status'
import {
getOverallStatus,
statusKind,
supportsAstra,
compareStatusMonitors,
} from './lib/status'
import { ProbeHistory } from './probe-history'
import { getStatusPage } from './status-page-api'
import {
@@ -41,11 +46,11 @@ function qualityLabel(quality: string): string {
return 'Unable to classify'
}
function supportsAstra(monitor: UptimeMonitor): boolean {
return monitor.models?.includes('gpt-6-astra') ?? false
}
function ArtworkThumbnail(props: { id: string; title: string }) {
function ArtworkThumbnail(props: {
id: string
title: string
artworkBase: string
}) {
const container = useRef<HTMLDivElement>(null)
const [scale, setScale] = useState(0)
useEffect(() => {
@@ -64,7 +69,7 @@ function ArtworkThumbnail(props: { id: string; title: string }) {
>
<iframe
title={props.title}
src={'/api/detections/artwork/' + props.id}
src={props.artworkBase + props.id}
sandbox='allow-scripts'
referrerPolicy='no-referrer'
loading='lazy'
@@ -102,10 +107,14 @@ export function StatusGroupRow(props: {
monitor: UptimeMonitor
updatedAt?: number
refreshing?: boolean
artworkBase?: string
actions?: ReactNode
detailsContent?: ReactNode
}) {
const { t, i18n } = useTranslation()
const monitor = props.monitor
const astra = supportsAstra(monitor)
const artworkBase = props.artworkBase || '/api/detections/artwork/'
const [selected, setSelected] = useState('')
const [details, setDetails] = useState(false)
const user = useAuthStore((state) => state.auth.user)
@@ -166,7 +175,7 @@ export function StatusGroupRow(props: {
</p>
</div>
)}
{astra && (
{(astra || props.detailsContent) && (
<Button
variant='ghost'
size='sm'
@@ -177,6 +186,7 @@ export function StatusGroupRow(props: {
<ArrowUpRight className='size-3.5' />
</Button>
)}
{props.actions}
</CardHeader>
{astra && (
<CardContent className='min-w-0 p-5'>
@@ -208,6 +218,7 @@ export function StatusGroupRow(props: {
>
<ArtworkThumbnail
id={work.id}
artworkBase={artworkBase}
title={t('Pelican sample {{number}}', {
number: index + 1,
})}
@@ -316,7 +327,7 @@ export function StatusGroupRow(props: {
<iframe
title={t('Pelican work preview')}
tabIndex={-1}
src={`/api/detections/artwork/${selectedWork.id}`}
src={artworkBase + selectedWork.id}
sandbox='allow-scripts'
referrerPolicy='no-referrer'
className='h-[65vh] w-full rounded-lg border bg-white'
@@ -328,7 +339,9 @@ export function StatusGroupRow(props: {
onOpenChange={setDetails}
title={`${monitor.name} · ${t('Detection details')}`}
>
<FingerprintPanel data={monitor.fingerprint} />
{props.detailsContent || (
<FingerprintPanel data={monitor.fingerprint} />
)}
</Dialog>
</Card>
)
@@ -345,7 +358,7 @@ export function GroupStatus() {
refetchInterval: 15_000,
})
const monitors = (query.data?.flatMap((group) => group.monitors) || []).sort(
(a, b) => Number(supportsAstra(b)) - Number(supportsAstra(a))
compareStatusMonitors
)
const overall = getOverallStatus(monitors.map((monitor) => monitor.status))
const operational = monitors.filter((monitor) => monitor.status === 1).length