mirror of
https://github.com/kennethnym/aris.git
synced 2026-03-24 19:01:17 +00:00
feat: add admin dashboard app (#91)
* feat: add admin dashboard app - React + Vite + TanStack Router + TanStack Query - Auth with better-auth (login, session, admin guard) - Source config management (WeatherKit credentials, user config) - Feed query panel - Location push card - General settings with health check - CORS middleware for cross-origin auth - Disable CSRF check in dev mode - Sonner toasts for mutation feedback Co-authored-by: Ona <no-reply@ona.com> * fix: use useQuery instead of getQueryData Co-authored-by: Ona <no-reply@ona.com> * refactor: remove backend changes from dashboard PR Backend CORS/CSRF changes moved to #92. Source registry removed (sources hardcoded in frontend). Co-authored-by: Ona <no-reply@ona.com> --------- Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { CircleCheck, CircleX, Loader2 } from "lucide-react"
|
||||
|
||||
import { getServerUrl } from "@/lib/server-url"
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
|
||||
async function checkHealth(serverUrl: string): Promise<boolean> {
|
||||
const res = await fetch(`${serverUrl}/health`)
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const data = (await res.json()) as { status: string }
|
||||
if (data.status !== "ok") throw new Error("Unexpected response")
|
||||
return true
|
||||
}
|
||||
|
||||
export function GeneralSettingsPanel() {
|
||||
const serverUrl = getServerUrl()
|
||||
|
||||
const { isLoading, isError, error } = useQuery({
|
||||
queryKey: ["health", serverUrl],
|
||||
queryFn: () => checkHealth(serverUrl),
|
||||
})
|
||||
|
||||
const status = isLoading ? "checking" : isError ? "error" : "ok"
|
||||
const errorMsg = error instanceof Error ? error.message : null
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-xl space-y-6">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-lg font-semibold tracking-tight">General</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Backend server information.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card className="-mx-4">
|
||||
<CardHeader className="pb-4">
|
||||
<CardTitle className="text-sm">Server</CardTitle>
|
||||
<CardDescription>
|
||||
Connected backend instance.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<span className="shrink-0 text-muted-foreground">URL</span>
|
||||
<span className="select-text truncate font-mono text-xs">{serverUrl}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Status</span>
|
||||
{status === "checking" && (
|
||||
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<Loader2 className="size-3 animate-spin" />
|
||||
Checking…
|
||||
</span>
|
||||
)}
|
||||
{status === "ok" && (
|
||||
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
<CircleCheck className="size-3.5 text-primary" />
|
||||
Connected
|
||||
</span>
|
||||
)}
|
||||
{status === "error" && (
|
||||
<span className="flex items-center gap-1.5 text-xs text-destructive">
|
||||
<CircleX className="size-3.5" />
|
||||
{errorMsg ?? "Unreachable"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user