feat: integrate project management into unified policy page
- Add project section: add/remove projects with policy selection - Each project card shows: policies, spending, monitor toggle, remove - Replaces separate project management dialog - All project and policy operations on one page Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fab4765e90
commit
dacc521c1c
@ -748,6 +748,8 @@ def iam_user_policies_overview_view(request, pk):
|
||||
'project_name': proj.project_name,
|
||||
'display_name': proj.display_name,
|
||||
'project_id': proj.id,
|
||||
'monitor_enabled': proj.monitor_enabled,
|
||||
'current_spending': str(proj.current_spending),
|
||||
'policies': proj_items,
|
||||
})
|
||||
except Exception:
|
||||
@ -755,6 +757,8 @@ def iam_user_policies_overview_view(request, pk):
|
||||
'project_name': proj.project_name,
|
||||
'display_name': proj.display_name,
|
||||
'project_id': proj.id,
|
||||
'monitor_enabled': proj.monitor_enabled,
|
||||
'current_spending': str(proj.current_spending),
|
||||
'policies': [],
|
||||
})
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div style="max-width: 1200px; margin: 0 auto;">
|
||||
<div class="page-header">
|
||||
<h2>{{ overview.display_name || overview.username }} 权限总览</h2>
|
||||
<h2>{{ overview.display_name || overview.username || '...' }} 权限管理</h2>
|
||||
<el-button @click="$router.push('/iam-users')">返回子账号列表</el-button>
|
||||
</div>
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:12px;">
|
||||
全局策略对所有项目生效。一般只放 Deny 策略(项目隔离),业务权限请加到项目级。
|
||||
</el-alert>
|
||||
<el-table :data="overview.global_policies || []" stripe empty-text="无全局策略">
|
||||
<el-table :data="overview.global_policies || []" stripe empty-text="无全局策略" table-layout="auto">
|
||||
<el-table-column prop="name" label="策略名" min-width="220" />
|
||||
<el-table-column label="类型" width="100">
|
||||
<template #default="{ row }">
|
||||
@ -42,6 +42,29 @@
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- Add Project Section -->
|
||||
<el-card style="margin-bottom: 20px;">
|
||||
<template #header>
|
||||
<span style="font-weight:600; font-size:16px;">关联项目</span>
|
||||
</template>
|
||||
<div style="display:flex; gap:8px; align-items:flex-start; flex-wrap:wrap;">
|
||||
<el-select v-model="projectToAdd" placeholder="选择火山项目" filterable style="width:260px;"
|
||||
:loading="volcProjectsLoading" @focus="loadVolcProjects">
|
||||
<el-option v-for="p in volcProjects" :key="p.name"
|
||||
:label="p.display_name || p.name" :value="p.name"
|
||||
:disabled="(overview.project_policies || []).some(pp => pp.project_name === p.name)" />
|
||||
</el-select>
|
||||
<div v-if="projectToAdd" style="display:flex; flex-wrap:wrap; gap:8px; align-items:center;">
|
||||
<el-checkbox-group v-model="projectPoliciesToAttach" style="display:flex; flex-wrap:wrap; gap:4px;">
|
||||
<el-checkbox label="ArkFullAccess" size="small">方舟完整</el-checkbox>
|
||||
<el-checkbox label="TOSFullAccess" size="small">TOS完整</el-checkbox>
|
||||
<el-checkbox label="ArkReadOnlyAccess" size="small">方舟只读</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<el-button type="primary" size="small" @click="handleAddProject">确认添加</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- Project-level Policies -->
|
||||
<el-card v-for="proj in (overview.project_policies || [])" :key="proj.project_name" style="margin-bottom: 16px;">
|
||||
<template #header>
|
||||
@ -50,21 +73,22 @@
|
||||
<el-tag type="success" size="small" style="margin-right:8px;">项目</el-tag>
|
||||
<span style="font-weight:600; font-size:15px;">{{ proj.project_name }}</span>
|
||||
<span v-if="proj.display_name" style="color:#999; margin-left:8px;">{{ proj.display_name }}</span>
|
||||
<span style="color:#e6a23c; margin-left:12px; font-size:13px;">消费: ¥{{ Number(proj.current_spending || 0).toLocaleString() }}</span>
|
||||
<el-switch :model-value="proj.monitor_enabled" @change="val => toggleMonitor(proj, val)"
|
||||
active-text="监测" inactive-text="" size="small" style="margin-left:12px;" />
|
||||
</span>
|
||||
<div style="display:flex; gap:8px;">
|
||||
<el-select v-model="projectPolicyToAdd[proj.project_name]" placeholder="添加策略" filterable size="small" style="width:280px;">
|
||||
<el-select v-model="projectPolicyToAdd[proj.project_name]" placeholder="添加策略" filterable size="small" style="width:260px;">
|
||||
<el-option v-for="opt in policyOptions" :key="opt.value"
|
||||
:value="opt.value" :label="opt.label"
|
||||
:disabled="proj.policies?.some(p => p.name === opt.value)" />
|
||||
</el-select>
|
||||
<el-button type="primary" size="small" @click="attachProject(proj)" :disabled="!projectPolicyToAdd[proj.project_name]">添加</el-button>
|
||||
<el-button type="danger" size="small" text @click="removeProject(proj)">移除项目</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:12px;">
|
||||
以下权限仅在 <strong>{{ proj.project_name }}</strong> 项目范围内生效。
|
||||
</el-alert>
|
||||
<el-table :data="proj.policies || []" stripe empty-text="无项目级策略">
|
||||
<el-table :data="proj.policies || []" stripe empty-text="无项目级策略" table-layout="auto">
|
||||
<el-table-column prop="name" label="策略名" min-width="220" />
|
||||
<el-table-column label="类型" width="100">
|
||||
<template #default="{ row }">
|
||||
@ -82,7 +106,8 @@
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-empty v-if="!(overview.project_policies || []).length && !loading" description="暂无关联项目,请先在子账号管理中添加项目" />
|
||||
<el-empty v-if="!(overview.project_policies || []).length && !loading"
|
||||
description="暂无关联项目,请在上方添加" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -101,6 +126,12 @@ const overview = ref({})
|
||||
const globalPolicyToAdd = ref('')
|
||||
const projectPolicyToAdd = reactive({})
|
||||
|
||||
// Add project
|
||||
const projectToAdd = ref('')
|
||||
const projectPoliciesToAttach = ref([])
|
||||
const volcProjects = ref([])
|
||||
const volcProjectsLoading = ref(false)
|
||||
|
||||
const policyOptions = [
|
||||
{ value: 'ArkFullAccess', label: 'ArkFullAccess(方舟/Seedance 完整权限)' },
|
||||
{ value: 'ArkExperienceAccess', label: 'ArkExperienceAccess(方舟体验权限)' },
|
||||
@ -122,6 +153,20 @@ async function loadOverview() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadVolcProjects() {
|
||||
if (volcProjects.value.length) return
|
||||
volcProjectsLoading.value = true
|
||||
try {
|
||||
const { data } = await api.get('/api/v1/projects/')
|
||||
volcProjects.value = data
|
||||
} catch (e) {
|
||||
ElMessage.error('获取火山项目列表失败')
|
||||
} finally {
|
||||
volcProjectsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// === Global policies ===
|
||||
async function attachGlobal() {
|
||||
if (!globalPolicyToAdd.value) return
|
||||
try {
|
||||
@ -151,11 +196,50 @@ async function detachGlobal(row) {
|
||||
}
|
||||
}
|
||||
|
||||
// === Project management ===
|
||||
async function handleAddProject() {
|
||||
if (!projectToAdd.value) return
|
||||
try {
|
||||
await api.post(`/api/v1/iam-users/${userId}/projects/add/`, {
|
||||
project_name: projectToAdd.value,
|
||||
policies: projectPoliciesToAttach.value,
|
||||
})
|
||||
ElMessage.success(`已关联项目 ${projectToAdd.value}`)
|
||||
projectToAdd.value = ''
|
||||
projectPoliciesToAttach.value = []
|
||||
await loadOverview()
|
||||
} catch (e) {
|
||||
ElMessage.error(e.response?.data?.message || '添加失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function removeProject(proj) {
|
||||
await ElMessageBox.confirm(`确定移除项目 "${proj.project_name}" 吗?权限将被回收。`, '确认移除', { type: 'warning' })
|
||||
try {
|
||||
await api.delete(`/api/v1/iam-users/${userId}/projects/${proj.project_id}/delete/`)
|
||||
ElMessage.success(`已移除项目 ${proj.project_name}`)
|
||||
await loadOverview()
|
||||
} catch (e) {
|
||||
ElMessage.error(e.response?.data?.message || '移除失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleMonitor(proj, val) {
|
||||
try {
|
||||
await api.put(`/api/v1/iam-users/${userId}/projects/${proj.project_id}/`, {
|
||||
monitor_enabled: val,
|
||||
})
|
||||
await loadOverview()
|
||||
} catch (e) {
|
||||
ElMessage.error('切换失败')
|
||||
}
|
||||
}
|
||||
|
||||
// === Project-level policies ===
|
||||
async function attachProject(proj) {
|
||||
const policyName = projectPolicyToAdd[proj.project_name]
|
||||
if (!policyName) return
|
||||
try {
|
||||
// Use the project policies update endpoint
|
||||
const newPolicies = [...(proj.policies || []).map(p => p.name), policyName]
|
||||
await api.put(`/api/v1/iam-users/${userId}/projects/${proj.project_id}/policies/`, {
|
||||
policies: newPolicies,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user