Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ npm run deploy
| `/posterize` | Image posterizer |
| `/wordchains` | Word chain puzzles |
| `/metrics` | Live service/host dashboards for the backend fleet |
| `/stats` | Traffic aggregates from the access logs: crawlers, scanners, short-link popularity |
| `/resilience` | Distributed-systems game |
| `/groups`, `/sets`, `/top` | Math learning modules (permutation groups, sets, Topology Quest) |

Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import PosterizePage from './apps/posterize/pages/PosterizePage'
import SystemsPage from './apps/metrics-systems/pages/SystemsPage'
import ResilienceGamePage from './apps/metrics-systems/pages/ResilienceGamePage'
import MetricsPage from './apps/metrics-systems/pages/MetricsPage'
import StatsPage from './apps/stats/pages/StatsPage'
import WordchainsPage from './apps/wordchains/pages/WordchainsPage'
import IiliPage from './apps/iili/pages/IiliPage'
import NotFoundPage from './core/pages/NotFoundPage'
Expand All @@ -35,6 +36,7 @@ function App() {
<Route path="/r3dr" element={<Navigate to="/iili" replace />} />
<Route path="/metrics" element={<Navigate to="/metrics/host" replace />} />
<Route path="/metrics/:tab" element={<MetricsPage />} />
<Route path="/stats" element={<StatsPage />} />
<Route path="/resilience" element={<SystemsPage />} />
<Route path="/resilience/phase1/level1" element={<ResilienceGamePage />} />
<Route path="/top" element={<Navigate to="/top/sets" replace />} />
Expand Down
5 changes: 5 additions & 0 deletions src/__tests__/App.routes.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ describe('App routes', () => {
at('/r3dr')
expect(within(screen.getByRole('navigation')).getByText('MuchQ : iili')).toBeDefined()
})

it('serves the traffic stats at /stats', () => {
at('/stats')
expect(within(screen.getByRole('navigation')).getByText('MuchQ : Stats')).toBeDefined()
})
})
30 changes: 0 additions & 30 deletions src/apps/metrics-systems/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,33 +416,3 @@ export function splitHostTimeseries(merged: TimeSeriesResponse): {
}
return { system, container }
}

// --- Log-derived stats (MoonBase#1460): aggregates the stats service
// computes from shipped Caddy access logs. Separate base URL because the
// backend is a different service behind the same gateway.

export const STATS_API_URL =
import.meta.env.VITE_STATS_API_URL || 'https://api.muchq.com/stats/v1'

export interface StatsSummaryRow {
date: string
host: string
agent_class: string
requests: number
errors: number
}

export interface StatsSummary {
days: number
rows: StatsSummaryRow[]
}

export interface TopSlugRow {
slug: string
requests: number
}

export interface TopSlugs {
days: number
rows: TopSlugRow[]
}
144 changes: 0 additions & 144 deletions src/apps/metrics-systems/components/StatsDashboard.tsx

This file was deleted.

This file was deleted.

6 changes: 1 addition & 5 deletions src/apps/metrics-systems/pages/MetricsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import ConnectionStatus, { ConnectionState } from '@/shared/components/nav/Conne
import RotatingText from '@/shared/components/nav/RotatingText'
import MetricsDashboard from '../components/MetricsDashboard'
import ContainersDashboard from '../components/ContainersDashboard'
import StatsDashboard from '../components/StatsDashboard'
import ServiceDashboard from '../components/ServiceDashboard'
import styles from '../components/MetricsDashboard.module.css'
import { METRICS_API_URL, fetchJson, serviceDisplayName, type ServiceCatalog } from '../api'
Expand Down Expand Up @@ -33,7 +32,7 @@ const LEGACY_TABS: Record<string, string> = {

// Tabs that aren't services, so the catalog can't vouch for them and the
// unknown-tab redirect must not bounce them.
const BUILT_IN_TABS = ['host', 'containers', 'stats']
const BUILT_IN_TABS = ['host', 'containers']

const MetricsPage = () => {
const { tab } = useParams<{ tab: string }>()
Expand Down Expand Up @@ -73,7 +72,6 @@ const MetricsPage = () => {
const tabs = [
{ id: 'host', label: 'Host' },
{ id: 'containers', label: 'Containers' },
{ id: 'stats', label: 'Stats' },
...(catalog?.services ?? []).map((service) => ({
id: service.name,
label: serviceDisplayName(service.name),
Expand All @@ -97,8 +95,6 @@ const MetricsPage = () => {
<MetricsDashboard onConnectionStateChange={handleConnectionStateChange} />
) : activeTab === 'containers' ? (
<ContainersDashboard onConnectionStateChange={handleConnectionStateChange} />
) : activeTab === 'stats' ? (
<StatsDashboard onConnectionStateChange={handleConnectionStateChange} />
) : (
<ServiceDashboard service={activeTab} onConnectionStateChange={handleConnectionStateChange} />
)}
Expand Down
21 changes: 17 additions & 4 deletions src/apps/metrics-systems/pages/__tests__/MetricsPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,12 @@ describe('MetricsPage', () => {
await new Promise((resolve) => setTimeout(resolve, 100))
})

// Host, Containers and Stats are the tabs that aren't services: the
// catalog never lists them, so they're prepended rather than derived
// from it.
// Host and Containers are the tabs that aren't services: the catalog
// never lists them, so they're prepended rather than derived from it.
// Stats is not a tab any more; it has its own page at /stats.
const tabs = screen.getAllByRole('button').map((button) => button.textContent)
expect(tabs.slice(0, 6)).toEqual(['Host', 'Containers', 'Stats', 'Golf Hub', 'MicroGPT', 'Portrait'])
expect(tabs.slice(0, 5)).toEqual(['Host', 'Containers', 'Golf Hub', 'MicroGPT', 'Portrait'])
expect(tabs).not.toContain('Stats')
expect(screen.getByText('Host Metrics')).toBeTruthy()
})

Expand Down Expand Up @@ -128,6 +129,18 @@ describe('MetricsPage', () => {
expect(screen.getByTestId('location').textContent).toBe('/metrics/microgpt-serve')
})

it('bounces the retired stats tab to host like any unknown name', async () => {
// Stats has its own page now; the old tab name must not resurrect an
// empty page here.
renderAt('/metrics/stats')

await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 100))
})

expect(screen.getByTestId('location').textContent).toBe('/metrics/host')
})

it('bounces unknown services to host once the catalog loads', async () => {
renderAt('/metrics/nonesuch')

Expand Down
Loading
Loading