From 0278b5491a8c9d09a07382db9c6c35e8bb011590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Janou=C5=A1ek?= Date: Thu, 24 Sep 2026 19:43:23 +0200 Subject: [PATCH 1/2] fix: expose admin resource overrides beside allocation controls Match legacy VPS and dataset forms by placing allocation exceptions beside resource inputs. Allow explicit retry inside VPS confirmation after an allocation failure, and avoid resending unrelated advanced ZFS properties. Keep overrides admin-only and clear them after a successful save. --- .../app/dataset_management_actions.spec.ts | 39 +++++- .../app/vps_lifecycle_tab_actions.spec.ts | 45 ++++++ e2e/specs/app/vps_storage_tab_mounts.spec.ts | 131 +++++++++--------- src/i18n/locales/cs/storage.ts | 3 +- src/i18n/locales/cs/vps/config.ts | 4 +- src/i18n/locales/cs/vps/storage.ts | 4 +- src/i18n/locales/en/storage.ts | 3 +- src/i18n/locales/en/vps/config.ts | 4 +- src/i18n/locales/en/vps/storage.ts | 4 +- .../app/datasets/DatasetOverviewPage.tsx | 84 +++++------ src/pages/app/vps/VpsConfigurationPage.tsx | 55 +++++--- 11 files changed, 243 insertions(+), 133 deletions(-) diff --git a/e2e/specs/app/dataset_management_actions.spec.ts b/e2e/specs/app/dataset_management_actions.spec.ts index 820c0d3f..a7cd60f0 100644 --- a/e2e/specs/app/dataset_management_actions.spec.ts +++ b/e2e/specs/app/dataset_management_actions.spec.ts @@ -59,6 +59,43 @@ test.describe('Dataset management actions', () => { expect(requestedStates).toContain(null); }); + test('retries a quota allocation failure with visible admin controls and no unrelated ZFS changes', async ({ page }) => { + let updateCalls = 0; + await bootstrapVpsAdminWindow(page, { sessionToken: 'TEST' }); + await installHaveApiMock(page, { + user: { id: 1, login: 'admin', level: 99 }, + handlers: { + 'GET datasets/10': () => ownedDataset(), + 'GET transaction_chains': () => ({ transaction_chains: [] }), + 'PUT datasets/10': () => ++updateCalls === 1 + ? failEnvelope('Resource allocation error: no diskspace left') + : { dataset: ownedDataset({ refquota: 20480 }) }, + }, + }); + await page.goto('/admin/datasets/10'); + const override = page.getByTestId('dataset.manage.admin_override'); + await expect(override).toBeVisible(); + await expect(override).not.toBeChecked(); + await expect(page.getByTestId('dataset.manage.sync')).toBeHidden(); + await page.getByTestId('dataset.manage.refquota').fill('20'); + const first = page.waitForRequest(r => r.method() === 'PUT' && r.url().includes('/datasets/10')); + await page.getByTestId('dataset.manage.edit.submit').click(); + expect((await first).postDataJSON()).toEqual({ dataset: { refquota: 20480 } }); + await expect(page.getByTestId('dataset.manage')).toContainText('no diskspace left'); + await expect(override).not.toBeChecked(); + await override.check(); + await page.getByTestId('dataset.manage.admin_lock_type').selectOption('not_less'); + expect(updateCalls).toBe(1); + const retry = page.waitForRequest(r => r.method() === 'PUT' && r.url().includes('/datasets/10')); + await page.getByTestId('dataset.manage.edit.submit').click(); + expect((await retry).postDataJSON()).toEqual({ + dataset: { refquota: 20480, admin_override: true, admin_lock_type: 'not_less' }, + }); + await expect(override).not.toBeChecked(); + await expect(page.getByTestId('dataset.manage.admin_lock_type')).toHaveValue(''); + await expect(page.getByTestId('dataset.manage.sync')).toBeHidden(); + }); + test('creates, edits, and deletes a dataset from the overview', async ({ page }) => { await bootstrapVpsAdminWindow(page, { sessionToken: 'TEST' }); @@ -277,7 +314,7 @@ test.describe('Dataset management actions', () => { await page.getByTestId('dataset.manage.delete.confirm.confirm').click(); await deleteReq; expect(deleteCalls).toBe(1); - await expect(page).toHaveURL(/\/app\/datasets$/); + await expect(page).toHaveURL(/\/app\/vps\/300\/storage$/); }); test('sends only quota fields and preserves untouched advanced properties on update', async ({ page }) => { diff --git a/e2e/specs/app/vps_lifecycle_tab_actions.spec.ts b/e2e/specs/app/vps_lifecycle_tab_actions.spec.ts index c0e7fd62..862298b5 100644 --- a/e2e/specs/app/vps_lifecycle_tab_actions.spec.ts +++ b/e2e/specs/app/vps_lifecycle_tab_actions.spec.ts @@ -119,6 +119,51 @@ async function installLifecycleMock(page: Page, options?: { } test.describe('@pr-smoke VPS lifecycle tab', () => { + test('retries a resource allocation failure only after an admin explicitly enables override', async ({ page }) => { + let updateCalls = 0; + await bootstrapVpsAdminWindow(page, { sessionToken: 'TEST' }); + await installLifecycleMock(page, { + updateVps: () => ++updateCalls === 1 + ? failEnvelope('Resource allocation error: no Memory left') + : { vps, _meta: { action_state_id: 506 } }, + }); + await page.goto('/admin/vps/123/config'); + await expect(page.getByTestId('vps.config.admin_override')).toBeVisible(); + await expect(page.getByTestId('vps.config.admin_override')).not.toBeChecked(); + await page.getByRole('spinbutton', { name: /^Memory/ }).fill('8192'); + await page.getByTestId('vps.config.header.save').click(); + const dialog = page.getByTestId('vps.config.confirm'); + const first = page.waitForRequest(r => r.method() === 'PUT' && r.url().includes('/vpses/123')); + await dialog.getByTestId('vps.config.confirm.confirm').click(); + expect((await first).postDataJSON()).toEqual({ vps: { memory: 8192 } }); + await expect(dialog.getByTestId('vps.config.confirm.error')).toContainText('no Memory left'); + await expect(dialog.getByTestId('vps.config.confirm.admin_override')).not.toBeChecked(); + await dialog.getByTestId('vps.config.confirm.admin_override').check(); + await expect(dialog.getByTestId('vps.config.confirm.error')).toHaveCount(0); + expect(updateCalls).toBe(1); + await page.screenshot({ path: test.info().outputPath('resource-review.png') }); + const retry = page.waitForRequest(r => r.method() === 'PUT' && r.url().includes('/vpses/123')); + await dialog.getByTestId('vps.config.confirm.confirm').click(); + expect((await retry).postDataJSON()).toEqual({ vps: { memory: 8192, admin_override: true } }); + await expect(dialog).toBeHidden(); + await expect(page.getByTestId('vps.config.admin_override')).not.toBeChecked(); + }); + + test('does not expose resource override to a member', async ({ page }) => { + await bootstrapVpsAdminWindow(page, { sessionToken: 'TEST' }); + await installLifecycleMock(page, { user: { id: 7, login: 'owner', level: 1 } }); + await page.goto('/app/vps/123/config'); + await page.getByRole('spinbutton', { name: /^Memory/ }).fill('4096'); + await expect(page.getByTestId('vps.config.admin_override')).toHaveCount(0); + await page.getByTestId('vps.config.header.save').click(); + const dialog = page.getByTestId('vps.config.confirm'); + await expect(dialog).toBeVisible(); + await expect(dialog.getByTestId('vps.config.confirm.admin_override')).toHaveCount(0); + const request = page.waitForRequest(r => r.method() === 'PUT' && r.url().includes('/vpses/123')); + await dialog.getByTestId('vps.config.confirm.confirm').click(); + expect((await request).postDataJSON()).toEqual({ vps: { memory: 4096 } }); + }); + test('@pr-smoke-mobile keeps rejected VPS configuration saves inside the review dialog and allows retry', async ({ page }) => { let updateCalls = 0; await bootstrapVpsAdminWindow(page, { sessionToken: 'TEST' }); diff --git a/e2e/specs/app/vps_storage_tab_mounts.spec.ts b/e2e/specs/app/vps_storage_tab_mounts.spec.ts index 7594ceb4..00674a63 100644 --- a/e2e/specs/app/vps_storage_tab_mounts.spec.ts +++ b/e2e/specs/app/vps_storage_tab_mounts.spec.ts @@ -50,72 +50,77 @@ function mountItemControl(page: Page, mountId: number, control: 'dataset' | 'del } test.describe('@smoke VPS storage tab mounts', () => { - test('lets an admin resize the VPS root SSD live from the configuration entrypoint', async ({ page }) => { - await bootstrapVpsAdminWindow(page, { sessionToken: 'TEST' }); - let vpsUpdateCount = 0; - - await installHaveApiMock(page, { - user: { id: 1, login: 'admin', level: 99 }, - handlers: { - 'GET vpses/123': () => ({ vps }), - 'GET datasets/10': () => ({ dataset }), - 'GET ip_addresses': () => ({ ip_addresses: [] }), - 'GET transaction_chains': () => ({ transaction_chains: [] }), - 'GET vpses/123/mounts': () => ({ mounts: [] }), - 'GET dns_resolvers': () => ({ dns_resolvers: [] }), - 'GET user_namespace_maps': () => ({ user_namespace_maps: [] }), - 'PUT vpses/123': () => { - vpsUpdateCount += 1; - return { vps }; - }, - 'PUT datasets/10': () => ({ _meta: { action_state_id: 905 } }), - 'GET action_states/905': () => ({ - action_state: { - id: 905, - label: 'Resize root dataset', - status: true, - finished: false, - current: 0, - total: 1, + for (const override of [false, true]) { + test(`lets an admin resize the VPS root SSD live from the configuration entrypoint (override: ${override})`, async ({ page }) => { + await bootstrapVpsAdminWindow(page, { sessionToken: 'TEST' }); + let vpsUpdateCount = 0; + + await installHaveApiMock(page, { + user: { id: 1, login: 'admin', level: 99 }, + handlers: { + 'GET vpses/123': () => ({ vps }), + 'GET datasets/10': () => ({ dataset }), + 'GET ip_addresses': () => ({ ip_addresses: [] }), + 'GET transaction_chains': () => ({ transaction_chains: [] }), + 'GET vpses/123/mounts': () => ({ mounts: [] }), + 'GET dns_resolvers': () => ({ dns_resolvers: [] }), + 'GET user_namespace_maps': () => ({ user_namespace_maps: [] }), + 'PUT vpses/123': () => { + vpsUpdateCount += 1; + return { vps }; }, - }), - }, + 'PUT datasets/10': () => ({ _meta: { action_state_id: 905 } }), + 'GET action_states/905': () => ({ + action_state: { + id: 905, + label: 'Resize root dataset', + status: true, + finished: false, + current: 0, + total: 1, + }, + }), + }, + }); + + await page.goto('/admin/vps/123/config'); + const resizeEntry = page.getByTestId('vps.config.ssd.resize'); + await expect(resizeEntry).toBeVisible(); + await expect(resizeEntry).toHaveAttribute('href', '/admin/vps/123/storage?resize=ssd'); + await expect(resizeEntry.locator('..')).toContainText('20 GiB'); + + await resizeEntry.click(); + await expect(page).toHaveURL(/\/admin\/vps\/123\/storage$/); + const modal = page.getByTestId('vps.storage.resize.modal'); + await expect(modal).toBeVisible(); + await expect(modal).toContainText('No VPS restart'); + await expect(modal).toContainText('Current size: 20 GiB'); + await expect(modal.getByTestId('vps.storage.resize.submit')).toBeDisabled(); + + await modal.getByTestId('vps.storage.resize.size').fill('4'); + await expect(modal).toContainText('cannot be smaller than the currently used space'); + await expect(modal.getByTestId('vps.storage.resize.submit')).toBeDisabled(); + + await modal.getByTestId('vps.storage.resize.size').fill('32'); + await expect(modal.getByTestId('vps.storage.resize.admin_override')).not.toBeChecked(); + if (override) await modal.getByTestId('vps.storage.resize.admin_override').check(); + const requestPromise = page.waitForRequest( + (request) => request.method() === 'PUT' && request.url().includes('/api/v7.0/datasets/10') + ); + await modal.getByTestId('vps.storage.resize.submit').click(); + + expect((await requestPromise).postDataJSON()).toEqual({ dataset: { refquota: 32 * 1024, ...(override ? { admin_override: true } : {}) } }); + expect(vpsUpdateCount).toBe(0); + await expect(modal).toBeHidden(); + await expect(page.getByTestId('vps.storage.root_dataset.resize')).toBeVisible(); + + await page.goto('/app/vps/123/config'); + await expect(page.getByTestId('vps.config.ssd.resize')).toHaveCount(0); + await page.goto('/app/vps/123/storage'); + await expect(page.getByTestId('vps.storage.root_dataset.resize')).toHaveCount(0); }); - await page.goto('/admin/vps/123/config'); - const resizeEntry = page.getByTestId('vps.config.ssd.resize'); - await expect(resizeEntry).toBeVisible(); - await expect(resizeEntry).toHaveAttribute('href', '/admin/vps/123/storage?resize=ssd'); - await expect(resizeEntry.locator('..')).toContainText('20 GiB'); - - await resizeEntry.click(); - await expect(page).toHaveURL(/\/admin\/vps\/123\/storage$/); - const modal = page.getByTestId('vps.storage.resize.modal'); - await expect(modal).toBeVisible(); - await expect(modal).toContainText('No VPS restart'); - await expect(modal).toContainText('Current size: 20 GiB'); - await expect(modal.getByTestId('vps.storage.resize.submit')).toBeDisabled(); - - await modal.getByTestId('vps.storage.resize.size').fill('4'); - await expect(modal).toContainText('cannot be smaller than the currently used space'); - await expect(modal.getByTestId('vps.storage.resize.submit')).toBeDisabled(); - - await modal.getByTestId('vps.storage.resize.size').fill('32'); - const requestPromise = page.waitForRequest( - (request) => request.method() === 'PUT' && request.url().includes('/api/v7.0/datasets/10') - ); - await modal.getByTestId('vps.storage.resize.submit').click(); - - expect((await requestPromise).postDataJSON()).toEqual({ dataset: { refquota: 32 * 1024 } }); - expect(vpsUpdateCount).toBe(0); - await expect(modal).toBeHidden(); - await expect(page.getByTestId('vps.storage.root_dataset.resize')).toBeVisible(); - - await page.goto('/app/vps/123/config'); - await expect(page.getByTestId('vps.config.ssd.resize')).toHaveCount(0); - await page.goto('/app/vps/123/storage'); - await expect(page.getByTestId('vps.storage.root_dataset.resize')).toHaveCount(0); - }); + } test('resets a mount deletion confirm when the VPS route changes', async ({ page }) => { await bootstrapVpsAdminWindow(page, { sessionToken: 'TEST' }); diff --git a/src/i18n/locales/cs/storage.ts b/src/i18n/locales/cs/storage.ts index bc3be733..4fbe49f5 100644 --- a/src/i18n/locales/cs/storage.ts +++ b/src/i18n/locales/cs/storage.ts @@ -170,7 +170,8 @@ export const csStorage = { "dataset.manage.field.atime": "Access time", "dataset.manage.field.relatime": "Relative access time", "dataset.manage.field.sharenfs": "NFS share", - "dataset.manage.field.admin_override": "Nastavit bez ohledu na volné prostředky", + "dataset.manage.field.admin_override": "Povolit překročení prostředků uživatele", + "dataset.manage.help.admin_override": "Povolí navýšení i při nedostatku přidělených prostředků uživatele. Ostatní kontroly zůstávají platné.", "dataset.manage.field.admin_lock_type": "Typ admin locku", "dataset.manage.sync.standard": "Standard", "dataset.manage.sync.always": "Vždy", diff --git a/src/i18n/locales/cs/vps/config.ts b/src/i18n/locales/cs/vps/config.ts index d1c79243..6e9751c7 100644 --- a/src/i18n/locales/cs/vps/config.ts +++ b/src/i18n/locales/cs/vps/config.ts @@ -49,7 +49,7 @@ export const csVps_config = { 'vps.config.field.allow_admin_modifications': 'Povolit admin úpravy', 'vps.config.field.change_reason': 'Důvod změny', 'vps.config.field.admin_lock_type': 'Typ admin locku', - 'vps.config.field.admin_override': 'Admin override', + 'vps.config.field.admin_override': 'Povolit překročení prostředků uživatele', 'vps.config.help.hostname_mode': 'Spravovaný režim zapisuje hostname přes vpsAdmin. Manuální režim vypne správu hostname vpsAdminem.', 'vps.config.help.hostname': 'Použije se pouze při zapnuté správě hostname.', 'vps.config.help.owner': 'Změna vlastníka se před odesláním potvrzuje.', @@ -60,7 +60,7 @@ export const csVps_config = { 'vps.config.help.ssd_unsaved': 'Nejdřív ulož nebo resetuj rozepsané změny CPU, paměti či swapu.', 'vps.config.help.change_reason': 'Volitelná admin poznámka posílaná se změnou prostředků.', 'vps.config.help.admin_lock_type': 'Volitelné vynucení locku použité se změnou prostředků.', - 'vps.config.help.admin_override': 'Pošle admin_override=true se změnou prostředků.', + 'vps.config.help.admin_override': 'Povolí navýšení i při nedostatku přidělených prostředků uživatele. Ostatní kontroly zůstávají platné.', 'vps.config.help.user_namespace_map': 'Změna namespace mapy se před odesláním potvrzuje.', 'vps.config.help.map_mode': 'Určuje, jak se UID/GID mapování použije na připojené datasety. Změna tohoto nastavení restartuje VPS.', 'vps.config.help.autostart_priority': 'Aktivní stav řídí backend. 0 je nejvyšší priorita; vyšší hodnoty startují později.', diff --git a/src/i18n/locales/cs/vps/storage.ts b/src/i18n/locales/cs/vps/storage.ts index 1a899e82..9a825a1f 100644 --- a/src/i18n/locales/cs/vps/storage.ts +++ b/src/i18n/locales/cs/vps/storage.ts @@ -82,8 +82,8 @@ export const csVps_storage = { 'vps.storage.resize.summary': 'Aktuální velikost: {current} · využito: {used}', 'vps.storage.resize.field.size': 'Nová velikost SSD (GiB)', 'vps.storage.resize.field.size_help': 'Zadej celkovou výslednou velikost, ne hodnotu, o kterou se má disk navýšit.', - 'vps.storage.resize.field.admin_override': 'Vynutit bez ohledu na dostupné prostředky', - 'vps.storage.resize.field.admin_override_help': 'Použij jen po ověření kapacity. API dostane parametr admin_override.', + 'vps.storage.resize.field.admin_override': 'Povolit překročení prostředků uživatele', + 'vps.storage.resize.field.admin_override_help': 'Povolí navýšení i při nedostatku přidělených prostředků uživatele. Ostatní kontroly zůstávají platné.', 'vps.storage.resize.error': 'Změnu velikosti SSD se nepodařilo spustit', 'vps.storage.resize.validation.required': 'Zadej novou velikost SSD.', 'vps.storage.resize.validation.invalid': 'Velikost musí být kladné číslo v GiB.', diff --git a/src/i18n/locales/en/storage.ts b/src/i18n/locales/en/storage.ts index 9f91340a..6cf42578 100644 --- a/src/i18n/locales/en/storage.ts +++ b/src/i18n/locales/en/storage.ts @@ -163,7 +163,8 @@ export const enStorage = { "dataset.manage.field.atime": "Access time", "dataset.manage.field.relatime": "Relative access time", "dataset.manage.field.sharenfs": "NFS share", - "dataset.manage.field.admin_override": "Set regardless of available resources", + "dataset.manage.field.admin_override": "Allow exceeding user resource allocation", + "dataset.manage.help.admin_override": "Allow increases even when the user has no allocated resources left. Other checks still apply.", "dataset.manage.field.admin_lock_type": "Admin lock type", "dataset.manage.sync.standard": "Standard", "dataset.manage.sync.always": "Always", diff --git a/src/i18n/locales/en/vps/config.ts b/src/i18n/locales/en/vps/config.ts index 566feb84..0c602ddb 100644 --- a/src/i18n/locales/en/vps/config.ts +++ b/src/i18n/locales/en/vps/config.ts @@ -49,7 +49,7 @@ export const enVps_config = { 'vps.config.field.allow_admin_modifications': 'Allow admin modifications', 'vps.config.field.change_reason': 'Change reason', 'vps.config.field.admin_lock_type': 'Admin lock type', - 'vps.config.field.admin_override': 'Admin override', + 'vps.config.field.admin_override': 'Allow exceeding user resource allocation', 'vps.config.help.hostname_mode': 'Managed mode writes the hostname through vpsAdmin. Manual mode disables vpsAdmin hostname management.', 'vps.config.help.hostname': 'Used only when hostname management is enabled.', 'vps.config.help.owner': 'Changing the owner is confirmed before it is sent.', @@ -60,7 +60,7 @@ export const enVps_config = { 'vps.config.help.ssd_unsaved': 'Save or reset pending CPU, memory or swap changes first.', 'vps.config.help.change_reason': 'Optional admin note sent with resource changes.', 'vps.config.help.admin_lock_type': 'Optional lock enforcement used with resource changes.', - 'vps.config.help.admin_override': 'Send admin_override=true with resource changes.', + 'vps.config.help.admin_override': 'Allow increases even when the user has no allocated resources left. Other checks still apply.', 'vps.config.help.user_namespace_map': 'Changing the namespace map is confirmed before it is sent.', 'vps.config.help.map_mode': 'Controls how UID/GID mappings are applied to mounted datasets. Changing this setting restarts the VPS.', 'vps.config.help.autostart_priority': 'The active state is managed by the backend. 0 is the highest priority; greater values start later.', diff --git a/src/i18n/locales/en/vps/storage.ts b/src/i18n/locales/en/vps/storage.ts index 41f1727d..9d309095 100644 --- a/src/i18n/locales/en/vps/storage.ts +++ b/src/i18n/locales/en/vps/storage.ts @@ -82,8 +82,8 @@ export const enVps_storage = { 'vps.storage.resize.summary': 'Current size: {current} · used: {used}', 'vps.storage.resize.field.size': 'New SSD size (GiB)', 'vps.storage.resize.field.size_help': 'Enter the resulting total size, not the amount to add.', - 'vps.storage.resize.field.admin_override': 'Override available-resource checks', - 'vps.storage.resize.field.admin_override_help': 'Use only after checking capacity. The API receives admin_override.', + 'vps.storage.resize.field.admin_override': 'Allow exceeding user resource allocation', + 'vps.storage.resize.field.admin_override_help': 'Allow increases even when the user has no allocated resources left. Other checks still apply.', 'vps.storage.resize.error': 'The SSD resize could not be started', 'vps.storage.resize.validation.required': 'Enter a new SSD size.', 'vps.storage.resize.validation.invalid': 'Size must be a positive number in GiB.', diff --git a/src/pages/app/datasets/DatasetOverviewPage.tsx b/src/pages/app/datasets/DatasetOverviewPage.tsx index 71acbca3..44a3cfaa 100644 --- a/src/pages/app/datasets/DatasetOverviewPage.tsx +++ b/src/pages/app/datasets/DatasetOverviewPage.tsx @@ -146,11 +146,14 @@ function buildEditablePayload( if (isAdmin) { if (form.sharenfs !== undefined) payload.sharenfs = form.sharenfs.trim(); - if (form.adminOverride !== undefined) payload.admin_override = form.adminOverride; - if (form.adminLockType !== undefined) payload.admin_lock_type = form.adminLockType; } } + if (isAdmin) { + if (form.adminOverride !== undefined) payload.admin_override = form.adminOverride; + if (form.adminLockType !== undefined) payload.admin_lock_type = form.adminLockType; + } + return payload; } @@ -211,13 +214,9 @@ function DatasetManagementCard() { ? ((dataset as any).sync as DatasetEditablePayload['sync']) : undefined, sharenfs: typeof (dataset as any).sharenfs === 'string' ? (dataset as any).sharenfs : undefined, - adminOverride: - typeof (dataset as any).admin_override === 'boolean' ? (dataset as any).admin_override : undefined, - adminLockType: ['no_lock', 'absolute', 'not_less', 'not_more'].includes( - String((dataset as any).admin_lock_type) - ) - ? ((dataset as any).admin_lock_type as DatasetEditForm['adminLockType']) - : undefined, + // Allocation exceptions are explicit per-save options, not dataset properties. + adminOverride: undefined, + adminLockType: undefined, })); const objectLabel = datasetLabel(dataset); @@ -285,6 +284,7 @@ function DatasetManagementCard() { onSuccess: (res) => { track(res.meta, 'action.dataset.update.label'); setEditAdvancedDirty(false); + setEdit((previous) => ({ ...previous, adminOverride: undefined, adminLockType: undefined })); }, onError: (e: any) => { if (e?.code === 'BUSY') chrome.openTasks(); @@ -456,36 +456,6 @@ function DatasetManagementCard() { testId="dataset.manage.sharenfs" /> - + { + setEdit((previous) => ({ ...previous, adminOverride: value })); + }} + label={t('dataset.manage.field.admin_override')} + description={t('dataset.manage.help.admin_override')} + disabled={updateM.isPending} + testId="dataset.manage.admin_override" + /> + + ) : null} + {advancedFields} ); diff --git a/src/pages/app/vps/VpsConfigurationPage.tsx b/src/pages/app/vps/VpsConfigurationPage.tsx index 79b8a7a1..0d828709 100644 --- a/src/pages/app/vps/VpsConfigurationPage.tsx +++ b/src/pages/app/vps/VpsConfigurationPage.tsx @@ -351,6 +351,31 @@ export function VpsConfigurationPage() { patchDraft({ swap: e.target.value })} disabled={saveM.isPending} /> + {canEditAdminConfig ? ( +
+ + patchDraft({ changeReason: e.target.value })} disabled={saveM.isPending} autoComplete="off" /> - -