113 lines
4.8 KiB
TypeScript
113 lines
4.8 KiB
TypeScript
import { useMemo, useState, type Dispatch, type SetStateAction } from "react";
|
|
import Search from "@/src/components/twui/elements/Search";
|
|
import Loading from "@/src/components/twui/elements/Loading";
|
|
import AdminCard from "@/src/components/general/admin-card";
|
|
import ClientRow from "@/src/components/general/client-row";
|
|
import H2 from "@/src/components/twui/layout/H2";
|
|
import Span from "@/src/components/twui/layout/Span";
|
|
import Row from "@/src/components/twui/layout/Row";
|
|
import Stack from "@/src/components/twui/layout/Stack";
|
|
import ClientFormModal from "../(partials)/client-form-modal";
|
|
import { useAdminCrudGet } from "@/src/hooks/use-admin-crud-get";
|
|
import type { BUN_SQLITE_WGUI_CLIENTS } from "@/db/types/db";
|
|
|
|
type Props = {
|
|
addOpen: boolean;
|
|
setAddOpen: Dispatch<SetStateAction<boolean>>;
|
|
};
|
|
|
|
const thClass =
|
|
"px-4 py-2 text-left text-[11px] font-semibold uppercase tracking-[0.08em] " +
|
|
"text-foreground-light/40 dark:text-foreground-dark/40 whitespace-nowrap";
|
|
|
|
const thRightClass = `${thClass} text-right`;
|
|
|
|
export default function ClientsTableSection({ addOpen, setAddOpen }: Props) {
|
|
const [query, setQuery] = useState("");
|
|
|
|
const { res, setRes } = useAdminCrudGet<BUN_SQLITE_WGUI_CLIENTS>({
|
|
table: "clients",
|
|
sql_query: {
|
|
order: { field: "created_at", strategy: "DESC" },
|
|
limit: 100,
|
|
},
|
|
});
|
|
|
|
const filtered = useMemo(() => {
|
|
const clients = res || [];
|
|
const q = query.trim().toLowerCase();
|
|
if (!q) {
|
|
return clients;
|
|
}
|
|
return clients.filter((client) =>
|
|
[client.name, client.wg_ip_address, client.allowed_ips].some(
|
|
(value) => (value || "").toLowerCase().includes(q),
|
|
),
|
|
);
|
|
}, [res, query]);
|
|
|
|
return (
|
|
<AdminCard className="w-full overflow-hidden">
|
|
<Row className="justify-between gap-3 px-5 h-12 flex-wrap">
|
|
<H2 className="text-[13.5px] font-medium text-foreground-light/60 dark:text-foreground-dark/60 mb-0!">
|
|
Clients
|
|
<Span className="tabular text-foreground-light/40 dark:text-foreground-dark/40 ml-2">
|
|
{String(filtered.length)}
|
|
</Span>
|
|
</H2>
|
|
<Search
|
|
no_search_button
|
|
placeholder="Search clients…"
|
|
changeHandler={(value) => setQuery(value || "")}
|
|
inputProps={{
|
|
className: "!text-[13.5px]",
|
|
wrapperProps: {
|
|
className:
|
|
"!py-[5px] !min-h-[30px] w-[220px] bg-foreground-light/[0.02] dark:bg-foreground-dark/[0.03]",
|
|
},
|
|
}}
|
|
/>
|
|
</Row>
|
|
{res === undefined ? (
|
|
<Stack center className="w-full py-10">
|
|
<Loading />
|
|
</Stack>
|
|
) : filtered.length ? (
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full min-w-[860px]">
|
|
<thead>
|
|
<tr className="border-y border-slate-200 dark:border-white/10 bg-foreground-light/[0.02] dark:bg-foreground-dark/[0.03]">
|
|
<th className={thClass}>Client</th>
|
|
<th className={thClass}>Tunnel IP</th>
|
|
<th className={thClass}>Allowed IPs</th>
|
|
<th className={thClass}>Public key</th>
|
|
<th className={thRightClass}>Created</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{filtered.map((client) => (
|
|
<ClientRow key={client.id} client={client} />
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
) : (
|
|
<Stack center className="w-full justify-center py-14 px-6 text-center gap-1">
|
|
<Span className="text-[13.5px] font-medium text-foreground-light/50 dark:text-foreground-dark/50">
|
|
{query ? "No clients found" : "No clients yet"}
|
|
</Span>
|
|
<Span className="text-[12.5px] text-foreground-light/35 dark:text-foreground-dark/35">
|
|
{query
|
|
? "Try adjusting your search terms"
|
|
: "Add a client to get started"}
|
|
</Span>
|
|
</Stack>
|
|
)}
|
|
<ClientFormModal
|
|
open={addOpen}
|
|
setOpen={setAddOpen}
|
|
onCreated={() => setRes(undefined)}
|
|
/>
|
|
</AdminCard>
|
|
);
|
|
} |