diff --git a/console/.gitignore b/console/.gitignore index 29c353c9..b172fd4c 100644 --- a/console/.gitignore +++ b/console/.gitignore @@ -1,4 +1,4 @@ -node_modules/ +node_modules .next/ out/ next-env.d.ts diff --git a/console/app/fonts/OFL.txt b/console/app/fonts/OFL.txt new file mode 100644 index 00000000..98835ac3 --- /dev/null +++ b/console/app/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2024 The Geist Project Authors (https://github.com/vercel/geist-font) Geist-Italic[wght].ttf: Copyright 2024 The Geist Project Authors (https://github.com/vercel/geist-font) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/console/app/fonts/geist-mono.woff2 b/console/app/fonts/geist-mono.woff2 new file mode 100644 index 00000000..0750682c Binary files /dev/null and b/console/app/fonts/geist-mono.woff2 differ diff --git a/console/app/fonts/geist.woff2 b/console/app/fonts/geist.woff2 new file mode 100644 index 00000000..991445d7 Binary files /dev/null and b/console/app/fonts/geist.woff2 differ diff --git a/console/app/globals.css b/console/app/globals.css index 17bad908..7f024b86 100644 --- a/console/app/globals.css +++ b/console/app/globals.css @@ -4,82 +4,152 @@ @custom-variant dark (&:is(.dark *)); +/* + * HomeCloud design tokens. One source of truth for the console, matching the + * landing page (pages/site.css): near-black neutrals, hairline low-alpha + * borders, Geist + Geist Mono, and the orange of the brand mark as the accent. + * + * surfaces background < card < popover, muted (wells), accent (hover) + * lines border (hairline), input (control edge), border-strong + * text foreground, muted-foreground, faint + * brand brand (the mark's orange), primary (accent used for links, + * focus, selection; tuned for text contrast), brand-soft + * semantic success, warning, danger (= destructive), info; each has a + * *-soft tint for badge/alert fills + * charts chart-1..5 (brand first), chart-grid + * code code-bg / code-fg: code blocks stay dark in both themes + * + * Light values live on :root, dark values on .dark (next-themes sets it, + * following the system by default). + */ :root { - --background: oklch(0.982 0.003 250); - --foreground: oklch(0.2 0.02 260); - --card: oklch(1 0 0); - --card-foreground: oklch(0.2 0.02 260); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.2 0.02 260); - --primary: oklch(0.53 0.18 256); - --primary-foreground: oklch(0.99 0 0); - --secondary: oklch(0.955 0.006 250); - --secondary-foreground: oklch(0.25 0.02 260); - --muted: oklch(0.962 0.005 250); - --muted-foreground: oklch(0.52 0.02 257); - --accent: oklch(0.955 0.01 250); - --accent-foreground: oklch(0.25 0.02 260); - --destructive: oklch(0.577 0.215 27.325); - --destructive-foreground: oklch(0.577 0.215 27.325); - --border: oklch(0.91 0.008 255); - --input: oklch(0.88 0.01 255); - --ring: oklch(0.6 0.15 256); - --chart-1: #2a78d6; - --chart-2: #eb6834; - --chart-3: #1baf7a; - --chart-4: #eda100; - --chart-5: #e87ba4; - --radius: 0.5rem; - --topbar: oklch(0.22 0.035 258); - --topbar-foreground: oklch(0.97 0.005 250); - --topbar-muted: oklch(0.3 0.035 258); - --brand: oklch(0.74 0.16 60); - --sidebar: oklch(1 0 0); - --sidebar-foreground: oklch(0.3 0.02 260); - --sidebar-accent: oklch(0.95 0.02 256); - --sidebar-accent-foreground: oklch(0.45 0.17 256); - --sidebar-border: oklch(0.91 0.008 255); + color-scheme: light; + --background: #fafaf9; + --foreground: #0b0b0d; + --card: #ffffff; + --card-foreground: #0b0b0d; + --popover: #ffffff; + --popover-foreground: #0b0b0d; + --muted: #f3f3f1; + --muted-foreground: #55555e; + --faint: #6b6b75; + --accent: #efefec; + --accent-foreground: #0b0b0d; + --secondary: #f1f1ef; + --secondary-foreground: #18181b; + + --border: rgba(10, 10, 20, 0.085); + --border-strong: rgba(10, 10, 20, 0.16); + --input: rgba(10, 10, 20, 0.14); + --ring: rgba(234, 90, 12, 0.55); + + --brand: #f06418; + --brand-soft: rgba(240, 100, 24, 0.09); + --brand-line: rgba(240, 100, 24, 0.3); + --brand-foreground: #160903; + --primary: #b8400a; + --primary-foreground: #ffffff; + + --success: #15803d; + --success-soft: rgba(21, 128, 61, 0.09); + --warning: #a16207; + --warning-soft: rgba(202, 138, 4, 0.11); + --destructive: #dc2626; + --destructive-foreground: #c81e1e; + --danger-soft: rgba(220, 38, 38, 0.08); + --info: #2563eb; + --info-soft: rgba(37, 99, 235, 0.08); + + --chart-1: #ea5a0c; + --chart-2: #2f6fe0; + --chart-3: #12a26a; + --chart-4: #9b51e0; + --chart-5: #c48a00; + --chart-grid: rgba(10, 10, 20, 0.07); + + --code-bg: #0d0d10; + --code-fg: #e4e4e7; + --code-line: rgba(255, 255, 255, 0.08); + /* code/terminal palette: always on the dark code surface, in both themes */ + --code-muted: #9a9aa4; + --code-comment: #7d8590; + --code-prompt: #ff9a5c; + --code-red: #ff7b72; + --code-green: #7ee787; + --code-yellow: #e3b341; + --code-blue: #79c0ff; + --code-violet: #d2a8ff; + --code-selection: #3f3f46; + + --glow: rgba(255, 120, 40, 0.16); + --shadow-xs-v: 0 1px 2px rgba(10, 10, 20, 0.04); + --shadow-sm-v: 0 1px 2px rgba(10, 10, 20, 0.05), 0 2px 8px -2px rgba(10, 10, 20, 0.06); + --shadow-md-v: 0 1px 2px rgba(10, 10, 20, 0.05), 0 8px 24px -8px rgba(10, 10, 20, 0.12); + --shadow-lg-v: 0 0 0 1px rgba(10, 10, 20, 0.06), 0 2px 4px rgba(10, 10, 20, 0.04), 0 24px 48px -16px rgba(10, 10, 20, 0.22); + + --radius: 0.625rem; + --header-h: 3.5rem; + --sidebar-w: 15rem; } .dark { - --background: oklch(0.165 0.012 258); - --foreground: oklch(0.95 0.005 250); - --card: oklch(0.205 0.014 258); - --card-foreground: oklch(0.95 0.005 250); - --popover: oklch(0.22 0.014 258); - --popover-foreground: oklch(0.95 0.005 250); - --primary: oklch(0.68 0.15 256); - --primary-foreground: oklch(0.16 0.02 258); - --secondary: oklch(0.27 0.015 258); - --secondary-foreground: oklch(0.95 0.005 250); - --muted: oklch(0.25 0.014 258); - --muted-foreground: oklch(0.7 0.015 256); - --accent: oklch(0.27 0.018 258); - --accent-foreground: oklch(0.95 0.005 250); - --destructive: oklch(0.6 0.2 25); - --destructive-foreground: oklch(0.7 0.19 25); - --border: oklch(0.3 0.015 258); - --input: oklch(0.34 0.016 258); - --ring: oklch(0.6 0.13 256); - --chart-1: #3987e5; - --chart-2: #d95926; - --chart-3: #199e70; - --chart-4: #c98500; - --chart-5: #d55181; - --topbar: oklch(0.13 0.02 258); - --topbar-foreground: oklch(0.96 0.005 250); - --topbar-muted: oklch(0.22 0.02 258); - --brand: oklch(0.76 0.15 60); - --sidebar: oklch(0.19 0.013 258); - --sidebar-foreground: oklch(0.85 0.01 256); - --sidebar-accent: oklch(0.27 0.03 256); - --sidebar-accent-foreground: oklch(0.85 0.1 256); - --sidebar-border: oklch(0.28 0.015 258); + color-scheme: dark; + --background: #09090b; + --foreground: #f2f2f3; + --card: #0f0f12; + --card-foreground: #f2f2f3; + --popover: #141417; + --popover-foreground: #f2f2f3; + --muted: #151518; + --muted-foreground: #a1a1aa; + --faint: #8a8a94; + --accent: #1b1b1f; + --accent-foreground: #f2f2f3; + --secondary: #1b1b1f; + --secondary-foreground: #f2f2f3; + + --border: rgba(255, 255, 255, 0.075); + --border-strong: rgba(255, 255, 255, 0.14); + --input: rgba(255, 255, 255, 0.12); + --ring: rgba(255, 122, 46, 0.6); + + --brand: #ff7a2e; + --brand-soft: rgba(255, 122, 46, 0.12); + --brand-line: rgba(255, 122, 46, 0.3); + --primary: #ffa36b; + --primary-foreground: #160903; + + --success: #4ade80; + --success-soft: rgba(74, 222, 128, 0.1); + --warning: #fbbf24; + --warning-soft: rgba(251, 191, 36, 0.1); + --destructive: #e5484d; + --destructive-foreground: #ff6b70; + --danger-soft: rgba(255, 99, 105, 0.1); + --info: #60a5fa; + --info-soft: rgba(96, 165, 250, 0.1); + + --chart-1: #ff7a2e; + --chart-2: #4f9cf9; + --chart-3: #3dd68c; + --chart-4: #b98cff; + --chart-5: #f5c542; + --chart-grid: rgba(255, 255, 255, 0.06); + + --code-bg: #0d0d10; + --code-line: rgba(255, 255, 255, 0.09); + + --glow: rgba(255, 112, 40, 0.2); + --shadow-xs-v: 0 1px 0 rgba(255, 255, 255, 0.02) inset; + --shadow-sm-v: 0 1px 0 rgba(255, 255, 255, 0.03) inset, 0 2px 8px -2px rgba(0, 0, 0, 0.5); + --shadow-md-v: 0 1px 0 rgba(255, 255, 255, 0.04) inset, 0 12px 32px -12px rgba(0, 0, 0, 0.7); + --shadow-lg-v: 0 0 0 1px rgba(255, 255, 255, 0.06), 0 2px 4px rgba(0, 0, 0, 0.3), 0 24px 48px -12px rgba(0, 0, 0, 0.65); } @theme inline { - --font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; - --font-mono: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, monospace; + --font-sans: var(--font-geist), ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; + --font-mono: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); @@ -92,31 +162,59 @@ --color-secondary-foreground: var(--secondary-foreground); --color-muted: var(--muted); --color-muted-foreground: var(--muted-foreground); + --color-faint: var(--faint); --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-destructive: var(--destructive); --color-destructive-foreground: var(--destructive-foreground); --color-border: var(--border); + --color-border-strong: var(--border-strong); --color-input: var(--input); --color-ring: var(--ring); + --color-brand: var(--brand); + --color-brand-soft: var(--brand-soft); + --color-brand-line: var(--brand-line); + --color-brand-foreground: var(--brand-foreground); + --color-success: var(--success); + --color-success-soft: var(--success-soft); + --color-warning: var(--warning); + --color-warning-soft: var(--warning-soft); + --color-danger: var(--destructive-foreground); + --color-danger-soft: var(--danger-soft); + --color-info: var(--info); + --color-info-soft: var(--info-soft); --color-chart-1: var(--chart-1); --color-chart-2: var(--chart-2); --color-chart-3: var(--chart-3); --color-chart-4: var(--chart-4); --color-chart-5: var(--chart-5); - --color-topbar: var(--topbar); - --color-topbar-foreground: var(--topbar-foreground); - --color-topbar-muted: var(--topbar-muted); - --color-brand: var(--brand); - --color-sidebar: var(--sidebar); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); + --color-chart-grid: var(--chart-grid); + --color-code: var(--code-bg); + --color-code-foreground: var(--code-fg); + --color-code-line: var(--code-line); + --color-code-muted: var(--code-muted); + --color-code-comment: var(--code-comment); + --color-code-prompt: var(--code-prompt); + --color-code-red: var(--code-red); + --color-code-green: var(--code-green); + --color-code-yellow: var(--code-yellow); + --color-code-blue: var(--code-blue); + --color-code-violet: var(--code-violet); + --color-violet: var(--chart-4); + + --radius-xs: calc(var(--radius) - 6px); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); + --radius-2xl: calc(var(--radius) + 8px); + + --shadow-xs: var(--shadow-xs-v); + --shadow-sm: var(--shadow-sm-v); + --shadow-md: var(--shadow-md-v); + --shadow-lg: var(--shadow-lg-v); + + --tracking-display: -0.035em; } @layer base { @@ -125,14 +223,82 @@ } html { -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; } body { @apply bg-background text-foreground font-sans; + font-feature-settings: 'ss01', 'cv11'; + } + h1, + h2, + h3 { + text-wrap: balance; + } + ::selection { + background: color-mix(in srgb, var(--brand) 32%, transparent); } button:not(:disabled), [role='button']:not(:disabled) { cursor: pointer; } + :focus-visible { + outline-color: var(--ring); + } + /* thin, quiet scrollbars */ + * { + scrollbar-width: thin; + scrollbar-color: var(--border-strong) transparent; + } +} + +@layer components { + /* Eyebrow / overline label (mono, uppercase), as on the landing page. */ + .hc-eyebrow { + font-family: var(--font-mono); + font-size: 11px; + font-weight: 500; + line-height: 1; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--faint); + } + /* Subtle grid + glow backdrop used by the sign-in page and first-run hero. */ + .hc-backdrop { + position: relative; + isolation: isolate; + } + .hc-backdrop::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + pointer-events: none; + background-image: + linear-gradient(var(--chart-grid) 1px, transparent 1px), + linear-gradient(90deg, var(--chart-grid) 1px, transparent 1px); + background-size: 48px 48px; + background-position: center top; + mask-image: radial-gradient(ellipse 60% 50% at 50% 0%, #000 10%, transparent 70%); + opacity: 0.7; + } + .hc-backdrop::after { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + pointer-events: none; + background: radial-gradient(ellipse 50% 40% at 50% 0%, var(--glow), transparent 70%); + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + transition-duration: 0.01ms !important; + } } /* xterm fills its container; the console supplies the frame. */ diff --git a/console/app/layout.tsx b/console/app/layout.tsx index aff38f58..ca4f4298 100644 --- a/console/app/layout.tsx +++ b/console/app/layout.tsx @@ -1,10 +1,13 @@ import type { Metadata, Viewport } from "next" -import { GeistMono } from "geist/font/mono" -import { GeistSans } from "geist/font/sans" +import localFont from "next/font/local" import { Providers } from "@/components/console/providers" import "./globals.css" +// Self-hosted Geist and Geist Mono: the same variable fonts the landing page serves. +const geist = localFont({ src: "./fonts/geist.woff2", weight: "100 900", variable: "--font-geist", display: "swap" }) +const geistMono = localFont({ src: "./fonts/geist-mono.woff2", weight: "100 900", variable: "--font-geist-mono", display: "swap" }) + export const metadata: Metadata = { title: { default: "HomeCloud Console", @@ -18,11 +21,15 @@ export const metadata: Metadata = { export const viewport: Viewport = { width: "device-width", initialScale: 1, + themeColor: [ + { media: "(prefers-color-scheme: dark)", color: "#09090b" }, + { media: "(prefers-color-scheme: light)", color: "#fafaf9" }, + ], } export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { return ( - + {children} diff --git a/console/app/login/page.tsx b/console/app/login/page.tsx index 535130cf..4c4a9b9b 100644 --- a/console/app/login/page.tsx +++ b/console/app/login/page.tsx @@ -2,12 +2,13 @@ import { Suspense, useEffect, useState } from "react" import { useSearchParams } from "next/navigation" -import { AlertCircle, Loader2 } from "lucide-react" +import { useTheme } from "next-themes" +import { AlertCircle, Loader2, Moon, Sun } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" -import { Logo } from "@/components/console/topbar" +import { BrandMark, Logo } from "@/components/console/brand" import { BASE_PATH, errorMessage, getSession, login } from "@/lib/api" // safeNext only lets a same-site path through. Browsers read "\" as "/" and drop @@ -56,7 +57,7 @@ function LoginForm() { return (
{error && ( -
+
{error}
@@ -69,7 +70,7 @@ function LoginForm() { setPassword(e.target.value)} />
- @@ -79,24 +80,46 @@ function LoginForm() { export default function LoginPage() { return ( -
-
+
+
+
-
-
-
-

Sign in

-

Sign in to the HomeCloud console with your IAM user name and password.

+
+
+
+ +

Sign in to HomeCloud

+

Use your IAM user name and password.

+
+
-

- Forgot the root password? Run homecloud serve --reset-root-password -

+
+

+ First run? The root password is printed once in the server log. +

+

+ Forgot it? Run{" "} + homecloud serve --reset-root-password +

+
) } + +function ThemeButton() { + const { resolvedTheme, setTheme } = useTheme() + const [mounted, setMounted] = useState(false) + useEffect(() => setMounted(true), []) + const dark = !mounted || resolvedTheme === "dark" + return ( + + ) +} diff --git a/console/components/acm/ca-panel.tsx b/console/components/acm/ca-panel.tsx index 13fa3226..babfd9f2 100644 --- a/console/components/acm/ca-panel.tsx +++ b/console/components/acm/ca-panel.tsx @@ -5,6 +5,7 @@ import { ChevronDown, Download, ShieldCheck } from "lucide-react" import { Button } from "@/components/ui/button" import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible" +import { CodeBlock } from "@/components/console/code-block" import { CopyButton } from "@/components/console/copy-button" import { ErrorState } from "@/components/console/error-state" import { KeyValueGrid } from "@/components/console/key-value" @@ -67,14 +68,11 @@ export function PrivateCaPanel({ className }: { className?: string }) { How to trust the CA -
    +
      {STEPS.map((s) => ( -
    • - {s.os} - - {s.cmd} - - +
    • + {s.os} +
    • ))}
    diff --git a/console/components/acm/certificate-detail.tsx b/console/components/acm/certificate-detail.tsx index 6e84ee76..7a8a3f7f 100644 --- a/console/components/acm/certificate-detail.tsx +++ b/console/components/acm/certificate-detail.tsx @@ -7,8 +7,9 @@ import { toast } from "sonner" import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" import { Button } from "@/components/ui/button" -import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" -import { CopyButton, CopyableText } from "@/components/console/copy-button" +import { ActionsMenu } from "@/components/console/actions-menu" +import { CodeBlock } from "@/components/console/code-block" +import { CopyableText } from "@/components/console/copy-button" import { EmptyState } from "@/components/console/empty-state" import { ErrorState } from "@/components/console/error-state" import { KeyValueGrid } from "@/components/console/key-value" @@ -16,6 +17,7 @@ import { DetailSkeleton } from "@/components/console/loading" import { PageHeader } from "@/components/console/page-header" import { Section } from "@/components/console/section" import { StatusBadge } from "@/components/console/status-badge" +import { Tag } from "@/components/console/tag" import { TagList } from "@/components/console/tags-editor" import { TimeAgo } from "@/components/console/time-ago" import { ApiError, api, errorMessage, seg } from "@/lib/api" @@ -55,14 +57,13 @@ function PemBlock({ title, pem, filename }: { title: string; pem: string; filena title={title} actions={ <> - } > -
    {pem}
    + ) } @@ -120,11 +121,6 @@ export function CertificateDetail() { } } - const deleteButton = ( - - ) return (
    @@ -143,30 +139,32 @@ export function CertificateDetail() { + , + destructive: true, + disabled: inUse, + hint: inUse ? "In use by a load balancer listener. Remove the listener or switch it to another certificate first." : undefined, + onSelect: () => setDeleting(true), + }, + ]} + /> {c.type === "PRIVATE" && ( - )} - {inUse ? ( - - - {deleteButton} - - In use by a load balancer listener. Remove the listener or switch it to another certificate first. - - ) : ( - deleteButton - )} } /> {(expired || left < EXPIRY_WARNING_DAYS) && ( - + {expired ? "This certificate has expired" : `This certificate expires in ${left} day${left === 1 ? "" : "s"}`} - + {c.type === "PRIVATE" ? "Renew it to issue fresh certificate material under the same ARN; listeners using it pick it up automatically." : "Import a renewed certificate and switch the listeners to it."} @@ -213,10 +211,12 @@ export function CertificateDetail() {
      {users.map((u) => (
    • - + + {u.lb} - {" "} - HTTPS:{u.port} + + HTTPS:{u.port} +
    • ))}
    diff --git a/console/components/acm/certificate-dialogs.tsx b/console/components/acm/certificate-dialogs.tsx index 181bb76b..517e5bfb 100644 --- a/console/components/acm/certificate-dialogs.tsx +++ b/console/components/acm/certificate-dialogs.tsx @@ -157,22 +157,7 @@ function PemField({ onChange(await f.text()) } return ( - - - {label} - {optional && - optional} - - - - } - htmlFor={id} - error={error} - help={help} - > +