diff --git a/app/pages/ProjectsPage.tsx b/app/pages/ProjectsPage.tsx index ca99041c53..ed86d90ed4 100644 --- a/app/pages/ProjectsPage.tsx +++ b/app/pages/ProjectsPage.tsx @@ -89,6 +89,7 @@ export default function ProjectsPage() { onActivate: confirmDelete({ doDelete: () => deleteProject({ path: { project: project.name } }), label: project.name, + resourceKind: 'project', }), }, ], diff --git a/app/pages/SiloAccessPage.tsx b/app/pages/SiloAccessPage.tsx index c1531bf1fc..66a7f3aabc 100644 --- a/app/pages/SiloAccessPage.tsx +++ b/app/pages/SiloAccessPage.tsx @@ -149,6 +149,7 @@ export default function SiloAccessPage() { the {row.siloRole} role for {row.name} ), + resourceKind: 'role assignment', extraContent: row.id === me.id ? 'This will remove your own silo access.' : undefined, }), diff --git a/app/pages/SiloImagesPage.tsx b/app/pages/SiloImagesPage.tsx index 16ddd126d8..6a830e3771 100644 --- a/app/pages/SiloImagesPage.tsx +++ b/app/pages/SiloImagesPage.tsx @@ -87,6 +87,7 @@ export default function SiloImagesPage() { onActivate: confirmDelete({ doDelete: () => deleteImage({ path: { image: image.name } }), label: image.name, + resourceKind: 'image', }), }, ], diff --git a/app/pages/project/access/ProjectAccessPage.tsx b/app/pages/project/access/ProjectAccessPage.tsx index 07ee850d57..bcf10c78bc 100644 --- a/app/pages/project/access/ProjectAccessPage.tsx +++ b/app/pages/project/access/ProjectAccessPage.tsx @@ -192,6 +192,7 @@ export default function ProjectAccessPage() { the {row.projectRole} role for {row.name} ), + resourceKind: 'role assignment', }), disabled: !row.projectRole && "You don't have permission to delete this user", }, diff --git a/app/pages/project/disks/DisksPage.tsx b/app/pages/project/disks/DisksPage.tsx index 7f6a37ae3f..469c4c8e95 100644 --- a/app/pages/project/disks/DisksPage.tsx +++ b/app/pages/project/disks/DisksPage.tsx @@ -133,6 +133,7 @@ export default function DisksPage() { onActivate: confirmDelete({ doDelete: () => deleteDisk({ path: { disk: disk.name }, query: { project } }), label: disk.name, + resourceKind: 'disk', }), disabled: !diskCan.delete(disk) && diff --git a/app/pages/project/external-subnets/ExternalSubnetsPage.tsx b/app/pages/project/external-subnets/ExternalSubnetsPage.tsx index 4d0aa1ec42..8c3bec28fd 100644 --- a/app/pages/project/external-subnets/ExternalSubnetsPage.tsx +++ b/app/pages/project/external-subnets/ExternalSubnetsPage.tsx @@ -139,7 +139,7 @@ export default function ExternalSubnetsPage() { path: { externalSubnet: subnet.name }, query: { project }, }), - modalTitle: 'Detach External Subnet', + modalTitle: 'Detach external subnet', modalContent: (

Are you sure you want to detach external subnet {subnet.name} @@ -181,6 +181,7 @@ export default function ExternalSubnetsPage() { query: { project }, }), label: subnet.name, + resourceKind: 'external subnet', }), }, ] diff --git a/app/pages/project/floating-ips/FloatingIpsPage.tsx b/app/pages/project/floating-ips/FloatingIpsPage.tsx index 1819e05f3d..d67476c1d4 100644 --- a/app/pages/project/floating-ips/FloatingIpsPage.tsx +++ b/app/pages/project/floating-ips/FloatingIpsPage.tsx @@ -153,7 +153,7 @@ export default function FloatingIpsPage() { path: { floatingIp: floatingIp.name }, query: { project }, }), - modalTitle: 'Detach Floating IP', + modalTitle: 'Detach floating IP', // instanceName! non-null because we only see this if there is an instance modalContent: (

