feat: Wire AddAccountDialog and UpdateCredentialsDialog into health page and AccountCard
- health.tsx: Add Account button + AddAccountDialog, Refresh button with tooltip and spinner calling refreshAccounts mutation, inline account error state instead of full-page error, updated empty state text, and active-agent warning on delete confirm - AccountCard.tsx: Show KeyRound button when credentials/token invalid, opens UpdateCredentialsDialog pre-populated with account identity Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { CheckCircle2, XCircle, AlertTriangle, Trash2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { CheckCircle2, XCircle, AlertTriangle, Trash2, KeyRound } from "lucide-react";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { UpdateCredentialsDialog } from "./UpdateCredentialsDialog";
|
||||
|
||||
function formatResetTime(isoDate: string): string {
|
||||
const now = Date.now();
|
||||
@@ -100,6 +102,7 @@ export function AccountCard({
|
||||
account: AccountData;
|
||||
onDelete?: (e: React.MouseEvent) => void;
|
||||
}) {
|
||||
const [updateCredOpen, setUpdateCredOpen] = useState(false);
|
||||
const hasWarning = account.credentialsValid && !account.isExhausted && account.error;
|
||||
|
||||
const statusIcon = !account.credentialsValid ? (
|
||||
@@ -123,6 +126,7 @@ export function AccountCard({
|
||||
const usage = account.usage;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardContent className="space-y-3 py-4">
|
||||
{/* Header row */}
|
||||
@@ -147,6 +151,17 @@ export function AccountCard({
|
||||
<span>{statusText}</span>
|
||||
</div>
|
||||
</div>
|
||||
{(!account.credentialsValid || !account.tokenValid) && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="shrink-0 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => setUpdateCredOpen(true)}
|
||||
title="Update credentials"
|
||||
>
|
||||
<KeyRound className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
{onDelete && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -220,5 +235,11 @@ export function AccountCard({
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<UpdateCredentialsDialog
|
||||
open={updateCredOpen}
|
||||
onOpenChange={setUpdateCredOpen}
|
||||
account={{ id: account.id, email: account.email, provider: account.provider }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { useState } from 'react'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import {
|
||||
CheckCircle2,
|
||||
XCircle,
|
||||
RefreshCw,
|
||||
Server,
|
||||
Plus,
|
||||
RotateCcw,
|
||||
Loader2,
|
||||
} from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
@@ -11,6 +15,8 @@ import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Skeleton } from '@/components/Skeleton'
|
||||
import { AccountCard } from '@/components/AccountCard'
|
||||
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { AddAccountDialog } from '@/components/AddAccountDialog'
|
||||
|
||||
export const Route = createFileRoute('/settings/health')({
|
||||
component: HealthCheckPage,
|
||||
@@ -45,7 +51,23 @@ function HealthCheckPage() {
|
||||
},
|
||||
})
|
||||
|
||||
const { data, isLoading, isError, error, refetch } = healthQuery
|
||||
const [addAccountOpen, setAddAccountOpen] = useState(false)
|
||||
|
||||
const refreshAccounts = trpc.refreshAccounts.useMutation({
|
||||
onSuccess: (data) => {
|
||||
const msg =
|
||||
data.cleared === 0
|
||||
? 'No expired flags to clear.'
|
||||
: `Cleared ${data.cleared} expired exhaustion flag(s).`
|
||||
toast.success(msg)
|
||||
void utils.systemHealthCheck.invalidate()
|
||||
},
|
||||
onError: (err) => {
|
||||
toast.error(`Failed to refresh: ${err.message}`)
|
||||
},
|
||||
})
|
||||
|
||||
const { data, isLoading, isError, refetch } = healthQuery
|
||||
|
||||
// Loading state
|
||||
if (isLoading) {
|
||||
@@ -61,24 +83,9 @@ function HealthCheckPage() {
|
||||
)
|
||||
}
|
||||
|
||||
// Error state
|
||||
if (isError) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-4 py-12">
|
||||
<XCircle className="h-8 w-8 text-status-error-fg" />
|
||||
<p className="text-sm text-status-error-fg">
|
||||
Failed to load health check: {error?.message ?? 'Unknown error'}
|
||||
</p>
|
||||
<Button variant="outline" size="sm" onClick={() => void refetch()}>
|
||||
Retry
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!data) return null
|
||||
|
||||
const { server, accounts, projects } = data
|
||||
const server = data?.server
|
||||
const accounts = data?.accounts ?? []
|
||||
const projects = data?.projects ?? []
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -95,37 +102,76 @@ function HealthCheckPage() {
|
||||
</div>
|
||||
|
||||
{/* Server Status */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 font-display text-lg">
|
||||
<Server className="h-5 w-5" />
|
||||
Server Status
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center gap-3">
|
||||
<CheckCircle2 className="h-5 w-5 text-status-success-dot" />
|
||||
<div>
|
||||
<p className="text-sm font-medium">Running</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Uptime: {formatUptime(server.uptime)}
|
||||
{server.startedAt && (
|
||||
<>
|
||||
{' '}
|
||||
· Started{' '}
|
||||
{new Date(server.startedAt).toLocaleString()}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
{server && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 font-display text-lg">
|
||||
<Server className="h-5 w-5" />
|
||||
Server Status
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center gap-3">
|
||||
<CheckCircle2 className="h-5 w-5 text-status-success-dot" />
|
||||
<div>
|
||||
<p className="text-sm font-medium">Running</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Uptime: {formatUptime(server.uptime)}
|
||||
{server.startedAt && (
|
||||
<>
|
||||
{' '}
|
||||
· Started{' '}
|
||||
{new Date(server.startedAt).toLocaleString()}
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Accounts */}
|
||||
<div className="space-y-3">
|
||||
<h2 className="font-display text-lg font-semibold">Accounts</h2>
|
||||
{accounts.length === 0 ? (
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="font-display text-lg font-semibold">Accounts</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => refreshAccounts.mutate()}
|
||||
disabled={refreshAccounts.isPending}
|
||||
>
|
||||
{refreshAccounts.isPending
|
||||
? <Loader2 className="h-4 w-4 animate-spin" />
|
||||
: <RotateCcw className="h-4 w-4" />}
|
||||
<span className="sr-only">Refresh</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Clear expired exhaustion flags</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => setAddAccountOpen(true)}
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Add Account
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{isError ? (
|
||||
<Card>
|
||||
<CardContent className="py-6">
|
||||
<p className="text-center text-sm text-status-error-fg">
|
||||
Could not load account status. Retrying…
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : accounts.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="py-6">
|
||||
<p className="text-center text-sm text-muted-foreground">
|
||||
@@ -133,7 +179,7 @@ function HealthCheckPage() {
|
||||
<code className="rounded bg-muted px-1 py-0.5 text-xs">
|
||||
cw account add
|
||||
</code>{' '}
|
||||
to register one.
|
||||
to register one, or click 'Add Account' above.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -143,7 +189,14 @@ function HealthCheckPage() {
|
||||
key={account.id}
|
||||
account={account}
|
||||
onDelete={(e) => {
|
||||
if (e.shiftKey || window.confirm(`Remove account "${account.email}"?`)) {
|
||||
if (
|
||||
e.shiftKey ||
|
||||
(account.activeAgentCount > 0
|
||||
? window.confirm(
|
||||
`This account has ${account.activeAgentCount} active agent(s). Deleting it will not stop those agents but they will lose their account association. Continue?`
|
||||
)
|
||||
: window.confirm(`Remove account "${account.email}"?`))
|
||||
) {
|
||||
removeAccount.mutate({ id: account.id })
|
||||
}
|
||||
}}
|
||||
@@ -186,6 +239,8 @@ function HealthCheckPage() {
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
<AddAccountDialog open={addAccountOpen} onOpenChange={setAddAccountOpen} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user