Docker images / prepare-release (push) Successful in 7s
Docker images / backend-image (push) Successful in 2m1s
Docker images / frontend-image (push) Successful in 2m7s
Docker images / updater-image (push) Successful in 41s
Docker images / notify-webhook (push) Has been skipped
Docker images / publish-release (push) Successful in 8s
168 lines
4.9 KiB
TypeScript
168 lines
4.9 KiB
TypeScript
"use client"
|
|
|
|
import { useMemo } from "react"
|
|
import type { Server } from "@/lib/data"
|
|
import { CompactDataGrid, type CompactDataGridColumn } from "@/components/data-grids/compact-data-grid"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
type PermLevel = "none" | "read" | "write"
|
|
type Role = "admin" | "operator" | "viewer"
|
|
|
|
interface SectionPerm {
|
|
section: string
|
|
level: PermLevel
|
|
}
|
|
|
|
interface ServerPerm {
|
|
serverId: string
|
|
level: PermLevel
|
|
}
|
|
|
|
export interface AccessSummaryUser {
|
|
id: string
|
|
name: string
|
|
avatar: string
|
|
active: boolean
|
|
role: Role
|
|
sections: SectionPerm[]
|
|
servers: ServerPerm[]
|
|
}
|
|
|
|
function AvatarCircle({ avatar, active }: { avatar: string; active: boolean }) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"size-7 rounded-full flex items-center justify-center text-[10px] font-bold shrink-0",
|
|
active ? "bg-primary/10 text-primary" : "bg-muted text-muted-foreground",
|
|
)}
|
|
>
|
|
{avatar}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
interface SettingsAccessSummaryDataGridProps {
|
|
users: AccessSummaryUser[]
|
|
servers: Server[]
|
|
allSectionsCount: number
|
|
}
|
|
|
|
function SettingsAccessSummaryDataGrid({
|
|
users,
|
|
servers,
|
|
allSectionsCount,
|
|
}: SettingsAccessSummaryDataGridProps) {
|
|
const columns = useMemo<CompactDataGridColumn<AccessSummaryUser>[]>(
|
|
() => [
|
|
{
|
|
id: "name",
|
|
header: "Пользователь",
|
|
accessorKey: "name",
|
|
cell: (u) => (
|
|
<div className="flex items-center gap-2">
|
|
<AvatarCircle avatar={u.avatar} active={u.active} />
|
|
<span className="text-sm font-medium">{u.name}</span>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
id: "sections",
|
|
header: "Разделы",
|
|
enableSorting: false,
|
|
cell: (u) => {
|
|
const writeSections =
|
|
u.role === "admin"
|
|
? []
|
|
: u.sections.filter((s) => s.level === "write").map((s) => s.section)
|
|
const readSections =
|
|
u.role === "admin"
|
|
? []
|
|
: u.sections.filter((s) => s.level === "read").map((s) => s.section)
|
|
return (
|
|
<span className="text-xs text-muted-foreground">
|
|
{u.role === "admin" ? (
|
|
<span className="text-violet-600 dark:text-violet-400 font-medium">
|
|
Все ({allSectionsCount})
|
|
</span>
|
|
) : (
|
|
<span>{readSections.length + writeSections.length} из {allSectionsCount}</span>
|
|
)}
|
|
</span>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
id: "servers",
|
|
header: "Серверы",
|
|
enableSorting: false,
|
|
cell: (u) => {
|
|
const accessServers =
|
|
u.role === "admin"
|
|
? servers
|
|
: servers.filter((s) => u.servers.find((p) => p.serverId === s.id && p.level !== "none"))
|
|
return (
|
|
<span className="text-xs text-muted-foreground">
|
|
{u.role === "admin" ? (
|
|
<span className="text-violet-600 dark:text-violet-400 font-medium">
|
|
Все ({servers.length})
|
|
</span>
|
|
) : (
|
|
<span>
|
|
{accessServers.length} из {servers.length}
|
|
</span>
|
|
)}
|
|
</span>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
id: "write",
|
|
header: "Права записи",
|
|
enableSorting: false,
|
|
cell: (u) => {
|
|
const writeSections =
|
|
u.role === "admin"
|
|
? []
|
|
: u.sections.filter((s) => s.level === "write").map((s) => s.section)
|
|
return (
|
|
<div className="flex flex-wrap gap-1">
|
|
{u.role === "admin" ? (
|
|
<span className="text-[10px] px-1.5 py-0.5 rounded bg-violet-500/10 text-violet-600 dark:text-violet-400 border border-violet-500/20">
|
|
Полный доступ
|
|
</span>
|
|
) : writeSections.length === 0 ? (
|
|
<span className="text-[10px] text-muted-foreground">Только просмотр</span>
|
|
) : (
|
|
<>
|
|
{writeSections.slice(0, 3).map((s) => (
|
|
<span
|
|
key={s}
|
|
className="text-[10px] px-1.5 py-0.5 rounded bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border border-emerald-500/20"
|
|
>
|
|
{s}
|
|
</span>
|
|
))}
|
|
{writeSections.length > 3 && (
|
|
<span className="text-[10px] text-muted-foreground">+{writeSections.length - 3}</span>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
)
|
|
},
|
|
},
|
|
],
|
|
[allSectionsCount, servers],
|
|
)
|
|
|
|
return (
|
|
<CompactDataGrid
|
|
data={users}
|
|
columns={columns}
|
|
emptyTitle="Нет пользователей"
|
|
/>
|
|
)
|
|
}
|
|
|
|
export { SettingsAccessSummaryDataGrid, type SettingsAccessSummaryDataGridProps }
|