feat(admin, i18n): implement user deletion functionality and enhance permission management translations
Added a new API function to delete admin users, improving user management capabilities. Updated the admin permission package selector to include new permission levels for nodes, roles, and users. Enhanced multi-language support by adding translations for these new permission levels in English, Nepali, and Chinese. Additionally, improved the agents console to handle deletion confirmations and display relevant block reasons, ensuring a smoother user experience.
This commit is contained in:
@@ -94,6 +94,14 @@ export async function putAgentAdminUserRoles(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteAgentAdminUser(
|
||||||
|
adminUserId: number,
|
||||||
|
): Promise<{ deleted: boolean; id: number }> {
|
||||||
|
return adminRequest.delete<{ deleted: boolean; id: number }>(
|
||||||
|
`${A}/agent-admin-users/${adminUserId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getAgentDelegationGrants(
|
export async function getAgentDelegationGrants(
|
||||||
agentNodeId: number,
|
agentNodeId: number,
|
||||||
): Promise<AgentDelegationGrantsData> {
|
): Promise<AgentDelegationGrantsData> {
|
||||||
|
|||||||
@@ -37,12 +37,18 @@ type RenderGroup = {
|
|||||||
|
|
||||||
const PACKAGE_LEVEL_ORDER: Record<string, number> = {
|
const PACKAGE_LEVEL_ORDER: Record<string, number> = {
|
||||||
view: 10,
|
view: 10,
|
||||||
|
node_view: 10,
|
||||||
|
role_view: 11,
|
||||||
|
user_view: 12,
|
||||||
review: 20,
|
review: 20,
|
||||||
export: 20,
|
export: 20,
|
||||||
manage: 30,
|
manage: 30,
|
||||||
|
node_manage: 30,
|
||||||
|
role_manage: 31,
|
||||||
|
user_manage: 32,
|
||||||
config: 30,
|
config: 30,
|
||||||
control: 30,
|
control: 30,
|
||||||
reopen: 30,
|
reopen: 32,
|
||||||
special: 40,
|
special: 40,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,12 @@
|
|||||||
},
|
},
|
||||||
"permissionLevels": {
|
"permissionLevels": {
|
||||||
"view": "View",
|
"view": "View",
|
||||||
|
"node_view": "Nodes · View",
|
||||||
|
"node_manage": "Nodes · Manage",
|
||||||
|
"role_view": "Roles · View",
|
||||||
|
"role_manage": "Roles · Manage",
|
||||||
|
"user_view": "Accounts · View",
|
||||||
|
"user_manage": "Accounts · Manage",
|
||||||
"manage": "Manage",
|
"manage": "Manage",
|
||||||
"review": "Review",
|
"review": "Review",
|
||||||
"export": "Export",
|
"export": "Export",
|
||||||
|
|||||||
@@ -9,6 +9,13 @@
|
|||||||
"editNode": "Edit node",
|
"editNode": "Edit node",
|
||||||
"deleteNode": "Delete node",
|
"deleteNode": "Delete node",
|
||||||
"deleteNodeConfirm": "This action cannot be undone. Make sure the node has no children, users, or roles.",
|
"deleteNodeConfirm": "This action cannot be undone. Make sure the node has no children, users, or roles.",
|
||||||
|
"deleteNodeBlockedHint": "Remove child agents, roles, and accounts before deleting this node",
|
||||||
|
"deleteNodeBlockedPrefix": "Cannot delete yet: ",
|
||||||
|
"deleteBlocked": {
|
||||||
|
"children": "{{count}} child agent(s) remain",
|
||||||
|
"roles": "{{count}} role(s) must be removed in the Roles tab first",
|
||||||
|
"users": "{{count}} bound account(s) remain"
|
||||||
|
},
|
||||||
"code": "Code",
|
"code": "Code",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"depth": "Depth",
|
"depth": "Depth",
|
||||||
@@ -48,6 +55,7 @@
|
|||||||
"deleteSuccess": "Deleted role {{name}}",
|
"deleteSuccess": "Deleted role {{name}}",
|
||||||
"permissionSaveSuccess": "Permissions updated",
|
"permissionSaveSuccess": "Permissions updated",
|
||||||
"readOnlyTemplate": "Read-only template",
|
"readOnlyTemplate": "Read-only template",
|
||||||
|
"inUse": "{{count}} in use",
|
||||||
"permissionSubsetHint": "Only permissions you hold can be assigned"
|
"permissionSubsetHint": "Only permissions you hold can be assigned"
|
||||||
},
|
},
|
||||||
"users": {
|
"users": {
|
||||||
@@ -57,6 +65,8 @@
|
|||||||
"password": "Password",
|
"password": "Password",
|
||||||
"roles": "Roles",
|
"roles": "Roles",
|
||||||
"createSuccess": "Created account {{name}}",
|
"createSuccess": "Created account {{name}}",
|
||||||
"roleSaveSuccess": "Roles updated for {{name}}"
|
"roleSaveSuccess": "Roles updated for {{name}}",
|
||||||
|
"deleteConfirm": "This admin will no longer be able to sign in. This cannot be undone.",
|
||||||
|
"deleteSuccess": "Deleted account {{name}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,12 @@
|
|||||||
},
|
},
|
||||||
"permissionLevels": {
|
"permissionLevels": {
|
||||||
"view": "हेर्नुहोस्",
|
"view": "हेर्नुहोस्",
|
||||||
|
"node_view": "नोड · हेर्नुहोस्",
|
||||||
|
"node_manage": "नोड · व्यवस्थापन",
|
||||||
|
"role_view": "भूमिका · हेर्नुहोस्",
|
||||||
|
"role_manage": "भूमिका · व्यवस्थापन",
|
||||||
|
"user_view": "खाता · हेर्नुहोस्",
|
||||||
|
"user_manage": "खाता · व्यवस्थापन",
|
||||||
"manage": "व्यवस्थापन",
|
"manage": "व्यवस्थापन",
|
||||||
"review": "समीक्षा",
|
"review": "समीक्षा",
|
||||||
"export": "निर्यात",
|
"export": "निर्यात",
|
||||||
|
|||||||
@@ -9,6 +9,13 @@
|
|||||||
"editNode": "Edit node",
|
"editNode": "Edit node",
|
||||||
"deleteNode": "Delete node",
|
"deleteNode": "Delete node",
|
||||||
"deleteNodeConfirm": "This action cannot be undone. Make sure the node has no children, users, or roles.",
|
"deleteNodeConfirm": "This action cannot be undone. Make sure the node has no children, users, or roles.",
|
||||||
|
"deleteNodeBlockedHint": "पहिले चाइल्ड एजेन्ट, भूमिका र खाता हटाउनुहोस्",
|
||||||
|
"deleteNodeBlockedPrefix": "अहिले मेटाउन मिल्दैन: ",
|
||||||
|
"deleteBlocked": {
|
||||||
|
"children": "{{count}} वटा चाइल्ड एजेन्ट बाँकी",
|
||||||
|
"roles": "{{count}} वटा भूमिका पहिले हटाउनुहोस्",
|
||||||
|
"users": "{{count}} वटा खाता बाँकी"
|
||||||
|
},
|
||||||
"code": "Code",
|
"code": "Code",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"depth": "Depth",
|
"depth": "Depth",
|
||||||
@@ -48,6 +55,7 @@
|
|||||||
"deleteSuccess": "Deleted role {{name}}",
|
"deleteSuccess": "Deleted role {{name}}",
|
||||||
"permissionSaveSuccess": "Permissions updated",
|
"permissionSaveSuccess": "Permissions updated",
|
||||||
"readOnlyTemplate": "Read-only template",
|
"readOnlyTemplate": "Read-only template",
|
||||||
|
"inUse": "{{count}} प्रयोगमा",
|
||||||
"permissionSubsetHint": "Only permissions you hold can be assigned"
|
"permissionSubsetHint": "Only permissions you hold can be assigned"
|
||||||
},
|
},
|
||||||
"users": {
|
"users": {
|
||||||
@@ -57,6 +65,8 @@
|
|||||||
"password": "Password",
|
"password": "Password",
|
||||||
"roles": "Roles",
|
"roles": "Roles",
|
||||||
"createSuccess": "Created account {{name}}",
|
"createSuccess": "Created account {{name}}",
|
||||||
"roleSaveSuccess": "Roles updated for {{name}}"
|
"roleSaveSuccess": "Roles updated for {{name}}",
|
||||||
|
"deleteConfirm": "यो खाता अब लगइन गर्न सक्दैन।",
|
||||||
|
"deleteSuccess": "खाता {{name}} मेटियो"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,12 @@
|
|||||||
},
|
},
|
||||||
"permissionLevels": {
|
"permissionLevels": {
|
||||||
"view": "查看",
|
"view": "查看",
|
||||||
|
"node_view": "节点·查看",
|
||||||
|
"node_manage": "节点·管理",
|
||||||
|
"role_view": "角色·查看",
|
||||||
|
"role_manage": "角色·管理",
|
||||||
|
"user_view": "账号·查看",
|
||||||
|
"user_manage": "账号·管理",
|
||||||
"manage": "管理",
|
"manage": "管理",
|
||||||
"review": "审核",
|
"review": "审核",
|
||||||
"export": "导出",
|
"export": "导出",
|
||||||
|
|||||||
@@ -9,6 +9,13 @@
|
|||||||
"editNode": "编辑节点",
|
"editNode": "编辑节点",
|
||||||
"deleteNode": "删除节点",
|
"deleteNode": "删除节点",
|
||||||
"deleteNodeConfirm": "删除后不可恢复,请确认该节点无下级、无账号、无角色绑定。",
|
"deleteNodeConfirm": "删除后不可恢复,请确认该节点无下级、无账号、无角色绑定。",
|
||||||
|
"deleteNodeBlockedHint": "请先删除下级代理、角色与账号后再删除本节点",
|
||||||
|
"deleteNodeBlockedPrefix": "暂不可删除:",
|
||||||
|
"deleteBlocked": {
|
||||||
|
"children": "仍有 {{count}} 个下级代理",
|
||||||
|
"roles": "仍有 {{count}} 个角色需先在「角色」里删除",
|
||||||
|
"users": "仍有 {{count}} 个账号需先处理"
|
||||||
|
},
|
||||||
"code": "编码",
|
"code": "编码",
|
||||||
"name": "名称",
|
"name": "名称",
|
||||||
"depth": "层级",
|
"depth": "层级",
|
||||||
@@ -48,6 +55,7 @@
|
|||||||
"deleteSuccess": "已删除角色 {{name}}",
|
"deleteSuccess": "已删除角色 {{name}}",
|
||||||
"permissionSaveSuccess": "权限已更新",
|
"permissionSaveSuccess": "权限已更新",
|
||||||
"readOnlyTemplate": "只读模板",
|
"readOnlyTemplate": "只读模板",
|
||||||
|
"inUse": "{{count}} 人使用中",
|
||||||
"permissionSubsetHint": "只能分配您当前拥有的权限",
|
"permissionSubsetHint": "只能分配您当前拥有的权限",
|
||||||
"selectedCount": "已选 {{selected}} / {{total}} 项",
|
"selectedCount": "已选 {{selected}} / {{total}} 项",
|
||||||
"groupSelectedCount": "已选 {{selected}} / {{total}}",
|
"groupSelectedCount": "已选 {{selected}} / {{total}}",
|
||||||
@@ -61,6 +69,8 @@
|
|||||||
"password": "密码",
|
"password": "密码",
|
||||||
"roles": "角色",
|
"roles": "角色",
|
||||||
"createSuccess": "已创建账号 {{name}}",
|
"createSuccess": "已创建账号 {{name}}",
|
||||||
"roleSaveSuccess": "已更新 {{name}} 的角色"
|
"roleSaveSuccess": "已更新 {{name}} 的角色",
|
||||||
|
"deleteConfirm": "删除后该管理员将无法登录,且不可恢复。",
|
||||||
|
"deleteSuccess": "已删除账号 {{name}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,16 +15,12 @@ export const ADMIN_PERMISSION_PACKAGES: Record<string, AdminPermissionPackage[]>
|
|||||||
{ key: "manage", label: "管理", slugs: ["prd.admin_role.manage"] },
|
{ key: "manage", label: "管理", slugs: ["prd.admin_role.manage"] },
|
||||||
],
|
],
|
||||||
agents: [
|
agents: [
|
||||||
{
|
{ key: "node_view", label: "节点·查看", slugs: ["prd.agent.view"] },
|
||||||
key: "view",
|
{ key: "node_manage", label: "节点·管理", slugs: ["prd.agent.manage"] },
|
||||||
label: "查看",
|
{ key: "role_view", label: "角色·查看", slugs: ["prd.agent.role.view"] },
|
||||||
slugs: ["prd.agent.view", "prd.agent.role.view", "prd.agent.user.view"],
|
{ key: "role_manage", label: "角色·管理", slugs: ["prd.agent.role.manage"] },
|
||||||
},
|
{ key: "user_view", label: "账号·查看", slugs: ["prd.agent.user.view"] },
|
||||||
{
|
{ key: "user_manage", label: "账号·管理", slugs: ["prd.agent.user.manage"] },
|
||||||
key: "manage",
|
|
||||||
label: "管理",
|
|
||||||
slugs: ["prd.agent.manage", "prd.agent.role.manage", "prd.agent.user.manage"],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
players: [
|
players: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,9 +55,8 @@ function permissionGroupLabel(key: string, fallback: string, t: (key: string) =>
|
|||||||
return translated === `permissionGroups.${key}` ? fallback : translated;
|
return translated === `permissionGroups.${key}` ? fallback : translated;
|
||||||
}
|
}
|
||||||
|
|
||||||
function permissionPackageLabel(key: string, fallback: string, t: (key: string) => string): string {
|
function permissionPackageLabel(key: string, fallback: string, t: (key: string, options?: { defaultValue?: string }) => string): string {
|
||||||
const translated = t(`permissionLevels.${key}`);
|
return t(`permissionLevels.${key}`, { defaultValue: fallback });
|
||||||
return translated === `permissionLevels.${key}` ? fallback : translated;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AdminRolesConsole(): React.ReactElement {
|
export function AdminRolesConsole(): React.ReactElement {
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { useConfirmAction } from "@/hooks/use-confirm-action";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
deleteAgentAdminUser,
|
||||||
|
deleteAgentNode,
|
||||||
deleteAgentRole,
|
deleteAgentRole,
|
||||||
getAgentNodeAdminUsers,
|
getAgentNodeAdminUsers,
|
||||||
getAgentNodeRoles,
|
getAgentNodeRoles,
|
||||||
@@ -81,10 +83,9 @@ function permissionGroupLabel(key: string, fallback: string, t: (key: string, op
|
|||||||
function permissionPackageLabel(
|
function permissionPackageLabel(
|
||||||
key: string,
|
key: string,
|
||||||
fallback: string,
|
fallback: string,
|
||||||
t: (key: string, options?: Record<string, unknown>) => string,
|
t: (key: string, options?: { defaultValue?: string }) => string,
|
||||||
): string {
|
): string {
|
||||||
const translated = t(`adminUsers:permissionLevels.${key}`);
|
return t(`adminUsers:permissionLevels.${key}`, { defaultValue: fallback });
|
||||||
return translated === `adminUsers:permissionLevels.${key}` ? fallback : translated;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function flattenTree(nodes: AgentNodeRow[]): AgentNodeRow[] {
|
function flattenTree(nodes: AgentNodeRow[]): AgentNodeRow[] {
|
||||||
@@ -269,6 +270,43 @@ export function AgentsConsole(): React.ReactElement {
|
|||||||
selected !== null &&
|
selected !== null &&
|
||||||
!selected.is_root &&
|
!selected.is_root &&
|
||||||
(isSuperAdmin || profile?.agent?.id === selected.parent_id);
|
(isSuperAdmin || profile?.agent?.id === selected.parent_id);
|
||||||
|
const blockingCustomRoleCount = useMemo(
|
||||||
|
() => roles.filter((role) => !role.is_read_only_template).length,
|
||||||
|
[roles],
|
||||||
|
);
|
||||||
|
const deleteBlockReasons = useMemo(() => {
|
||||||
|
if (!selected || selected.is_root) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const reasons: string[] = [];
|
||||||
|
if (selectedChildrenCount > 0) {
|
||||||
|
reasons.push(
|
||||||
|
t("deleteBlocked.children", {
|
||||||
|
count: selectedChildrenCount,
|
||||||
|
defaultValue: "仍有 {{count}} 个下级代理",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (blockingCustomRoleCount > 0) {
|
||||||
|
reasons.push(
|
||||||
|
t("deleteBlocked.roles", {
|
||||||
|
count: blockingCustomRoleCount,
|
||||||
|
defaultValue: "仍有 {{count}} 个可编辑角色需先删除",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (users.length > 0) {
|
||||||
|
reasons.push(
|
||||||
|
t("deleteBlocked.users", {
|
||||||
|
count: users.length,
|
||||||
|
defaultValue: "仍有 {{count}} 个绑定账号",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return reasons;
|
||||||
|
}, [blockingCustomRoleCount, selected, selectedChildrenCount, t, users.length]);
|
||||||
|
const canDeleteSelectedNode =
|
||||||
|
canManageNode && selected !== null && !selected.is_root && deleteBlockReasons.length === 0;
|
||||||
const defaultDetailTab = canViewRoles ? "roles" : canViewUsers ? "users" : canManageDelegation ? "delegation" : "roles";
|
const defaultDetailTab = canViewRoles ? "roles" : canViewUsers ? "users" : canManageDelegation ? "delegation" : "roles";
|
||||||
|
|
||||||
const assignablePermissionSlugs = useMemo(() => {
|
const assignablePermissionSlugs = useMemo(() => {
|
||||||
@@ -332,13 +370,15 @@ export function AgentsConsole(): React.ReactElement {
|
|||||||
}, [selectedId, tRef]);
|
}, [selectedId, tRef]);
|
||||||
|
|
||||||
const loadDetail = useCallback(async (nodeId: number) => {
|
const loadDetail = useCallback(async (nodeId: number) => {
|
||||||
if (canViewRoles) {
|
const needRoleRows = canViewRoles || canManageNode;
|
||||||
|
const needUserRows = canViewUsers || canManageNode;
|
||||||
|
if (needRoleRows) {
|
||||||
const roleData = await getAgentNodeRoles(nodeId);
|
const roleData = await getAgentNodeRoles(nodeId);
|
||||||
setRoles(roleData.items);
|
setRoles(roleData.items);
|
||||||
} else {
|
} else {
|
||||||
setRoles([]);
|
setRoles([]);
|
||||||
}
|
}
|
||||||
if (canViewUsers) {
|
if (needUserRows) {
|
||||||
const userData = await getAgentNodeAdminUsers(nodeId);
|
const userData = await getAgentNodeAdminUsers(nodeId);
|
||||||
setUsers(userData.items);
|
setUsers(userData.items);
|
||||||
} else {
|
} else {
|
||||||
@@ -474,7 +514,9 @@ export function AgentsConsole(): React.ReactElement {
|
|||||||
}
|
}
|
||||||
setPermSaving(true);
|
setPermSaving(true);
|
||||||
try {
|
try {
|
||||||
await putAgentRolePermissions(permRoleId, draftPerms);
|
const result = await putAgentRolePermissions(permRoleId, draftPerms);
|
||||||
|
setDraftPerms([...result.permission_slugs].sort());
|
||||||
|
setRoles((prev) => prev.map((role) => (role.id === result.id ? result : role)));
|
||||||
toast.success(t("roles.permissionSaveSuccess"));
|
toast.success(t("roles.permissionSaveSuccess"));
|
||||||
setPermDialogOpen(false);
|
setPermDialogOpen(false);
|
||||||
if (selectedId !== null) {
|
if (selectedId !== null) {
|
||||||
@@ -672,6 +714,46 @@ export function AgentsConsole(): React.ReactElement {
|
|||||||
{t("editNode")}
|
{t("editNode")}
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
{canManageNode && !selected.is_root ? (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="text-destructive hover:text-destructive"
|
||||||
|
disabled={!canDeleteSelectedNode}
|
||||||
|
title={
|
||||||
|
canDeleteSelectedNode
|
||||||
|
? undefined
|
||||||
|
: deleteBlockReasons.join(";") ||
|
||||||
|
t("deleteNodeBlockedHint", {
|
||||||
|
defaultValue: "请先删除下级代理、角色与账号后再删除本节点",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if (!canDeleteSelectedNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
requestConfirm({
|
||||||
|
title: t("deleteNode"),
|
||||||
|
description: t("deleteNodeConfirm"),
|
||||||
|
confirmLabel: t("deleteNode"),
|
||||||
|
confirmVariant: "destructive",
|
||||||
|
onConfirm: async () => {
|
||||||
|
const deletedId = selected.id;
|
||||||
|
const deletedName = selected.name;
|
||||||
|
const parentId = selected.parent_id;
|
||||||
|
await deleteAgentNode(deletedId);
|
||||||
|
toast.success(t("deleteSuccess", { name: deletedName }));
|
||||||
|
setSelectedId(parentId);
|
||||||
|
await loadTree(adminSiteId);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 className="mr-1 size-3.5" />
|
||||||
|
{t("deleteNode")}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
{canManageNode ? (
|
{canManageNode ? (
|
||||||
<Button type="button" size="sm" onClick={openCreateChild}>
|
<Button type="button" size="sm" onClick={openCreateChild}>
|
||||||
<Plus className="mr-1 size-3.5" />
|
<Plus className="mr-1 size-3.5" />
|
||||||
@@ -679,6 +761,12 @@ export function AgentsConsole(): React.ReactElement {
|
|||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
{canManageNode && !selected.is_root && deleteBlockReasons.length > 0 ? (
|
||||||
|
<p className="mb-4 text-xs text-muted-foreground">
|
||||||
|
{t("deleteNodeBlockedPrefix", { defaultValue: "暂不可删除:" })}
|
||||||
|
{deleteBlockReasons.join(";")}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{canViewRoles ? (
|
{canViewRoles ? (
|
||||||
<TabsContent value="roles">
|
<TabsContent value="roles">
|
||||||
@@ -717,41 +805,52 @@ export function AgentsConsole(): React.ReactElement {
|
|||||||
<TableCell>{role.user_count}</TableCell>
|
<TableCell>{role.user_count}</TableCell>
|
||||||
<TableCell className="sticky right-0 z-10 bg-card text-center shadow-[-1px_0_0_rgba(203,213,225,0.7)]">
|
<TableCell className="sticky right-0 z-10 bg-card text-center shadow-[-1px_0_0_rgba(203,213,225,0.7)]">
|
||||||
{canManageRoles && !role.is_read_only_template ? (
|
{canManageRoles && !role.is_read_only_template ? (
|
||||||
<AdminRowActionsMenu
|
<div className="flex flex-col items-center gap-1">
|
||||||
actions={[
|
{(role.user_count ?? 0) > 0 ? (
|
||||||
{
|
<span className="text-xs text-muted-foreground">
|
||||||
key: "permissions",
|
{t("roles.inUse", {
|
||||||
label: t("roles.permissions"),
|
count: role.user_count ?? 0,
|
||||||
icon: KeyRound,
|
defaultValue: "{{count}} 人使用中",
|
||||||
onClick: () => {
|
})}
|
||||||
setPermRoleId(role.id);
|
</span>
|
||||||
setDraftPerms([...role.permission_slugs]);
|
) : null}
|
||||||
setPermDialogOpen(true);
|
<AdminRowActionsMenu
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
key: "permissions",
|
||||||
|
label: t("roles.permissions"),
|
||||||
|
icon: KeyRound,
|
||||||
|
onClick: () => {
|
||||||
|
setPermRoleId(role.id);
|
||||||
|
setDraftPerms([...role.permission_slugs]);
|
||||||
|
setPermDialogOpen(true);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
key: "delete",
|
||||||
key: "delete",
|
label: t("common:actions.delete", { defaultValue: "Delete" }),
|
||||||
label: t("common:actions.delete", { defaultValue: "Delete" }),
|
icon: Trash2,
|
||||||
icon: Trash2,
|
destructive: true,
|
||||||
destructive: true,
|
disabled: (role.user_count ?? 0) > 0,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
requestConfirm({
|
requestConfirm({
|
||||||
title: role.name,
|
title: role.name,
|
||||||
description: t("common:confirm.deleteDescription", {
|
description: t("common:confirm.deleteDescription", {
|
||||||
defaultValue: "This cannot be undone.",
|
defaultValue: "This cannot be undone.",
|
||||||
}),
|
}),
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
await deleteAgentRole(role.id);
|
await deleteAgentRole(role.id);
|
||||||
toast.success(t("roles.deleteSuccess", { name: role.name }));
|
toast.success(t("roles.deleteSuccess", { name: role.name }));
|
||||||
if (selectedId !== null) {
|
if (selectedId !== null) {
|
||||||
await loadDetail(selectedId);
|
await loadDetail(selectedId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
]}
|
||||||
]}
|
/>
|
||||||
/>
|
</div>
|
||||||
) : role.is_read_only_template ? (
|
) : role.is_read_only_template ? (
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-muted-foreground">
|
||||||
{t("roles.readOnlyTemplate")}
|
{t("roles.readOnlyTemplate")}
|
||||||
@@ -793,6 +892,7 @@ export function AgentsConsole(): React.ReactElement {
|
|||||||
<TableHead>{t("users.username")}</TableHead>
|
<TableHead>{t("users.username")}</TableHead>
|
||||||
<TableHead>{t("name")}</TableHead>
|
<TableHead>{t("name")}</TableHead>
|
||||||
<TableHead>{t("users.roles")}</TableHead>
|
<TableHead>{t("users.roles")}</TableHead>
|
||||||
|
<TableHead className="sticky right-0 z-20 bg-muted w-[80px] shadow-[-1px_0_0_rgba(203,213,225,0.7)]" />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
@@ -801,6 +901,41 @@ export function AgentsConsole(): React.ReactElement {
|
|||||||
<TableCell>{user.username}</TableCell>
|
<TableCell>{user.username}</TableCell>
|
||||||
<TableCell>{user.nickname}</TableCell>
|
<TableCell>{user.nickname}</TableCell>
|
||||||
<TableCell className="text-xs">{user.roles.join(", ") || "—"}</TableCell>
|
<TableCell className="text-xs">{user.roles.join(", ") || "—"}</TableCell>
|
||||||
|
<TableCell className="sticky right-0 z-10 bg-card text-center shadow-[-1px_0_0_rgba(203,213,225,0.7)]">
|
||||||
|
{canManageUsers ? (
|
||||||
|
<AdminRowActionsMenu
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
key: "delete",
|
||||||
|
label: t("common:actions.delete", { defaultValue: "Delete" }),
|
||||||
|
icon: Trash2,
|
||||||
|
destructive: true,
|
||||||
|
onClick: () => {
|
||||||
|
requestConfirm({
|
||||||
|
title: user.username,
|
||||||
|
description: t("users.deleteConfirm", {
|
||||||
|
defaultValue: "删除后该管理员将无法登录,且不可恢复。",
|
||||||
|
}),
|
||||||
|
confirmLabel: t("common:actions.delete", {
|
||||||
|
defaultValue: "Delete",
|
||||||
|
}),
|
||||||
|
confirmVariant: "destructive",
|
||||||
|
onConfirm: async () => {
|
||||||
|
await deleteAgentAdminUser(user.id);
|
||||||
|
toast.success(
|
||||||
|
t("users.deleteSuccess", { name: user.nickname }),
|
||||||
|
);
|
||||||
|
if (selectedId !== null) {
|
||||||
|
await loadDetail(selectedId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type ConfigVersionActionsProps = {
|
|||||||
|
|
||||||
export function ConfigVersionActions({
|
export function ConfigVersionActions({
|
||||||
isDraft,
|
isDraft,
|
||||||
canManage = true,
|
canManage = false,
|
||||||
loadingList = false,
|
loadingList = false,
|
||||||
loadingDetail = false,
|
loadingDetail = false,
|
||||||
saving = false,
|
saving = false,
|
||||||
|
|||||||
Reference in New Issue
Block a user