feat(admin): add edit flows for all catalogs and member authentik sync

This commit is contained in:
Chris
2026-03-30 03:25:53 +08:00
parent 137861df1c
commit 0582b00f5f
13 changed files with 659 additions and 85 deletions

View File

@@ -15,7 +15,9 @@
@submit.prevent="handleGrant"
>
<el-form-item label="Authentik Sub" prop="authentik_sub">
<el-input v-model="grantForm.authentik_sub" placeholder="authentik-sub-xxx" />
<el-select v-model="grantForm.authentik_sub" filterable allow-create default-first-option placeholder="選擇會員或輸入 sub" style="width: 100%">
<el-option v-for="m in members" :key="m.authentik_sub" :label="`${m.display_name || m.email || '(no-name)'} (${m.authentik_sub})`" :value="m.authentik_sub" />
</el-select>
</el-form-item>
<el-form-item label="Email" prop="email">
<el-input v-model="grantForm.email" placeholder="user@example.com" />
@@ -30,16 +32,24 @@
</el-select>
</el-form-item>
<el-form-item label="Scope ID" prop="scope_id">
<el-input v-model="grantForm.scope_id" placeholder="company_key or site_key" />
<el-select v-model="grantForm.scope_id" placeholder="選擇 Scope ID" filterable style="width: 100%">
<el-option v-for="s in grantScopeOptions" :key="s.value" :label="s.label" :value="s.value" />
</el-select>
</el-form-item>
<el-form-item label="系統" prop="system">
<el-input v-model="grantForm.system" placeholder="mkt" />
<el-select v-model="grantForm.system" placeholder="選擇系統" filterable style="width: 100%">
<el-option v-for="s in systems" :key="s.system_key" :label="`${s.name} (${s.system_key})`" :value="s.system_key" />
</el-select>
</el-form-item>
<el-form-item label="模組(選填)" prop="module">
<el-input v-model="grantForm.module" placeholder="campaign空值代表系統層" clearable />
<el-select v-model="grantForm.module" placeholder="系統層(留空) 或選模組" clearable filterable style="width: 100%">
<el-option v-for="m in grantModuleOptions" :key="m.value" :label="m.label" :value="m.value" />
</el-select>
</el-form-item>
<el-form-item label="操作" prop="action">
<el-input v-model="grantForm.action" placeholder="view" />
<el-select v-model="grantForm.action" filterable allow-create default-first-option style="width: 100%">
<el-option v-for="a in actionOptions" :key="a" :label="a" :value="a" />
</el-select>
</el-form-item>
<el-alert
@@ -83,7 +93,9 @@
@submit.prevent="handleRevoke"
>
<el-form-item label="Authentik Sub" prop="authentik_sub">
<el-input v-model="revokeForm.authentik_sub" placeholder="authentik-sub-xxx" />
<el-select v-model="revokeForm.authentik_sub" filterable allow-create default-first-option placeholder="選擇會員或輸入 sub" style="width: 100%">
<el-option v-for="m in members" :key="m.authentik_sub" :label="`${m.display_name || m.email || '(no-name)'} (${m.authentik_sub})`" :value="m.authentik_sub" />
</el-select>
</el-form-item>
<el-form-item label="Scope 類型" prop="scope_type">
<el-select v-model="revokeForm.scope_type" placeholder="選擇 Scope 類型">
@@ -92,16 +104,24 @@
</el-select>
</el-form-item>
<el-form-item label="Scope ID" prop="scope_id">
<el-input v-model="revokeForm.scope_id" placeholder="company_key or site_key" />
<el-select v-model="revokeForm.scope_id" placeholder="選擇 Scope ID" filterable style="width: 100%">
<el-option v-for="s in revokeScopeOptions" :key="s.value" :label="s.label" :value="s.value" />
</el-select>
</el-form-item>
<el-form-item label="系統" prop="system">
<el-input v-model="revokeForm.system" placeholder="mkt" />
<el-select v-model="revokeForm.system" placeholder="選擇系統" filterable style="width: 100%">
<el-option v-for="s in systems" :key="s.system_key" :label="`${s.name} (${s.system_key})`" :value="s.system_key" />
</el-select>
</el-form-item>
<el-form-item label="模組(選填)" prop="module">
<el-input v-model="revokeForm.module" placeholder="campaign空值代表系統層" clearable />
<el-select v-model="revokeForm.module" placeholder="系統層(留空) 或選模組" clearable filterable style="width: 100%">
<el-option v-for="m in revokeModuleOptions" :key="m.value" :label="m.label" :value="m.value" />
</el-select>
</el-form-item>
<el-form-item label="操作" prop="action">
<el-input v-model="revokeForm.action" placeholder="view" />
<el-select v-model="revokeForm.action" filterable allow-create default-first-option style="width: 100%">
<el-option v-for="a in actionOptions" :key="a" :label="a" :value="a" />
</el-select>
</el-form-item>
<el-alert
@@ -138,13 +158,24 @@
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ref, reactive, onMounted, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { usePermissionStore } from '@/stores/permission'
import { getSystems } from '@/api/systems'
import { getModules } from '@/api/modules'
import { getCompanies } from '@/api/companies'
import { getSites } from '@/api/sites'
import { getMembers } from '@/api/members'
const permissionStore = usePermissionStore()
const activeTab = ref('grant')
const systems = ref([])
const modules = ref([])
const companies = ref([])
const sites = ref([])
const members = ref([])
const actionOptions = ['view', 'edit', 'manage', 'admin']
// Grant
const grantFormRef = ref()
@@ -163,6 +194,26 @@ const grantForm = reactive({
action: ''
})
const grantModuleOptions = computed(() => {
if (!grantForm.system) return []
return modules.value
.filter(m => m.system_key === grantForm.system && !m.module_key.endsWith('.__system__'))
.map(m => ({
value: m.module_key.split('.', 2)[1] || m.module_key,
label: `${m.name} (${m.module_key})`
}))
})
const grantScopeOptions = computed(() => {
if (grantForm.scope_type === 'company') {
return companies.value.map(c => ({ value: c.company_key, label: `${c.name} (${c.company_key})` }))
}
if (grantForm.scope_type === 'site') {
return sites.value.map(s => ({ value: s.site_key, label: `${s.name} (${s.site_key})` }))
}
return []
})
const required = { required: true, message: '必填', trigger: 'blur' }
const grantRules = {
authentik_sub: [required],
@@ -212,6 +263,26 @@ const revokeForm = reactive({
action: ''
})
const revokeModuleOptions = computed(() => {
if (!revokeForm.system) return []
return modules.value
.filter(m => m.system_key === revokeForm.system && !m.module_key.endsWith('.__system__'))
.map(m => ({
value: m.module_key.split('.', 2)[1] || m.module_key,
label: `${m.name} (${m.module_key})`
}))
})
const revokeScopeOptions = computed(() => {
if (revokeForm.scope_type === 'company') {
return companies.value.map(c => ({ value: c.company_key, label: `${c.name} (${c.company_key})` }))
}
if (revokeForm.scope_type === 'site') {
return sites.value.map(s => ({ value: s.site_key, label: `${s.name} (${s.site_key})` }))
}
return []
})
const revokeRules = {
authentik_sub: [required],
scope_type: [required],
@@ -265,4 +336,33 @@ function formatAdminError(err) {
if (status === 503) return '後端設定不完整,請聯絡管理員'
return '操作失敗,請稍後再試'
}
async function loadCatalogs() {
const [systemsRes, modulesRes, companiesRes, sitesRes, membersRes] = await Promise.all([
getSystems(),
getModules(),
getCompanies(),
getSites(),
getMembers()
])
systems.value = systemsRes.data?.items || []
modules.value = modulesRes.data?.items || []
companies.value = companiesRes.data?.items || []
sites.value = sitesRes.data?.items || []
members.value = membersRes.data?.items || []
}
watch(() => grantForm.scope_type, () => { grantForm.scope_id = '' })
watch(() => grantForm.system, () => { grantForm.module = '' })
watch(() => revokeForm.scope_type, () => { revokeForm.scope_id = '' })
watch(() => revokeForm.system, () => { revokeForm.module = '' })
watch(() => grantForm.authentik_sub, (sub) => {
const user = members.value.find(m => m.authentik_sub === sub)
if (!user) return
grantForm.email = user.email || ''
grantForm.display_name = user.display_name || ''
})
onMounted(loadCatalogs)
</script>