Skip to content
Open
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
8 changes: 4 additions & 4 deletions console-web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Then configure the backend with `CORS_ALLOWED_ORIGINS` (see deploy README).

## Environment variables

| Variable | Description | Default |
|----------|-------------|--------|
| `NEXT_PUBLIC_BASE_PATH` | Base path for the app (e.g. `/console`) | `""` |
| `NEXT_PUBLIC_API_BASE_URL` | API base URL (relative or absolute) | `"/api/v1"` |
| Variable | Description | Default |
| -------------------------- | --------------------------------------- | ----------- |
| `NEXT_PUBLIC_BASE_PATH` | Base path for the app (e.g. `/console`) | `""` |
| `NEXT_PUBLIC_API_BASE_URL` | API base URL (relative or absolute) | `"/api/v1"` |
5 changes: 4 additions & 1 deletion console-web/app/(auth)/auth/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ export default function LoginPage() {
await login(token.trim())
toast.success(t("Login successful"))
} catch (error: unknown) {
const message = error && typeof error === "object" && "message" in error ? (error as { message: string }).message : t("Login failed")
const message =
error && typeof error === "object" && "message" in error
? (error as { message: string }).message
: t("Login failed")
toast.error(message)
} finally {
setLoading(false)
Expand Down
12 changes: 2 additions & 10 deletions console-web/app/(auth)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
export default function AuthLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex min-h-screen items-center justify-center bg-background">
{children}
</div>
)
export default function AuthLayout({ children }: { children: React.ReactNode }) {
return <div className="flex min-h-screen items-center justify-center bg-background">{children}</div>
}
25 changes: 4 additions & 21 deletions console-web/app/(dashboard)/cluster/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,8 @@ import { RiAddLine } from "@remixicon/react"
import { Page } from "@/components/page"
import { PageHeader } from "@/components/page-header"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Spinner } from "@/components/ui/spinner"
Expand Down Expand Up @@ -96,9 +83,7 @@ export default function ClusterPage() {
<Page>
<PageHeader>
<h1 className="text-lg font-semibold">{t("Cluster")}</h1>
<p className="text-sm text-muted-foreground">
{t("Cluster nodes, capacity and namespaces.")}
</p>
<p className="text-sm text-muted-foreground">{t("Cluster nodes, capacity and namespaces.")}</p>
</PageHeader>

<div className="flex gap-2 border-b border-border mb-4">
Expand Down Expand Up @@ -259,9 +244,7 @@ export default function ClusterPage() {
<TableCell className="font-medium">{ns.name}</TableCell>
<TableCell>{ns.status}</TableCell>
<TableCell className="text-muted-foreground">
{ns.created_at
? new Date(ns.created_at).toLocaleString()
: "-"}
{ns.created_at ? new Date(ns.created_at).toLocaleString() : "-"}
</TableCell>
</TableRow>
))}
Expand Down
22 changes: 5 additions & 17 deletions console-web/app/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,15 @@ import { routes } from "@/lib/routes"
import { cn } from "@/lib/utils"
import { LanguageSwitcher } from "@/components/language-switcher"
import { ThemeSwitcher } from "@/components/theme-switcher"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"

const navItems = [
{ href: routes.dashboard, icon: RiDashboardLine, labelKey: "Dashboard" },
{ href: routes.tenants, icon: RiServerLine, labelKey: "Tenants" },
{ href: routes.cluster, icon: RiNodeTree, labelKey: "Cluster" },
]