@@ -196,6 +196,7 @@ export default function FloatingIpsPage() { query: { project }, }), label: floatingIp.name, + resourceKind: 'floating IP', }), }, ] diff --git a/app/pages/project/images/ImagesPage.tsx b/app/pages/project/images/ImagesPage.tsx index 047287a3d7..f3e7880072 100644 --- a/app/pages/project/images/ImagesPage.tsx +++ b/app/pages/project/images/ImagesPage.tsx @@ -85,6 +85,7 @@ export default function ImagesPage() { query: { project }, }), label: image.name, + resourceKind: 'image', }), }, ], diff --git a/app/pages/project/instances/NetworkingTab.tsx b/app/pages/project/instances/NetworkingTab.tsx index 249952d2e8..10817eac6e 100644 --- a/app/pages/project/instances/NetworkingTab.tsx +++ b/app/pages/project/instances/NetworkingTab.tsx @@ -445,6 +445,7 @@ export default function NetworkingTab() { query: instanceSelector, }), label: nic.name, + resourceKind: 'network interface', }), disabled: deleteDisabledReason(), }, @@ -511,7 +512,7 @@ export default function NetworkingTab() { path: { externalSubnet: subnet.name }, query: { project }, }), - modalTitle: 'Detach External Subnet', + modalTitle: 'Detach external subnet', modalContent: (

Are you sure you want to detach external subnet {subnet.name} from{' '} @@ -586,7 +587,7 @@ export default function NetworkingTab() { confirmAction({ actionType: 'danger', doAction: doDetach, - modalTitle: `Confirm detach ${externalIp.kind} IP`, + modalTitle: `Detach ${externalIp.kind} IP`, modalContent: (

Are you sure you want to detach {label} from {instanceName}? The diff --git a/app/pages/project/instances/StorageTab.tsx b/app/pages/project/instances/StorageTab.tsx index 8ded4431f7..86bb123e2a 100644 --- a/app/pages/project/instances/StorageTab.tsx +++ b/app/pages/project/instances/StorageTab.tsx @@ -40,6 +40,7 @@ import { CardBlock } from '~/ui/lib/CardBlock' import { EMBody, EmptyMessage } from '~/ui/lib/EmptyMessage' import { TableEmptyBox } from '~/ui/lib/Table' import { links } from '~/util/links' +import { capitalize } from '~/util/str' import { snapshotDisabledReason } from './common' @@ -189,7 +190,7 @@ export default function StorageTab() { }, }), errorTitle: 'Could not unset boot disk', - modalTitle: 'Confirm unset boot disk', + modalTitle: 'Unset boot disk', // TODO: copy + link to docs modalContent: (

@@ -254,7 +255,7 @@ export default function StorageTab() { }, }), errorTitle: `Could not ${verb} boot disk`, - modalTitle: `Confirm ${verb} boot disk`, + modalTitle: `${capitalize(verb)} boot disk`, modalContent: bootDiskName ? (

Are you sure you want to change the boot disk to {disk.name}? @@ -286,7 +287,7 @@ export default function StorageTab() { doAction: () => detachDisk({ body: { disk: disk.name }, path: { instance: instance.id } }), errorTitle: 'Could not detach disk', - modalTitle: 'Confirm detach disk', + modalTitle: 'Detach disk', // prettier-ignore modalContent:

Are you sure you want to detach {disk.name}?

, actionType: 'danger', diff --git a/app/pages/project/instances/actions.tsx b/app/pages/project/instances/actions.tsx index f939c858b8..62e9ccb4ed 100644 --- a/app/pages/project/instances/actions.tsx +++ b/app/pages/project/instances/actions.tsx @@ -64,7 +64,7 @@ export const useMakeInstanceActions = ( // prettier-ignore onSuccess: () => addToast(<>Starting instance {instance.name}), }), - modalTitle: 'Confirm start instance', + modalTitle: 'Start instance', modalContent: (

Are you sure you want to start {instance.name}? @@ -88,7 +88,7 @@ export const useMakeInstanceActions = ( // prettier-ignore addToast(<>Stopping instance {instance.name}), }), - modalTitle: 'Confirm stop instance', + modalTitle: 'Stop instance', modalContent: (

@@ -128,7 +128,7 @@ export const useMakeInstanceActions = ( // prettier-ignore addToast(<>Rebooting instance {instance.name}), }), - modalTitle: 'Confirm reboot instance', + modalTitle: 'Reboot instance', modalContent: (

Are you sure you want to reboot {instance.name}? diff --git a/app/pages/project/snapshots/SnapshotsPage.tsx b/app/pages/project/snapshots/SnapshotsPage.tsx index 16831b0485..3e2f29ce46 100644 --- a/app/pages/project/snapshots/SnapshotsPage.tsx +++ b/app/pages/project/snapshots/SnapshotsPage.tsx @@ -154,6 +154,7 @@ export default function SnapshotsPage() { query: { project }, }), label: snapshot.name, + resourceKind: 'snapshot', }), }, ], diff --git a/app/pages/project/vpcs/RouterPage.tsx b/app/pages/project/vpcs/RouterPage.tsx index 70b01936f7..52428ae69b 100644 --- a/app/pages/project/vpcs/RouterPage.tsx +++ b/app/pages/project/vpcs/RouterPage.tsx @@ -151,8 +151,8 @@ export default function RouterPage() { onActivate: () => confirmAction({ doAction: () => deleteRouterRoute({ path: { route: routerRoute.id } }), - errorTitle: 'Could not remove route', - modalTitle: 'Confirm remove route', + errorTitle: 'Could not delete route', + modalTitle: 'Delete route', modalContent: (

Are you sure you want to delete route {routerRoute.name}? diff --git a/app/pages/project/vpcs/VpcFirewallRulesTab.tsx b/app/pages/project/vpcs/VpcFirewallRulesTab.tsx index 471aed5b96..96f75640fb 100644 --- a/app/pages/project/vpcs/VpcFirewallRulesTab.tsx +++ b/app/pages/project/vpcs/VpcFirewallRulesTab.tsx @@ -161,6 +161,7 @@ export default function VpcFirewallRulesTab() { }, }), label: rule.name, + resourceKind: 'firewall rule', }), }, ]), diff --git a/app/pages/project/vpcs/VpcPage.tsx b/app/pages/project/vpcs/VpcPage.tsx index 100984ac63..6209e5495c 100644 --- a/app/pages/project/vpcs/VpcPage.tsx +++ b/app/pages/project/vpcs/VpcPage.tsx @@ -60,6 +60,7 @@ export default function VpcPage() { onSelect={confirmDelete({ doDelete: () => deleteVpc({ path: { vpc: vpcName }, query: { project } }), label: vpcName, + resourceKind: 'VPC', })} className="destructive" /> diff --git a/app/pages/project/vpcs/VpcRoutersTab.tsx b/app/pages/project/vpcs/VpcRoutersTab.tsx index 7b8fc5790d..5e32ad2eb6 100644 --- a/app/pages/project/vpcs/VpcRoutersTab.tsx +++ b/app/pages/project/vpcs/VpcRoutersTab.tsx @@ -95,6 +95,7 @@ export default function VpcRoutersTab() { }), extraContent: 'This will also delete any routes belonging to this router.', label: router.name, + resourceKind: 'VPC router', }), disabled: router.kind === 'system' && routeFormMessage.noDeletingSystemRouters, }, diff --git a/app/pages/project/vpcs/VpcSubnetsTab.tsx b/app/pages/project/vpcs/VpcSubnetsTab.tsx index 198a722ca6..cd1dca8223 100644 --- a/app/pages/project/vpcs/VpcSubnetsTab.tsx +++ b/app/pages/project/vpcs/VpcSubnetsTab.tsx @@ -60,6 +60,7 @@ export default function VpcSubnetsTab() { onActivate: confirmDelete({ doDelete: () => deleteSubnet({ path: { subnet: subnet.id } }), label: subnet.name, + resourceKind: 'VPC subnet', }), }, ], diff --git a/app/pages/project/vpcs/VpcsPage.tsx b/app/pages/project/vpcs/VpcsPage.tsx index 3091816ac2..b7b0aaac74 100644 --- a/app/pages/project/vpcs/VpcsPage.tsx +++ b/app/pages/project/vpcs/VpcsPage.tsx @@ -104,6 +104,7 @@ export default function VpcsPage() { onActivate: confirmDelete({ doDelete: () => deleteVpc({ path: { vpc: vpc.name }, query: { project } }), label: vpc.name, + resourceKind: 'VPC', }), }, ], diff --git a/app/pages/settings/AccessTokensPage.tsx b/app/pages/settings/AccessTokensPage.tsx index cf70bf405d..0ff8a4edb3 100644 --- a/app/pages/settings/AccessTokensPage.tsx +++ b/app/pages/settings/AccessTokensPage.tsx @@ -58,6 +58,7 @@ export default function AccessTokensPage() { onActivate: confirmDelete({ doDelete: () => deleteToken({ path: { tokenId: token.id } }), label: token.id, + resourceKind: 'access token', extraContent: 'This cannot be undone. Any application or instance of the Oxide CLI that depends on this token will need a new one.', }), diff --git a/app/pages/settings/SSHKeysPage.tsx b/app/pages/settings/SSHKeysPage.tsx index 92013e9664..2d4cc9f251 100644 --- a/app/pages/settings/SSHKeysPage.tsx +++ b/app/pages/settings/SSHKeysPage.tsx @@ -63,6 +63,7 @@ export default function SSHKeysPage() { onActivate: confirmDelete({ doDelete: () => deleteSshKey({ path: { sshKey: sshKey.name } }), label: sshKey.name, + resourceKind: 'SSH key', }), }, ], diff --git a/app/pages/system/FleetAccessPage.tsx b/app/pages/system/FleetAccessPage.tsx index ec48e5063b..3410208497 100644 --- a/app/pages/system/FleetAccessPage.tsx +++ b/app/pages/system/FleetAccessPage.tsx @@ -227,6 +227,7 @@ export default function FleetAccessPage() { the {row.fleetRole} role for {row.name} ), + resourceKind: 'role assignment', extraContent: row.id === me.id ? 'This will remove your own fleet access.' : undefined, }), diff --git a/app/pages/system/UpdatePage.tsx b/app/pages/system/UpdatePage.tsx index e9f3de05d5..4fd9ca5241 100644 --- a/app/pages/system/UpdatePage.tsx +++ b/app/pages/system/UpdatePage.tsx @@ -245,7 +245,7 @@ export default function UpdatePage() { setTargetRelease({ body: { systemVersion: repo.systemVersion }, }), - modalTitle: 'Confirm set target release', + modalTitle: 'Set target release', modalContent: (

Are you sure you want to set {repo.systemVersion}{' '} diff --git a/app/pages/system/networking/IpPoolPage.tsx b/app/pages/system/networking/IpPoolPage.tsx index 4177afbac4..6634122a14 100644 --- a/app/pages/system/networking/IpPoolPage.tsx +++ b/app/pages/system/networking/IpPoolPage.tsx @@ -57,6 +57,7 @@ import { ALL_ISH } from '~/util/consts' import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' import type * as PP from '~/util/path-params' +import { capitalize } from '~/util/str' const ipPoolView = ({ pool }: PP.IpPool) => q(api.systemIpPoolView, { path: { pool } }) const ipPoolUtilizationView = ({ pool }: PP.IpPool) => @@ -164,6 +165,7 @@ export default function IpPoolpage() { onSelect={confirmDelete({ doDelete: () => deletePool({ path: { pool: pool.name } }), label: pool.name, + resourceKind: 'IP pool', })} disabled={ !!ranges.items.length && @@ -256,7 +258,7 @@ function IpRangesTable() { body: range, }), errorTitle: 'Could not remove range', - modalTitle: 'Confirm remove range', + modalTitle: 'Remove range', modalContent: (

Are you sure you want to remove range{' '} @@ -326,7 +328,7 @@ function LinkedSilosTable() { path: { silo: link.siloId, pool: link.ipPoolId }, body: { isDefault: false }, }), - modalTitle: 'Confirm clear default', + modalTitle: 'Clear default', modalContent: (

Are you sure you want {pool.name} to stop being the default{' '} @@ -376,7 +378,7 @@ function LinkedSilosTable() { path: { silo: link.siloId, pool: link.ipPoolId }, body: { isDefault: true }, }), - modalTitle: `Confirm ${verb} default`, + modalTitle: `${capitalize(verb)} default`, modalContent, errorTitle: `Could not ${verb} default`, actionType: 'primary', @@ -395,7 +397,7 @@ function LinkedSilosTable() { confirmAction({ doAction: () => unlinkSilo({ path: { silo: link.siloId, pool: link.ipPoolId } }), - modalTitle: 'Confirm unlink silo', + modalTitle: 'Unlink silo', modalContent: (

Are you sure you want to unlink {siloLabel} from {pool.name}? Users diff --git a/app/pages/system/networking/IpPoolsPage.tsx b/app/pages/system/networking/IpPoolsPage.tsx index 914a841ca5..64e14c3d46 100644 --- a/app/pages/system/networking/IpPoolsPage.tsx +++ b/app/pages/system/networking/IpPoolsPage.tsx @@ -113,6 +113,7 @@ export default function IpPoolsPage() { onActivate: confirmDelete({ doDelete: () => deletePool({ path: { pool: pool.name } }), label: pool.name, + resourceKind: 'IP pool', }), }, ], diff --git a/app/pages/system/networking/SubnetPoolPage.tsx b/app/pages/system/networking/SubnetPoolPage.tsx index 3403b133ed..524e951d97 100644 --- a/app/pages/system/networking/SubnetPoolPage.tsx +++ b/app/pages/system/networking/SubnetPoolPage.tsx @@ -57,6 +57,7 @@ import { ALL_ISH } from '~/util/consts' import { docLinks } from '~/util/links' import { pb } from '~/util/path-builder' import type * as PP from '~/util/path-params' +import { capitalize } from '~/util/str' const subnetPoolView = ({ subnetPool }: PP.SubnetPool) => q(api.systemSubnetPoolView, { path: { pool: subnetPool } }) @@ -127,6 +128,7 @@ export default function SubnetPoolPage() { onSelect={confirmDelete({ doDelete: () => deletePool({ path: { pool: pool.name } }), label: pool.name, + resourceKind: 'subnet pool', })} disabled={ !!members.items.length && @@ -218,7 +220,7 @@ function MembersTable() { body: { subnet: member.subnet }, }), errorTitle: 'Could not remove member', - modalTitle: 'Confirm remove member', + modalTitle: 'Remove member', modalContent: (

Are you sure you want to remove subnet {member.subnet} from the @@ -318,7 +320,7 @@ function LinkedSilosTable() { path: { silo: link.siloId, pool: link.subnetPoolId }, body: { isDefault: false }, }), - modalTitle: 'Confirm clear default', + modalTitle: 'Clear default', modalContent: (

Are you sure you want {pool.name} to stop being the default{' '} @@ -358,7 +360,7 @@ function LinkedSilosTable() { path: { silo: link.siloId, pool: link.subnetPoolId }, body: { isDefault: true }, }), - modalTitle: `Confirm ${verb} default`, + modalTitle: `${capitalize(verb)} default`, modalContent, errorTitle: `Could not ${verb} default`, actionType: 'primary', @@ -376,7 +378,7 @@ function LinkedSilosTable() { confirmAction({ doAction: () => unlinkSilo({ path: { silo: link.siloId, pool: link.subnetPoolId } }), - modalTitle: 'Confirm unlink silo', + modalTitle: 'Unlink silo', modalContent: (

Are you sure you want to unlink {siloLabel} from {pool.name}? Users diff --git a/app/pages/system/networking/SubnetPoolsPage.tsx b/app/pages/system/networking/SubnetPoolsPage.tsx index 20cb35a816..aa45a0c8e4 100644 --- a/app/pages/system/networking/SubnetPoolsPage.tsx +++ b/app/pages/system/networking/SubnetPoolsPage.tsx @@ -116,6 +116,7 @@ export default function SubnetPoolsPage() { onActivate: confirmDelete({ doDelete: () => deletePool({ path: { pool: pool.name } }), label: pool.name, + resourceKind: 'subnet pool', }), }, ], diff --git a/app/pages/system/silos/SiloIpPoolsTab.tsx b/app/pages/system/silos/SiloIpPoolsTab.tsx index 5303ca6d39..7750d816cb 100644 --- a/app/pages/system/silos/SiloIpPoolsTab.tsx +++ b/app/pages/system/silos/SiloIpPoolsTab.tsx @@ -43,6 +43,7 @@ import { Modal } from '~/ui/lib/Modal' import { Tooltip } from '~/ui/lib/Tooltip' import { ALL_ISH } from '~/util/consts' import { pb } from '~/util/path-builder' +import { capitalize } from '~/util/str' function toIpPoolComboboxItem(p: IpPool): ComboboxItem { return { @@ -177,7 +178,7 @@ export default function SiloIpPoolsTab() { path: { silo, pool: pool.id }, body: { isDefault: false }, }), - modalTitle: 'Confirm clear default', + modalTitle: 'Clear default', modalContent: (

Are you sure you want {pool.name} to stop being the default{' '} @@ -209,7 +210,7 @@ export default function SiloIpPoolsTab() { path: { silo, pool: pool.id }, body: { isDefault: true }, }), - modalTitle: `Confirm ${verb} default`, + modalTitle: `${capitalize(verb)} default`, modalContent, errorTitle: `Could not ${verb} default`, actionType: 'primary', @@ -223,7 +224,7 @@ export default function SiloIpPoolsTab() { onActivate() { confirmAction({ doAction: () => unlinkPool({ path: { silo, pool: pool.id } }), - modalTitle: `Confirm unlink pool`, + modalTitle: 'Unlink pool', modalContent: (

Are you sure you want to unlink {pool.name}? Users in this silo diff --git a/app/pages/system/silos/SiloSubnetPoolsTab.tsx b/app/pages/system/silos/SiloSubnetPoolsTab.tsx index da4826e14d..349d42fb47 100644 --- a/app/pages/system/silos/SiloSubnetPoolsTab.tsx +++ b/app/pages/system/silos/SiloSubnetPoolsTab.tsx @@ -43,6 +43,7 @@ import { Modal } from '~/ui/lib/Modal' import { Tooltip } from '~/ui/lib/Tooltip' import { ALL_ISH } from '~/util/consts' import { pb } from '~/util/path-builder' +import { capitalize } from '~/util/str' function toSubnetPoolComboboxItem(p: SubnetPool): ComboboxItem { return { @@ -165,7 +166,7 @@ export default function SiloSubnetPoolsTab() { path: { silo, pool: pool.id }, body: { isDefault: false }, }), - modalTitle: 'Confirm clear default', + modalTitle: 'Clear default', modalContent: (

Are you sure you want {pool.name} to stop being the default{' '} @@ -197,7 +198,7 @@ export default function SiloSubnetPoolsTab() { path: { silo, pool: pool.id }, body: { isDefault: true }, }), - modalTitle: `Confirm ${verb} default`, + modalTitle: `${capitalize(verb)} default`, modalContent, errorTitle: `Could not ${verb} default`, actionType: 'primary', @@ -211,7 +212,7 @@ export default function SiloSubnetPoolsTab() { onActivate() { confirmAction({ doAction: () => unlinkPool({ path: { silo, pool: pool.id } }), - modalTitle: 'Confirm unlink pool', + modalTitle: 'Unlink pool', modalContent: (

Are you sure you want to unlink {pool.name}? Users in this silo diff --git a/app/pages/system/silos/SilosPage.tsx b/app/pages/system/silos/SilosPage.tsx index baed459dab..e4546d51a0 100644 --- a/app/pages/system/silos/SilosPage.tsx +++ b/app/pages/system/silos/SilosPage.tsx @@ -84,6 +84,7 @@ export default function SilosPage() { onActivate: confirmDelete({ doDelete: () => deleteSilo({ path: { silo: silo.name } }), label: silo.name, + resourceKind: 'silo', }), }, ], diff --git a/app/stores/confirm-action.ts b/app/stores/confirm-action.ts index 7b315bc7d3..a8d2f0d90f 100644 --- a/app/stores/confirm-action.ts +++ b/app/stores/confirm-action.ts @@ -11,7 +11,7 @@ import { create } from 'zustand' type ActionConfig = { /** Must be `mutateAsync`, otherwise we can't catch the error generically */ doAction: () => Promise - /** e.g., Confirm delete, Confirm unlink */ + /** e.g., Delete project, Unlink pool */ modalTitle: string modalContent: ReactNode /** Title of error toast */ diff --git a/app/stores/confirm-delete.tsx b/app/stores/confirm-delete.tsx index 2486257358..420ffbd4e3 100644 --- a/app/stores/confirm-delete.tsx +++ b/app/stores/confirm-delete.tsx @@ -23,7 +23,9 @@ type DeleteConfig = { * directly. */ label: React.ReactNode - resourceKind?: string + /** Lowercase resource type, used in the modal title ("Delete X") and + * the error toast title ("Could not delete X"). */ + resourceKind: string extraContent?: React.ReactNode } @@ -31,7 +33,6 @@ export const confirmDelete = ({ doDelete, label, resourceKind, extraContent }: DeleteConfig) => () => { const displayLabel = typeof label === 'string' ? {label} : label - const modalTitle = resourceKind ? `Confirm delete ${resourceKind}` : 'Confirm delete' useConfirmAction.setState({ actionConfig: { doAction: doDelete, @@ -41,8 +42,8 @@ export const confirmDelete = {extraContent ?

{extraContent}

: null}
), - errorTitle: 'Could not delete resource', - modalTitle, + errorTitle: `Could not delete ${resourceKind}`, + modalTitle: `Delete ${resourceKind}`, actionType: 'danger', }, }) diff --git a/test/e2e/anti-affinity.e2e.ts b/test/e2e/anti-affinity.e2e.ts index 466b623174..09250980fb 100644 --- a/test/e2e/anti-affinity.e2e.ts +++ b/test/e2e/anti-affinity.e2e.ts @@ -170,7 +170,7 @@ test('can delete an anti-affinity group', async ({ page }) => { test('can delete anti-affinity group from detail page', async ({ page }) => { await page.goto('/projects/mock-project/affinity/romulus-remus') - const modal = page.getByRole('dialog', { name: 'Confirm delete' }) + const modal = page.getByRole('dialog', { name: 'Delete anti-affinity group' }) await expect(modal).toBeHidden() await page.getByLabel('Anti-affinity group actions').click() @@ -207,7 +207,7 @@ test('add and remove instance from group on instance settings', async ({ page }) // Stop the instance await page.getByRole('button', { name: 'Stop' }).click() - const confirmStopModal = page.getByRole('dialog', { name: 'Confirm stop' }) + const confirmStopModal = page.getByRole('dialog', { name: 'Stop instance' }) await expect(confirmStopModal).toBeVisible() await confirmStopModal.getByRole('button', { name: 'Confirm' }).click() await expect(confirmStopModal).toBeHidden() diff --git a/test/e2e/images.e2e.ts b/test/e2e/images.e2e.ts index a966acdd23..1c3ef736c6 100644 --- a/test/e2e/images.e2e.ts +++ b/test/e2e/images.e2e.ts @@ -175,7 +175,7 @@ test("Silo viewer can't delete silo image", async ({ browser }) => { await expect(spinner).toBeVisible() // Check deletion was successful - await expect(page.getByText('Could not delete resource', { exact: true })).toBeVisible() + await expect(page.getByText('Could not delete image', { exact: true })).toBeVisible() await expect(cell).toBeVisible() await expect(spinner).toBeHidden() }) diff --git a/test/e2e/instance-networking.e2e.ts b/test/e2e/instance-networking.e2e.ts index 112a4cb03d..e3c3d06a88 100644 --- a/test/e2e/instance-networking.e2e.ts +++ b/test/e2e/instance-networking.e2e.ts @@ -154,7 +154,7 @@ test('Instance networking tab — Detach / Attach Ephemeral IPs', async ({ page // an explicit ipVersion selector), then reattach it. await clickRowAction(page, 'fd00::1', 'Detach') const confirmDetachDialog = page.getByRole('dialog', { - name: 'Confirm detach ephemeral IP', + name: 'Detach ephemeral IP', }) await expect(confirmDetachDialog).toBeVisible() await confirmDetachDialog.getByRole('button', { name: 'Confirm' }).click() diff --git a/test/e2e/ip-pools.e2e.ts b/test/e2e/ip-pools.e2e.ts index 3df0e782e5..ba4427ac0c 100644 --- a/test/e2e/ip-pools.e2e.ts +++ b/test/e2e/ip-pools.e2e.ts @@ -87,7 +87,7 @@ test('IP pool silo list', async ({ page }) => { // unlink silo and the row is gone await clickRowAction(page, 'maze-war', 'Unlink') - await expect(page.getByRole('dialog', { name: 'Confirm unlink' })).toBeVisible() + await expect(page.getByRole('dialog', { name: 'Unlink silo' })).toBeVisible() await page.getByRole('button', { name: 'Confirm' }).click() await expect(siloLink).toBeHidden() }) @@ -133,7 +133,7 @@ test('IP pool silo make default (no existing default)', async ({ page }) => { await clickRowAction(page, 'pelerines', 'Make default') - const dialog = page.getByRole('dialog', { name: 'Confirm make default' }) + const dialog = page.getByRole('dialog', { name: 'Make default' }) await expect( dialog.getByText( 'Are you sure you want to make ip-pool-1 the default IPv4 unicast pool for silo pelerines?' @@ -153,7 +153,7 @@ test('IP pool silo make default (with existing default)', async ({ page }) => { await clickRowAction(page, 'myriad', 'Make default') - const dialog = page.getByRole('dialog', { name: 'Confirm change default' }) + const dialog = page.getByRole('dialog', { name: 'Change default' }) await expect( dialog.getByText( 'Are you sure you want to change the default IPv4 unicast pool for silo myriad from ip-pool-1 to ip-pool-3?' @@ -172,7 +172,7 @@ test('IP pool silo clear default', async ({ page }) => { await clickRowAction(page, 'maze-war', 'Clear default') - const dialog = page.getByRole('dialog', { name: 'Confirm clear default' }) + const dialog = page.getByRole('dialog', { name: 'Clear default' }) await expect( dialog.getByText( 'Are you sure you want ip-pool-1 to stop being the default IPv4 unicast pool for silo maze-war?' @@ -188,19 +188,19 @@ test('IP pool delete from IP Pools list page', async ({ page }) => { // can't delete a pool containing ranges await clickRowAction(page, 'ip-pool-1', 'Delete') - await expect(page.getByRole('dialog', { name: 'Confirm delete' })).toBeVisible() + await expect(page.getByRole('dialog', { name: 'Delete IP pool' })).toBeVisible() await page.getByRole('button', { name: 'Confirm' }).click() await expectToast( page, - 'Could not delete resourceIP pool cannot be deleted while it contains IP ranges' + 'Could not delete IP poolIP pool cannot be deleted while it contains IP ranges' ) await expect(page.getByRole('cell', { name: 'ip-pool-3' })).toBeVisible() // can delete a pool with no ranges await clickRowAction(page, 'ip-pool-3', 'Delete') - await expect(page.getByRole('dialog', { name: 'Confirm delete' })).toBeVisible() + await expect(page.getByRole('dialog', { name: 'Delete IP pool' })).toBeVisible() await page.getByRole('button', { name: 'Confirm' }).click() await expect(page.getByRole('cell', { name: 'ip-pool-3' })).toBeHidden() @@ -216,7 +216,7 @@ test('IP pool delete from IP Pool view page', async ({ page }) => { await page.goto('/system/networking/ip-pools/ip-pool-3') await page.getByRole('button', { name: 'IP pool actions' }).click() await page.getByRole('menuitem', { name: 'Delete' }).click() - await expect(page.getByRole('dialog', { name: 'Confirm delete' })).toBeVisible() + await expect(page.getByRole('dialog', { name: 'Delete IP pool' })).toBeVisible() await page.getByRole('button', { name: 'Confirm' }).click() // get redirected back to the list after successful delete @@ -375,7 +375,7 @@ test('remove range', async ({ page }) => { await clickRowAction(page, '10.0.0.20', 'Remove') - const confirmModal = page.getByRole('dialog', { name: 'Confirm remove range' }) + const confirmModal = page.getByRole('dialog', { name: 'Remove range' }) await expect(confirmModal.getByText('range 10.0.0.20–10.0.0.22')).toBeVisible() await page.getByRole('button', { name: 'Cancel' }).click() diff --git a/test/e2e/scim-tokens.e2e.ts b/test/e2e/scim-tokens.e2e.ts index 96a57d8b63..9948ab3351 100644 --- a/test/e2e/scim-tokens.e2e.ts +++ b/test/e2e/scim-tokens.e2e.ts @@ -91,7 +91,7 @@ test('Delete SCIM token', async ({ page }) => { await clickRowAction(page, 'a1b2c3d4', 'Delete') // Confirm deletion modal should appear - const confirmModal = page.getByRole('dialog', { name: 'Confirm delete' }) + const confirmModal = page.getByRole('dialog', { name: 'Delete SCIM token' }) await expect(confirmModal).toBeVisible() await expect(confirmModal.getByText('Are you sure you want to delete')).toBeVisible() diff --git a/test/e2e/silos.e2e.ts b/test/e2e/silos.e2e.ts index 410df41c65..9a96d16d6c 100644 --- a/test/e2e/silos.e2e.ts +++ b/test/e2e/silos.e2e.ts @@ -287,7 +287,7 @@ test('Silo IP pools', async ({ page }) => { await clickRowAction(page, 'ip-pool-1', 'Unlink') await expect( page - .getByRole('dialog', { name: 'Confirm unlink pool' }) + .getByRole('dialog', { name: 'Unlink pool' }) .getByText('Are you sure you want to unlink ip-pool-1?') ).toBeVisible() await page.getByRole('button', { name: 'Confirm' }).click() @@ -299,7 +299,7 @@ test('Silo IP pools', async ({ page }) => { await clickRowAction(page, 'ip-pool-2', 'Clear default') await expect( page - .getByRole('dialog', { name: 'Confirm clear default' }) + .getByRole('dialog', { name: 'Clear default' }) .getByText('Are you sure you want ip-pool-2 to stop being the default') ).toBeVisible() await page.getByRole('button', { name: 'Confirm' }).click() diff --git a/test/e2e/snapshots.e2e.ts b/test/e2e/snapshots.e2e.ts index c6ddc8183d..9338a3648a 100644 --- a/test/e2e/snapshots.e2e.ts +++ b/test/e2e/snapshots.e2e.ts @@ -53,7 +53,7 @@ test('Confirm delete snapshot', async ({ page }) => { await clickDelete() - const modal = page.getByRole('dialog', { name: 'Confirm delete' }) + const modal = page.getByRole('dialog', { name: 'Delete snapshot' }) await expect(modal).toBeVisible() // cancel works @@ -77,7 +77,7 @@ test('Error on delete snapshot', async ({ page }) => { await row.getByRole('button', { name: 'Row actions' }).click() await page.getByRole('menuitem', { name: 'Delete' }).click() - const modal = page.getByRole('dialog', { name: 'Confirm delete' }) + const modal = page.getByRole('dialog', { name: 'Delete snapshot' }) await expect(modal).toBeVisible() const spinner = page.getByRole('dialog').getByLabel('Spinner') @@ -90,7 +90,7 @@ test('Error on delete snapshot', async ({ page }) => { await expect(modal).toBeHidden() await expectVisible(page, [ row, - page.getByText('Could not delete resource', { exact: true }), + page.getByText('Could not delete snapshot', { exact: true }), ]) }) @@ -99,7 +99,8 @@ test('Create image from snapshot', async ({ page }) => { await clickRowAction(page, 'disk-1-snapshot-8', 'Create image') - await expectVisible(page, ['role=dialog[name="Create image from snapshot"]']) + const modal = page.getByRole('dialog', { name: 'Create image from snapshot' }) + await expect(modal).toBeVisible() await page.fill('role=textbox[name="Name"]', 'image-from-snapshot-8') await page.fill('role=textbox[name="Description"]', 'image description') @@ -109,8 +110,10 @@ test('Create image from snapshot', async ({ page }) => { await page.click('role=button[name="Create image"]') await expect(page).toHaveURL('/projects/mock-project/snapshots') + await expect(modal).toBeHidden() - await page.click('role=link[name*="Images"]') + await page.getByRole('link', { name: 'Images', exact: true }).click() + await expect(page).toHaveURL('/projects/mock-project/images') await expectRowVisible(page.getByRole('table'), { name: 'image-from-snapshot-8', description: 'image description', diff --git a/test/e2e/subnet-pools.e2e.ts b/test/e2e/subnet-pools.e2e.ts index 71d6e69528..92493314bc 100644 --- a/test/e2e/subnet-pools.e2e.ts +++ b/test/e2e/subnet-pools.e2e.ts @@ -144,7 +144,7 @@ test('Subnet pool remove member', async ({ page }) => { await page.goto('/system/networking/subnet-pools/secondary-v4-subnet-pool') await clickRowAction(page, '172.20.0.0/16', 'Remove') - await expect(page.getByRole('dialog', { name: 'Confirm remove' })).toBeVisible() + await expect(page.getByRole('dialog', { name: 'Remove member' })).toBeVisible() await page.getByRole('button', { name: 'Confirm' }).click() // The row should be gone @@ -168,7 +168,7 @@ test('Subnet pool linked silos', async ({ page }) => { // Unlink fails when silo still has external subnets allocated from the pool await clickRowAction(page, 'maze-war', 'Unlink') - await expect(page.getByRole('dialog', { name: 'Confirm unlink' })).toBeVisible() + await expect(page.getByRole('dialog', { name: 'Unlink silo' })).toBeVisible() await page.getByRole('button', { name: 'Confirm' }).click() await expectToast(page, 'Could not unlink silo') // Row should still be there @@ -183,7 +183,7 @@ test('Subnet pool unlink silo succeeds when no subnets allocated', async ({ page await expectRowVisible(table, { Silo: 'maze-war' }) await clickRowAction(page, 'maze-war', 'Unlink') - await expect(page.getByRole('dialog', { name: 'Confirm unlink' })).toBeVisible() + await expect(page.getByRole('dialog', { name: 'Unlink silo' })).toBeVisible() await page.getByRole('button', { name: 'Confirm' }).click() await expect(page.getByRole('link', { name: 'maze-war' })).toBeHidden() }) @@ -212,7 +212,7 @@ test('Subnet pool silo make default (no existing default)', async ({ page }) => await clickRowAction(page, 'maze-war', 'Make default') - const dialog = page.getByRole('dialog', { name: 'Confirm make default' }) + const dialog = page.getByRole('dialog', { name: 'Make default' }) await expect( dialog.getByText( 'Are you sure you want to make ipv6-subnet-pool the default IPv6 subnet pool for silo maze-war?' @@ -233,7 +233,7 @@ test('Subnet pool silo make default (with existing default)', async ({ page }) = await clickRowAction(page, 'maze-war', 'Make default') - const dialog = page.getByRole('dialog', { name: 'Confirm change default' }) + const dialog = page.getByRole('dialog', { name: 'Change default' }) await expect( dialog.getByText( 'Are you sure you want to change the default IPv4 subnet pool for silo maze-war from default-v4-subnet-pool to secondary-v4-subnet-pool?' @@ -252,7 +252,7 @@ test('Subnet pool silo clear default', async ({ page }) => { await clickRowAction(page, 'maze-war', 'Clear default') - const dialog = page.getByRole('dialog', { name: 'Confirm clear default' }) + const dialog = page.getByRole('dialog', { name: 'Clear default' }) await expect( dialog.getByText( 'Are you sure you want default-v4-subnet-pool to stop being the default IPv4 subnet pool for silo maze-war?' @@ -281,7 +281,7 @@ test('Subnet pool delete', async ({ page }) => { // First remove the member so the pool can be deleted await page.goto('/system/networking/subnet-pools/secondary-v4-subnet-pool') await clickRowAction(page, '172.20.0.0/16', 'Remove') - const removeDialog = page.getByRole('dialog', { name: 'Confirm remove' }) + const removeDialog = page.getByRole('dialog', { name: 'Remove member' }) await expect(removeDialog).toBeVisible() await removeDialog.getByRole('button', { name: 'Confirm' }).click() await expect(page.getByRole('cell', { name: '172.20.0.0/16' })).toBeHidden() @@ -289,7 +289,7 @@ test('Subnet pool delete', async ({ page }) => { // Use client-side navigation to preserve MSW db state await page.getByLabel('Breadcrumbs').getByRole('link', { name: 'Subnet Pools' }).click() await clickRowAction(page, 'secondary-v4-subnet-pool', 'Delete') - const deleteDialog = page.getByRole('dialog', { name: 'Confirm delete' }) + const deleteDialog = page.getByRole('dialog', { name: 'Delete subnet pool' }) await expect(deleteDialog).toBeVisible() await deleteDialog.getByRole('button', { name: 'Confirm' }).click() await expect(page.getByRole('cell', { name: 'secondary-v4-subnet-pool' })).toBeHidden() diff --git a/test/e2e/system-update.e2e.ts b/test/e2e/system-update.e2e.ts index b3daf6f447..b68f1ddd0d 100644 --- a/test/e2e/system-update.e2e.ts +++ b/test/e2e/system-update.e2e.ts @@ -61,7 +61,7 @@ test('Set target release', async ({ page }) => { await page.getByRole('button', { name: '18.0.0 actions' }).click() await page.getByRole('menuitem', { name: 'Set as target release' }).click() - const modal = page.getByRole('dialog', { name: 'Confirm set target release' }) + const modal = page.getByRole('dialog', { name: 'Set target release' }) await expect(modal).toBeVisible() await expect( modal.getByText('Are you sure you want to set 18.0.0 as the target release?')