Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions app/(auth)/config/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@ function ConfigPageContent() {
}

return (
<div className="relative flex min-h-dvh flex-col items-center justify-center overflow-hidden bg-muted p-4 sm:p-8 lg:p-12">
<div className="z-10 mx-auto flex w-full max-w-6xl flex-col overflow-hidden border bg-card shadow-none lg:min-h-[600px] lg:flex-row">
<div className="hidden lg:block lg:w-1/2">
<div className="auth-shell min-h-dvh bg-background">
<div className="grid min-h-dvh w-full lg:grid-cols-[1.05fr_1fr]">
<div className="hidden lg:block">
<AuthHeroStatic />
</div>
<div className="flex w-full flex-col items-center justify-center bg-card lg:w-1/2">
<div className="w-full max-w-md p-6 sm:p-8 lg:p-10">
<div className="flex min-h-dvh w-full flex-col items-center justify-center bg-background px-4 py-16 sm:px-8">
<div className="w-full max-w-md">
<ThemeLogo width={112} height={24} className="lg:hidden" priority />
<div className="mt-8 lg:mt-0">
<h1 className="block font-heading text-2xl font-semibold tracking-tight text-foreground">
Expand Down
50 changes: 18 additions & 32 deletions app/(dashboard)/403/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useRouter } from "next/navigation"
import { useTranslation } from "react-i18next"
import { Empty, EmptyContent, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription } from "@/components/ui/empty"
import { RiArrowLeftLine } from "@remixicon/react"
import { Button } from "@/components/ui/button"
import { useAuth } from "@/contexts/auth-context"
import { useFirstAccessibleDashboardRoute } from "@/hooks/use-first-accessible-dashboard-route"
Expand All @@ -26,36 +26,22 @@ export default function ForbiddenPage() {
}

return (
<div className="flex min-h-[60vh] flex-1 items-center justify-center">
<Empty className="py-8">
<EmptyContent className="max-w-sm text-center">
<EmptyHeader>
<EmptyMedia variant="icon" className="mx-auto">
<svg viewBox="0 0 48 48" fill="none" className="h-12 w-12 text-muted-foreground" aria-hidden>
<circle cx="24" cy="24" r="22" className="fill-muted stroke-border" strokeWidth="2" />
<path
d="M16 21V19a8 8 0 0116 0v2"
className="stroke-muted-foreground"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<rect x="16" y="28" width="16" height="4" rx="2" className="fill-muted-foreground" />
<rect x="22" y="32" width="4" height="4" rx="2" className="fill-muted-foreground" />
</svg>
</EmptyMedia>
<EmptyTitle>{t("Access Denied")}</EmptyTitle>
<EmptyDescription>
{t(
"You do not have permission to access this page. This may be due to insufficient permissions or not being logged in.",
)}
</EmptyDescription>
</EmptyHeader>
<Button variant="outline" className="mt-6" onClick={handleBack}>
{hasSafeHomeRoute ? t("Back to Home") : t("Back to Login")}
</Button>
</EmptyContent>
</Empty>
</div>
<section className="console-page mx-auto grid min-h-[60dvh] max-w-4xl content-center gap-8 sm:grid-cols-[auto_minmax(0,1fr)] sm:items-start sm:gap-12">
<p className="font-heading text-7xl font-semibold tracking-tight text-muted-foreground/40" aria-hidden="true">
403
</p>
<div className="space-y-5">
<h1 className="font-heading text-3xl font-semibold tracking-tight">{t("Access Denied")}</h1>
<p className="max-w-lg text-sm leading-relaxed text-muted-foreground">
{t(
"You do not have permission to access this page. This may be due to insufficient permissions or not being logged in.",
)}
</p>
<Button className="mt-3" onClick={handleBack}>
<RiArrowLeftLine className="size-4 rtl:rotate-180" aria-hidden />
{hasSafeHomeRoute ? t("Back to Home") : t("Back to Login")}
</Button>
</div>
</section>
)
}
18 changes: 8 additions & 10 deletions app/(dashboard)/_components/performance-backend-card.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"use client"

import { Card, CardContent, CardDescription, CardHeader } from "@/components/ui/card"

