feat: 全站 PageShell 设计系统与专业 UI 统一
- 新增 PageShell/PageTabs/ContentPanel/PageLoading/PageEmpty 页面骨架 - index.css 设计令牌文档化,布局令牌与纯色主按钮(去除渐变粉彩) - 全站路由页接入 PageShell,云账单三 Tab 为标杆 - 主题体系:跟随系统 + 主流/开发者分组,新增 Dracula/Tokyo Night,One Dark 暖灰差异化 - 登录页/侧栏/顶栏去渐变光球,企业克制风 - 修复 AIAlertAnalyzer JSX 与构建依赖导入问题
This commit is contained in:
1 parent
1acb0c49af
commit
e363ded445
213 files changed
+7148
-6818
No files matched your search
@@ -9,6 +9,9 @@ import { Label } from '@/components/ui/label';
|
||||
import { toast } from 'sonner';
|
||||
import { deployApi, userApi, pipelineApi } from '@/api';
|
||||
import { useConfirm } from '@/components/ui/confirm-dialog';
|
||||
import { PageShell } from '@/components/layout/PageShell';
|
||||
import { PageEmpty } from '@/components/layout/PageEmpty';
|
||||
import { StatStrip } from '@/components/ui/stat-strip';
|
||||
import type { JsonData } from '@/types/common';
|
||||
|
||||
interface ItemRole {
|
||||
@@ -159,36 +162,41 @@ export default function DeployRBAC() {
|
||||
};
|
||||
|
||||
const roleBadgeClass = (role: string) =>
|
||||
role === 'owner' ? 'bg-amber-500/20 text-amber-400 border-amber-500/30' :
|
||||
role === 'editor' ? 'bg-blue-500/20 text-blue-400 border-blue-500/30' :
|
||||
'bg-slate-500/20 text-slate-400 border-slate-500/30';
|
||||
role === 'owner' ? 'bg-amber-500/20 dg-text-yellow border-amber-500/30' :
|
||||
role === 'editor' ? 'bg-blue-500/20 dg-text-blue border-blue-500/30' :
|
||||
'bg-[var(--dg-bg-active)]/20 dg-text-secondary border-[var(--dg-border)]';
|
||||
|
||||
const selectedUser = users.find(u => String(u.id) === selectedUserId);
|
||||
|
||||
if (loading) return <div className="flex items-center justify-center py-20"><Loader2 className="w-6 h-6 animate-spin" /></div>;
|
||||
const activeRoles = roles.filter(r => r.status === 1).length;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold flex items-center gap-2 text-[var(--dg-text-primary)]">
|
||||
<Shield className="w-5 h-5 text-purple-500" />发布权限
|
||||
</h1>
|
||||
<p className="text-sm text-[var(--dg-text-secondary)] mt-1">
|
||||
Jenkins-style Role Strategy — 按流水线 code 正则匹配分配角色
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => setShowCreate(true)} disabled={showCreate}>
|
||||
<PageShell
|
||||
variant="list"
|
||||
title="发布权限"
|
||||
subtitle="Jenkins-style Role Strategy — 按流水线 code 正则匹配分配角色"
|
||||
icon={<Shield className="h-5 w-5" />}
|
||||
loading={loading}
|
||||
metrics={(
|
||||
<StatStrip
|
||||
items={[
|
||||
{ label: 'Item Role', value: roles.length, color: 'purple' },
|
||||
{ label: '已启用', value: activeRoles, color: 'emerald' },
|
||||
{ label: '流水线', value: pipelines.length, color: 'blue' },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
actions={(
|
||||
<Button className="dg-btn-primary" onClick={() => setShowCreate(true)} disabled={showCreate}>
|
||||
<Plus className="w-4 h-4 mr-1" />新建角色
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
)}
|
||||
>
|
||||
{/* ====== Pipeline Preview ====== */}
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<GitBranch className="w-4 h-4 text-blue-500" />
|
||||
<GitBranch className="w-4 h-4 dg-text-blue" />
|
||||
<span className="text-sm font-medium text-[var(--dg-text-primary)]">流水线命中预览</span>
|
||||
<span className="text-xs text-[var(--dg-text-muted)]">— 选择一个角色查看哪些流水线会被匹配</span>
|
||||
</div>
|
||||
@@ -215,7 +223,7 @@ export default function DeployRBAC() {
|
||||
})
|
||||
}
|
||||
{previewRoleId !== null && previewMatches.length === 0 && (
|
||||
<span className="text-xs text-red-400">无匹配 — 请调整正则表达式</span>
|
||||
<span className="text-xs dg-text-red">无匹配 — 请调整正则表达式</span>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -266,10 +274,7 @@ export default function DeployRBAC() {
|
||||
|
||||
{/* Role list */}
|
||||
{roles.length === 0 ? (
|
||||
<div className="text-center py-12 text-[var(--dg-text-secondary)]">
|
||||
<Shield className="w-10 h-10 mx-auto mb-2 opacity-20" />
|
||||
<p className="text-sm">暂无 Item Role</p>
|
||||
</div>
|
||||
<PageEmpty icon={<Shield className="h-10 w-10" />} title="暂无 Item Role" description="创建角色后按正则匹配流水线并分配给用户" />
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{roles.map(role => (
|
||||
@@ -278,21 +283,21 @@ export default function DeployRBAC() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
<div className="w-9 h-9 rounded-lg bg-purple-500/20 flex items-center justify-center shrink-0">
|
||||
<Regex className="w-4 h-4 text-purple-400" />
|
||||
<Regex className="w-4 h-4 dg-text-purple" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
<span className="text-sm font-semibold text-[var(--dg-text-primary)]">{role.name}</span>
|
||||
<Badge className={`text-xs border ${roleBadgeClass(role.role)}`}>{role.role === 'owner' ? '拥有者' : role.role === 'editor' ? '编辑者' : '查看者'}</Badge>
|
||||
{role.status === 0 && <Badge variant="secondary" className="text-xs text-red-400">已禁用</Badge>}
|
||||
{role.status === 0 && <Badge variant="secondary" className="text-xs dg-text-red">已禁用</Badge>}
|
||||
<Badge variant="secondary" className="text-xs cursor-pointer" onClick={() => setPreviewRoleId(previewRoleId === role.id ? null : role.id)}>
|
||||
<GitBranch className="w-3 h-3 mr-0.5" />{matchCount[role.id]?.length || 0} 条
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="text-xs text-[var(--dg-text-secondary)] mt-0.5 truncate">
|
||||
<code className="font-mono text-purple-400 bg-purple-500/10 px-1 rounded">{role.code}</code>
|
||||
<code className="font-mono dg-text-purple bg-purple-500/10 px-1 rounded">{role.code}</code>
|
||||
<span className="mx-1">→</span>
|
||||
<code className="font-mono text-blue-400 bg-blue-500/10 px-1 rounded">{role.pattern}</code>
|
||||
<code className="font-mono dg-text-blue bg-blue-500/10 px-1 rounded">{role.pattern}</code>
|
||||
{role.description && <span className="ml-2 opacity-60">{role.description}</span>}
|
||||
</div>
|
||||
</div>
|
||||
@@ -309,8 +314,8 @@ export default function DeployRBAC() {
|
||||
<SelectItem value="viewer">查看者</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button size="icon" variant="ghost" className="h-7 w-7 text-green-400" onClick={() => handleUpdate(role.id)}><Check className="w-3.5 h-3.5" /></Button>
|
||||
<Button size="icon" variant="ghost" className="h-7 w-7 text-red-400" onClick={() => setEditing(null)}><X className="w-3.5 h-3.5" /></Button>
|
||||
<Button size="icon" variant="ghost" className="h-7 w-7 dg-text-green" onClick={() => handleUpdate(role.id)}><Check className="w-3.5 h-3.5" /></Button>
|
||||
<Button size="icon" variant="ghost" className="h-7 w-7 dg-text-red" onClick={() => setEditing(null)}><X className="w-3.5 h-3.5" /></Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -331,7 +336,7 @@ export default function DeployRBAC() {
|
||||
<Card className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)]">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<UserPlus className="w-4 h-4 text-amber-400" />
|
||||
<UserPlus className="w-4 h-4 dg-text-yellow" />
|
||||
<span className="text-sm font-medium text-[var(--dg-text-primary)]">分配角色给用户</span>
|
||||
</div>
|
||||
<div className="flex gap-3 items-end mb-4">
|
||||
@@ -344,7 +349,7 @@ export default function DeployRBAC() {
|
||||
{users.map(u => (
|
||||
<SelectItem key={u.id} value={String(u.id)}>
|
||||
{u.real_name || u.username} ({u.username})
|
||||
{assignments.some(a => a.user_id === u.id) && <span className="ml-2 text-xs text-purple-400">已分配</span>}
|
||||
{assignments.some(a => a.user_id === u.id) && <span className="ml-2 text-xs dg-text-purple">已分配</span>}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -395,7 +400,7 @@ export default function DeployRBAC() {
|
||||
</code>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6 text-red-400" onClick={() => handleRevoke(a.id)}>
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6 dg-text-red" onClick={() => handleRevoke(a.id)}>
|
||||
<X className="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -407,6 +412,6 @@ export default function DeployRBAC() {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user