mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
Merge pull request #6659 from ouchihao/codex/issue-77-remove-inactive-groups
fix(accounts): allow removing inactive group assignments
This commit is contained in:
@@ -2851,7 +2851,7 @@
|
||||
<!-- Group Selection - 仅标准模式显示 -->
|
||||
<GroupSelector
|
||||
v-model="form.group_ids"
|
||||
:groups="groups"
|
||||
:groups="selectableGroups"
|
||||
:platform="account?.platform"
|
||||
:mixed-scheduling="mixedScheduling"
|
||||
data-tour="account-form-groups"
|
||||
@@ -2921,6 +2921,7 @@ import type {
|
||||
Account,
|
||||
Proxy,
|
||||
AdminGroup,
|
||||
Group,
|
||||
CheckMixedChannelResponse,
|
||||
OpenAICompactMode,
|
||||
OpenAIResponsesMode,
|
||||
@@ -3011,6 +3012,17 @@ const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
const browserTimeZone = getBrowserTimeZone()
|
||||
|
||||
const selectableGroups = computed(() => {
|
||||
const groups = new Map<number, Group>(props.groups.map(group => [group.id, group]))
|
||||
const assignedIds = new Set(props.account?.group_ids ?? [])
|
||||
for (const group of props.account?.groups ?? []) {
|
||||
if (assignedIds.has(group.id) && !groups.has(group.id)) {
|
||||
groups.set(group.id, group)
|
||||
}
|
||||
}
|
||||
return Array.from(groups.values())
|
||||
})
|
||||
|
||||
// Spark 影子账号(parent_account_id 非空):代理恒继承母账号,不可独立编辑(外审 B/P1),
|
||||
// 故隐藏代理选择器。
|
||||
const isSparkShadow = computed(() => props.account?.parent_account_id != null)
|
||||
|
||||
@@ -302,7 +302,7 @@ function buildOpenAIOAuthParentAccount() {
|
||||
} as any
|
||||
}
|
||||
|
||||
function mountModal(account = buildAccount()) {
|
||||
function mountModal(account = buildAccount(), renderGroupSelector = false) {
|
||||
return mount(EditAccountModal, {
|
||||
props: {
|
||||
show: true,
|
||||
@@ -316,7 +316,7 @@ function mountModal(account = buildAccount()) {
|
||||
Select: SelectStub,
|
||||
Icon: true,
|
||||
ProxySelector: true,
|
||||
GroupSelector: GroupSelectorStub,
|
||||
GroupSelector: renderGroupSelector ? false : GroupSelectorStub,
|
||||
ModelWhitelistSelector: ModelWhitelistSelectorStub
|
||||
}
|
||||
}
|
||||
@@ -372,6 +372,52 @@ describe('EditAccountModal', () => {
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('allows removing assigned inactive groups and undoing the selection before saving', async () => {
|
||||
authIsSimpleMode.value = false
|
||||
const account = buildAccount()
|
||||
const activeGroup = {
|
||||
id: 1,
|
||||
name: 'Active group',
|
||||
platform: 'openai',
|
||||
status: 'active',
|
||||
subscription_type: 'standard',
|
||||
rate_multiplier: 1
|
||||
}
|
||||
const inactiveGroup = { ...activeGroup, id: 2, name: 'Paused group', status: 'inactive' }
|
||||
account.group_ids = [1, 2]
|
||||
account.groups = [
|
||||
{ ...activeGroup, name: 'Outdated name' },
|
||||
inactiveGroup,
|
||||
inactiveGroup,
|
||||
{ ...inactiveGroup, id: 3, name: 'Unassigned paused group' }
|
||||
]
|
||||
updateAccountMock.mockReset()
|
||||
checkMixedChannelRiskMock.mockReset()
|
||||
checkMixedChannelRiskMock.mockResolvedValue({ has_risk: false })
|
||||
updateAccountMock.mockResolvedValue(account)
|
||||
|
||||
const wrapper = mountModal(account, true)
|
||||
await wrapper.setProps({ groups: [activeGroup] as any })
|
||||
const selector = wrapper.get('[data-tour="account-form-groups"]')
|
||||
expect(selector.findAll('input[type="checkbox"]').map(input => input.attributes('value')))
|
||||
.toEqual(['1', '2'])
|
||||
expect(selector.text()).toContain('Active group')
|
||||
expect(selector.text()).not.toContain('Outdated name')
|
||||
const pausedCheckbox = selector.get<HTMLInputElement>('input[value="2"]')
|
||||
expect(pausedCheckbox.element.checked).toBe(true)
|
||||
|
||||
await pausedCheckbox.setValue(false)
|
||||
expect(selector.get<HTMLInputElement>('input[value="2"]').element.checked).toBe(false)
|
||||
await pausedCheckbox.setValue(true)
|
||||
expect(pausedCheckbox.element.checked).toBe(true)
|
||||
await pausedCheckbox.setValue(false)
|
||||
await wrapper.get('form#edit-account-form').trigger('submit.prevent')
|
||||
|
||||
expect(updateAccountMock).toHaveBeenCalledTimes(1)
|
||||
expect(updateAccountMock.mock.calls[0]?.[1]?.group_ids).toEqual([1])
|
||||
expect(account.group_ids).toEqual([1, 2])
|
||||
})
|
||||
|
||||
it('reopening the same account rehydrates the OpenAI whitelist from props', async () => {
|
||||
const account = buildAccount()
|
||||
updateAccountMock.mockReset()
|
||||
|
||||
@@ -61,7 +61,7 @@ import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import GroupBadge from './GroupBadge.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import type { AdminGroup, GroupPlatform } from '@/types'
|
||||
import type { Group, GroupPlatform } from '@/types'
|
||||
import { useAuthStore } from '@/stores'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -69,7 +69,7 @@ const authStore = useAuthStore()
|
||||
|
||||
interface Props {
|
||||
modelValue: number[]
|
||||
groups: AdminGroup[]
|
||||
groups: (Group & { account_count?: number })[]
|
||||
platform?: GroupPlatform // Optional platform filter
|
||||
mixedScheduling?: boolean // For antigravity accounts: allow anthropic/gemini groups
|
||||
searchable?: boolean | 'auto'
|
||||
@@ -91,7 +91,7 @@ const isSearchable = computed(() => {
|
||||
|
||||
// Filter groups by platform if specified
|
||||
const filteredGroups = computed(() => {
|
||||
let result: AdminGroup[] = authStore.isSimpleMode
|
||||
let result = authStore.isSimpleMode
|
||||
? props.groups.filter((g) => g.platform !== 'composite')
|
||||
: props.groups
|
||||
if (props.platform) {
|
||||
|
||||
Reference in New Issue
Block a user