Extract shared ClientsTable component and useClientRulesMap hook
This commit is contained in:
@@ -0,0 +1,57 @@
|
|||||||
|
import ClientRow from "@/src/components/general/client-row";
|
||||||
|
import type {
|
||||||
|
BUN_SQLITE_WGUI_CLIENTS,
|
||||||
|
BUN_SQLITE_WGUI_CLIENT_RULES,
|
||||||
|
} from "@/db/types/db";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
clients: BUN_SQLITE_WGUI_CLIENTS[];
|
||||||
|
rules_by_client_id: Map<number, BUN_SQLITE_WGUI_CLIENT_RULES[]>;
|
||||||
|
accessLabel?: string;
|
||||||
|
minWidthClass?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 ClientsTable({
|
||||||
|
clients,
|
||||||
|
rules_by_client_id,
|
||||||
|
accessLabel = "Access",
|
||||||
|
minWidthClass = "min-w-[860px]",
|
||||||
|
}: Props) {
|
||||||
|
return (
|
||||||
|
<div className="overflow-x-auto w-full">
|
||||||
|
<table className={`w-full ${minWidthClass}`}>
|
||||||
|
<thead className="w-full">
|
||||||
|
<tr className="border-y border-slate-200 dark:border-white/10 bg-foreground-light/[0.02] dark:bg-foreground-dark/[0.03] w-full">
|
||||||
|
<th className={thClass}>Client</th>
|
||||||
|
<th className={thClass}>Host</th>
|
||||||
|
<th className={thClass}>Tunnel IP</th>
|
||||||
|
<th className={thClass}>Allowed IPs</th>
|
||||||
|
<th className={thClass}>{accessLabel}</th>
|
||||||
|
<th className={thClass}>Public key</th>
|
||||||
|
<th className={thRightClass}>Created</th>
|
||||||
|
<th className={thRightClass}>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{clients.map((client) => (
|
||||||
|
<ClientRow
|
||||||
|
key={client.id}
|
||||||
|
client={client}
|
||||||
|
rules={
|
||||||
|
client.id
|
||||||
|
? rules_by_client_id.get(Number(client.id))
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import { useAdminCrudGet } from "@/src/hooks/use-admin-crud-get";
|
||||||
|
import type { BUN_SQLITE_WGUI_CLIENT_RULES } from "@/db/types/db";
|
||||||
|
|
||||||
|
type ClientRulesMap = Map<number, BUN_SQLITE_WGUI_CLIENT_RULES[]>;
|
||||||
|
|
||||||
|
export default function useClientRulesMap(
|
||||||
|
initial_res?: BUN_SQLITE_WGUI_CLIENT_RULES[] | null,
|
||||||
|
): ClientRulesMap {
|
||||||
|
const { res: client_rules } = useAdminCrudGet<BUN_SQLITE_WGUI_CLIENT_RULES>(
|
||||||
|
{
|
||||||
|
table: "client_rules",
|
||||||
|
sql_query: {
|
||||||
|
limit: 500,
|
||||||
|
},
|
||||||
|
initial_res: initial_res ?? undefined,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const map: ClientRulesMap = new Map();
|
||||||
|
|
||||||
|
for (let i = 0; i < (client_rules || []).length; i++) {
|
||||||
|
const rule = client_rules?.[i];
|
||||||
|
const client_id = Number(rule?.client_id);
|
||||||
|
|
||||||
|
if (!rule || !client_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = map.get(client_id) || [];
|
||||||
|
existing.push(rule);
|
||||||
|
map.set(client_id, existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}, [client_rules]);
|
||||||
|
}
|
||||||
@@ -5,56 +5,20 @@ import Row from "@/src/components/twui/layout/Row";
|
|||||||
import Stack from "@/src/components/twui/layout/Stack";
|
import Stack from "@/src/components/twui/layout/Stack";
|
||||||
import Span from "@/src/components/twui/layout/Span";
|
import Span from "@/src/components/twui/layout/Span";
|
||||||
import AdminCard from "@/src/components/general/admin-card";
|
import AdminCard from "@/src/components/general/admin-card";
|
||||||
import ClientRow from "@/src/components/general/client-row";
|
import ClientsTable from "@/src/components/general/clients-table";
|
||||||
import type {
|
import useClientRulesMap from "@/src/hooks/use-client-rules-map";
|
||||||
BUN_SQLITE_WGUI_CLIENT_RULES,
|
import type { BUN_SQLITE_WGUI_CLIENTS } from "@/db/types/db";
|
||||||
BUN_SQLITE_WGUI_CLIENTS,
|
|
||||||
} from "@/db/types/db";
|
|
||||||
import { useAdminCrudGet } from "@/src/hooks/use-admin-crud-get";
|
|
||||||
import { useMemo } from "react";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
clients?: BUN_SQLITE_WGUI_CLIENTS[];
|
clients?: BUN_SQLITE_WGUI_CLIENTS[];
|
||||||
};
|
};
|
||||||
|
|
||||||
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`;
|
|
||||||
|
|
||||||
const RECENT_LIMIT = 8;
|
const RECENT_LIMIT = 8;
|
||||||
|
|
||||||
export default function PeersTableSection({ clients }: Props) {
|
export default function PeersTableSection({ clients }: Props) {
|
||||||
const recent = (clients || []).slice(0, RECENT_LIMIT);
|
const recent = (clients || []).slice(0, RECENT_LIMIT);
|
||||||
|
|
||||||
const { res: client_rules } = useAdminCrudGet<BUN_SQLITE_WGUI_CLIENT_RULES>(
|
const rules_by_client_id = useClientRulesMap();
|
||||||
{
|
|
||||||
table: "client_rules",
|
|
||||||
sql_query: {
|
|
||||||
limit: 500,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const rules_by_client_id = useMemo(() => {
|
|
||||||
const map = new Map<number, BUN_SQLITE_WGUI_CLIENT_RULES[]>();
|
|
||||||
|
|
||||||
for (let i = 0; i < (client_rules || []).length; i++) {
|
|
||||||
const rule = client_rules?.[i];
|
|
||||||
const client_id = Number(rule?.client_id);
|
|
||||||
|
|
||||||
if (!rule || !client_id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = map.get(client_id) || [];
|
|
||||||
existing.push(rule);
|
|
||||||
map.set(client_id, existing);
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}, [client_rules]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminCard className="w-full overflow-hidden">
|
<AdminCard className="w-full overflow-hidden">
|
||||||
@@ -71,37 +35,11 @@ export default function PeersTableSection({ clients }: Props) {
|
|||||||
</Link>
|
</Link>
|
||||||
</Row>
|
</Row>
|
||||||
{recent.length ? (
|
{recent.length ? (
|
||||||
<div className="overflow-x-auto">
|
<ClientsTable
|
||||||
<table className="w-full min-w-[860px]">
|
clients={recent}
|
||||||
<thead className="w-full">
|
rules_by_client_id={rules_by_client_id}
|
||||||
<tr className="border-y border-slate-200 dark:border-white/10 bg-foreground-light/[0.02] dark:bg-foreground-dark/[0.03] w-full">
|
accessLabel="Access List"
|
||||||
<th className={thClass}>Client</th>
|
|
||||||
<th className={thClass}>Host</th>
|
|
||||||
<th className={thClass}>Tunnel IP</th>
|
|
||||||
<th className={thClass}>Allowed IPs</th>
|
|
||||||
<th className={thClass}>Access List</th>
|
|
||||||
<th className={thClass}>Public key</th>
|
|
||||||
<th className={thRightClass}>Created</th>
|
|
||||||
<th className={thRightClass}>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{recent.map((client) => (
|
|
||||||
<ClientRow
|
|
||||||
key={client.id}
|
|
||||||
client={client}
|
|
||||||
rules={
|
|
||||||
client.id
|
|
||||||
? rules_by_client_id.get(
|
|
||||||
Number(client.id),
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<Stack
|
<Stack
|
||||||
center
|
center
|
||||||
|
|||||||
@@ -2,29 +2,21 @@ import { useMemo, useState, type Dispatch, type SetStateAction } from "react";
|
|||||||
import Search from "@/src/components/twui/elements/Search";
|
import Search from "@/src/components/twui/elements/Search";
|
||||||
import Loading from "@/src/components/twui/elements/Loading";
|
import Loading from "@/src/components/twui/elements/Loading";
|
||||||
import AdminCard from "@/src/components/general/admin-card";
|
import AdminCard from "@/src/components/general/admin-card";
|
||||||
import ClientRow from "@/src/components/general/client-row";
|
import ClientsTable from "@/src/components/general/clients-table";
|
||||||
import H2 from "@/src/components/twui/layout/H2";
|
import H2 from "@/src/components/twui/layout/H2";
|
||||||
import Span from "@/src/components/twui/layout/Span";
|
import Span from "@/src/components/twui/layout/Span";
|
||||||
import Row from "@/src/components/twui/layout/Row";
|
import Row from "@/src/components/twui/layout/Row";
|
||||||
import Stack from "@/src/components/twui/layout/Stack";
|
import Stack from "@/src/components/twui/layout/Stack";
|
||||||
import ClientFormModal from "../(partials)/client-form-modal";
|
import ClientFormModal from "../(partials)/client-form-modal";
|
||||||
|
import useClientRulesMap from "@/src/hooks/use-client-rules-map";
|
||||||
import { useAdminCrudGet } from "@/src/hooks/use-admin-crud-get";
|
import { useAdminCrudGet } from "@/src/hooks/use-admin-crud-get";
|
||||||
import type {
|
import type { BUN_SQLITE_WGUI_CLIENTS } from "@/db/types/db";
|
||||||
BUN_SQLITE_WGUI_CLIENTS,
|
|
||||||
BUN_SQLITE_WGUI_CLIENT_RULES,
|
|
||||||
} from "@/db/types/db";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
addOpen: boolean;
|
addOpen: boolean;
|
||||||
setAddOpen: Dispatch<SetStateAction<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) {
|
export default function ClientsTableSection({ addOpen, setAddOpen }: Props) {
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
|
|
||||||
@@ -36,31 +28,7 @@ export default function ClientsTableSection({ addOpen, setAddOpen }: Props) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { res: client_rules } = useAdminCrudGet<BUN_SQLITE_WGUI_CLIENT_RULES>({
|
const rules_by_client_id = useClientRulesMap();
|
||||||
table: "client_rules",
|
|
||||||
sql_query: {
|
|
||||||
limit: 500,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const rules_by_client_id = useMemo(() => {
|
|
||||||
const map = new Map<number, BUN_SQLITE_WGUI_CLIENT_RULES[]>();
|
|
||||||
|
|
||||||
for (let i = 0; i < (client_rules || []).length; i++) {
|
|
||||||
const rule = client_rules?.[i];
|
|
||||||
const client_id = Number(rule?.client_id);
|
|
||||||
|
|
||||||
if (!rule || !client_id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = map.get(client_id) || [];
|
|
||||||
existing.push(rule);
|
|
||||||
map.set(client_id, existing);
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}, [client_rules]);
|
|
||||||
|
|
||||||
const filtered = useMemo(() => {
|
const filtered = useMemo(() => {
|
||||||
const clients = res || [];
|
const clients = res || [];
|
||||||
@@ -102,37 +70,11 @@ export default function ClientsTableSection({ addOpen, setAddOpen }: Props) {
|
|||||||
<Loading />
|
<Loading />
|
||||||
</Stack>
|
</Stack>
|
||||||
) : filtered.length ? (
|
) : filtered.length ? (
|
||||||
<div className="overflow-x-auto">
|
<ClientsTable
|
||||||
<table className="w-full min-w-[940px]">
|
clients={filtered}
|
||||||
<thead>
|
rules_by_client_id={rules_by_client_id}
|
||||||
<tr className="border-y border-slate-200 dark:border-white/10 bg-foreground-light/[0.02] dark:bg-foreground-dark/[0.03]">
|
minWidthClass="min-w-[940px]"
|
||||||
<th className={thClass}>Client</th>
|
|
||||||
<th className={thClass}>Host</th>
|
|
||||||
<th className={thClass}>Tunnel IP</th>
|
|
||||||
<th className={thClass}>Allowed IPs</th>
|
|
||||||
<th className={thClass}>Access</th>
|
|
||||||
<th className={thClass}>Public key</th>
|
|
||||||
<th className={thRightClass}>Created</th>
|
|
||||||
<th className={thRightClass}>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{filtered.map((client) => (
|
|
||||||
<ClientRow
|
|
||||||
key={client.id}
|
|
||||||
client={client}
|
|
||||||
rules={
|
|
||||||
client.id
|
|
||||||
? rules_by_client_id.get(
|
|
||||||
Number(client.id),
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<Stack center className="w-full justify-center py-14 px-6 text-center gap-1">
|
<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">
|
<Span className="text-[13.5px] font-medium text-foreground-light/50 dark:text-foreground-dark/50">
|
||||||
|
|||||||
@@ -2,27 +2,21 @@ import Loading from "@/src/components/twui/elements/Loading";
|
|||||||
import Paper from "@/src/components/twui/elements/Paper";
|
import Paper from "@/src/components/twui/elements/Paper";
|
||||||
import Center from "@/src/components/twui/layout/Center";
|
import Center from "@/src/components/twui/layout/Center";
|
||||||
import Span from "@/src/components/twui/layout/Span";
|
import Span from "@/src/components/twui/layout/Span";
|
||||||
import Stack from "@/src/components/twui/layout/Stack";
|
import ClientsTable from "@/src/components/general/clients-table";
|
||||||
import ClientRow from "@/src/components/general/client-row";
|
import useClientRulesMap from "@/src/hooks/use-client-rules-map";
|
||||||
import { useAdminCrudGet } from "@/src/hooks/use-admin-crud-get";
|
import { useAdminCrudGet } from "@/src/hooks/use-admin-crud-get";
|
||||||
import { AppContext } from "@/src/pages/__root";
|
import { AppContext } from "@/src/pages/__root";
|
||||||
import { useContext, useMemo } from "react";
|
import { useContext } from "react";
|
||||||
import type { BUN_SQLITE_WGUI_CLIENT_RULES } from "@/db/types/db";
|
import type { BUN_SQLITE_WGUI_CLIENTS } from "@/db/types/db";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
host_id: string | number;
|
host_id: string | number;
|
||||||
};
|
};
|
||||||
|
|
||||||
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 ClientsList({ host_id }: Props) {
|
export default function ClientsList({ host_id }: Props) {
|
||||||
const { pageProps } = useContext(AppContext);
|
const { pageProps } = useContext(AppContext);
|
||||||
|
|
||||||
const { res: clients } = useAdminCrudGet({
|
const { res: clients } = useAdminCrudGet<BUN_SQLITE_WGUI_CLIENTS>({
|
||||||
table: "clients",
|
table: "clients",
|
||||||
sql_query: {
|
sql_query: {
|
||||||
query: {
|
query: {
|
||||||
@@ -33,32 +27,7 @@ export default function ClientsList({ host_id }: Props) {
|
|||||||
initial_res: pageProps?.clients || undefined,
|
initial_res: pageProps?.clients || undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { res: client_rules } = useAdminCrudGet<BUN_SQLITE_WGUI_CLIENT_RULES>({
|
const rules_by_client_id = useClientRulesMap(pageProps?.client_rules);
|
||||||
table: "client_rules",
|
|
||||||
sql_query: {
|
|
||||||
limit: 500,
|
|
||||||
},
|
|
||||||
initial_res: pageProps?.client_rules || undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const rules_by_client_id = useMemo(() => {
|
|
||||||
const map = new Map<number, BUN_SQLITE_WGUI_CLIENT_RULES[]>();
|
|
||||||
|
|
||||||
for (let i = 0; i < (client_rules || []).length; i++) {
|
|
||||||
const rule = client_rules?.[i];
|
|
||||||
const client_id = Number(rule?.client_id);
|
|
||||||
|
|
||||||
if (!rule || !client_id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = map.get(client_id) || [];
|
|
||||||
existing.push(rule);
|
|
||||||
map.set(client_id, existing);
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}, [client_rules]);
|
|
||||||
|
|
||||||
if (!clients) {
|
if (!clients) {
|
||||||
return (
|
return (
|
||||||
@@ -81,33 +50,11 @@ export default function ClientsList({ host_id }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="overflow-x-auto">
|
<Paper>
|
||||||
<table className="w-full min-w-[860px]">
|
<ClientsTable
|
||||||
<thead>
|
clients={clients}
|
||||||
<tr className="border-y border-slate-200 dark:border-white/10 bg-foreground-light/[0.02] dark:bg-foreground-dark/[0.03]">
|
rules_by_client_id={rules_by_client_id}
|
||||||
<th className={thClass}>Client</th>
|
|
||||||
<th className={thClass}>Tunnel IP</th>
|
|
||||||
<th className={thClass}>Allowed IPs</th>
|
|
||||||
<th className={thClass}>Access</th>
|
|
||||||
<th className={thClass}>Public key</th>
|
|
||||||
<th className={thRightClass}>Created</th>
|
|
||||||
<th className={thRightClass}>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{clients.map((client) => (
|
|
||||||
<ClientRow
|
|
||||||
key={client.id}
|
|
||||||
client={client}
|
|
||||||
rules={
|
|
||||||
client.id
|
|
||||||
? rules_by_client_id.get(Number(client.id))
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user