From c74a877ab52f28f4cb10033960239dfada4b97ea Mon Sep 17 00:00:00 2001 From: Evan Yang Date: Mon, 23 Feb 2026 14:43:05 +0800 Subject: [PATCH] Fix build and formatting --- routstr/core/admin.py | 3 +- .../test_general_info_endpoints.py | 5 +- ui/.prettierignore | 5 + ui/app/globals.css | 20 +- ui/app/layout.tsx | 17 +- ui/app/logs/log-filters.tsx | 25 +- ui/app/logs/multi-select-command-filter.tsx | 15 +- ui/app/logs/page.tsx | 5 +- ui/app/page.tsx | 80 +- ui/app/providers/page.tsx | 33 +- ui/app/unauthorized/page.tsx | 6 +- ui/components/api-endpoint-form.tsx | 89 +- ui/components/api-endpoint-response.tsx | 30 +- ui/components/app-page-shell.tsx | 28 +- ui/components/app-sidebar.tsx | 20 +- ui/components/auth-page-shell.tsx | 8 +- ui/components/cost-calculator.tsx | 4 +- ui/components/currency-toggle.tsx | 4 +- ui/components/dashboard-balance-summary.tsx | 12 +- ui/components/data-table.tsx | 6 +- ui/components/detailed-wallet-balance.tsx | 180 +- ui/components/edit-group-form.tsx | 6 +- ui/components/edit-model-form.tsx | 12 +- ui/components/landing/cheat-sheet.tsx | 4 +- .../landing/lightning-payment-workflow.tsx | 39 +- .../landing/wallet-balance-stats.tsx | 9 +- ui/components/nav-main.tsx | 3 +- ui/components/nav-secondary.tsx | 3 +- ui/components/page-header.tsx | 12 +- ui/components/provider-balance.tsx | 41 +- ui/components/provider-card.tsx | 11 +- .../provider-form-dialog-content.tsx | 17 +- ui/components/provider-form-fields.tsx | 20 +- ui/components/provider-models-panel.tsx | 4 +- ui/components/revenue-by-model-table.tsx | 4 +- ui/components/settings/admin-settings.tsx | 25 +- ui/components/temporary-balances.tsx | 87 +- ui/components/theme-toggle.tsx | 18 +- ui/components/ui/ModalShell.tsx | 15 +- ui/components/ui/SettingsDialog.tsx | 22 +- ui/components/ui/accordion.tsx | 50 +- ui/components/ui/alert-dialog.tsx | 109 +- ui/components/ui/alert.tsx | 64 +- ui/components/ui/aspect-ratio.tsx | 8 +- ui/components/ui/avatar.tsx | 65 +- ui/components/ui/badge.tsx | 42 +- ui/components/ui/breadcrumb.tsx | 93 +- ui/components/ui/button-group.tsx | 44 +- ui/components/ui/button.tsx | 61 +- ui/components/ui/calendar.tsx | 135 +- ui/components/ui/card.tsx | 109 +- ui/components/ui/carousel.tsx | 198 +- ui/components/ui/chart.tsx | 225 +- ui/components/ui/checkbox.tsx | 25 +- ui/components/ui/collapsible.tsx | 16 +- ui/components/ui/combobox.tsx | 152 +- ui/components/ui/command.tsx | 44 +- ui/components/ui/context-menu.tsx | 130 +- ui/components/ui/dialog.tsx | 102 +- ui/components/ui/direction.tsx | 14 +- ui/components/ui/drawer.tsx | 68 +- ui/components/ui/dropdown-menu.tsx | 132 +- ui/components/ui/empty.tsx | 65 +- ui/components/ui/field.tsx | 181 +- ui/components/ui/hover-card.tsx | 26 +- ui/components/ui/input-group.tsx | 111 +- ui/components/ui/input-otp.tsx | 73 +- ui/components/ui/input.tsx | 14 +- ui/components/ui/item.tsx | 129 +- ui/components/ui/kbd.tsx | 20 +- ui/components/ui/label.tsx | 16 +- ui/components/ui/menubar.tsx | 135 +- ui/components/ui/native-select.tsx | 42 +- ui/components/ui/navigation-menu.tsx | 80 +- ui/components/ui/pagination.tsx | 100 +- ui/components/ui/popover.tsx | 18 +- ui/components/ui/progress.tsx | 20 +- ui/components/ui/radio-group.tsx | 26 +- ui/components/ui/resizable.tsx | 32 +- ui/components/ui/scroll-area.tsx | 32 +- ui/components/ui/searchable-select.tsx | 46 +- ui/components/ui/select.tsx | 101 +- ui/components/ui/separator.tsx | 14 +- ui/components/ui/sheet.tsx | 87 +- ui/components/ui/sidebar.tsx | 519 +- ui/components/ui/skeleton.tsx | 12 +- ui/components/ui/slider.tsx | 30 +- ui/components/ui/sonner.tsx | 78 +- ui/components/ui/spinner.tsx | 17 +- ui/components/ui/switch.tsx | 24 +- ui/components/ui/table.tsx | 89 +- ui/components/ui/tabs.tsx | 52 +- ui/components/ui/textarea.tsx | 14 +- ui/components/ui/toggle-group.tsx | 52 +- ui/components/ui/toggle.tsx | 36 +- ui/components/ui/tooltip.tsx | 26 +- ui/components/usage-metrics-chart.tsx | 36 +- ui/components/usage-summary-cards.tsx | 2 +- ui/components/withdraw-modal.tsx | 9 +- ui/eslint.config.mjs | 14 +- ui/hooks/use-mobile.ts | 24 +- ui/hooks/useMediaQuery.ts | 8 +- ui/lib/api/schemas/models.ts | 37 +- ui/package.json | 5 +- ui/pnpm-lock.yaml | 8543 +++++++++-------- ui/tsconfig.json | 14 +- 106 files changed, 7560 insertions(+), 6282 deletions(-) create mode 100644 ui/.prettierignore diff --git a/routstr/core/admin.py b/routstr/core/admin.py index 21f54f74..ef363d2c 100644 --- a/routstr/core/admin.py +++ b/routstr/core/admin.py @@ -2,6 +2,7 @@ import json import secrets from datetime import datetime, timezone from pathlib import Path +from typing import NoReturn from fastapi import APIRouter, Depends, HTTPException, Query, Request from pydantic import BaseModel @@ -46,7 +47,7 @@ def _cleanup_expired_admin_sessions(now_timestamp: int | None = None) -> None: admin_sessions.pop(token, None) -def _raise_unauthorized(detail: str) -> None: +def _raise_unauthorized(detail: str) -> NoReturn: raise HTTPException( status_code=401, detail=detail, diff --git a/tests/integration/test_general_info_endpoints.py b/tests/integration/test_general_info_endpoints.py index d9399f09..48528793 100644 --- a/tests/integration/test_general_info_endpoints.py +++ b/tests/integration/test_general_info_endpoints.py @@ -264,12 +264,13 @@ async def test_models_endpoint_accept_headers(integration_client: AsyncClient) - async def test_admin_endpoint_unauthenticated( integration_client: AsyncClient, db_snapshot: Any ) -> None: - """Test GET /admin/ endpoint redirects to /""" + """Test unauthenticated access to admin settings endpoint is rejected.""" await db_snapshot.capture() response = await integration_client.get("/admin/api/settings") - assert response.status_code == 403 + assert response.status_code == 401 + assert response.headers.get("www-authenticate") == "Bearer" diff = await db_snapshot.diff() assert len(diff["api_keys"]["added"]) == 0 diff --git a/ui/.prettierignore b/ui/.prettierignore new file mode 100644 index 00000000..50268ec1 --- /dev/null +++ b/ui/.prettierignore @@ -0,0 +1,5 @@ +pnpm-lock.yaml +.next +node_modules +out +next-env.d.ts diff --git a/ui/app/globals.css b/ui/app/globals.css index d56b97f0..30eb4979 100644 --- a/ui/app/globals.css +++ b/ui/app/globals.css @@ -1,16 +1,16 @@ -@import "tailwindcss"; -@import "tw-animate-css"; +@import 'tailwindcss'; +@import 'tw-animate-css'; @custom-variant dark (&:is(.dark *, .red *)); :root { --radius: 0.625rem; --font-geist-sans: - ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, - "Apple Color Emoji", "Segoe UI Emoji"; + ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, + Arial, 'Apple Color Emoji', 'Segoe UI Emoji'; --font-geist-mono: - ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", - "Courier New", monospace; + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', + 'Courier New', monospace; --card: oklch(1 0 0); --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); @@ -171,10 +171,10 @@ body { } button, - [type="button"], - [type="submit"], - [type="reset"], - [role="button"] { + [type='button'], + [type='submit'], + [type='reset'], + [role='button'] { cursor: pointer; } } diff --git a/ui/app/layout.tsx b/ui/app/layout.tsx index 08eecccb..92c51b56 100644 --- a/ui/app/layout.tsx +++ b/ui/app/layout.tsx @@ -1,19 +1,10 @@ import type { Metadata } from 'next'; -import { Geist, Geist_Mono } from 'next/font/google'; +import { GeistMono } from 'geist/font/mono'; +import { GeistSans } from 'geist/font/sans'; import './globals.css'; import { Providers } from './providers'; import { SuppressHydrationWarning } from '@/components/suppress-hydration-warning'; -const geistSans = Geist({ - variable: '--font-geist-sans', - subsets: ['latin'], -}); - -const geistMono = Geist_Mono({ - variable: '--font-geist-mono', - subsets: ['latin'], -}); - export const metadata: Metadata = { title: 'Routstr', description: 'Routstr model management', @@ -29,7 +20,9 @@ export default function RootLayout({ }>) { return ( - + {children} diff --git a/ui/app/logs/log-filters.tsx b/ui/app/logs/log-filters.tsx index 88a2ea54..38adc300 100644 --- a/ui/app/logs/log-filters.tsx +++ b/ui/app/logs/log-filters.tsx @@ -1,4 +1,9 @@ -import { useEffect, useState, type ChangeEvent, type KeyboardEvent } from 'react'; +import { + useEffect, + useState, + type ChangeEvent, + type KeyboardEvent, +} from 'react'; import { format } from 'date-fns'; import { CalendarIcon, Filter, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; @@ -88,8 +93,12 @@ const ENDPOINT_OPTIONS = [ '/embeddings/models', ]; -const STATUS_4XX_CODES = STATUS_CODE_OPTIONS.filter((code) => code.startsWith('4')); -const STATUS_5XX_CODES = STATUS_CODE_OPTIONS.filter((code) => code.startsWith('5')); +const STATUS_4XX_CODES = STATUS_CODE_OPTIONS.filter((code) => + code.startsWith('4') +); +const STATUS_5XX_CODES = STATUS_CODE_OPTIONS.filter((code) => + code.startsWith('5') +); export function LogFilters({ selectedDate, @@ -194,7 +203,9 @@ export function LogFilters({ const handleQuickStatusCode = (range: '4xx' | '5xx') => { const rangeCodes = range === '4xx' ? STATUS_4XX_CODES : STATUS_5XX_CODES; const nextSelection = new Set(selectedStatusCodes); - const allSelected = rangeCodes.every((code) => selectedStatusCodes.includes(code)); + const allSelected = rangeCodes.every((code) => + selectedStatusCodes.includes(code) + ); if (allSelected) { rangeCodes.forEach((code) => nextSelection.delete(code)); @@ -414,7 +425,11 @@ export function LogFilters({
-
diff --git a/ui/app/logs/multi-select-command-filter.tsx b/ui/app/logs/multi-select-command-filter.tsx index 1f141b84..00b49ede 100644 --- a/ui/app/logs/multi-select-command-filter.tsx +++ b/ui/app/logs/multi-select-command-filter.tsx @@ -44,7 +44,10 @@ interface MultiSelectCommandFilterProps { function FilterBadge({ value }: { value: string }) { return ( - + {value} @@ -93,7 +96,10 @@ export function MultiSelectCommandFilter({ - - + - + + {customRange?.from && customRange?.to && ( - {compactCustomRangeLabel} + + {compactCustomRangeLabel} + )} {TIME_RANGE_PRESETS.map((option) => ( @@ -831,7 +849,10 @@ export default function DashboardPage() { className='h-8 w-full px-2.5 text-xs sm:ml-auto sm:w-auto' > {isManualRefreshing ? 'Refreshing...' : 'Refresh'} @@ -850,7 +871,9 @@ export default function DashboardPage() { metricType={activeChartConfig.metricType} tabs={chartConfigs.map((config) => ({ id: config.id, - label: isMobile ? config.mobileTitle ?? config.title : config.title, + label: isMobile + ? (config.mobileTitle ?? config.title) + : config.title, }))} activeTabId={activeChartId} onTabChange={setActiveChartId} @@ -865,7 +888,8 @@ export default function DashboardPage() { No data available - No metrics data exists for this range yet. Try a broader range. + No metrics data exists for this range yet. Try a broader + range. diff --git a/ui/app/providers/page.tsx b/ui/app/providers/page.tsx index 8a6536f9..fb62f054 100644 --- a/ui/app/providers/page.tsx +++ b/ui/app/providers/page.tsx @@ -104,17 +104,16 @@ export default function ProvidersPage() { refetchOnWindowFocus: false, }); - const { data: providerModels, isLoading: isLoadingModels } = useQuery< - ProviderModels | null - >({ - queryKey: ['provider-models', viewingModels], - queryFn: () => - viewingModels - ? AdminService.getProviderModels(viewingModels) - : Promise.resolve(null), - enabled: !!viewingModels, - refetchOnWindowFocus: false, - }); + const { data: providerModels, isLoading: isLoadingModels } = + useQuery({ + queryKey: ['provider-models', viewingModels], + queryFn: () => + viewingModels + ? AdminService.getProviderModels(viewingModels) + : Promise.resolve(null), + enabled: !!viewingModels, + refetchOnWindowFocus: false, + }); const createMutation = useMutation({ mutationFn: (data: CreateUpstreamProvider) => @@ -379,7 +378,9 @@ export default function ProvidersPage() { -

No providers configured

+

+ No providers configured +

Get started by adding your first upstream provider

@@ -398,9 +399,13 @@ export default function ProvidersPage() { isExpanded={expandedProviders.has(provider.id)} canShowBalance={canShowBalance(provider.provider_type)} platformUrl={getPlatformUrl(provider.provider_type)} - isModelsLoading={isLoadingModels && viewingModels === provider.id} + isModelsLoading={ + isLoadingModels && viewingModels === provider.id + } providerModels={ - viewingModels === provider.id ? providerModels ?? null : null + viewingModels === provider.id + ? (providerModels ?? null) + : null } isDeletingModel={deleteModelMutation.isPending} onToggleExpansion={() => toggleProviderExpansion(provider.id)} diff --git a/ui/app/unauthorized/page.tsx b/ui/app/unauthorized/page.tsx index 99718acb..48a0e159 100644 --- a/ui/app/unauthorized/page.tsx +++ b/ui/app/unauthorized/page.tsx @@ -22,7 +22,11 @@ export default function UnauthorizedPage() { - diff --git a/ui/components/api-endpoint-form.tsx b/ui/components/api-endpoint-form.tsx index 146c6f1d..071eb153 100644 --- a/ui/components/api-endpoint-form.tsx +++ b/ui/components/api-endpoint-form.tsx @@ -51,7 +51,9 @@ interface ApiEndpointFormProps { setImageCount: Dispatch>; imageSize: '256x256' | '512x512' | '1024x1024' | '1792x1024' | '1024x1792'; setImageSize: Dispatch< - SetStateAction<'256x256' | '512x512' | '1024x1024' | '1792x1024' | '1024x1792'> + SetStateAction< + '256x256' | '512x512' | '1024x1024' | '1792x1024' | '1024x1792' + > >; imageQuality: 'standard' | 'hd'; setImageQuality: Dispatch>; @@ -167,7 +169,9 @@ export function ApiEndpointForm({ min={1} max={4000} value={maxTokens} - onChange={(event) => setMaxTokens(parseInt(event.target.value) || 150)} + onChange={(event) => + setMaxTokens(parseInt(event.target.value) || 150) + } />
@@ -244,7 +248,9 @@ export function ApiEndpointForm({
- +