diff --git a/ui/src/app/(authenticated)/connections/detail/page.tsx b/ui/src/app/(authenticated)/connections/detail/page.tsx
index 0c196cf..17306e8 100644
--- a/ui/src/app/(authenticated)/connections/detail/page.tsx
+++ b/ui/src/app/(authenticated)/connections/detail/page.tsx
@@ -20,7 +20,7 @@ function ConnectionDetailContent() {
provider && connection ? ["authsome-connection-detail", provider, connection, principal] : null,
() => fetchConnectionDetail(provider, connection, principal),
);
- const { mutate: mutateDashboard } = useSWR("authsome-dashboard", fetchDashboard);
+ const { data: dashboard, mutate: mutateDashboard } = useSWR("authsome-dashboard", fetchDashboard);
if (!provider || !connection) {
return (
@@ -45,6 +45,7 @@ function ConnectionDetailContent() {
data={data}
onRefresh={() => { void mutate(); void mutateDashboard(); }}
principal={principal}
+ providers={dashboard?.providers}
/>
);
}
diff --git a/ui/src/app/(authenticated)/connections/page.tsx b/ui/src/app/(authenticated)/connections/page.tsx
index 07989ac..e60b47c 100644
--- a/ui/src/app/(authenticated)/connections/page.tsx
+++ b/ui/src/app/(authenticated)/connections/page.tsx
@@ -14,6 +14,7 @@ export default function ConnectionsPage() {
globalConnections={data.globalConnections}
isAdmin={data.account.isAdmin}
onRefresh={() => void mutate()}
+ providers={data.providers}
/>
);
}
diff --git a/ui/src/components/dashboard/connection-detail-view.tsx b/ui/src/components/dashboard/connection-detail-view.tsx
index 6a4c216..f5c390e 100644
--- a/ui/src/components/dashboard/connection-detail-view.tsx
+++ b/ui/src/components/dashboard/connection-detail-view.tsx
@@ -1,20 +1,22 @@
"use client";
-import { ArrowLeft, Check, Clipboard, Eye, EyeOff, Globe2, LogOut } from "lucide-react";
+import { Check, Clipboard, Clock, Eye, EyeOff, Globe2, KeyRound, Link2, LogOut, Server, Shield } from "lucide-react";
import Link from "next/link";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
-import { useEffect, useState } from "react";
+import { type ReactNode, useEffect, useState } from "react";
import useSWR from "swr";
import {
- KeyValue,
+ ProviderLogo,
+ StatusBadge,
connectionDetailHref,
providerDetailHref,
} from "@/components/dashboard/dashboard-primitives";
import { currentBrowserPath, isUnauthorized } from "@/components/dashboard/dashboard-routing";
import { DashboardDetailShell, ErrorState, LoadingScreen } from "@/components/dashboard/dashboard-shell";
+import { Badge } from "@/components/ui/badge";
import { Button, buttonVariants } from "@/components/ui/button";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import {
Dialog,
DialogContent,
@@ -23,8 +25,10 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
+import { H3 } from "@/components/ui/typography";
import {
ConnectionDetail,
+ DashboardData,
fetchConnectionDetail,
fetchDashboard,
logoutConnection,
@@ -87,7 +91,7 @@ export function AuthsomeConnectionDetail({
return (
- { void mutate(); void mutateDashboard(); }} principal={principal} />
+ { void mutate(); void mutateDashboard(); }} principal={principal} providers={dashboard.providers} />
);
}
@@ -96,58 +100,173 @@ export function ConnectionDetailBody({
data,
onRefresh,
principal,
+ providers,
}: {
data: ConnectionDetail;
onRefresh: () => void;
principal?: string;
+ providers?: DashboardData["providers"];
}) {
+ const provider = providers?.find((p) => p.name === data.provider);
+ const hasSecrets = data.secrets.access_token || data.secrets.refresh_token || data.secrets.api_key || Object.keys(data.secrets.credentials).length > 0;
+ const hasEndpoints = data.base_url || data.api_url;
+
return (
-
-
-
- Back to connections
-
-
-
-
{data.provider_display_name}
-
{data.connection_name}
+
+
+
+ {provider ? (
+
+ ) : null}
+
+
+
+ {data.provider_display_name}
+
+ {data.is_global ? (
+
+
+ Global
+
+ ) : null}
+
+
{data.connection_name}
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Secrets
-
-
-
-
-
- {Object.entries(data.secrets.credentials).map(([key, value]) => (
-
- ))}
-
-
+
+
+
+
+
+
+ Identity & Access
+
+
+
+
+
+
+
+ {authTypeLabel(data.auth_type)}
+
+
+ {data.principal_id || "-"}
+
+
+ {data.identity || "-"}
+
+ {data.scopes.length > 0 ? (
+
+
+
+ {data.scopes.map((scope) => (
+ {scope}
+ ))}
+
+
+
+ ) : null}
+
+
+
+
+
+
+
+ Token
+
+
+
+
+ {data.token_type || "-"}
+
+
+ {formatTimestamp(data.obtained_at)}
+
+
+ {formatTimestamp(data.expires_at)}
+
+
+
+
+ {hasEndpoints ? (
+
+
+
+
+ Endpoints
+
+
+
+ {data.base_url ? (
+
+ {data.base_url}
+
+ ) : null}
+ {data.api_url ? (
+
+ {data.api_url}
+
+ ) : null}
+
+
+ ) : null}
+
+
+ {hasSecrets ? (
+
+
+
+
+ Secrets
+
+ Encrypted at rest in the vault.
+
+
+
+
+
+ {Object.entries(data.secrets.credentials).map(([key, value]) => (
+
+ ))}
+
+
+ ) : null}
);
}
+function DetailField({ children, label }: { children: ReactNode; label: string }) {
+ return (
+
+ );
+}
+
+function authTypeLabel(authType: string): string {
+ return authType === "oauth2" ? "OAuth 2.0" : authType === "api_key" ? "API Key" : authType || "-";
+}
+
+function formatTimestamp(value: string | null): string {
+ if (!value) return "-";
+ const parsed = new Date(value);
+ if (Number.isNaN(parsed.valueOf())) return value;
+ return parsed.toLocaleString(undefined, {
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ timeZoneName: "short",
+ });
+}
+
function redactedValue(value: string): string {
return value.length <= 8 ? "********" : `${value.slice(0, 4)}...${value.slice(-4)}`;
}
@@ -164,10 +283,10 @@ function SecretValue({ label, value }: { label: string; value: string | null })
}
return (
-
-
{label}
-
-
{revealed ? value : redactedValue(value)}
+
+
{label}
+
+ {revealed ? value : redactedValue(value)}
@@ -243,7 +361,7 @@ function ConnectionActions({
return (
<>
-
+
{data.can_set_default ? (
) : null}
{data.can_set_global ? (
-
-
- {globalMessage?.text}
-
+ {globalMessage ? (
+
+ {globalMessage.text}
+
+ ) : null}