Initial commit: SQL audit platform v0.1.0
FastAPI backend with auth, user, instance, ticket, query, dashboard APIs. React + TypeScript frontend with mock data UI (10 pages). SQLite + async SQLAlchemy, JWT auth, role-based access control. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
commit
439155c92e
290 files changed
+61950
No files matched your search
@@ -0,0 +1,109 @@
|
||||
import { useState } from 'react';
|
||||
import { Plus, Search, Edit2, Trash2, Database, Table, Filter, Shield } from 'lucide-react';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { dataPermissions } from '@/lib/mock';
|
||||
|
||||
export default function DataPermissionPage() {
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const filtered = dataPermissions.filter(p =>
|
||||
!search || p.user.includes(search) || p.instance.includes(search) || p.database.includes(search)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">数据权限</h1>
|
||||
<p className="text-sm text-[var(--dg-text-secondary)] mt-1">配置用户对数据库、表、字段的访问权限,支持行级权限控制</p>
|
||||
</div>
|
||||
<button className="flex items-center gap-2 px-4 py-2.5 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg text-sm font-medium text-white hover:opacity-90 transition-opacity">
|
||||
<Plus className="w-4 h-4" /> 添加权限
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
{[
|
||||
{ label: '权限规则数', value: dataPermissions.length, icon: Shield, color: 'text-blue-400', bg: 'bg-blue-400/10' },
|
||||
{ label: '受控用户', value: new Set(dataPermissions.map(p => p.user)).size, icon: Database, color: 'text-purple-400', bg: 'bg-purple-400/10' },
|
||||
{ label: '受控实例', value: new Set(dataPermissions.map(p => p.instance)).size, icon: Table, color: 'text-green-400', bg: 'bg-green-400/10' },
|
||||
{ label: '行级过滤规则', value: dataPermissions.filter(p => p.rowFilter).length, icon: Filter, color: 'text-orange-400', bg: 'bg-orange-400/10' },
|
||||
].map((s, i) => (
|
||||
<Card key={i} className="dg-card">
|
||||
<CardContent className="p-4 flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-lg ${s.bg} flex items-center justify-center`}><s.icon className={`w-5 h-5 ${s.color}`} /></div>
|
||||
<div><div className="text-2xl font-bold">{s.value}</div><div className="text-xs text-[var(--dg-text-muted)]">{s.label}</div></div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="relative max-w-xs">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--dg-text-muted)]" />
|
||||
<input className="w-full pl-10 pr-4 py-2.5 bg-[var(--dg-bg-tertiary)] border border-[var(--dg-border)] rounded-lg text-sm placeholder:text-[var(--dg-text-muted)] focus:border-blue-400 focus:outline-none" placeholder="搜索用户/实例/数据库" value={search} onChange={e => setSearch(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<Card className="dg-card">
|
||||
<CardContent className="p-0">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-[var(--dg-border)] bg-[var(--dg-bg-tertiary)]">
|
||||
<th className="text-left px-5 py-3 font-semibold">用户</th>
|
||||
<th className="text-left px-5 py-3 font-semibold">实例</th>
|
||||
<th className="text-left px-5 py-3 font-semibold">数据库</th>
|
||||
<th className="text-left px-5 py-3 font-semibold">表</th>
|
||||
<th className="text-left px-5 py-3 font-semibold">字段权限</th>
|
||||
<th className="text-left px-5 py-3 font-semibold">行级过滤</th>
|
||||
<th className="text-left px-5 py-3 font-semibold">操作权限</th>
|
||||
<th className="text-left px-5 py-3 font-semibold">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filtered.map(p => (
|
||||
<tr key={p.id} className="border-b border-[var(--dg-border-light)] hover:bg-[var(--dg-bg-hover)] transition-colors">
|
||||
<td className="px-5 py-3.5 font-medium">{p.user}</td>
|
||||
<td className="px-5 py-3.5 text-[var(--dg-text-secondary)]">{p.instance}</td>
|
||||
<td className="px-5 py-3.5 text-[var(--dg-text-secondary)]">{p.database}</td>
|
||||
<td className="px-5 py-3.5 text-[var(--dg-text-secondary)]">{p.table}</td>
|
||||
<td className="px-5 py-3.5">
|
||||
{p.columns === '*' ? (
|
||||
<span className="px-2 py-1 bg-green-500/15 text-green-400 text-xs rounded-md">全部字段</span>
|
||||
) : (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{p.columns.split(',').map(c => (
|
||||
<span key={c} className="px-2 py-1 bg-[var(--dg-bg-tertiary)] text-[var(--dg-text-secondary)] text-xs rounded-md">{c}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-5 py-3.5">
|
||||
{p.rowFilter ? (
|
||||
<span className="px-2 py-1 bg-yellow-500/15 text-yellow-400 text-xs rounded-md">{p.rowFilter}</span>
|
||||
) : (
|
||||
<span className="text-[var(--dg-text-muted)]">-</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-5 py-3.5">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{p.operations.map(op => (
|
||||
<span key={op} className="px-2 py-1 bg-blue-500/15 text-blue-400 text-xs rounded-md">{op}</span>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-5 py-3.5">
|
||||
<div className="flex gap-2">
|
||||
<button className="text-blue-400 hover:text-blue-300"><Edit2 className="w-4 h-4" /></button>
|
||||
<button className="text-red-400 hover:text-red-300"><Trash2 className="w-4 h-4" /></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user