mirror of
https://github.com/tale/headplane.git
synced 2026-08-31 09:18:29 +00:00
89 lines
2.9 KiB
TypeScript
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>
|
|
);
|
|
}
|