diff --git a/app/gateway-api/src/adminRouter.ts b/app/gateway-api/src/adminRouter.ts index acc9ed3b..1a8b665d 100644 --- a/app/gateway-api/src/adminRouter.ts +++ b/app/gateway-api/src/adminRouter.ts @@ -464,6 +464,24 @@ const zInstallOptions = z.object({ gitRef: z.string().min(1).max(128).optional(), }); const zOperationInstallOptions = zInstallOptions.omit({ gitRef: true }); +const zProfileResetDefaults = zInstallOptions.omit({ + scenarioId: true, + openAt: true, + preopenAt: true, + gitRef: true, +}); +const SYSTEM_PROFILE_RESET_DEFAULTS: z.infer = { + turnTermMinutes: 60, + sync: true, + fiction: 1, + extend: true, + blockGeneralCreate: 0, + npcMode: 0, + showImgLevel: 3, + tournamentTrig: true, + joinMode: 'full', + autorunUser: null, +}; const zSourceMode = z.enum(['BRANCH', 'COMMIT']); const zResetSourceMode = z.enum(['CURRENT', 'BRANCH', 'COMMIT']); @@ -533,6 +551,16 @@ const readMetaObject = (value: unknown): Record => { return value as Record; }; +const readProfileResetDefaults = ( + meta: Record +): { defaults: z.infer; source: 'SYSTEM' | 'PROFILE' } => { + const parsed = zProfileResetDefaults.partial().safeParse(meta.resetDefaults); + if (meta.resetDefaults === undefined || !parsed.success) { + return { defaults: { ...SYSTEM_PROFILE_RESET_DEFAULTS }, source: 'SYSTEM' }; + } + return { defaults: { ...SYSTEM_PROFILE_RESET_DEFAULTS, ...parsed.data }, source: 'PROFILE' }; +}; + const applyMetaPatch = ( meta: Record, patch: Record @@ -1474,6 +1502,18 @@ export const adminRouter = router({ }), }), profiles: router({ + getResetDefaults: adminProcedure + .input(z.object({ profileName: z.string().min(1) })) + .query(async ({ ctx, input }) => { + const adminAuth = requireAdminAuth(ctx); + assertAnyPermission(adminAuth, [ROLE_ADMIN_PROFILES, ROLE_ADMIN_SCENARIO_RESET], input.profileName); + const profile = await ctx.profiles.getProfile(input.profileName); + if (!profile) { + throw new TRPCError({ code: 'NOT_FOUND', message: 'Profile not found.' }); + } + const meta = readMetaObject(profile.meta); + return readProfileResetDefaults(meta); + }), listNavigation: adminProcedure.query(async ({ ctx }) => { const adminAuth = requireAdminAuth(ctx); return orderGatewayProfiles(await ctx.profiles.listProfiles()) @@ -1660,6 +1700,7 @@ export const adminRouter = router({ nextSeasonIdx: z.number().int().min(0).nullable().optional(), localAccountAccessGraceDays: z.number().int().min(0).max(365).nullable().optional(), localAccountGeneralCreationGraceDays: z.number().int().min(0).max(365).nullable().optional(), + resetDefaults: zProfileResetDefaults.nullable().optional(), }), reason: z.string().trim().min(3).max(200), }) diff --git a/app/gateway-api/test/adminOperations.test.ts b/app/gateway-api/test/adminOperations.test.ts index 47259749..092db6eb 100644 --- a/app/gateway-api/test/adminOperations.test.ts +++ b/app/gateway-api/test/adminOperations.test.ts @@ -29,6 +29,7 @@ const buildCaller = async ( initialNotice?: string; initialProfileStatus?: GatewayProfileRecord['status']; profileScenario?: string; + profileMeta?: GatewayProfileRecord['meta']; releaseLogVisibilityAfterPolls?: number; } = {} ) => { @@ -75,7 +76,7 @@ const buildCaller = async ( status: options.initialProfileStatus ?? ('STOPPED' as const), buildStatus: 'SUCCEEDED' as const, buildCommitSha: 'HEAD', - meta: {}, + meta: options.profileMeta ?? {}, createdAt: '2026-07-25T00:00:00.000Z', updatedAt: '2026-07-25T00:00:00.000Z', }; @@ -511,6 +512,102 @@ describe('admin operation API', () => { }); }); + it('returns validated profile reset defaults to a scenario-only operator', async () => { + const harness = await buildCaller( + async () => { + throw new Error('not used'); + }, + { + adminRoles: ['admin.scenarios.reset:che:2'], + firstUserIsAdmin: false, + profileMeta: { + resetDefaults: { + turnTermMinutes: 20, + sync: false, + fiction: 0, + extend: false, + blockGeneralCreate: 2, + npcMode: 1, + showImgLevel: 1, + tournamentTrig: false, + joinMode: 'onlyRandom', + autorunUser: { limitMinutes: 720, options: ['develop', 'train'] }, + }, + }, + } + ); + + await expect(harness.caller.admin.profiles.getResetDefaults({ profileName: 'che:2' })).resolves.toEqual({ + source: 'PROFILE', + defaults: { + turnTermMinutes: 20, + sync: false, + fiction: 0, + extend: false, + blockGeneralCreate: 2, + npcMode: 1, + showImgLevel: 1, + tournamentTrig: false, + joinMode: 'onlyRandom', + autorunUser: { limitMinutes: 720, options: ['develop', 'train'] }, + }, + }); + }); + + it('falls back to system reset defaults when profile metadata is malformed', async () => { + const harness = await buildCaller( + async () => { + throw new Error('not used'); + }, + { + adminRoles: ['admin.scenarios.reset:che:2'], + firstUserIsAdmin: false, + profileMeta: { resetDefaults: { npcMode: 99 } }, + } + ); + + await expect(harness.caller.admin.profiles.getResetDefaults({ profileName: 'che:2' })).resolves.toMatchObject({ + source: 'SYSTEM', + defaults: { turnTermMinutes: 60, npcMode: 0, tournamentTrig: true, autorunUser: null }, + }); + }); + + it('stores reset defaults only after validating the complete metadata object', async () => { + const harness = await buildCaller( + async () => { + throw new Error('not used'); + }, + { adminRoles: ['admin.profiles.settings:che:2'], firstUserIsAdmin: false } + ); + const resetDefaults = { + turnTermMinutes: 10, + sync: true, + fiction: 1 as const, + extend: true, + blockGeneralCreate: 0 as const, + npcMode: 2 as const, + showImgLevel: 3 as const, + tournamentTrig: true, + joinMode: 'full' as const, + autorunUser: null, + }; + + await harness.caller.admin.profiles.updateMeta({ + profileName: 'che:2', + patch: { resetDefaults }, + reason: 'set server reset defaults', + }); + expect(harness.updatedMetas.at(-1)).toMatchObject({ resetDefaults }); + + await expect( + harness.caller.admin.profiles.updateMeta({ + profileName: 'che:2', + patch: { resetDefaults: { ...resetDefaults, turnTermMinutes: 7 } }, + reason: 'reject invalid reset defaults', + }) + ).rejects.toBeDefined(); + }); + it('does not let a scenario-only operator combine a Git update with reset', async () => { const harness = await buildCaller( async () => { diff --git a/app/gateway-frontend/e2e/server-operations.spec.ts b/app/gateway-frontend/e2e/server-operations.spec.ts index ad8dd4c7..b646102f 100644 --- a/app/gateway-frontend/e2e/server-operations.spec.ts +++ b/app/gateway-frontend/e2e/server-operations.spec.ts @@ -40,9 +40,10 @@ type FixtureState = { profileNavigationRequests?: number; profileNavigationResolved?: boolean; scenarioFailuresRemaining?: number; + resetDefaults?: Record; }; -const profile = (runtimeRunning: boolean) => ({ +const profile = (runtimeRunning: boolean, resetDefaults?: Record) => ({ profileName: 'che:2', profile: 'che', scenario: '2', @@ -51,9 +52,11 @@ const profile = (runtimeRunning: boolean) => ({ buildStatus: 'SUCCEEDED', buildCommitSha: '0123456789abcdef0123456789abcdef01234567', buildWorkspace: '/srv/sammo/worktrees/0123456789abcdef0123456789abcdef01234567', - meta: {}, + meta: resetDefaults ? { resetDefaults } : {}, createdAt: '2026-07-25T00:00:00.000Z', updatedAt: '2026-07-25T00:00:00.000Z', + activeOperation: null, + runtimeActions: [], runtime: { profileName: 'che:2', frontendRunning: runtimeRunning, @@ -132,7 +135,7 @@ const installFixture = async (page: Page, state: FixtureState) => { state.requestBodies.push({ operation: name, body }); } if (name === 'admin.profiles.list') { - return response([profile(state.runtimeRunning)]); + return response([profile(state.runtimeRunning, state.resetDefaults)]); } if (name === 'admin.profiles.listNavigation') { return response([ @@ -206,6 +209,26 @@ const installFixture = async (page: Page, state: FixtureState) => { expect(names).toEqual(['admin.profiles.listScenarios']); return response(scenarios); } + if (name === 'admin.profiles.getResetDefaults') { + return response({ + source: state.resetDefaults ? 'PROFILE' : 'SYSTEM', + defaults: state.resetDefaults ?? { + turnTermMinutes: 60, + sync: true, + fiction: 1, + extend: true, + blockGeneralCreate: 0, + npcMode: 0, + showImgLevel: 3, + tournamentTrig: true, + joinMode: 'full', + autorunUser: null, + }, + }); + } + if (name === 'admin.profiles.updateMeta') { + return response(profile(state.runtimeRunning, state.resetDefaults)); + } if (name === 'admin.operations.requestReset') { const operation: Operation = { id: '11111111-1111-4111-8111-111111111111', @@ -436,6 +459,68 @@ test('separates DB-preserving profile deployment from DB reset', async ({ page } expect(state.requestBodies.some((entry) => entry.operation === 'admin.operations.requestReset')).toBe(false); }); +test('loads server metadata defaults into the reset form and submits them', async ({ page }) => { + const state: FixtureState = { + operations: [], + gatewayOperations: [], + runtimeRunning: true, + requestBodies: [], + resetDefaults: { + turnTermMinutes: 20, + sync: false, + fiction: 0, + extend: false, + blockGeneralCreate: 2, + npcMode: 1, + showImgLevel: 1, + tournamentTrig: false, + joinMode: 'onlyRandom', + autorunUser: { limitMinutes: 720, options: ['develop', 'train'] }, + }, + }; + await installFixture(page, state); + page.on('dialog', (dialog) => dialog.accept()); + + await page.goto('admin/servers/che%3A2/scenario'); + await expect(page.getByTestId('reset-turn-term')).toHaveValue('20'); + await page.getByText('고급 시나리오 옵션').click(); + await expect(page.getByTestId('reset-defaults-source')).toContainText('서버의 메타'); + await expect(page.getByTestId('reset-npc-mode')).toHaveValue('1'); + await page.getByTestId('request-reset').click(); + + await expect + .poll(() => state.requestBodies.find((entry) => entry.operation === 'admin.operations.requestReset')) + .toBeTruthy(); + const request = JSON.stringify( + state.requestBodies.find((entry) => entry.operation === 'admin.operations.requestReset')?.body + ); + expect(request).toContain('"turnTermMinutes":20'); + expect(request).toContain('"npcMode":1'); + expect(request).toContain('"joinMode":"onlyRandom"'); + expect(request).toContain('"limitMinutes":720'); + expect(request).toContain('"options":["develop","train"]'); +}); + +test('edits server reset defaults through profile metadata settings', async ({ page }) => { + const state: FixtureState = { operations: [], gatewayOperations: [], runtimeRunning: true, requestBodies: [] }; + await installFixture(page, state); + + await page.goto('admin/servers/che%3A2'); + await page.getByText('서버 리셋 기본 옵션').click(); + await page.getByTestId('meta-reset-turn-term').selectOption('10'); + await page.getByTestId('meta-reset-npc-mode').selectOption('2'); + await page.getByPlaceholder('변경 사유 (필수)').fill('set reset defaults'); + await page.getByRole('button', { name: '메타 저장' }).click(); + + await expect(page.getByText('메타 저장 완료')).toBeVisible(); + const request = JSON.stringify( + state.requestBodies.find((entry) => entry.operation === 'admin.profiles.updateMeta')?.body + ); + expect(request).toContain('"resetDefaults"'); + expect(request).toContain('"turnTermMinutes":10'); + expect(request).toContain('"npcMode":2'); +}); + test('renders the fixed-profile version form without waiting for the server list', async ({ page }) => { const state: FixtureState = { operations: [], diff --git a/app/gateway-frontend/src/utils/resetDefaults.ts b/app/gateway-frontend/src/utils/resetDefaults.ts new file mode 100644 index 00000000..669a5fff --- /dev/null +++ b/app/gateway-frontend/src/utils/resetDefaults.ts @@ -0,0 +1,74 @@ +export const RESET_AUTORUN_OPTIONS = ['develop', 'warp', 'recruit', 'train', 'battle'] as const; + +export type ResetAutorunOption = (typeof RESET_AUTORUN_OPTIONS)[number]; + +export type ProfileResetDefaults = { + turnTermMinutes: number; + sync: boolean; + fiction: 0 | 1; + extend: boolean; + blockGeneralCreate: 0 | 1 | 2; + npcMode: 0 | 1 | 2; + showImgLevel: 0 | 1 | 2 | 3; + tournamentTrig: boolean; + joinMode: 'full' | 'onlyRandom'; + autorunUser: { + limitMinutes: number; + options: ResetAutorunOption[]; + } | null; +}; + +export const SYSTEM_PROFILE_RESET_DEFAULTS: ProfileResetDefaults = { + turnTermMinutes: 60, + sync: true, + fiction: 1, + extend: true, + blockGeneralCreate: 0, + npcMode: 0, + showImgLevel: 3, + tournamentTrig: true, + joinMode: 'full', + autorunUser: null, +}; + +const isRecord = (value: unknown): value is Record => + Boolean(value && typeof value === 'object' && !Array.isArray(value)); + +const enumNumber = (value: unknown, allowed: readonly T[], fallback: T): T => + typeof value === 'number' && allowed.includes(value as T) ? (value as T) : fallback; + +export const normalizeProfileResetDefaults = (value: unknown): ProfileResetDefaults => { + const raw = isRecord(value) ? value : {}; + const rawAutorun = isRecord(raw.autorunUser) ? raw.autorunUser : null; + const autorunOptions = Array.isArray(rawAutorun?.options) + ? rawAutorun.options.filter((option): option is ResetAutorunOption => + RESET_AUTORUN_OPTIONS.includes(option as ResetAutorunOption) + ) + : []; + const autorunLimit = rawAutorun?.limitMinutes; + + return { + turnTermMinutes: enumNumber(raw.turnTermMinutes, [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 24, 30, 40, 60, 120], 60), + sync: typeof raw.sync === 'boolean' ? raw.sync : SYSTEM_PROFILE_RESET_DEFAULTS.sync, + fiction: enumNumber(raw.fiction, [0, 1], SYSTEM_PROFILE_RESET_DEFAULTS.fiction), + extend: typeof raw.extend === 'boolean' ? raw.extend : SYSTEM_PROFILE_RESET_DEFAULTS.extend, + blockGeneralCreate: enumNumber( + raw.blockGeneralCreate, + [0, 1, 2], + SYSTEM_PROFILE_RESET_DEFAULTS.blockGeneralCreate + ), + npcMode: enumNumber(raw.npcMode, [0, 1, 2], SYSTEM_PROFILE_RESET_DEFAULTS.npcMode), + showImgLevel: enumNumber(raw.showImgLevel, [0, 1, 2, 3], SYSTEM_PROFILE_RESET_DEFAULTS.showImgLevel), + tournamentTrig: + typeof raw.tournamentTrig === 'boolean' ? raw.tournamentTrig : SYSTEM_PROFILE_RESET_DEFAULTS.tournamentTrig, + joinMode: raw.joinMode === 'onlyRandom' ? 'onlyRandom' : 'full', + autorunUser: + typeof autorunLimit === 'number' && + Number.isInteger(autorunLimit) && + autorunLimit > 0 && + autorunLimit <= 43200 && + autorunOptions.length > 0 + ? { limitMinutes: autorunLimit, options: autorunOptions } + : null, + }; +}; diff --git a/app/gateway-frontend/src/views/AdminView.vue b/app/gateway-frontend/src/views/AdminView.vue index 88f97d3a..189dfef7 100644 --- a/app/gateway-frontend/src/views/AdminView.vue +++ b/app/gateway-frontend/src/views/AdminView.vue @@ -2,6 +2,11 @@ import { computed, onMounted, ref } from 'vue'; import ServerProfileTabs from '../components/ServerProfileTabs.vue'; import AdminConsoleLayout from '../layouts/AdminConsoleLayout.vue'; +import { + normalizeProfileResetDefaults, + type ProfileResetDefaults, + type ResetAutorunOption, +} from '../utils/resetDefaults'; import { trpc } from '../utils/trpc'; type AdminSection = 'users' | 'servers' | 'system' | 'audit'; @@ -325,6 +330,7 @@ type AdminClient = { nextSeasonIdx?: number | null; localAccountAccessGraceDays?: number | null; localAccountGeneralCreationGraceDays?: number | null; + resetDefaults?: ProfileResetDefaults | null; }; reason: string; }) => Promise; @@ -379,6 +385,8 @@ const profileEdits = ref< nextSeasonIdx: string; localAccountAccessGraceDays: string; localAccountGeneralCreationGraceDays: string; + resetDefaults: ProfileResetDefaults; + resetAutorunEnabled: boolean; reason: string; } > @@ -393,6 +401,13 @@ const profileActions = ref< } > >({}); +const resetAutorunLabels: Array<{ value: ResetAutorunOption; label: string }> = [ + { value: 'develop', label: '내정' }, + { value: 'warp', label: '이동' }, + { value: 'recruit', label: '징병' }, + { value: 'train', label: '훈련' }, + { value: 'battle', label: '전투' }, +]; const profileActionStatus = ref>({}); const profileActionSubmitting = ref>({}); const visibleProfiles = computed(() => @@ -543,6 +558,7 @@ const saveNotice = async () => { const ensureProfileBuffers = (profile: AdminProfile) => { if (!profileEdits.value[profile.profileName]) { const meta = (profile.meta ?? {}) as Record; + const resetDefaults = normalizeProfileResetDefaults(meta.resetDefaults); profileEdits.value[profile.profileName] = { korName: String(meta.korName ?? profile.profile), color: String(meta.color ?? '#ffffff'), @@ -560,6 +576,8 @@ const ensureProfileBuffers = (profile: AdminProfile) => { typeof meta.localAccountGeneralCreationGraceDays === 'number' ? String(Math.floor(meta.localAccountGeneralCreationGraceDays)) : '', + resetDefaults, + resetAutorunEnabled: resetDefaults.autorunUser !== null, reason: '', }; } @@ -663,6 +681,19 @@ const updateProfileMeta = async (profileName: string) => { profileActionStatus.value = { ...profileActionStatus.value, [profileName]: '변경 사유를 입력하세요.' }; return; } + if ( + edit.resetAutorunEnabled && + (!edit.resetDefaults.autorunUser || + edit.resetDefaults.autorunUser.limitMinutes <= 0 || + edit.resetDefaults.autorunUser.limitMinutes > 43200 || + edit.resetDefaults.autorunUser.options.length === 0) + ) { + profileActionStatus.value = { + ...profileActionStatus.value, + [profileName]: '유저 자동턴은 제한 시간과 한 개 이상의 동작을 선택해야 합니다.', + }; + return; + } const patch = { korName: edit.korName.trim() || null, color: edit.color.trim() || null, @@ -671,6 +702,10 @@ const updateProfileMeta = async (profileName: string) => { nextSeasonIdx: nextSeasonIdx === null ? null : Math.floor(nextSeasonIdx), localAccountAccessGraceDays: accessGraceDays, localAccountGeneralCreationGraceDays: creationGraceDays, + resetDefaults: { + ...edit.resetDefaults, + autorunUser: edit.resetAutorunEnabled ? edit.resetDefaults.autorunUser : null, + }, }; try { const updated = await adminClient.profiles.updateMeta.mutate({ @@ -695,6 +730,15 @@ const updateProfileMeta = async (profileName: string) => { } }; +const ensureResetAutorun = (profileName: string) => { + const edit = profileEdits.value[profileName]; + if (!edit?.resetAutorunEnabled || edit.resetDefaults.autorunUser) return; + edit.resetDefaults.autorunUser = { + limitMinutes: 1440, + options: resetAutorunLabels.map(({ value }) => value), + }; +}; + const requestProfileAction = async (profileName: string, action: AdminAction) => { if (profileActionSubmitting.value[profileName]) { return; @@ -2035,6 +2079,168 @@ onMounted(() => { placeholder="예: 12" />
리셋 시 적용할 시즌 번호를 지정합니다.
+
+ + 서버 리셋 기본 옵션 + +

+ 이 서버의 시나리오 초기화 화면을 열 때 자동으로 채울 값을 저장합니다. + 시나리오와 예약·오픈 시각은 실행할 때 선택합니다. +

+
+ + + + + + + + + + + +
+
('SYSTEM'); let pollTimer: ReturnType | undefined; let stateRequestInFlight = false; let releaseLogLoopGeneration = 0; @@ -99,15 +105,15 @@ const form = reactive({ sourceMode: (props.mode === 'scenario' ? 'CURRENT' : 'BRANCH') as 'CURRENT' | 'BRANCH' | 'COMMIT', sourceRef: 'main', scenarioId: null as number | null, - turnTermMinutes: 60, - sync: true, - fiction: 1, - extend: true, - blockGeneralCreate: 0, - npcMode: 0, - showImgLevel: 3, - tournamentTrig: true, - joinMode: 'full' as 'full' | 'onlyRandom', + turnTermMinutes: SYSTEM_PROFILE_RESET_DEFAULTS.turnTermMinutes, + sync: SYSTEM_PROFILE_RESET_DEFAULTS.sync, + fiction: SYSTEM_PROFILE_RESET_DEFAULTS.fiction, + extend: SYSTEM_PROFILE_RESET_DEFAULTS.extend, + blockGeneralCreate: SYSTEM_PROFILE_RESET_DEFAULTS.blockGeneralCreate, + npcMode: SYSTEM_PROFILE_RESET_DEFAULTS.npcMode, + showImgLevel: SYSTEM_PROFILE_RESET_DEFAULTS.showImgLevel, + tournamentTrig: SYSTEM_PROFILE_RESET_DEFAULTS.tournamentTrig, + joinMode: SYSTEM_PROFILE_RESET_DEFAULTS.joinMode, autorunEnabled: false, autorunUserMinutes: 1440, autorunDevelop: true, @@ -177,7 +183,12 @@ const toIso = (value: string): string | undefined => { const formatTime = (value?: string): string => (value ? new Date(value).toLocaleString('ko-KR') : '-'); const formatLogTime = (value: string): string => - new Date(value).toLocaleTimeString('ko-KR', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }); + new Date(value).toLocaleTimeString('ko-KR', { + hour12: false, + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }); const shortSha = (value?: string): string => (value ? value.slice(0, 12) : '-'); const clearStatus = () => { @@ -185,6 +196,40 @@ const clearStatus = () => { errorMessage.value = ''; }; +const applyResetDefaults = (defaults: ProfileResetDefaults) => { + form.turnTermMinutes = defaults.turnTermMinutes; + form.sync = defaults.sync; + form.fiction = defaults.fiction; + form.extend = defaults.extend; + form.blockGeneralCreate = defaults.blockGeneralCreate; + form.npcMode = defaults.npcMode; + form.showImgLevel = defaults.showImgLevel; + form.tournamentTrig = defaults.tournamentTrig; + form.joinMode = defaults.joinMode; + form.autorunEnabled = defaults.autorunUser !== null; + form.autorunUserMinutes = defaults.autorunUser?.limitMinutes ?? 1440; + const autorunOptions = new Set(defaults.autorunUser?.options ?? []); + form.autorunDevelop = autorunOptions.has('develop'); + form.autorunWarp = autorunOptions.has('warp'); + form.autorunRecruit = autorunOptions.has('recruit'); + form.autorunTrain = autorunOptions.has('train'); + form.autorunBattle = autorunOptions.has('battle'); +}; + +const loadResetDefaults = async () => { + if (props.mode !== 'scenario' || !selectedProfileName.value) return; + try { + const result = await adminClient.profiles.getResetDefaults.query({ + profileName: selectedProfileName.value, + }); + applyResetDefaults(normalizeProfileResetDefaults(result.defaults)); + resetDefaultsSource.value = result.source; + } catch { + applyResetDefaults(SYSTEM_PROFILE_RESET_DEFAULTS); + resetDefaultsSource.value = 'SYSTEM'; + } +}; + const loadCapabilities = async () => { try { capabilities.value = (await adminClient.capabilities.list.query()) as typeof capabilities.value; @@ -244,7 +289,11 @@ const scrollReleaseLogToEnd = async () => { }; const pollGatewayReleaseLogs = async (operationId: string, generation: number) => { - while (componentMounted && generation === releaseLogLoopGeneration && selectedGatewayOperationId.value === operationId) { + while ( + componentMounted && + generation === releaseLogLoopGeneration && + selectedGatewayOperationId.value === operationId + ) { try { const result = await adminClient.releases.logs.query({ id: operationId, @@ -516,6 +565,7 @@ onMounted(async () => { await Promise.all([ loadCapabilities(), loadState(), + loadResetDefaults(), props.mode === 'scenario' ? loadScenarios() : Promise.resolve(), ]); pollTimer = setInterval(() => void loadState(true), 3000); @@ -671,9 +721,10 @@ onBeforeUnmount(() => { +