Files
doktainer-upstream/src/app/servers/components/server-config/ServerConfigPrimitives.tsx
T

223 lines
5.5 KiB
TypeScript

"use client";
import type { ReactNode } from "react";
import { LockKeyhole, Pencil, ShieldCheck } from "lucide-react";
import type { ServerSystemUser } from "@/lib/api";
import type { UserBadgeTone } from "@/app/servers/components/server-config-utils";
export function ConfigInfoRow({
label,
value,
}: {
label: string;
value: string;
}) {
return (
<div
style={{
display: "grid",
gridTemplateColumns: "140px 1fr",
gap: 10,
alignItems: "start",
}}
>
<span style={{ fontSize: 12, color: "var(--text-muted)" }}>{label}</span>
<span
style={{
fontSize: 13,
color: "var(--text-primary)",
wordBreak: "break-word",
}}
>
{value}
</span>
</div>
);
}
export function UserBadge({
label,
tone,
}: {
label: string;
tone: UserBadgeTone;
}) {
const palette = {
danger: {
color: "#ef4444",
background: "rgba(239,68,68,0.08)",
border: "rgba(239,68,68,0.2)",
},
success: {
color: "#10b981",
background: "rgba(16,185,129,0.08)",
border: "rgba(16,185,129,0.2)",
},
neutral: {
color: "var(--text-secondary)",
background: "var(--bg-input)",
border: "var(--border)",
},
warning: {
color: "#f59e0b",
background: "rgba(245,158,11,0.08)",
border: "rgba(245,158,11,0.2)",
},
info: {
color: "#3b82f6",
background: "rgba(59,130,246,0.08)",
border: "rgba(59,130,246,0.2)",
},
} as const;
const style = palette[tone];
return (
<span
className="ui-badge"
title={label}
style={{
color: style.color,
background: style.background,
border: `1px solid ${style.border}`,
}}
>
{label}
</span>
);
}
export function ServiceStatusBadge({ state }: { state: string }) {
const normalized = state.toLowerCase();
const tone =
normalized === "active"
? "success"
: normalized === "enabled"
? "info"
: normalized === "inactive"
? "danger"
: "warning";
return <UserBadge label={state} tone={tone} />;
}
export function ServerUserCard({
user,
canEdit = false,
isEditing = false,
onEdit,
children,
}: {
user: ServerSystemUser;
canEdit?: boolean;
isEditing?: boolean;
onEdit?: () => void;
children?: ReactNode;
}) {
const accountLabel = user.isRoot ? "root" : "non-root";
const accountTone = user.isRoot ? "danger" : "info";
return (
<div
className="card"
style={{
padding: 16,
display: "flex",
flexDirection: "column",
gap: 12,
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "flex-start",
gap: 12,
flexWrap: "wrap",
}}
>
<div>
<div
style={{
display: "flex",
alignItems: "center",
gap: 10,
flexWrap: "wrap",
}}
>
<strong style={{ color: "var(--text-primary)", fontSize: 14 }}>
{user.username}
</strong>
<UserBadge label={accountLabel} tone={accountTone} />
{user.isSshUser ? (
<UserBadge label="SSH LOGIN" tone="neutral" />
) : null}
</div>
<p style={{ marginTop: 6, fontSize: 12, color: "var(--text-muted)" }}>
UID {user.uid ?? "—"} • GID {user.gid ?? "—"}
</p>
</div>
{user.isRoot ? (
<span
className="ui-badge"
title="Root account is protected and cannot be edited here"
style={{
display: "inline-flex",
alignItems: "center",
gap: 6,
color: "#ef4444",
background: "rgba(239,68,68,0.08)",
border: "1px solid rgba(239,68,68,0.2)",
}}
>
<LockKeyhole size={13} /> Protected
</span>
) : (
<button
type="button"
className="btn btn-ghost btn-sm"
onClick={onEdit}
disabled={!canEdit}
aria-expanded={isEditing}
title={
canEdit
? user.isSshUser
? "Edit access with SSH login protections"
: "Edit user access"
: "Operator or Super Admin role required"
}
style={{
color: user.isSshUser ? "#f59e0b" : "#3b82f6",
border: user.isSshUser
? "1px solid rgba(245,158,11,0.2)"
: "1px solid rgba(59,130,246,0.2)",
background: user.isSshUser
? "rgba(245,158,11,0.08)"
: "rgba(59,130,246,0.08)",
}}
>
{user.isSshUser ? (
<ShieldCheck size={13} />
) : (
<Pencil size={13} />
)}
{user.isSshUser ? "Edit access" : "Edit"}
</button>
)}
</div>
<div style={{ display: "grid", gap: 8 }}>
<ConfigInfoRow label="Home" value={user.home ?? "—"} />
<ConfigInfoRow label="Shell" value={user.shell ?? "—"} />
<ConfigInfoRow
label="Primary group"
value={user.primaryGroup ?? "—"}
/>
<ConfigInfoRow
label="All groups"
value={user.groups.length > 0 ? user.groups.join(", ") : "—"}
/>
</div>
{children}
</div>
);
}