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
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:
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', () => {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user