前端权限管理:实现接口级权限管理UI和监控仪表板
新增功能: - 新增 ApiPermissions.vue 主页面,支持模块级/接口级权限切换 - 新增 ProjectPermissionsModule.vue 组件,展示模块级权限矩阵 - 新增 ApiEndpointPermissions.vue 组件,展示接口级权限矩阵 - 新增 PermissionMonitoring.vue 组件,展示权限系统监控仪表板 - 更新 projectPermissions.ts API 客户端,支持接口级权限和监控API - 新增权限相关的 TypeScript 类型定义 - 更新路由配置,添加接口级权限管理路由 - 更新国际化文本,添加权限管理相关的中文文本 API 客户端新增函数: - fetchApiEndpointPermissions: 获取接口级权限矩阵 - updateApiEndpointPermissions: 更新接口级权限矩阵 - fetchPermissionMetrics: 获取权限检查指标 - fetchCacheStats: 获取缓存统计 - fetchPermissionAlerts: 获取告警列表 - fetchPermissionHealth: 获取系统健康状态 - resetPermissionMetrics: 重置指标 - clearPermissionAlerts: 清除告警 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<div class="api-permissions">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="api-permissions-toolbar">
|
||||
<el-input
|
||||
v-model="searchText"
|
||||
placeholder="搜索端点..."
|
||||
clearable
|
||||
style="width: 250px"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-select v-model="filterModule" placeholder="筛选模块" clearable style="width: 150px">
|
||||
<el-option label="全部模块" value="" />
|
||||
<el-option
|
||||
v-for="module in uniqueModules"
|
||||
:key="module"
|
||||
:label="module"
|
||||
:value="module"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
<el-select v-model="filterMethod" placeholder="筛选方法" clearable style="width: 120px">
|
||||
<el-option label="全部方法" value="" />
|
||||
<el-option label="GET" value="GET" />
|
||||
<el-option label="POST" value="POST" />
|
||||
<el-option label="PATCH" value="PATCH" />
|
||||
<el-option label="DELETE" value="DELETE" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 权限矩阵表格 -->
|
||||
<div class="api-permissions-table-wrapper">
|
||||
<el-table :data="filteredEndpoints" border stripe>
|
||||
<el-table-column prop="endpoint_key" label="端点" width="250">
|
||||
<template #default="{ row }">
|
||||
<div class="endpoint-cell">
|
||||
<el-tag :type="getMethodType(row.method)" size="small">
|
||||
{{ row.method }}
|
||||
</el-tag>
|
||||
<span class="endpoint-path">{{ row.path }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="module" label="模块" width="100" />
|
||||
|
||||
<el-table-column
|
||||
v-for="role in roles"
|
||||
:key="role"
|
||||
:label="role"
|
||||
:width="100"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-checkbox
|
||||
:model-value="isEndpointAllowed(row.endpoint_key, role)"
|
||||
@change="(val) => onPermissionChange(row.endpoint_key, role, val as boolean)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { Search } from "@element-plus/icons-vue";
|
||||
import type { ApiEndpointPermissionsResponse } from "@/types/api";
|
||||
|
||||
interface Endpoint {
|
||||
endpoint_key: string;
|
||||
method: string;
|
||||
path: string;
|
||||
module: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
project: any;
|
||||
matrix: ApiEndpointPermissionsResponse | null;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: "update", matrix: ApiEndpointPermissionsResponse): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const searchText = ref("");
|
||||
const filterModule = ref("");
|
||||
const filterMethod = ref("");
|
||||
|
||||
// 从矩阵中提取角色列表
|
||||
const roles = computed(() => {
|
||||
if (!props.matrix) return [];
|
||||
return Object.keys(props.matrix).sort();
|
||||
});
|
||||
|
||||
// 从矩阵中提取端点列表
|
||||
const endpoints = computed(() => {
|
||||
if (!props.matrix) return [];
|
||||
|
||||
const endpointSet = new Set<string>();
|
||||
Object.values(props.matrix).forEach((rolePerms) => {
|
||||
Object.keys(rolePerms).forEach((endpoint) => {
|
||||
endpointSet.add(endpoint);
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(endpointSet)
|
||||
.map((endpoint_key) => {
|
||||
const [method, ...pathParts] = endpoint_key.split(":");
|
||||
const path = pathParts.join(":");
|
||||
const module = extractModule(path);
|
||||
|
||||
return {
|
||||
endpoint_key,
|
||||
method,
|
||||
path,
|
||||
module,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.endpoint_key.localeCompare(b.endpoint_key));
|
||||
});
|
||||
|
||||
// 提取模块名称
|
||||
const extractModule = (path: string): string => {
|
||||
const match = path.match(/\/(\w+)/);
|
||||
return match ? match[1] : "unknown";
|
||||
};
|
||||
|
||||
// 获取唯一的模块列表
|
||||
const uniqueModules = computed(() => {
|
||||
return [...new Set(endpoints.value.map((e) => e.module))].sort();
|
||||
});
|
||||
|
||||
// 过滤后的端点列表
|
||||
const filteredEndpoints = computed(() => {
|
||||
return endpoints.value.filter((endpoint) => {
|
||||
const matchSearch =
|
||||
!searchText.value ||
|
||||
endpoint.endpoint_key.toLowerCase().includes(searchText.value.toLowerCase()) ||
|
||||
endpoint.path.toLowerCase().includes(searchText.value.toLowerCase());
|
||||
|
||||
const matchModule = !filterModule.value || endpoint.module === filterModule.value;
|
||||
const matchMethod = !filterMethod.value || endpoint.method === filterMethod.value;
|
||||
|
||||
return matchSearch && matchModule && matchMethod;
|
||||
});
|
||||
});
|
||||
|
||||
// 检查端点是否允许
|
||||
const isEndpointAllowed = (endpoint_key: string, role: string): boolean => {
|
||||
if (!props.matrix || !props.matrix[role]) return false;
|
||||
return props.matrix[role][endpoint_key]?.allowed ?? false;
|
||||
};
|
||||
|
||||
// 获取方法的标签类型
|
||||
const getMethodType = (method: string): string => {
|
||||
const typeMap: Record<string, string> = {
|
||||
GET: "info",
|
||||
POST: "success",
|
||||
PATCH: "warning",
|
||||
DELETE: "danger",
|
||||
};
|
||||
return typeMap[method] || "info";
|
||||
};
|
||||
|
||||
// 权限变更处理
|
||||
const onPermissionChange = (endpoint_key: string, role: string, allowed: boolean) => {
|
||||
if (!props.matrix) return;
|
||||
|
||||
const updatedMatrix: ApiEndpointPermissionsResponse = JSON.parse(JSON.stringify(props.matrix));
|
||||
|
||||
if (!updatedMatrix[role]) {
|
||||
updatedMatrix[role] = {};
|
||||
}
|
||||
|
||||
updatedMatrix[role][endpoint_key] = { allowed };
|
||||
|
||||
emit("update", updatedMatrix);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.api-permissions {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.api-permissions-toolbar {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.api-permissions-table-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.endpoint-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
:deep(.el-tag) {
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.endpoint-path {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user