Files
headplane/app/routes/users/components/headscale-user-row.tsx
T

89 lines
2.9 KiB
TypeScript

import { CircleUser } from "lucide-react";
import Chip from "~/components/chip";
import StatusCircle from "~/components/status-circle";
import cn from "~/utils/cn";
import type { UnlinkedHeadscaleUser } from "../overview";
import HeadscaleUserMenu from "./headscale-user-menu";
interface HeadscaleUserRowProps {
user: UnlinkedHeadscaleUser;
writable?: boolean;
canEditGroups?: boolean;
policyGroups?: string[];
policyHasComments?: boolean;
}
export default function HeadscaleUserRow({
user,
writable,
canEditGroups,
policyGroups,
policyHasComments,
}: HeadscaleUserRowProps) {
const isOnline = user.machines.some((machine) => machine.online);
const lastSeen = user.machines.reduce(
(acc, machine) => Math.max(acc, new Date(machine.lastSeen).getTime()),
0,
);
const displayName = user.displayName || user.name;
const displayUsername =
user.displayName && user.displayName !== user.name ? user.name : undefined;
return (
<tr className="group hover:bg-mist-50 dark:hover:bg-mist-950" key={user.id}>
<td className="py-2 pl-0.5">
<div className="flex items-center">
{user.profilePicUrl ? (
<img alt={displayName} className="h-10 w-10 rounded-full" src={user.profilePicUrl} />
) : (
<CircleUser className="h-10 w-10" />
)}
<div className="ml-4">
<p className="leading-snug font-semibold">{displayName}</p>
{displayUsername && <p className="text-sm opacity-50">{displayUsername}</p>}
{user.email && <p className="text-sm opacity-50">{user.email}</p>}
{user.groups.length > 0 && (
<div className="mt-1 flex flex-wrap gap-1">
{user.groups.map((group) => (
<Chip className="font-mono" key={group} text={group} />
))}
</div>
)}
</div>
</div>
</td>
<td className="py-2 pl-0.5">
<p className="text-sm text-mist-600 dark:text-mist-300" suppressHydrationWarning>
{new Date(user.createdAt).toLocaleDateString()}
</p>
</td>
<td className="py-2 pl-0.5">
{user.machines.length > 0 ? (
<span
className={cn("flex items-center gap-x-1 text-sm", "text-mist-600 dark:text-mist-300")}
>
<StatusCircle className="h-4 w-4" isOnline={isOnline} />
<p suppressHydrationWarning>
{isOnline ? "Connected" : new Date(lastSeen).toLocaleString()}
</p>
</span>
) : (
<p className="text-sm text-mist-600 dark:text-mist-300">No machines</p>
)}
</td>
<td className="py-2 pr-0.5">
{writable ? (
<HeadscaleUserMenu
canEditGroups={canEditGroups}
policyGroups={policyGroups}
policyHasComments={policyHasComments}
user={user}
/>
) : null}
</td>
</tr>
);
}