export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
const { t } = useTranslation()
const { logout } = useAuth()
const pathname = usePathname()
Expand All @@ -59,16 +50,15 @@ export default function DashboardLayout({
{navItems.map((item) => {
const Icon = item.icon
const isActive =
pathname === item.href ||
(item.href !== routes.dashboard && pathname.startsWith(item.href))
pathname === item.href || (item.href !== routes.dashboard && pathname.startsWith(item.href))
return (
<Link
key={item.href}
href={item.href}
prefetch={false}
className={cn(
"flex items-center gap-3 rounded-none px-2.5 py-2 text-xs font-medium transition-colors",
isActive ? "bg-muted text-foreground" : "text-foreground/70 hover:bg-muted"
isActive ? "bg-muted text-foreground" : "text-foreground/70 hover:bg-muted",
)}
>
<Icon className="size-4 shrink-0" />
Expand All @@ -85,8 +75,7 @@ export default function DashboardLayout({
const activeItem =
navItems.find(
(item) =>
pathname === item.href ||
(item.href !== routes.dashboard && pathname.startsWith(item.href)),
pathname === item.href || (item.href !== routes.dashboard && pathname.startsWith(item.href)),
) ?? navItems[0]
const ActiveIcon = activeItem.icon
return (
Expand Down Expand Up @@ -114,7 +103,6 @@ export default function DashboardLayout({
<Button asChild variant="ghost" size="icon-sm" aria-label="X">
<Link href={X_URL} prefetch={false} target="_blank" rel="noopener noreferrer">
<RiTwitterXLine className="size-4" />

</Link>
</Button>
)}
Expand Down
20 changes: 8 additions & 12 deletions console-web/app/(dashboard)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@ import Link from "next/link"
import { RiServerLine, RiNodeTree } from "@remixicon/react"
import { Page } from "@/components/page"
import { PageHeader } from "@/components/page-header"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { routes } from "@/lib/routes"

Expand All @@ -37,7 +31,9 @@ export default function DashboardPage() {
</CardHeader>
<CardContent>
<Button asChild variant="default" size="sm">
<Link href={routes.tenants} prefetch={false}>{t("View Tenants")}</Link>
<Link href={routes.tenants} prefetch={false}>
{t("View Tenants")}
</Link>
</Button>
</CardContent>
</Card>
Expand All @@ -48,13 +44,13 @@ export default function DashboardPage() {
<RiNodeTree className="size-5" />
<CardTitle className="text-base">{t("Cluster")}</CardTitle>
</div>
<CardDescription className="text-sm">
{t("View cluster nodes, resources and namespaces.")}
</CardDescription>
<CardDescription className="text-sm">{t("View cluster nodes, resources and namespaces.")}</CardDescription>
</CardHeader>
<CardContent>
<Button asChild variant="default" size="sm">
<Link href={routes.cluster} prefetch={false}>{t("View Cluster")}</Link>
<Link href={routes.cluster} prefetch={false}>
{t("View Cluster")}
</Link>
</Button>
</CardContent>
</Card>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,12 @@ import Link from "next/link"
import { useEffect, useState } from "react"
import { useTranslation } from "react-i18next"
import { toast } from "sonner"
import {
RiArrowLeftLine,
RiDeleteBinLine,
RiAddLine,
RiFileList3Line,
RiRestartLine,
} from "@remixicon/react"
import { RiArrowLeftLine, RiDeleteBinLine, RiAddLine, RiFileList3Line, RiRestartLine } from "@remixicon/react"
import { Page } from "@/components/page"
import { PageHeader } from "@/components/page-header"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Spinner } from "@/components/ui/spinner"
Expand Down Expand Up @@ -152,7 +133,7 @@ export function TenantDetailClient({ namespace, name }: TenantDetailClientProps)
}

const handleDeletePool = async (poolName: string) => {
if (!confirm(t("Delete pool \"{{name}}\"?", { name: poolName }))) return
if (!confirm(t('Delete pool "{{name}}"?', { name: poolName }))) return
setDeletingPool(poolName)
try {
await api.deletePool(namespace, name, poolName)
Expand Down Expand Up @@ -181,7 +162,7 @@ export function TenantDetailClient({ namespace, name }: TenantDetailClientProps)
}

const handleDeletePod = async (podName: string) => {
if (!confirm(t("Delete pod \"{{name}}\"?", { name: podName }))) return
if (!confirm(t('Delete pod "{{name}}"?', { name: podName }))) return
setDeletingPod(podName)
try {
await api.deletePod(namespace, name, podName)
Expand Down Expand Up @@ -264,12 +245,7 @@ export function TenantDetailClient({ namespace, name }: TenantDetailClientProps)
{t("Back")}
</Link>
</Button>
<Button
variant="destructive"
size="sm"
disabled={deleting}
onClick={handleDeleteTenant}
>
<Button variant="destructive" size="sm" disabled={deleting} onClick={handleDeleteTenant}>
{deleting ? <Spinner className="mr-1 size-4" /> : <RiDeleteBinLine className="mr-1 size-4" />}
{t("Delete Tenant")}
</Button>
Expand All @@ -279,7 +255,9 @@ export function TenantDetailClient({ namespace, name }: TenantDetailClientProps)
<h1 className="text-lg font-semibold">
{tenant.name} <span className="text-muted-foreground">/ {tenant.namespace}</span>
</h1>
<p className="text-sm text-muted-foreground">{t("State")}: {tenant.state}</p>
<p className="text-sm text-muted-foreground">
{t("State")}: {tenant.state}
</p>
</PageHeader>

<div className="flex gap-2 border-b border-border mb-4">
Expand All @@ -306,9 +284,14 @@ export function TenantDetailClient({ namespace, name }: TenantDetailClientProps)
<CardTitle className="text-base">{t("Details")}</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm">
<p><span className="text-muted-foreground">{t("Image")}:</span> {tenant.image || "-"}</p>
<p><span className="text-muted-foreground">{t("Mount Path")}:</span> {tenant.mount_path || "-"}</p>
<p><span className="text-muted-foreground">{t("Created")}:</span>{" "}
<p>
<span className="text-muted-foreground">{t("Image")}:</span> {tenant.image || "-"}
</p>
<p>
<span className="text-muted-foreground">{t("Mount Path")}:</span> {tenant.mount_path || "-"}
</p>
<p>
<span className="text-muted-foreground">{t("Created")}:</span>{" "}
{tenant.created_at ? new Date(tenant.created_at).toLocaleString() : "-"}
</p>
</CardContent>
Expand All @@ -335,9 +318,7 @@ export function TenantDetailClient({ namespace, name }: TenantDetailClientProps)
<TableRow key={svc.name}>
<TableCell>{svc.name}</TableCell>
<TableCell>{svc.service_type}</TableCell>
<TableCell>
{svc.ports.map((p) => `${p.name}:${p.port}`).join(", ")}
</TableCell>
<TableCell>{svc.ports.map((p) => `${p.name}:${p.port}`).join(", ")}</TableCell>
</TableRow>
))}
</TableBody>
Expand Down Expand Up @@ -397,7 +378,9 @@ export function TenantDetailClient({ namespace, name }: TenantDetailClientProps)
<Card>
<CardHeader className="pb-2">
<CardDescription>
{t("All pools in this tenant form one unified cluster. Data is distributed across all pools (erasure-coded); every pool is in use. To see disk usage per pool, use RustFS Console (S3 API port 9001) or check PVC usage in the cluster (e.g. kubectl).")}
{t(
"All pools in this tenant form one unified cluster. Data is distributed across all pools (erasure-coded); every pool is in use. To see disk usage per pool, use RustFS Console (S3 API port 9001) or check PVC usage in the cluster (e.g. kubectl).",
)}
</CardDescription>
</CardHeader>
</Card>
Expand Down Expand Up @@ -429,9 +412,7 @@ export function TenantDetailClient({ namespace, name }: TenantDetailClientProps)
type="number"
min={1}
value={addPoolForm.servers}
onChange={(e) =>
setAddPoolForm((f) => ({ ...f, servers: parseInt(e.target.value, 10) || 0 }))
}
onChange={(e) => setAddPoolForm((f) => ({ ...f, servers: parseInt(e.target.value, 10) || 0 }))}
/>
</div>
<div className="space-y-2">
Expand Down Expand Up @@ -495,7 +476,9 @@ export function TenantDetailClient({ namespace, name }: TenantDetailClientProps)
<TableCell>{p.servers}</TableCell>
<TableCell>{p.volumes_per_server}</TableCell>
<TableCell>{p.state}</TableCell>
<TableCell>{p.ready_replicas}/{p.replicas}</TableCell>
<TableCell>
{p.ready_replicas}/{p.replicas}
</TableCell>
<TableCell>
<Button
variant="ghost"
Expand All @@ -504,7 +487,11 @@ export function TenantDetailClient({ namespace, name }: TenantDetailClientProps)
disabled={deletingPool === p.name}
onClick={() => handleDeletePool(p.name)}
>
{deletingPool === p.name ? <Spinner className="size-4" /> : <RiDeleteBinLine className="size-4" />}
{deletingPool === p.name ? (
<Spinner className="size-4" />
) : (
<RiDeleteBinLine className="size-4" />
)}
</Button>
</TableCell>
</TableRow>
Expand Down Expand Up @@ -539,12 +526,7 @@ export function TenantDetailClient({ namespace, name }: TenantDetailClientProps)
<TableCell>{p.age}</TableCell>
<TableCell>
<div className="flex gap-1">
<Button
variant="ghost"
size="icon-xs"
title={t("Logs")}
onClick={() => loadLogs(p.name)}
>
<Button variant="ghost" size="icon-xs" title={t("Logs")} onClick={() => loadLogs(p.name)}>
<RiFileList3Line className="size-4" />
</Button>
<Button
Expand Down Expand Up @@ -584,7 +566,9 @@ export function TenantDetailClient({ namespace, name }: TenantDetailClientProps)
{logsPod && (
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle className="text-base">{t("Logs")}: {logsPod}</CardTitle>
<CardTitle className="text-base">
{t("Logs")}: {logsPod}
</CardTitle>
<Button variant="ghost" size="sm" onClick={() => setLogsPod(null)}>
{t("Close")}
</Button>
Expand Down Expand Up @@ -629,9 +613,7 @@ export function TenantDetailClient({ namespace, name }: TenantDetailClientProps)
<TableCell>{ev.reason}</TableCell>
<TableCell className="max-w-md truncate">{ev.message}</TableCell>
<TableCell>{ev.involved_object}</TableCell>
<TableCell className="text-muted-foreground">
{ev.last_timestamp || "-"}
</TableCell>
<TableCell className="text-muted-foreground">{ev.last_timestamp || "-"}</TableCell>
</TableRow>
))
)}
Expand Down
Loading