feat(users): implement user management features and database schema
Docker images / prepare-release (push) Successful in 10s
Docker images / backend-image (push) Successful in 1m57s
Docker images / frontend-image (push) Successful in 4m8s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 44s
Docker images / publish-release (push) Successful in 11s
Docker images / prepare-release (push) Successful in 10s
Docker images / backend-image (push) Successful in 1m57s
Docker images / frontend-image (push) Successful in 4m8s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 44s
Docker images / publish-release (push) Successful in 11s
Added user management functionality, including the creation of app_users and user_interface_bindings tables in the database. Implemented API routes for user data retrieval and permissions handling. Enhanced the traffic monitoring system to include user traffic statistics and interface bindings. Updated relevant components and services to support the new user features, improving overall application functionality and user experience.
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
"use client"
|
||||
|
||||
import { Badge } from "@/components/reui/badge"
|
||||
import { IconTile } from "@/components/reui/icon-tile"
|
||||
import { Flag } from "@/components/flag"
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
ALL_SECTIONS,
|
||||
groupBindingsByServer,
|
||||
IFACE_TYPE_LABEL,
|
||||
summarizeUserAccess,
|
||||
type AppUser,
|
||||
type InterfaceType,
|
||||
} from "@/lib/users"
|
||||
import { CableIcon, NetworkIcon, ShieldIcon } from "lucide-react"
|
||||
|
||||
const TYPE_VARIANT: Record<InterfaceType, "outline" | "info-light" | "success-light" | "secondary"> = {
|
||||
ether: "outline",
|
||||
gre: "info-light",
|
||||
wg: "success-light",
|
||||
other: "secondary",
|
||||
}
|
||||
|
||||
const TYPE_ICON: Record<InterfaceType, { icon: typeof CableIcon; className: string }> = {
|
||||
ether: { icon: CableIcon, className: "text-muted-foreground" },
|
||||
gre: { icon: NetworkIcon, className: "text-info" },
|
||||
wg: { icon: ShieldIcon, className: "text-success" },
|
||||
other: { icon: CableIcon, className: "text-muted-foreground" },
|
||||
}
|
||||
|
||||
interface UsersExpandedDetailProps {
|
||||
user: AppUser
|
||||
serversCount: number
|
||||
allSectionsCount?: number
|
||||
}
|
||||
|
||||
function UsersExpandedDetail({
|
||||
user,
|
||||
serversCount,
|
||||
allSectionsCount = ALL_SECTIONS.length,
|
||||
}: UsersExpandedDetailProps) {
|
||||
const access = summarizeUserAccess(user, serversCount, allSectionsCount)
|
||||
const groups = groupBindingsByServer(user.bindings)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-5 px-5 py-5 bg-muted/20">
|
||||
<div className="flex flex-wrap items-center gap-x-6 gap-y-2 text-xs">
|
||||
<span className="text-muted-foreground">
|
||||
Разделы:{" "}
|
||||
<span className="font-mono text-foreground">
|
||||
{access.sectionsGranted}/{access.sectionsTotal}
|
||||
</span>
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
Серверы:{" "}
|
||||
<span className="font-mono text-foreground">
|
||||
{access.serversGranted}/{access.serversTotal}
|
||||
</span>
|
||||
</span>
|
||||
{access.writeKind === "full" ? (
|
||||
<Badge variant="success-light" size="sm">Полный доступ</Badge>
|
||||
) : access.writeKind === "none" ? (
|
||||
<Badge variant="outline" size="sm">Только просмотр</Badge>
|
||||
) : (
|
||||
<span className="inline-flex flex-wrap items-center gap-1">
|
||||
{access.writeSections.map((section) => (
|
||||
<Badge key={section} variant="info-light" size="sm">{section}</Badge>
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{groups.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground">Нет привязанных интерфейсов</p>
|
||||
) : (
|
||||
groups.map((g) => {
|
||||
const items = g.types.flatMap((tg) => tg.items)
|
||||
return (
|
||||
<div key={g.serverId}>
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<p className="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider">
|
||||
{g.serverName}
|
||||
</p>
|
||||
<Flag code={g.serverCountry} size={12} />
|
||||
<span className="text-[11px] font-mono text-muted-foreground">{g.serverSite}</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 xl:grid-cols-5 gap-2">
|
||||
{items.map((b) => {
|
||||
const meta = TYPE_ICON[b.interfaceType]
|
||||
const Icon = meta.icon
|
||||
return (
|
||||
<div
|
||||
key={b.id}
|
||||
className="flex items-start gap-2 rounded-md border px-3 py-2.5"
|
||||
>
|
||||
<IconTile variant="elevated" className={cn("size-10.5 shrink-0", meta.className)}>
|
||||
<Icon />
|
||||
</IconTile>
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs font-mono font-medium leading-tight truncate">
|
||||
{b.interfaceName}
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center gap-1 mt-0.5">
|
||||
<Badge variant={TYPE_VARIANT[b.interfaceType]} size="sm">
|
||||
{IFACE_TYPE_LABEL[b.interfaceType]}
|
||||
</Badge>
|
||||
{b.comment ? (
|
||||
<span className="text-[10px] text-muted-foreground leading-tight truncate">
|
||||
{b.comment}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { UsersExpandedDetail, TYPE_VARIANT, type UsersExpandedDetailProps }
|
||||
Reference in New Issue
Block a user