polish(approval-grants-ui): fix active-count pill + reuse mcConfirm for revoke

This commit is contained in:
matevip 2026-05-27 15:54:47 +08:00
parent fe4f051672
commit b81660a2a1
3 changed files with 42 additions and 13 deletions

View File

@ -3753,6 +3753,8 @@ export default {
createWorkspaceWarning: 'This rule will auto-approve every tool call from every user in this workspace. Safety-floor patterns still block disaster commands, but every other risk gets bypassed. Confirm with your login password.',
revokeBtn: 'Revoke',
revokeConfirm: 'Revoking will disable this rule. Continue?',
revokeConfirmDetailed: 'After revoking, auto-approve for {tool} will no longer apply — the next call will require manual review again. Continue?',
anyTool: 'all tools',
viewResolutions: 'View {count} auto-approve events triggered by this rule →',
empty: 'No auto-approve rules configured yet',
columns: {

View File

@ -3845,6 +3845,8 @@ export default {
createWorkspaceWarning: '该策略将允许此工作区内所有用户的所有工具调用自动通过审批。地板规则仍会阻断灾难性命令,但其他风险都将被绕过。请输入登录密码确认。',
revokeBtn: '撤销',
revokeConfirm: '撤销后此策略将不再生效。继续?',
revokeConfirmDetailed: '撤销后 {tool} 的自动批准将不再生效,下次调用会重新走人工审批。继续?',
anyTool: '所有工具',
viewResolutions: '查看本规则触发的 {count} 次自动通过 →',
empty: '尚未配置自动批准策略',
columns: {

View File

@ -18,9 +18,13 @@
<h2 class="section-title">{{ t('approval.grant.title') }}</h2>
<!-- Inline summary pill same "danger-tinted info" tokens as the
sidebar chip so the two surfaces feel like one design language.
No el-tag here: keeps the page entirely native + token-driven. -->
<span v-if="total > 0" class="summary-pill">
{{ t('approval.grant.chipLabel', { count: total }) }}
No el-tag here: keeps the page entirely native + token-driven.
Bound to activeCount (from /approval/grants/active) NOT total
total includes revoked rows since the list endpoint returns
history by default, and the pill messaging ("已启用") only
makes sense for grants that are currently in force. -->
<span v-if="activeCount > 0" class="summary-pill">
{{ t('approval.grant.chipLabel', { count: activeCount }) }}
</span>
<p class="section-desc">{{ t('approval.grant.desc') }}</p>
</div>
@ -283,13 +287,14 @@
</div>
</div>
</Teleport>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, reactive } from 'vue'
import { useI18n } from 'vue-i18n'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ElMessage } from 'element-plus'
import {
Delete,
Lock,
@ -301,6 +306,7 @@ import {
} from '@element-plus/icons-vue'
import { approvalApi } from '@/api'
import McPagination from '@/components/common/McPagination.vue'
import { mcConfirm } from '@/components/common/useConfirm'
import type {
ApprovalGrant,
CreateGrantPayload,
@ -313,6 +319,10 @@ const { t } = useI18n()
const rows = ref<ApprovalGrant[]>([])
const total = ref(0)
// Active grants count for the summary pill separate from `total` because the
// list endpoint returns revoked history by default and the pill should only
// reflect grants currently in force.
const activeCount = ref(0)
const currentPage = ref(1)
const pageSize = ref(20)
const loading = ref(false)
@ -376,6 +386,9 @@ async function loadGrants() {
// numeric page metadata at the boundary so el-pagination gets real numbers.
rows.value = Array.isArray(data?.records) ? data.records : []
total.value = Number(data?.total ?? 0)
// Refresh active-only count for the summary pill in parallel with the
// list; non-blocking on failure so the page still renders normally.
refreshActiveCount()
} catch (e: any) {
ElMessage.error(e?.message || 'Failed to load grants')
rows.value = []
@ -385,6 +398,16 @@ async function loadGrants() {
}
}
async function refreshActiveCount() {
try {
const res = await approvalApi.activeSummary()
const data = (res as any).data ?? res
activeCount.value = Number(data?.count ?? 0)
} catch {
activeCount.value = 0
}
}
function onMoreCommand(cmd: string | number | object) {
if (cmd === 'workspaceWide') {
openCreateDialog(true)
@ -443,15 +466,17 @@ async function submitCreate() {
}
async function confirmRevoke(g: ApprovalGrant) {
try {
await ElMessageBox.confirm(
t('approval.grant.revokeConfirm'),
t('approval.grant.revokeBtn'),
{ type: 'warning' },
)
} catch {
return
}
// Project-wide imperative confirm same component used by McpServers,
// Channels, LivePanel, etc. so the destructive prompt feels consistent
// across the app. `tone: 'danger'` paints the confirm button red.
const target = g.toolName || t('approval.grant.anyTool')
const ok = await mcConfirm({
title: t('approval.grant.revokeBtn'),
message: t('approval.grant.revokeConfirmDetailed', { tool: target }),
confirmText: t('approval.grant.revokeBtn'),
tone: 'danger',
})
if (!ok) return
try {
await approvalApi.revokeGrant(g.id)
ElMessage.success(t('common.success'))