!disabled && toggle(s)}
title={disabled ? "At most 5 series per graph" : undefined}
>
@@ -202,7 +202,7 @@ export function MetricsExplorer() {
- {dimsText(s.dimensions) || "(no dimensions)"}
+ {dimsText(s.dimensions) || (no dimensions)}
{s.dimensions?.InstanceId && names.get(s.dimensions.InstanceId) && (
{names.get(s.dimensions.InstanceId)}
)}
@@ -279,8 +279,8 @@ export function MetricsExplorer() {
) : (
{byUnit.length > 1 && (
-
-
+
+
The selected series use different units, so they are drawn on {byUnit.length} separate graphs (one per unit).
)}
@@ -307,14 +307,14 @@ export function MetricsExplorer() {
@@ -92,7 +89,7 @@ export function CloudWatchOverview() {
) : inAlarm.length === 0 ? (
-
+
No alarms in ALARM state.
{alarms.data?.length === 0 && (
diff --git a/console/components/cognito/clients-tab.tsx b/console/components/cognito/clients-tab.tsx
index e374aada..04ed81ab 100644
--- a/console/components/cognito/clients-tab.tsx
+++ b/console/components/cognito/clients-tab.tsx
@@ -9,9 +9,10 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D
import { Input } from "@/components/ui/input"
import { ConfirmDialog } from "@/components/console/confirm-dialog"
import { CopyButton } from "@/components/console/copy-button"
-import { DataTable, type Column } from "@/components/console/data-table"
+import { CellText, DataTable, type Column } from "@/components/console/data-table"
import { EmptyState } from "@/components/console/empty-state"
import { Field } from "@/components/console/form-field"
+import { Tag } from "@/components/console/tag"
import { TimeAgo } from "@/components/console/time-ago"
import { api, errorMessage, seg } from "@/lib/api"
import { revalidate, useApi } from "@/lib/hooks"
@@ -36,7 +37,19 @@ export function ClientsTab({ pool }: { pool: UserPool }) {
const [deleting, setDeleting] = useState(null)
const columns: Column[] = [
- { id: "name", header: "App client name", cell: (c) => {c.name || (unnamed)}, value: (c) => c.name },
+ {
+ id: "name",
+ header: "App client name",
+ cell: (c) =>
+ c.name ? (
+
+ {c.name}
+
+ ) : (
+ (unnamed)
+ ),
+ value: (c) => c.name,
+ },
{
id: "id",
header: "Client ID",
@@ -48,7 +61,13 @@ export function ClientsTab({ pool }: { pool: UserPool }) {
),
value: (c) => c.id,
},
- { id: "secret", header: "Client secret", cell: (c) => (c.has_secret ? "Yes" : No), value: (c) => (c.has_secret ? 1 : 0), hideBelow: "sm" },
+ {
+ id: "secret",
+ header: "Client secret",
+ cell: (c) => (c.has_secret ? Confidential : Public),
+ value: (c) => (c.has_secret ? 1 : 0),
+ hideBelow: "sm",
+ },
{ id: "access", header: "ID/access token", cell: (c) => formatMinutes(c.access_token_minutes), value: (c) => c.access_token_minutes, hideBelow: "md" },
{ id: "refresh", header: "Refresh token", cell: (c) => `${c.refresh_token_days} day${c.refresh_token_days === 1 ? "" : "s"}`, value: (c) => c.refresh_token_days, hideBelow: "md" },
{ id: "created", header: "Created", cell: (c) => , value: (c) => c.created_at, hideBelow: "lg" },
diff --git a/console/components/cognito/groups-tab.tsx b/console/components/cognito/groups-tab.tsx
index 8ecd199f..8210721a 100644
--- a/console/components/cognito/groups-tab.tsx
+++ b/console/components/cognito/groups-tab.tsx
@@ -8,7 +8,7 @@ import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { ConfirmDialog } from "@/components/console/confirm-dialog"
-import { DataTable, type Column } from "@/components/console/data-table"
+import { CellText, DataTable, type Column } from "@/components/console/data-table"
import { EmptyState } from "@/components/console/empty-state"
import { Field } from "@/components/console/form-field"
import { api, errorMessage, seg } from "@/lib/api"
@@ -25,8 +25,17 @@ export function GroupsTab({ pool, onChanged }: { pool: UserPool; onChanged: () =
const members = (g: string) => (users.data ?? []).filter((u) => u.groups?.includes(g)).length
const columns: Column[] = [
- { id: "name", header: "Group name", cell: (g) => {g.name}, value: (g) => g.name },
- { id: "desc", header: "Description", cell: (g) => g.description || -, value: (g) => g.description, hideBelow: "md" },
+ {
+ id: "name",
+ header: "Group name",
+ cell: (g) => (
+
+ {g.name}
+
+ ),
+ value: (g) => g.name,
+ },
+ { id: "desc", header: "Description", cell: (g) => {g.description}, value: (g) => g.description, hideBelow: "md" },
{ id: "precedence", header: "Precedence", cell: (g) => {g.precedence}, value: (g) => g.precedence, hideBelow: "sm" },
{ id: "members", header: "Members", cell: (g) => (users.data ? {members(g.name)} : "..."), value: (g) => members(g.name) },
{
diff --git a/console/components/cognito/integration-tab.tsx b/console/components/cognito/integration-tab.tsx
index 3b94980f..b34005e6 100644
--- a/console/components/cognito/integration-tab.tsx
+++ b/console/components/cognito/integration-tab.tsx
@@ -2,10 +2,14 @@
import { useEffect, useState } from "react"
import Link from "next/link"
-import { ExternalLink } from "lucide-react"
+import { AlertTriangle, ExternalLink, Info } from "lucide-react"
+import { Alert, AlertDescription } from "@/components/ui/alert"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
-import { CopyButton, CopyableText } from "@/components/console/copy-button"
+import { CodeBlock } from "@/components/console/code-block"
+import { CopyableText } from "@/components/console/copy-button"
+import { CellLink } from "@/components/console/data-table"
+import { Tag } from "@/components/console/tag"
import { KeyValueGrid } from "@/components/console/key-value"
import { Section } from "@/components/console/section"
import { useApi } from "@/lib/hooks"
@@ -16,11 +20,7 @@ import { useClients } from "./clients-tab"
function Snippet({ title, code, note }: { title: string; code: string; note?: React.ReactNode }) {
return (
-
- {title}
-
-
- {code}
+
{note && {note} }
)
@@ -120,9 +120,17 @@ export function IntegrationTab({ pool }: { pool: UserPool }) {
>
{clients.data && clients.data.length === 0 && (
- Create an app client on the App clients tab; the examples use its ID.
+
+
+ Create an app client on the App clients tab; the examples use its ID.
+
+ )}
+ {!pool.self_sign_up && (
+
+
+ Self sign-up is disabled for this pool, so the sign-up call returns NotAuthorizedException.
+
)}
- {!pool.self_sign_up && Self sign-up is disabled for this pool, so the sign-up call returns NotAuthorizedException. }
- APIs using this pool
+ APIs using this pool
{protectedApis.length ? (
-
+
{protectedApis.map((a) => (
- -
-
+
-
+
{a.name}
- {" "}
-
- {(a.routes ?? []).filter((r) => r.authorization === "JWT").length} JWT route(s)
- {a.authorizer?.audience ? `, audience ${a.authorizer.audience}` : ""}
-
+
+ {(a.routes ?? []).filter((r) => r.authorization === "JWT").length} JWT route(s)
+ {a.authorizer?.audience && aud {a.authorizer.audience}}
))}
diff --git a/console/components/cognito/pool-detail.tsx b/console/components/cognito/pool-detail.tsx
index 21288913..c2ad7f4f 100644
--- a/console/components/cognito/pool-detail.tsx
+++ b/console/components/cognito/pool-detail.tsx
@@ -6,6 +6,7 @@ import { AlertCircle, ArrowLeft, RefreshCw, Trash2 } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { ActionsMenu } from "@/components/console/actions-menu"
import { CopyableText } from "@/components/console/copy-button"
import { EmptyState } from "@/components/console/empty-state"
import { ErrorState } from "@/components/console/error-state"
@@ -13,6 +14,7 @@ import { KeyValueGrid } from "@/components/console/key-value"
import { DetailSkeleton } from "@/components/console/loading"
import { PageHeader } from "@/components/console/page-header"
import { Section } from "@/components/console/section"
+import { StatTile } from "@/components/console/stat-tile"
import { TimeAgo } from "@/components/console/time-ago"
import { ApiError } from "@/lib/api"
import { formatDate } from "@/lib/format"
@@ -25,7 +27,7 @@ import { GroupsTab } from "./groups-tab"
import { IntegrationTab } from "./integration-tab"
import { DeletePoolDialog } from "./pools-list"
import { SettingsTab } from "./settings-tab"
-import { policySummary, poolPath } from "./shared"
+import { SignUpBadge, policySummary, poolPath } from "./shared"
import { UsersTab } from "./users-tab"
const TABS = ["users", "groups", "clients", "settings", "integration"] as const
@@ -87,22 +89,37 @@ export function PoolDetail() {
-
+ setParam("tab", "settings") },
+ { label: "View integration", onSelect: () => setParam("tab", "integration") },
+ { separator: true },
+ { label: "Delete user pool", icon: , destructive: true, onSelect: () => setDeleting(true) },
+ ]}
+ />
>
}
/>
+
+
+
+
+
+
+
},
- { label: "Estimated number of users", value: String(pool.users) },
- { label: "App clients", value: String(pool.clients) },
{ label: "Password policy", value: policySummary(pool.password_policy) },
- { label: "Self sign-up", value: pool.self_sign_up ? (pool.auto_confirm ? "Enabled, auto-confirmed" : "Enabled, requires confirmation") : "Disabled" },
+ { label: "Self sign-up", value: },
{ label: "Created", value: {formatDate(pool.created_at)} () },
{ label: "Token issuer", value: , wide: true },
{ label: "ARN", value: , wide: true },
diff --git a/console/components/cognito/pools-list.tsx b/console/components/cognito/pools-list.tsx
index 10daaef5..443b6408 100644
--- a/console/components/cognito/pools-list.tsx
+++ b/console/components/cognito/pools-list.tsx
@@ -1,7 +1,6 @@
"use client"
import { useEffect, useState } from "react"
-import Link from "next/link"
import { useRouter } from "next/navigation"
import { Loader2, Plus, UsersRound } from "lucide-react"
import { toast } from "sonner"
@@ -12,7 +11,7 @@ import { Input } from "@/components/ui/input"
import { ActionsMenu, type ActionItem } from "@/components/console/actions-menu"
import { ConfirmDialog } from "@/components/console/confirm-dialog"
import { CopyButton } from "@/components/console/copy-button"
-import { DataTable, cellLinkClass, type Column } from "@/components/console/data-table"
+import { CellLink, DataTable, type Column } from "@/components/console/data-table"
import { EmptyState } from "@/components/console/empty-state"
import { Field } from "@/components/console/form-field"
import { PageHeader } from "@/components/console/page-header"
@@ -22,7 +21,7 @@ import { pluralize } from "@/lib/format"
import { revalidate, useQueryParam } from "@/lib/hooks"
import type { CreateUserPoolInput, PasswordPolicy, UserPool } from "@/lib/types"
-import { COGNITO_PATH, DEFAULT_POLICY, NAME_RE, POOLS_PATH, PasswordPolicyFields, SwitchRow, minLengthError, poolHref, poolPath, usePools } from "./shared"
+import { COGNITO_PATH, DEFAULT_POLICY, NAME_RE, POOLS_PATH, PasswordPolicyFields, SignUpBadge, SwitchRow, minLengthError, poolHref, poolPath, usePools } from "./shared"
export function PoolsList() {
const router = useRouter()
@@ -36,11 +35,7 @@ export function PoolsList() {
{
id: "name",
header: "User pool name",
- cell: (p) => (
- e.stopPropagation()} className={cellLinkClass()}>
- {p.name}
-
- ),
+ cell: (p) => {p.name},
value: (p) => p.name,
},
{
@@ -59,11 +54,7 @@ export function PoolsList() {
{
id: "signup",
header: "Self sign-up",
- cell: (p) => (
-
- {p.self_sign_up ? (p.auto_confirm ? "Auto-confirmed" : "Needs confirmation") : Disabled}
-
- ),
+ cell: (p) => ,
value: (p) => (p.self_sign_up ? 1 : 0),
hideBelow: "md",
},
diff --git a/console/components/cognito/shared.tsx b/console/components/cognito/shared.tsx
index 5e1738d2..aedc5e10 100644
--- a/console/components/cognito/shared.tsx
+++ b/console/components/cognito/shared.tsx
@@ -56,6 +56,16 @@ export function EnabledBadge({ enabled }: { enabled: boolean }) {
return enabled ? :
}
+/** SignUpBadge shows a pool's self sign-up mode: auto-confirmed, needs confirmation, or disabled. */
+export function SignUpBadge({ pool }: { pool: Pick }) {
+ if (!pool.self_sign_up) return
+ return pool.auto_confirm ? (
+
+ ) : (
+
+ )
+}
+
/** PasswordPolicyFields edits a password policy (min length 6-128 and the four character classes). */
export function PasswordPolicyFields({ value, onChange, idPrefix, error }: { value: PasswordPolicy; onChange: (p: PasswordPolicy) => void; idPrefix: string; error?: string }) {
const boxes: [keyof PasswordPolicy, string][] = [
@@ -77,9 +87,8 @@ export function PasswordPolicyFields({ value, onChange, idPrefix, error }: { val
className="w-28"
/>
-
- Require
-
+
+
{boxes.map(([k, label]) => (
))}
-
+
)
}
diff --git a/console/components/cognito/show-once-dialog.tsx b/console/components/cognito/show-once-dialog.tsx
index 8b9f3de4..52a49121 100644
--- a/console/components/cognito/show-once-dialog.tsx
+++ b/console/components/cognito/show-once-dialog.tsx
@@ -3,9 +3,10 @@
import type { ReactNode } from "react"
import { AlertTriangle } from "lucide-react"
+import { Alert, AlertDescription } from "@/components/ui/alert"
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
-import { CopyButton } from "@/components/console/copy-button"
+import { CodeBlock } from "@/components/console/code-block"
export interface OnceValue {
label: string
@@ -27,22 +28,20 @@ export function ShowOnceDialog({ open, onClose, title, description, values }: {
{values.map((v) => (
-
- {v.label}
-
-
- {v.value}
-
-
-
-
+
+
+ {v.value}
+
+
))}
{values.some((v) => v.secret) && (
-
-
- This is the only time the {values.filter((v) => v.secret).map((v) => v.label.toLowerCase()).join(" and ")} is shown. Copy it now and store it
- safely.
-
+
+
+
+ This is the only time the {values.filter((v) => v.secret).map((v) => v.label.toLowerCase()).join(" and ")} is shown. Copy it now and store it
+ safely.
+
+
)}
diff --git a/console/components/cognito/users-tab.tsx b/console/components/cognito/users-tab.tsx
index 98175c89..577e0e18 100644
--- a/console/components/cognito/users-tab.tsx
+++ b/console/components/cognito/users-tab.tsx
@@ -12,10 +12,11 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { ActionsMenu, type ActionItem } from "@/components/console/actions-menu"
import { ConfirmDialog } from "@/components/console/confirm-dialog"
import { CopyableText } from "@/components/console/copy-button"
-import { DataTable, type Column } from "@/components/console/data-table"
+import { CellText, DataTable, type Column } from "@/components/console/data-table"
import { EmptyState } from "@/components/console/empty-state"
import { Field } from "@/components/console/form-field"
import { KeyValueGrid } from "@/components/console/key-value"
+import { Tag } from "@/components/console/tag"
import { TagsEditor, tagsToRows, type TagRow } from "@/components/console/tags-editor"
import { TimeAgo } from "@/components/console/time-ago"
import { api, errorMessage, seg } from "@/lib/api"
@@ -77,14 +78,23 @@ export function UsersTab({ pool }: { pool: UserPool }) {
}
const columns: Column[] = [
- { id: "username", header: "User name", cell: (u) => {u.username}, value: (u) => u.username },
- { id: "email", header: "Email", cell: (u) => u.attributes?.email || -, value: (u) => u.attributes?.email, hideBelow: "md" },
+ {
+ id: "username",
+ header: "User name",
+ cell: (u) => (
+
+ {u.username}
+
+ ),
+ value: (u) => u.username,
+ },
+ { id: "email", header: "Email", cell: (u) => {u.attributes?.email}, value: (u) => u.attributes?.email, hideBelow: "md" },
{ id: "status", header: "Confirmation status", cell: (u) => , value: (u) => u.status },
{ id: "enabled", header: "Status", cell: (u) => , value: (u) => (u.enabled ? 1 : 0) },
{
id: "groups",
header: "Groups",
- cell: (u) => (u.groups?.length ? {u.groups.join(", ")} : -),
+ cell: (u) => {u.groups?.length ? u.groups.join(", ") : null},
value: (u) => (u.groups ?? []).join(" "),
hideBelow: "lg",
},
@@ -211,9 +221,11 @@ function UserDetails({ user }: { user: CognitoUser }) {
value: attrs.length ? (
{attrs.map(([k, v]) => (
-
- {k}={v}
-
+
+
+ {k}={v}
+
+
))}
) : (
@@ -304,8 +316,7 @@ function CreateUserDialog({ pool, open, onOpenChange, onCreated }: { pool: UserP
setEmail(e.target.value)} placeholder="ann@example.com" autoComplete="off" />
-
- Password
+
setMode(v as PwMode)} className="gap-2">
)}
-
+
{pool.groups.length > 0 && (
@@ -459,8 +470,9 @@ function AttributesDialog({ pool, user, onClose }: { pool: UserPool; user: Cogni
Attributes of {user?.username}
Attributes become claims in new ID tokens. Remove a row to delete the attribute. An empty value also deletes it.
-
- {rErr && {rErr} }
+
+
+
|