export interface BackendInfoItem {
icon: React.ComponentType<{ className?: string; "aria-hidden"?: boolean | "true" | "false" }>
title: string
Expand All @@ -10,15 +8,15 @@ export interface BackendInfoItem {

export function PerformanceBackendCard({ items, t }: { items: BackendInfoItem[]; t: (key: string) => string }) {
return (
<Card className="shadow-none">
<CardHeader className="pb-3">
<section className="grid gap-6 border-t pt-6 xl:grid-cols-[minmax(0,1fr)_2fr]">
<div className="space-y-2">
<h2 id="storage-configuration-title" className="text-base font-semibold">
{t("Storage Configuration")}
</h2>
<CardDescription>{t("Parity and backend settings reported by the cluster.")}</CardDescription>
</CardHeader>
<CardContent>
<dl className="grid gap-0 border-y md:grid-cols-3" aria-labelledby="storage-configuration-title">
<p className="text-sm text-muted-foreground">{t("Parity and backend settings reported by the cluster.")}</p>
</div>
<div>
<dl className="grid gap-0 md:grid-cols-3" aria-labelledby="storage-configuration-title">
{items.map((item) => (
<div
key={item.title}
Expand All @@ -34,7 +32,7 @@ export function PerformanceBackendCard({ items, t }: { items: BackendInfoItem[];
</div>
))}
</dl>
</CardContent>
</Card>
</div>
</section>
)
}
19 changes: 9 additions & 10 deletions app/(dashboard)/_components/performance-infrastructure-card.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client"

import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardDescription, CardHeader } from "@/components/ui/card"
import type { RunningStatusTopology, StatusDiagnostic } from "@/lib/performance-data"

export type InfrastructureHealthState = "healthy" | "attention" | "unknown"
Expand Down Expand Up @@ -52,9 +51,9 @@ export function getInfrastructureHealthState({

function HealthMetric({ label, value, unavailableLabel }: { label: string; value?: number; unavailableLabel: string }) {
return (
<div className="min-w-0 border-t pt-3">
<div className="min-w-0">
<dt className="text-xs text-muted-foreground">{label}</dt>
<dd className="mt-1 text-lg font-semibold tabular-nums">{value ?? unavailableLabel}</dd>
<dd className="mt-1 font-heading text-2xl font-medium tabular-nums">{value ?? unavailableLabel}</dd>
</div>
)
}
Expand Down Expand Up @@ -112,14 +111,14 @@ export function PerformanceInfrastructureCard({
: t("All reported servers and disks are online.")

return (
<Card className="h-full shadow-none">
<CardHeader className="pb-3">
<section className="space-y-6" aria-labelledby="cluster-health-title">
<div>
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="space-y-1.5">
<h2 id="cluster-health-title" className="text-base font-semibold">
{t("Cluster Health")}
</h2>
<CardDescription>{description}</CardDescription>
<p className="max-w-xl text-sm text-muted-foreground">{description}</p>
</div>
<Badge
variant={healthState === "attention" ? "destructive" : healthState === "unknown" ? "outline" : "secondary"}
Expand All @@ -128,8 +127,8 @@ export function PerformanceInfrastructureCard({
{status}
</Badge>
</div>
</CardHeader>
<CardContent>
</div>
<div>
<div className="grid gap-6 lg:grid-cols-2" aria-labelledby="cluster-health-title">
<section aria-labelledby="server-health-title">
<h3 id="server-health-title" className="text-sm font-medium text-muted-foreground">
Expand Down Expand Up @@ -185,7 +184,7 @@ export function PerformanceInfrastructureCard({
) : null}
</section>
</div>
</CardContent>
</Card>
</div>
</section>
)
}
3 changes: 2 additions & 1 deletion app/(dashboard)/_components/performance-status-summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ export function PerformanceStatusSummary({

return (
<section
className="flex flex-col gap-3 border bg-card p-4 sm:flex-row sm:items-center sm:justify-between"
className="status-summary flex flex-col gap-3 border-s-2 bg-muted/60 px-5 py-4 sm:flex-row sm:items-center sm:justify-between"
data-health={healthState}
aria-labelledby="running-status-summary-title"
>
<div className="flex min-w-0 items-center gap-3">
Expand Down
58 changes: 25 additions & 33 deletions app/(dashboard)/_components/performance-summary-cards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import * as React from "react"
import Link from "next/link"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { cn } from "@/lib/utils"

export interface SummaryMetric {
label: string
Expand All @@ -15,42 +13,36 @@ export interface SummaryMetric {

export function PerformanceSummaryCards({ metrics, title }: { metrics: SummaryMetric[]; title: string }) {
return (
<section aria-labelledby="inventory-title" className="space-y-3">
<h2 id="inventory-title" className="text-base font-semibold">
<section aria-labelledby="inventory-title" className="space-y-4">
<h2 id="inventory-title" className="text-sm font-medium text-muted-foreground">
{title}
</h2>
<div className="grid grid-cols-2 gap-3">
<dl className="grid grid-cols-2 gap-6">
{metrics.map((metric) => {
const cardContent = (
<Card
className={cn("h-full shadow-none", metric.href && "cursor-pointer transition-colors hover:bg-muted/50")}
>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">{metric.label}</CardTitle>
<metric.icon className="size-5 text-muted-foreground" aria-hidden />
</CardHeader>
<CardContent>
<div className="flex min-w-0 flex-col gap-1 sm:flex-row sm:items-baseline sm:justify-between sm:gap-4">
<p className="break-words text-2xl font-semibold text-foreground tabular-nums">{metric.display}</p>
{metric.caption ? <p className="text-xs text-muted-foreground">{metric.caption}</p> : null}
</div>
</CardContent>
</Card>
)
return metric.href ? (
<Link
key={metric.label}
href={metric.href}
aria-label={`${metric.label}: ${metric.display}`}
className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
{cardContent}
</Link>
) : (
<React.Fragment key={metric.label}>{cardContent}</React.Fragment>
return (
<div key={metric.label} className="min-w-0 border-t pt-4">
<dt className="flex items-center gap-2 text-xs text-muted-foreground">
<metric.icon className="size-4" aria-hidden />
{metric.label}
</dt>
<dd className="mt-2 break-words font-heading text-3xl font-semibold tabular-nums">
{metric.href ? (
<Link
href={metric.href}
aria-label={`${metric.label}: ${metric.display}`}
className="block transition-colors hover:text-link focus-visible:outline-2 focus-visible:outline-ring"
>
{metric.display}
</Link>
) : (
metric.display
)}
</dd>
{metric.caption ? <dd className="mt-1 text-xs text-muted-foreground">{metric.caption}</dd> : null}
</div>
)
})}
</div>
</dl>
</section>
)
}
38 changes: 26 additions & 12 deletions app/(dashboard)/_components/performance-usage-card.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
"use client"

import { RiDatabase2Line } from "@remixicon/react"
import { Card, CardContent, CardDescription, CardHeader } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { niceBytes } from "@/lib/functions"
import type { StatusDiagnostic } from "@/lib/performance-data"

Expand Down Expand Up @@ -33,7 +31,7 @@ export function PerformanceUsageCard({
const freshnessNeedsAttention = usageFreshness?.state === "degraded" || usageFreshness?.state === "stale"

return (
<Card className="h-full shadow-none">
<Card className="capacity-panel shadow-none">
<CardHeader className="pb-3">
<h2 id="storage-usage-title" className="text-base font-semibold">
{t("Storage Usage and Scanner")}
Expand All @@ -43,25 +41,41 @@ export function PerformanceUsageCard({
<CardContent className="space-y-6">
<div className="space-y-4" aria-labelledby="storage-usage-title">
<div className="flex items-center gap-4">
<RiDatabase2Line className="size-6 text-primary" aria-hidden />
<div className="min-w-0">
<p className="text-sm text-muted-foreground">{t("Usable Capacity Used")}</p>
<p className="text-2xl font-semibold text-foreground tabular-nums">{formatCapacity(totalUsedCapacity)}</p>
<p className="mt-2 font-heading text-4xl font-semibold tracking-tight text-foreground tabular-nums">
{formatCapacity(totalUsedCapacity)}
</p>
</div>
</div>
<div className="space-y-2">
<Progress
value={usedPercent ?? null}
aria-label={`${t("Usable Capacity Used")}: ${usedPercent === undefined ? t("Unknown") : `${usedPercent.toFixed(0)}%`}`}
className="h-2"
/>
<div
role={usedPercent === undefined ? "img" : "meter"}
aria-label={`${t("Usable Capacity Used")}${usedPercent === undefined ? `: ${t("Unknown")}` : ""}`}
aria-valuemin={usedPercent === undefined ? undefined : 0}
aria-valuemax={usedPercent === undefined ? undefined : 100}
aria-valuenow={usedPercent}
aria-valuetext={usedPercent === undefined ? t("Unknown") : `${usedPercent.toFixed(0)}%`}
className="grid h-6 grid-cols-[repeat(32,minmax(0,1fr))] gap-0.5"
>
{Array.from({ length: 32 }, (_, index) => (
<span key={index} className="overflow-hidden bg-muted" aria-hidden="true">
<span
className="block size-full origin-left bg-brand transition-transform duration-300 rtl:origin-right"
style={{
transform: `scaleX(${usedPercent === undefined ? 0 : Math.max(0, Math.min(1, (usedPercent / 100) * 32 - index))})`,
}}
/>
</span>
))}
</div>
<p className="text-end text-xs text-muted-foreground tabular-nums">
{usedPercent === undefined ? t("Unknown") : `${usedPercent.toFixed(0)}%`}
</p>
</div>
</div>

<dl className="grid grid-cols-3 gap-3 border-y py-4">
<dl className="grid grid-cols-3 gap-3 border-b pb-5">
<div>
<dt className="text-xs text-muted-foreground">{t("Used")}</dt>
<dd className="mt-1 text-sm font-medium tabular-nums">{formatCapacity(totalUsedCapacity)}</dd>
Expand Down Expand Up @@ -89,7 +103,7 @@ export function PerformanceUsageCard({
</div>
) : null}

<dl className="grid gap-4 sm:grid-cols-3">
<dl className="grid gap-4 sm:grid-cols-3 xl:grid-cols-1">
{usageStats.map((item) => (
<div key={item.label} className="min-w-0">
<dt className="text-xs text-muted-foreground">{item.label}</dt>
Expand Down
35 changes: 21 additions & 14 deletions app/(dashboard)/access-keys/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -217,23 +217,11 @@ export default function AccessKeysPage() {
return (
<Page>
<PageHeader
count={!loading && !loadError ? data.length : undefined}
actions={
<>
<SearchInput
value={searchTerm}
onChange={setSearchTerm}
placeholder={t("Search Access Key")}
clearable
className="max-w-xs"
/>
{selectedKeys.length > 0 && canBulkDeleteAccessKeys && (
<Button variant="outline" disabled={!selectedKeys.length} onClick={deleteSelected}>
<RiDeleteBin5Line className="size-4" aria-hidden />
<span>{t("Delete Selected")}</span>
</Button>
)}
{canCreateAccessKey ? (
<Button variant="outline" onClick={() => setNewItemVisible(true)}>
<Button onClick={() => setNewItemVisible(true)}>
<RiAddLine className="size-4" aria-hidden />
<span>{t("Add Access Key")}</span>
</Button>
Expand All @@ -246,6 +234,25 @@ export default function AccessKeysPage() {

<div className="space-y-3">
<DataTable
toolbar={
<>
<SearchInput
value={searchTerm}
onChange={setSearchTerm}
placeholder={t("Search Access Key")}
clearable
className="max-w-xs"
/>
<div className="flex flex-wrap items-center gap-2">
{selectedKeys.length > 0 && canBulkDeleteAccessKeys && (
<Button variant="outline" disabled={!selectedKeys.length} onClick={deleteSelected}>
<RiDeleteBin5Line className="size-4" aria-hidden />
<span>{t("Delete Selected")}</span>
</Button>
)}
</div>
</>
}
keepPreviousData
caption={t("Access Keys")}
errorTitle={loadError ? t("Failed to get data") : undefined}
Expand Down
Loading