fix(groups): 用户分组不下发内部路由信息
- 普通用户 Group DTO 移除 model_routing/account_count/account_groups,避免泄露内部路由与账号信息\n- 新增 AdminGroup DTO,并仅在管理员分组接口返回完整字段\n- 前端拆分 Group/AdminGroup,管理端页面与 API 使用 AdminGroup\n- 增加 /api/v1/groups/available 契约测试,防止回归
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type {
|
||||
Group,
|
||||
AdminGroup,
|
||||
GroupPlatform,
|
||||
CreateGroupRequest,
|
||||
UpdateGroupRequest,
|
||||
@@ -31,8 +31,8 @@ export async function list(
|
||||
options?: {
|
||||
signal?: AbortSignal
|
||||
}
|
||||
): Promise<PaginatedResponse<Group>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<Group>>('/admin/groups', {
|
||||
): Promise<PaginatedResponse<AdminGroup>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<AdminGroup>>('/admin/groups', {
|
||||
params: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
@@ -48,8 +48,8 @@ export async function list(
|
||||
* @param platform - Optional platform filter
|
||||
* @returns List of all active groups
|
||||
*/
|
||||
export async function getAll(platform?: GroupPlatform): Promise<Group[]> {
|
||||
const { data } = await apiClient.get<Group[]>('/admin/groups/all', {
|
||||
export async function getAll(platform?: GroupPlatform): Promise<AdminGroup[]> {
|
||||
const { data } = await apiClient.get<AdminGroup[]>('/admin/groups/all', {
|
||||
params: platform ? { platform } : undefined
|
||||
})
|
||||
return data
|
||||
@@ -60,7 +60,7 @@ export async function getAll(platform?: GroupPlatform): Promise<Group[]> {
|
||||
* @param platform - Platform to filter by
|
||||
* @returns List of groups for the specified platform
|
||||
*/
|
||||
export async function getByPlatform(platform: GroupPlatform): Promise<Group[]> {
|
||||
export async function getByPlatform(platform: GroupPlatform): Promise<AdminGroup[]> {
|
||||
return getAll(platform)
|
||||
}
|
||||
|
||||
@@ -69,8 +69,8 @@ export async function getByPlatform(platform: GroupPlatform): Promise<Group[]> {
|
||||
* @param id - Group ID
|
||||
* @returns Group details
|
||||
*/
|
||||
export async function getById(id: number): Promise<Group> {
|
||||
const { data } = await apiClient.get<Group>(`/admin/groups/${id}`)
|
||||
export async function getById(id: number): Promise<AdminGroup> {
|
||||
const { data } = await apiClient.get<AdminGroup>(`/admin/groups/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -79,8 +79,8 @@ export async function getById(id: number): Promise<Group> {
|
||||
* @param groupData - Group data
|
||||
* @returns Created group
|
||||
*/
|
||||
export async function create(groupData: CreateGroupRequest): Promise<Group> {
|
||||
const { data } = await apiClient.post<Group>('/admin/groups', groupData)
|
||||
export async function create(groupData: CreateGroupRequest): Promise<AdminGroup> {
|
||||
const { data } = await apiClient.post<AdminGroup>('/admin/groups', groupData)
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -90,8 +90,8 @@ export async function create(groupData: CreateGroupRequest): Promise<Group> {
|
||||
* @param updates - Fields to update
|
||||
* @returns Updated group
|
||||
*/
|
||||
export async function update(id: number, updates: UpdateGroupRequest): Promise<Group> {
|
||||
const { data } = await apiClient.put<Group>(`/admin/groups/${id}`, updates)
|
||||
export async function update(id: number, updates: UpdateGroupRequest): Promise<AdminGroup> {
|
||||
const { data } = await apiClient.put<AdminGroup>(`/admin/groups/${id}`, updates)
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export async function deleteGroup(id: number): Promise<{ message: string }> {
|
||||
* @param status - New status
|
||||
* @returns Updated group
|
||||
*/
|
||||
export async function toggleStatus(id: number, status: 'active' | 'inactive'): Promise<Group> {
|
||||
export async function toggleStatus(id: number, status: 'active' | 'inactive'): Promise<AdminGroup> {
|
||||
return update(id, { status })
|
||||
}
|
||||
|
||||
|
||||
@@ -648,7 +648,7 @@ import { ref, watch, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
import type { Proxy, Group } from '@/types'
|
||||
import type { Proxy, AdminGroup } from '@/types'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import ProxySelector from '@/components/common/ProxySelector.vue'
|
||||
@@ -659,7 +659,7 @@ interface Props {
|
||||
show: boolean
|
||||
accountIds: number[]
|
||||
proxies: Proxy[]
|
||||
groups: Group[]
|
||||
groups: AdminGroup[]
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
@@ -1816,7 +1816,7 @@ import {
|
||||
import { useOpenAIOAuth } from '@/composables/useOpenAIOAuth'
|
||||
import { useGeminiOAuth } from '@/composables/useGeminiOAuth'
|
||||
import { useAntigravityOAuth } from '@/composables/useAntigravityOAuth'
|
||||
import type { Proxy, Group, AccountPlatform, AccountType } from '@/types'
|
||||
import type { Proxy, AdminGroup, AccountPlatform, AccountType } from '@/types'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import ProxySelector from '@/components/common/ProxySelector.vue'
|
||||
@@ -1862,7 +1862,7 @@ const apiKeyHint = computed(() => {
|
||||
interface Props {
|
||||
show: boolean
|
||||
proxies: Proxy[]
|
||||
groups: Group[]
|
||||
groups: AdminGroup[]
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
@@ -883,7 +883,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
import type { Account, Proxy, Group } from '@/types'
|
||||
import type { Account, Proxy, AdminGroup } from '@/types'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
@@ -901,7 +901,7 @@ interface Props {
|
||||
show: boolean
|
||||
account: Account | null
|
||||
proxies: Proxy[]
|
||||
groups: Group[]
|
||||
groups: AdminGroup[]
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
@@ -42,13 +42,13 @@
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import GroupBadge from './GroupBadge.vue'
|
||||
import type { Group, GroupPlatform } from '@/types'
|
||||
import type { AdminGroup, GroupPlatform } from '@/types'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
interface Props {
|
||||
modelValue: number[]
|
||||
groups: Group[]
|
||||
groups: AdminGroup[]
|
||||
platform?: GroupPlatform // Optional platform filter
|
||||
mixedScheduling?: boolean // For antigravity accounts: allow anthropic/gemini groups
|
||||
}
|
||||
|
||||
@@ -269,14 +269,19 @@ export interface Group {
|
||||
// Claude Code 客户端限制
|
||||
claude_code_only: boolean
|
||||
fallback_group_id: number | null
|
||||
// 模型路由配置(仅 anthropic 平台使用)
|
||||
model_routing: Record<string, number[]> | null
|
||||
model_routing_enabled: boolean
|
||||
account_count?: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface AdminGroup extends Group {
|
||||
// 模型路由配置(仅管理员可见,内部信息)
|
||||
model_routing: Record<string, number[]> | null
|
||||
model_routing_enabled: boolean
|
||||
|
||||
// 分组下账号数量(仅管理员可见)
|
||||
account_count?: number
|
||||
}
|
||||
|
||||
export interface ApiKey {
|
||||
id: number
|
||||
user_id: number
|
||||
|
||||
@@ -187,14 +187,14 @@ import AccountCapacityCell from '@/components/account/AccountCapacityCell.vue'
|
||||
import PlatformTypeBadge from '@/components/common/PlatformTypeBadge.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import { formatDateTime, formatRelativeTime } from '@/utils/format'
|
||||
import type { Account, Proxy, Group } from '@/types'
|
||||
import type { Account, Proxy, AdminGroup } from '@/types'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const proxies = ref<Proxy[]>([])
|
||||
const groups = ref<Group[]>([])
|
||||
const groups = ref<AdminGroup[]>([])
|
||||
const selIds = ref<number[]>([])
|
||||
const showCreate = ref(false)
|
||||
const showEdit = ref(false)
|
||||
|
||||
@@ -1107,7 +1107,7 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useOnboardingStore } from '@/stores/onboarding'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
import type { Group, GroupPlatform, SubscriptionType } from '@/types'
|
||||
import type { AdminGroup, GroupPlatform, SubscriptionType } from '@/types'
|
||||
import type { Column } from '@/components/common/types'
|
||||
import AppLayout from '@/components/layout/AppLayout.vue'
|
||||
import TablePageLayout from '@/components/layout/TablePageLayout.vue'
|
||||
@@ -1202,7 +1202,7 @@ const fallbackGroupOptionsForEdit = computed(() => {
|
||||
return options
|
||||
})
|
||||
|
||||
const groups = ref<Group[]>([])
|
||||
const groups = ref<AdminGroup[]>([])
|
||||
const loading = ref(false)
|
||||
const searchQuery = ref('')
|
||||
const filters = reactive({
|
||||
@@ -1223,8 +1223,8 @@ const showCreateModal = ref(false)
|
||||
const showEditModal = ref(false)
|
||||
const showDeleteDialog = ref(false)
|
||||
const submitting = ref(false)
|
||||
const editingGroup = ref<Group | null>(null)
|
||||
const deletingGroup = ref<Group | null>(null)
|
||||
const editingGroup = ref<AdminGroup | null>(null)
|
||||
const deletingGroup = ref<AdminGroup | null>(null)
|
||||
|
||||
const createForm = reactive({
|
||||
name: '',
|
||||
@@ -1529,7 +1529,7 @@ const handleCreateGroup = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleEdit = async (group: Group) => {
|
||||
const handleEdit = async (group: AdminGroup) => {
|
||||
editingGroup.value = group
|
||||
editForm.name = group.name
|
||||
editForm.description = group.description || ''
|
||||
@@ -1585,7 +1585,7 @@ const handleUpdateGroup = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = (group: Group) => {
|
||||
const handleDelete = (group: AdminGroup) => {
|
||||
deletingGroup.value = group
|
||||
showDeleteDialog.value = true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user