mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
ui: redesign
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
@apply bg-white dark:bg-[#0D0D0D];
|
@apply bg-white dark:bg-[#131313];
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -30,6 +30,7 @@ body {
|
|||||||
--color-foreground: var(--foreground);
|
--color-foreground: var(--foreground);
|
||||||
--color-card: var(--card);
|
--color-card: var(--card);
|
||||||
--color-card-foreground: var(--card-foreground);
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-card-header: var(--card-header);
|
||||||
--color-popover: var(--popover);
|
--color-popover: var(--popover);
|
||||||
--color-popover-foreground: var(--popover-foreground);
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
--color-primary: var(--primary);
|
--color-primary: var(--primary);
|
||||||
@@ -57,6 +58,7 @@ body {
|
|||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
--color-strong-accent: var(--strong-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -65,6 +67,7 @@ body {
|
|||||||
--foreground: oklch(0.145 0 0);
|
--foreground: oklch(0.145 0 0);
|
||||||
--card: oklch(1 0 0);
|
--card: oklch(1 0 0);
|
||||||
--card-foreground: oklch(0.145 0 0);
|
--card-foreground: oklch(0.145 0 0);
|
||||||
|
--card-header: oklch(0.922 0 0);
|
||||||
--popover: oklch(1 0 0);
|
--popover: oklch(1 0 0);
|
||||||
--popover-foreground: oklch(0.145 0 0);
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
--primary: oklch(0.205 0 0);
|
--primary: oklch(0.205 0 0);
|
||||||
@@ -92,12 +95,14 @@ body {
|
|||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
--strong-accent: #ff543a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: oklch(0.145 0 0);
|
--background: #131313;
|
||||||
--foreground: oklch(0.985 0 0);
|
--foreground: oklch(0.985 0 0);
|
||||||
--card: oklch(0.1448 0 0);
|
--card: #131313;
|
||||||
|
--card-header: #1b1b1b;
|
||||||
/* --card: oklch(0.205 0 0); ORIGINAL */
|
/* --card: oklch(0.205 0 0); ORIGINAL */
|
||||||
--card-foreground: oklch(0.985 0 0);
|
--card-foreground: oklch(0.985 0 0);
|
||||||
--popover: oklch(0.205 0 0);
|
--popover: oklch(0.205 0 0);
|
||||||
@@ -128,6 +133,7 @@ body {
|
|||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
--sidebar-ring: oklch(0.556 0 0);
|
--sidebar-ring: oklch(0.556 0 0);
|
||||||
|
--strong-accent: #ff543a;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
@@ -6,15 +6,7 @@ import { createVolumeMutation } from "~/api-client/@tanstack/react-query.gen";
|
|||||||
import { parseError } from "~/lib/errors";
|
import { parseError } from "~/lib/errors";
|
||||||
import { CreateVolumeForm } from "./create-volume-form";
|
import { CreateVolumeForm } from "./create-volume-form";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import {
|
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "./ui/dialog";
|
|
||||||
import { ScrollArea } from "./ui/scroll-area";
|
import { ScrollArea } from "./ui/scroll-area";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -41,7 +33,7 @@ export const CreateVolumeDialog = ({ open, setOpen }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button className="bg-blue-900 hover:bg-blue-800">
|
<Button className="bg-transparent border hover:bg-strong-accent">
|
||||||
<Plus size={16} className="mr-2" />
|
<Plus size={16} className="mr-2" />
|
||||||
Create volume
|
Create volume
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { Outlet, useNavigate } from "react-router";
|
import { Outlet, useNavigate } from "react-router";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { logoutMutation } from "~/api-client/@tanstack/react-query.gen";
|
||||||
|
import { appContext } from "~/context";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
import { authMiddleware } from "~/middleware/auth";
|
||||||
|
import type { Route } from "./+types/layout";
|
||||||
import { AppBreadcrumb } from "./app-breadcrumb";
|
import { AppBreadcrumb } from "./app-breadcrumb";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { logoutMutation } from "~/api-client/@tanstack/react-query.gen";
|
|
||||||
import type { Route } from "./+types/layout";
|
|
||||||
import { appContext } from "~/context";
|
|
||||||
import { authMiddleware } from "~/middleware/auth";
|
|
||||||
|
|
||||||
export const clientMiddleware = [authMiddleware];
|
export const clientMiddleware = [authMiddleware];
|
||||||
|
|
||||||
@@ -39,19 +39,23 @@ export default function Layout({ loaderData }: Route.ComponentProps) {
|
|||||||
"dark:[background-image:linear-gradient(to_right,#262626_1px,transparent_1px),linear-gradient(to_bottom,#262626_1px,transparent_1px)]",
|
"dark:[background-image:linear-gradient(to_right,#262626_1px,transparent_1px),linear-gradient(to_bottom,#262626_1px,transparent_1px)]",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-white [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)] dark:bg-black"></div>
|
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-white [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)] dark:bg-card"></div>
|
||||||
<main className="relative flex flex-col pt-4 sm:pt-8 px-2 sm:px-4 pb-4 container mx-auto">
|
<header className="relative bg-card-header border-b border-border/50">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between py-3 sm:py-4 px-2 sm:px-4 container mx-auto">
|
||||||
<AppBreadcrumb />
|
<AppBreadcrumb />
|
||||||
{loaderData.user && (
|
{loaderData.user && (
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<span className="text-sm text-muted-foreground">Welcome, {loaderData.user?.username}</span>
|
<span className="text-sm text-muted-foreground">
|
||||||
|
Welcome, <span className="text-strong-accent">{loaderData.user?.username}</span>
|
||||||
|
</span>
|
||||||
<Button variant="outline" size="sm" onClick={() => logout.mutate({})} loading={logout.isPending}>
|
<Button variant="outline" size="sm" onClick={() => logout.mutate({})} loading={logout.isPending}>
|
||||||
Logout
|
Logout
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</header>
|
||||||
|
<main className="relative flex flex-col pt-4 sm:pt-8 px-2 sm:px-4 pb-4 container mx-auto">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,23 +6,23 @@ import type * as React from "react";
|
|||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex cursor-pointer uppercase border items-center justify-center dark:border-white dark:bg-secondary dark:text-white gap-2 whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"inline-flex cursor-pointer uppercase rounded-sm items-center justify-center gap-2 whitespace-nowrap text-xs font-semibold tracking-wide transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-ring border-0",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
default:
|
||||||
destructive:
|
"bg-[var(--strong-accent)] text-white hover:bg-[var(--strong-accent)]/90 focus-visible:ring-[var(--strong-accent)]/50",
|
||||||
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/50",
|
||||||
outline:
|
outline: "border border-border bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
secondary:
|
||||||
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
"bg-[#464646] text-white hover:bg-[#464646]/80 dark:bg-[#464646] dark:text-white dark:hover:bg-[#464646]/80",
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
default: "h-9 px-5 py-2 has-[>svg]:px-4",
|
||||||
sm: "rounded-xs h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
|
sm: "h-8 px-3 py-1.5 has-[>svg]:px-2.5",
|
||||||
lg: "h-10 px-6 has-[>svg]:px-4",
|
lg: "h-10 px-6 py-2.5 has-[>svg]:px-5",
|
||||||
icon: "size-9",
|
icon: "size-9",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ const getIconAndColor = (backend: BackendType) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const VolumeIcon = ({ backend, size = 10 }: VolumeIconProps) => {
|
export const VolumeIcon = ({ backend, size = 10 }: VolumeIconProps) => {
|
||||||
const { icon: Icon, color, label } = getIconAndColor(backend);
|
const { icon: Icon, label } = getIconAndColor(backend);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={`flex items-center gap-2 ${color} rounded-md px-2 py-1`}>
|
<span className={`flex items-center gap-2 rounded-md px-2 py-1`}>
|
||||||
<Icon size={size} />
|
<Icon size={size} />
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function StorageChart({ statfs }: Props) {
|
|||||||
{
|
{
|
||||||
name: "Used",
|
name: "Used",
|
||||||
value: statfs.used,
|
value: statfs.used,
|
||||||
fill: "#2B7EFF",
|
fill: "#ff543a",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Free",
|
name: "Free",
|
||||||
@@ -63,7 +63,7 @@ export function StorageChart({ statfs }: Props) {
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex-1 pb-0">
|
<CardContent className="flex-1 pb-0">
|
||||||
<div className="">
|
<div>
|
||||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||||
<PieChart>
|
<PieChart>
|
||||||
<ChartTooltip
|
<ChartTooltip
|
||||||
@@ -105,9 +105,9 @@ export function StorageChart({ statfs }: Props) {
|
|||||||
<ByteSize bytes={statfs.total} className="font-mono text-sm" />
|
<ByteSize bytes={statfs.total} className="font-mono text-sm" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between p-3 rounded-lg bg-blue-500/10">
|
<div className="flex items-center justify-between p-3 rounded-lg bg-strong-accent/10">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="h-4 w-4 rounded-full bg-blue-500" />
|
<div className="h-4 w-4 rounded-full bg-strong-accent" />
|
||||||
<span className="font-medium">Used Space</span>
|
<span className="font-medium">Used Space</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import { StatusDot } from "~/components/status-dot";
|
|||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select";
|
||||||
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
|
||||||
import { VolumeIcon } from "~/components/volume-icon";
|
import { VolumeIcon } from "~/components/volume-icon";
|
||||||
import type { Route } from "./+types/home";
|
import type { Route } from "./+types/home";
|
||||||
|
import { Card, CardHeader } from "~/components/ui/card";
|
||||||
|
|
||||||
export function meta(_: Route.MetaArgs) {
|
export function meta(_: Route.MetaArgs) {
|
||||||
return [
|
return [
|
||||||
@@ -60,85 +61,96 @@ export default function Home({ loaderData }: Route.ComponentProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="text-2xl sm:text-3xl font-bold mb-0 uppercase">Ironmount</h1>
|
{/* <h1 className="text-2xl sm:text-3xl font-bold mb-0 uppercase">Ironmount</h1> */}
|
||||||
<h2 className="text-xs sm:text-sm font-semibold mb-2 text-muted-foreground">
|
{/* <h2 className="text-xs sm:text-sm font-semibold mb-2 text-muted-foreground"> */}
|
||||||
Create, manage, monitor, and automate your volumes with ease.
|
{/* Create, manage, monitor, and automate your volumes with ease. */}
|
||||||
</h2>
|
{/* </h2> */}
|
||||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 mt-4 sm:justify-between">
|
<Card className="p-0 gap-0">
|
||||||
<span className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2">
|
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:justify-between p-4 bg-card-header py-4">
|
||||||
<Input
|
<span className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2">
|
||||||
className="w-full sm:w-[180px]"
|
<Input
|
||||||
placeholder="Search volumes…"
|
className="w-full sm:w-[180px]"
|
||||||
value={searchQuery}
|
placeholder="Search volumes…"
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
value={searchQuery}
|
||||||
/>
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
/>
|
||||||
<SelectTrigger className="w-full sm:w-[180px]">
|
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||||
<SelectValue placeholder="All status" />
|
<SelectTrigger className="w-full sm:w-[180px]">
|
||||||
</SelectTrigger>
|
<SelectValue placeholder="All status" />
|
||||||
<SelectContent>
|
</SelectTrigger>
|
||||||
<SelectItem value="mounted">Mounted</SelectItem>
|
<SelectContent>
|
||||||
<SelectItem value="unmounted">Unmounted</SelectItem>
|
<SelectItem value="mounted">Mounted</SelectItem>
|
||||||
<SelectItem value="error">Error</SelectItem>
|
<SelectItem value="unmounted">Unmounted</SelectItem>
|
||||||
</SelectContent>
|
<SelectItem value="error">Error</SelectItem>
|
||||||
</Select>
|
</SelectContent>
|
||||||
<Select value={backendFilter} onValueChange={setBackendFilter}>
|
</Select>
|
||||||
<SelectTrigger className="w-full sm:w-[180px]">
|
<Select value={backendFilter} onValueChange={setBackendFilter}>
|
||||||
<SelectValue placeholder="All backends" />
|
<SelectTrigger className="w-full sm:w-[180px]">
|
||||||
</SelectTrigger>
|
<SelectValue placeholder="All backends" />
|
||||||
<SelectContent>
|
</SelectTrigger>
|
||||||
<SelectItem value="directory">Directory</SelectItem>
|
<SelectContent>
|
||||||
<SelectItem value="nfs">NFS</SelectItem>
|
<SelectItem value="directory">Directory</SelectItem>
|
||||||
<SelectItem value="smb">SMB</SelectItem>
|
<SelectItem value="nfs">NFS</SelectItem>
|
||||||
</SelectContent>
|
<SelectItem value="smb">SMB</SelectItem>
|
||||||
</Select>
|
</SelectContent>
|
||||||
{(searchQuery || statusFilter || backendFilter) && (
|
</Select>
|
||||||
<Button variant="outline" size="sm" onClick={clearFilters} className="w-full sm:w-auto">
|
{(searchQuery || statusFilter || backendFilter) && (
|
||||||
<RotateCcw className="h-4 w-4 mr-2" />
|
<Button variant="outline" size="sm" onClick={clearFilters} className="w-full sm:w-auto">
|
||||||
Clear filters
|
<RotateCcw className="h-4 w-4 mr-2" />
|
||||||
</Button>
|
Clear filters
|
||||||
)}
|
</Button>
|
||||||
</span>
|
)}
|
||||||
<CreateVolumeDialog open={createVolumeOpen} setOpen={setCreateVolumeOpen} />
|
</span>
|
||||||
</div>
|
<CreateVolumeDialog open={createVolumeOpen} setOpen={setCreateVolumeOpen} />
|
||||||
<div className="mt-4 overflow-x-auto">
|
</div>
|
||||||
<Table className="border bg-white dark:bg-secondary">
|
<div className="overflow-x-auto">
|
||||||
<TableCaption>A list of your managed volumes.</TableCaption>
|
<Table className="border-t">
|
||||||
<TableHeader>
|
<TableHeader className="bg-card-header">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="w-[100px] uppercase">Name</TableHead>
|
<TableHead className="w-[100px] uppercase">Name</TableHead>
|
||||||
<TableHead className="uppercase text-left">Backend</TableHead>
|
<TableHead className="uppercase text-left">Backend</TableHead>
|
||||||
<TableHead className="uppercase hidden sm:table-cell">Mountpoint</TableHead>
|
<TableHead className="uppercase hidden sm:table-cell">Mountpoint</TableHead>
|
||||||
<TableHead className="uppercase text-center">Status</TableHead>
|
<TableHead className="uppercase text-center">Status</TableHead>
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{filteredVolumes.map((volume) => (
|
|
||||||
<TableRow
|
|
||||||
key={volume.name}
|
|
||||||
className="hover:bg-accent/50 hover:cursor-pointer"
|
|
||||||
onClick={() => navigate(`/volumes/${volume.name}`)}
|
|
||||||
>
|
|
||||||
<TableCell className="font-medium">{volume.name}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<VolumeIcon backend={volume.type} />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="hidden sm:table-cell">
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
<span className="text-muted-foreground text-xs truncate bg-primary/10 rounded-md px-2 py-1">
|
|
||||||
{volume.path}
|
|
||||||
</span>
|
|
||||||
<Copy size={10} />
|
|
||||||
</span>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-center">
|
|
||||||
<StatusDot status={volume.status} />
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
</TableHeader>
|
||||||
</TableBody>
|
<TableBody>
|
||||||
</Table>
|
{filteredVolumes.map((volume) => (
|
||||||
</div>
|
<TableRow
|
||||||
|
key={volume.name}
|
||||||
|
className="hover:bg-accent/50 hover:cursor-pointer"
|
||||||
|
onClick={() => navigate(`/volumes/${volume.name}`)}
|
||||||
|
>
|
||||||
|
<TableCell className="font-medium text-strong-accent">{volume.name}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<VolumeIcon backend={volume.type} />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="hidden sm:table-cell">
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<span className="text-muted-foreground text-xs truncate bg-primary/10 rounded-md px-2 py-1">
|
||||||
|
{volume.path}
|
||||||
|
</span>
|
||||||
|
<Copy size={10} />
|
||||||
|
</span>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-center">
|
||||||
|
<StatusDot status={volume.status} />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
<div className="px-4 py-2 text-sm text-muted-foreground bg-card-header flex justify-end border-t">
|
||||||
|
{filteredVolumes.length === 0 ? (
|
||||||
|
"No volumes found."
|
||||||
|
) : (
|
||||||
|
<span>
|
||||||
|
<span className="text-strong-accent">{filteredVolumes.length}</span> volume
|
||||||
|
{filteredVolumes.length > 1 ? "s" : ""}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,9 @@ export default function LoginPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center p-4">
|
<div className="relative min-h-screen flex items-center justify-center p-4 [background-size:20px_20px] sm:[background-size:40px_40px] [background-image:linear-gradient(to_right,#e4e4e7_1px,transparent_1px),linear-gradient(to_bottom,#e4e4e7_1px,transparent_1px)] dark:[background-image:linear-gradient(to_right,#262626_1px,transparent_1px),linear-gradient(to_bottom,#262626_1px,transparent_1px)]">
|
||||||
<Card className="w-full max-w-md">
|
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-white [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)] dark:bg-black" />
|
||||||
|
<Card className="relative w-full max-w-md">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-2xl font-bold">Welcome Back</CardTitle>
|
<CardTitle className="text-2xl font-bold">Welcome Back</CardTitle>
|
||||||
<CardDescription>Sign in to your account</CardDescription>
|
<CardDescription>Sign in to your account</CardDescription>
|
||||||
|
|||||||
@@ -60,8 +60,9 @@ export default function OnboardingPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center p-4">
|
<div className="relative min-h-screen flex items-center justify-center p-4 [background-size:20px_20px] sm:[background-size:40px_40px] [background-image:linear-gradient(to_right,#e4e4e7_1px,transparent_1px),linear-gradient(to_bottom,#e4e4e7_1px,transparent_1px)] dark:[background-image:linear-gradient(to_right,#262626_1px,transparent_1px),linear-gradient(to_bottom,#262626_1px,transparent_1px)]">
|
||||||
<Card className="w-full max-w-md">
|
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-white [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)] dark:bg-black" />
|
||||||
|
<Card className="relative w-full max-w-md">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-2xl font-bold">Welcome to Ironmount</CardTitle>
|
<CardTitle className="text-2xl font-bold">Welcome to Ironmount</CardTitle>
|
||||||
<CardDescription>Create the admin user to get started</CardDescription>
|
<CardDescription>Create the admin user to get started</CardDescription>
|
||||||
|
|||||||
Reference in New Issue
Block a user