From 6c0b49818c2b2edf627f1e1de6eaf89f417a5e29 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:02:25 +0100 Subject: [PATCH 01/18] pnpm build --- web/src/routeTree.gen.ts | 43 ++++++++++++------------- web/src/routes/error/migration-auth.tsx | 2 +- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/web/src/routeTree.gen.ts b/web/src/routeTree.gen.ts index e9fa1c3d2e..443c5dd0e8 100644 --- a/web/src/routeTree.gen.ts +++ b/web/src/routeTree.gen.ts @@ -16,6 +16,7 @@ import { Route as AuthorizedRouteImport } from './routes/_authorized' import { Route as R404RouteImport } from './routes/404' import { Route as IndexRouteImport } from './routes/index' import { Route as AuthIndexRouteImport } from './routes/auth/index' +import { Route as ErrorMigrationAuthRouteImport } from './routes/error/migration-auth' import { Route as AuthMfaRouteImport } from './routes/auth/mfa' import { Route as AuthLoginRouteImport } from './routes/auth/login' import { Route as AuthLoadingRouteImport } from './routes/auth/loading' @@ -31,7 +32,6 @@ import { Route as AuthMfaTotpRouteImport } from './routes/auth/mfa/totp' import { Route as AuthMfaRecoveryRouteImport } from './routes/auth/mfa/recovery' import { Route as AuthMfaEmailRouteImport } from './routes/auth/mfa/email' import { Route as WizardMigrationLocationsRouteImport } from './routes/_wizard/migration/locations' -import { Route as AuthorizedErrorMigrationAuthRouteImport } from './routes/_authorized/error/migration-auth' import { Route as AuthorizedWizardSetupEdgeRouteImport } from './routes/_authorized/_wizard/setup-edge' import { Route as AuthorizedWizardAddLocationRouteImport } from './routes/_authorized/_wizard/add-location' import { Route as AuthorizedWizardAddExternalOpenidRouteImport } from './routes/_authorized/_wizard/add-external-openid' @@ -102,6 +102,11 @@ const AuthIndexRoute = AuthIndexRouteImport.update({ path: '/', getParentRoute: () => AuthRoute, } as any) +const ErrorMigrationAuthRoute = ErrorMigrationAuthRouteImport.update({ + id: '/error/migration-auth', + path: '/error/migration-auth', + getParentRoute: () => rootRouteImport, +} as any) const AuthMfaRoute = AuthMfaRouteImport.update({ id: '/mfa', path: '/mfa', @@ -177,12 +182,6 @@ const WizardMigrationLocationsRoute = path: '/migration/locations', getParentRoute: () => rootRouteImport, } as any) -const AuthorizedErrorMigrationAuthRoute = - AuthorizedErrorMigrationAuthRouteImport.update({ - id: '/error/migration-auth', - path: '/error/migration-auth', - getParentRoute: () => AuthorizedRoute, - } as any) const AuthorizedWizardSetupEdgeRoute = AuthorizedWizardSetupEdgeRouteImport.update({ id: '/_wizard/setup-edge', @@ -404,6 +403,7 @@ export interface FileRoutesByFullPath { '/auth/loading': typeof AuthLoadingRoute '/auth/login': typeof AuthLoginRoute '/auth/mfa': typeof AuthMfaRouteWithChildren + '/error/migration-auth': typeof ErrorMigrationAuthRoute '/auth/': typeof AuthIndexRoute '/activity': typeof AuthorizedDefaultActivityRoute '/edges': typeof AuthorizedDefaultEdgesRoute @@ -415,7 +415,6 @@ export interface FileRoutesByFullPath { '/add-external-openid': typeof AuthorizedWizardAddExternalOpenidRoute '/add-location': typeof AuthorizedWizardAddLocationRoute '/setup-edge': typeof AuthorizedWizardSetupEdgeRoute - '/error/migration-auth': typeof AuthorizedErrorMigrationAuthRoute '/migration/locations': typeof WizardMigrationLocationsRoute '/auth/mfa/email': typeof AuthMfaEmailRoute '/auth/mfa/recovery': typeof AuthMfaRecoveryRoute @@ -461,6 +460,7 @@ export interface FileRoutesByTo { '/auth/loading': typeof AuthLoadingRoute '/auth/login': typeof AuthLoginRoute '/auth/mfa': typeof AuthMfaRouteWithChildren + '/error/migration-auth': typeof ErrorMigrationAuthRoute '/auth': typeof AuthIndexRoute '/activity': typeof AuthorizedDefaultActivityRoute '/edges': typeof AuthorizedDefaultEdgesRoute @@ -472,7 +472,6 @@ export interface FileRoutesByTo { '/add-external-openid': typeof AuthorizedWizardAddExternalOpenidRoute '/add-location': typeof AuthorizedWizardAddLocationRoute '/setup-edge': typeof AuthorizedWizardSetupEdgeRoute - '/error/migration-auth': typeof AuthorizedErrorMigrationAuthRoute '/migration/locations': typeof WizardMigrationLocationsRoute '/auth/mfa/email': typeof AuthMfaEmailRoute '/auth/mfa/recovery': typeof AuthMfaRecoveryRoute @@ -522,6 +521,7 @@ export interface FileRoutesById { '/auth/loading': typeof AuthLoadingRoute '/auth/login': typeof AuthLoginRoute '/auth/mfa': typeof AuthMfaRouteWithChildren + '/error/migration-auth': typeof ErrorMigrationAuthRoute '/auth/': typeof AuthIndexRoute '/_authorized/_default/activity': typeof AuthorizedDefaultActivityRoute '/_authorized/_default/edges': typeof AuthorizedDefaultEdgesRoute @@ -533,7 +533,6 @@ export interface FileRoutesById { '/_authorized/_wizard/add-external-openid': typeof AuthorizedWizardAddExternalOpenidRoute '/_authorized/_wizard/add-location': typeof AuthorizedWizardAddLocationRoute '/_authorized/_wizard/setup-edge': typeof AuthorizedWizardSetupEdgeRoute - '/_authorized/error/migration-auth': typeof AuthorizedErrorMigrationAuthRoute '/_wizard/migration/locations': typeof WizardMigrationLocationsRoute '/auth/mfa/email': typeof AuthMfaEmailRoute '/auth/mfa/recovery': typeof AuthMfaRecoveryRoute @@ -582,6 +581,7 @@ export interface FileRouteTypes { | '/auth/loading' | '/auth/login' | '/auth/mfa' + | '/error/migration-auth' | '/auth/' | '/activity' | '/edges' @@ -593,7 +593,6 @@ export interface FileRouteTypes { | '/add-external-openid' | '/add-location' | '/setup-edge' - | '/error/migration-auth' | '/migration/locations' | '/auth/mfa/email' | '/auth/mfa/recovery' @@ -639,6 +638,7 @@ export interface FileRouteTypes { | '/auth/loading' | '/auth/login' | '/auth/mfa' + | '/error/migration-auth' | '/auth' | '/activity' | '/edges' @@ -650,7 +650,6 @@ export interface FileRouteTypes { | '/add-external-openid' | '/add-location' | '/setup-edge' - | '/error/migration-auth' | '/migration/locations' | '/auth/mfa/email' | '/auth/mfa/recovery' @@ -699,6 +698,7 @@ export interface FileRouteTypes { | '/auth/loading' | '/auth/login' | '/auth/mfa' + | '/error/migration-auth' | '/auth/' | '/_authorized/_default/activity' | '/_authorized/_default/edges' @@ -710,7 +710,6 @@ export interface FileRouteTypes { | '/_authorized/_wizard/add-external-openid' | '/_authorized/_wizard/add-location' | '/_authorized/_wizard/setup-edge' - | '/_authorized/error/migration-auth' | '/_wizard/migration/locations' | '/auth/mfa/email' | '/auth/mfa/recovery' @@ -754,6 +753,7 @@ export interface RootRouteChildren { WizardSetupRoute: typeof WizardSetupRoute WizardSetupGatewayRoute: typeof WizardSetupGatewayRoute WizardSetupLoginRoute: typeof WizardSetupLoginRoute + ErrorMigrationAuthRoute: typeof ErrorMigrationAuthRoute WizardMigrationLocationsRoute: typeof WizardMigrationLocationsRoute WizardMigrationIndexRoute: typeof WizardMigrationIndexRoute } @@ -809,6 +809,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthIndexRouteImport parentRoute: typeof AuthRoute } + '/error/migration-auth': { + id: '/error/migration-auth' + path: '/error/migration-auth' + fullPath: '/error/migration-auth' + preLoaderRoute: typeof ErrorMigrationAuthRouteImport + parentRoute: typeof rootRouteImport + } '/auth/mfa': { id: '/auth/mfa' path: '/mfa' @@ -914,13 +921,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof WizardMigrationLocationsRouteImport parentRoute: typeof rootRouteImport } - '/_authorized/error/migration-auth': { - id: '/_authorized/error/migration-auth' - path: '/error/migration-auth' - fullPath: '/error/migration-auth' - preLoaderRoute: typeof AuthorizedErrorMigrationAuthRouteImport - parentRoute: typeof AuthorizedRoute - } '/_authorized/_wizard/setup-edge': { id: '/_authorized/_wizard/setup-edge' path: '/setup-edge' @@ -1258,7 +1258,6 @@ interface AuthorizedRouteChildren { AuthorizedWizardAddExternalOpenidRoute: typeof AuthorizedWizardAddExternalOpenidRoute AuthorizedWizardAddLocationRoute: typeof AuthorizedWizardAddLocationRoute AuthorizedWizardSetupEdgeRoute: typeof AuthorizedWizardSetupEdgeRoute - AuthorizedErrorMigrationAuthRoute: typeof AuthorizedErrorMigrationAuthRoute } const AuthorizedRouteChildren: AuthorizedRouteChildren = { @@ -1268,7 +1267,6 @@ const AuthorizedRouteChildren: AuthorizedRouteChildren = { AuthorizedWizardAddExternalOpenidRoute, AuthorizedWizardAddLocationRoute: AuthorizedWizardAddLocationRoute, AuthorizedWizardSetupEdgeRoute: AuthorizedWizardSetupEdgeRoute, - AuthorizedErrorMigrationAuthRoute: AuthorizedErrorMigrationAuthRoute, } const AuthorizedRouteWithChildren = AuthorizedRoute._addFileChildren( @@ -1320,6 +1318,7 @@ const rootRouteChildren: RootRouteChildren = { WizardSetupRoute: WizardSetupRoute, WizardSetupGatewayRoute: WizardSetupGatewayRoute, WizardSetupLoginRoute: WizardSetupLoginRoute, + ErrorMigrationAuthRoute: ErrorMigrationAuthRoute, WizardMigrationLocationsRoute: WizardMigrationLocationsRoute, WizardMigrationIndexRoute: WizardMigrationIndexRoute, } diff --git a/web/src/routes/error/migration-auth.tsx b/web/src/routes/error/migration-auth.tsx index 685d65e5fb..6d0eccb32d 100644 --- a/web/src/routes/error/migration-auth.tsx +++ b/web/src/routes/error/migration-auth.tsx @@ -2,7 +2,7 @@ import { createFileRoute, redirect } from '@tanstack/react-router'; import { ErrorMigrationInProgressPage } from '../../pages/ErrorMigrationInProgressPage/ErrorMigrationInProgressPage'; import { getSessionInfoQueryOptions } from '../../shared/query'; -export const Route = createFileRoute('/_authorized/error/migration-auth')({ +export const Route = createFileRoute('/error/migration-auth')({ beforeLoad: async ({ context }) => { const sessionInfo = (await context.queryClient.fetchQuery(getSessionInfoQueryOptions)) .data; From 3b9830c4d9a432832b46b9a1d3be8a2786e74a65 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:08:20 +0100 Subject: [PATCH 02/18] delete user --- web/messages/en/modal.json | 2 ++ web/src/pages/UsersOverviewPage/UsersTable.tsx | 16 +++++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/web/messages/en/modal.json b/web/messages/en/modal.json index 0e0ed3b7af..341151b9d6 100644 --- a/web/messages/en/modal.json +++ b/web/messages/en/modal.json @@ -12,6 +12,8 @@ "modal_delete_location_body": "Deleting location {name} will also delete related gateways. This action cannot be undone.", "modal_delete_network_device_title": "Delete device", "modal_delete_network_device_body": "Are you sure you want to delete this device? This action cannot be undone.", + "modal_delete_user_title": "Delete user", + "modal_delete_user_body": "Are you sure you want to delete user {name}? This action cannot be undone.", "modal_delete_user_device_title": "Delete device", "modal_delete_user_device_body": "Are you sure you want to delete the {name} device? This action cannot be undone.", "modal_delete_openid_client_title": "Delete application", diff --git a/web/src/pages/UsersOverviewPage/UsersTable.tsx b/web/src/pages/UsersOverviewPage/UsersTable.tsx index 13bd69f9dc..dbb65e6629 100644 --- a/web/src/pages/UsersOverviewPage/UsersTable.tsx +++ b/web/src/pages/UsersOverviewPage/UsersTable.tsx @@ -98,13 +98,6 @@ export const UsersTable = () => { [groups?.map, groups], ); - const { mutate: deleteUser } = useMutation({ - mutationFn: api.user.deleteUser, - meta: { - invalidate: [['user-overview'], ['user'], ['enterprise_info']], - }, - }); - const { mutate: changeAccountActiveState } = useMutation({ mutationFn: api.user.activeStateChange, meta: { @@ -368,7 +361,13 @@ export const UsersTable = () => { icon: 'delete', variant: 'danger', onClick: () => { - deleteUser(rowData.username); + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_user_title(), + contentMd: m.modal_delete_user_body({ name: rowData.name }), + actionPromise: () => api.user.deleteUser(rowData.username), + invalidateKeys: [['user-overview'], ['user'], ['enterprise_info']], + submitProps: { text: m.users_row_menu_delete(), variant: 'critical' }, + }); }, }, ], @@ -454,7 +453,6 @@ export const UsersTable = () => { reservedEmails, reservedUsernames, changeAccountActiveState, - deleteUser, groupsOptions, handleEditGroups, groups, From 89bafadfa05e16558aa5e0dcbfbb9fad165adcc7 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:12:26 +0100 Subject: [PATCH 03/18] delete edge --- web/messages/en/modal.json | 2 ++ web/src/pages/EdgesPage/EdgesTable.tsx | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/web/messages/en/modal.json b/web/messages/en/modal.json index 341151b9d6..7e3ab7362c 100644 --- a/web/messages/en/modal.json +++ b/web/messages/en/modal.json @@ -14,6 +14,8 @@ "modal_delete_network_device_body": "Are you sure you want to delete this device? This action cannot be undone.", "modal_delete_user_title": "Delete user", "modal_delete_user_body": "Are you sure you want to delete user {name}? This action cannot be undone.", + "modal_delete_edge_title": "Delete edge", + "modal_delete_edge_body": "Are you sure you want to delete edge {name}? This action cannot be undone.", "modal_delete_user_device_title": "Delete device", "modal_delete_user_device_body": "Are you sure you want to delete the {name} device? This action cannot be undone.", "modal_delete_openid_client_title": "Delete application", diff --git a/web/src/pages/EdgesPage/EdgesTable.tsx b/web/src/pages/EdgesPage/EdgesTable.tsx index d608f823e8..3ea13d2cbb 100644 --- a/web/src/pages/EdgesPage/EdgesTable.tsx +++ b/web/src/pages/EdgesPage/EdgesTable.tsx @@ -24,6 +24,8 @@ import { TableCell } from '../../shared/defguard-ui/components/table/TableCell/T import { TableEditCell } from '../../shared/defguard-ui/components/table/TableEditCell/TableEditCell'; import { TableTop } from '../../shared/defguard-ui/components/table/TableTop/TableTop'; import { isPresent } from '../../shared/defguard-ui/utils/isPresent'; +import { openModal } from '../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../shared/hooks/modalControls/modalTypes'; import { getEdgesQueryOptions, getLicenseInfoQueryOptions } from '../../shared/query'; import { displayDate } from '../../shared/utils/displayDate'; import { canUseEnterpriseFeature, licenseActionCheck } from '../../shared/utils/license'; @@ -72,13 +74,6 @@ export const EdgesTable = () => { const navigate = useNavigate(); - const { mutate: deleteEdge } = useMutation({ - mutationFn: api.edge.deleteEdge, - meta: { - invalidate: ['edge'], - }, - }); - const { mutate: toggleEdge } = useMutation({ mutationFn: api.edge.editEdge, meta: { @@ -239,7 +234,13 @@ export const EdgesTable = () => { icon: 'delete', variant: 'danger', onClick: () => { - deleteEdge(rowData.id); + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_edge_title(), + contentMd: m.modal_delete_edge_body({ name: rowData.name }), + actionPromise: () => api.edge.deleteEdge(rowData.id), + invalidateKeys: [['edge']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + }); }, }, ], @@ -250,7 +251,7 @@ export const EdgesTable = () => { }, }), ], - [deleteEdge, navigate, toggleEdge], + [navigate, toggleEdge], ); const table = useReactTable({ From 996f51ae8ab9e0175fa326efe8c5cb134a2dc76d Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:29:11 +0100 Subject: [PATCH 04/18] delete group --- web/messages/en/modal.json | 2 ++ .../components/GroupsTable/GroupsTable.tsx | 17 ++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/web/messages/en/modal.json b/web/messages/en/modal.json index 7e3ab7362c..d9c7437d17 100644 --- a/web/messages/en/modal.json +++ b/web/messages/en/modal.json @@ -16,6 +16,8 @@ "modal_delete_user_body": "Are you sure you want to delete user {name}? This action cannot be undone.", "modal_delete_edge_title": "Delete edge", "modal_delete_edge_body": "Are you sure you want to delete edge {name}? This action cannot be undone.", + "modal_delete_group_title": "Delete group", + "modal_delete_group_body": "Are you sure you want to delete group {name}? This action cannot be undone.", "modal_delete_user_device_title": "Delete device", "modal_delete_user_device_body": "Are you sure you want to delete the {name} device? This action cannot be undone.", "modal_delete_openid_client_title": "Delete application", diff --git a/web/src/pages/GroupsPage/components/GroupsTable/GroupsTable.tsx b/web/src/pages/GroupsPage/components/GroupsTable/GroupsTable.tsx index 80580dce46..c23cc29d41 100644 --- a/web/src/pages/GroupsPage/components/GroupsTable/GroupsTable.tsx +++ b/web/src/pages/GroupsPage/components/GroupsTable/GroupsTable.tsx @@ -1,4 +1,3 @@ -import { useMutation } from '@tanstack/react-query'; import { createColumnHelper, getCoreRowModel, @@ -33,12 +32,6 @@ const columnHelper = createColumnHelper(); export const GroupsTable = ({ groups, users }: Props) => { const [search, setSearch] = useState(''); const reservedNames = useMemo(() => groups.map((g) => g.name), [groups]); - const { mutate: deleteGroup } = useMutation({ - mutationFn: api.group.deleteGroup, - meta: { - invalidate: [['group'], ['group-info']], - }, - }); const transformedData = useMemo(() => { let data = groups; @@ -123,7 +116,13 @@ export const GroupsTable = ({ groups, users }: Props) => { icon: 'delete', variant: 'danger', onClick: () => { - deleteGroup(rowData.name); + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_group_title(), + contentMd: m.modal_delete_group_body({ name: rowData.name }), + actionPromise: () => api.group.deleteGroup(rowData.name), + invalidateKeys: [['group'], ['group-info']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + }); }, }, ]; @@ -131,7 +130,7 @@ export const GroupsTable = ({ groups, users }: Props) => { }, }), ], - [deleteGroup, reservedNames, users], + [reservedNames, users], ); const table = useReactTable({ From f961b89de6b27e5b8b1167850f8c9742811780e1 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:07:26 +0100 Subject: [PATCH 05/18] delete edge via edit --- web/src/pages/EditEdgePage/EditEdgePage.tsx | 33 +++++++++------------ 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/web/src/pages/EditEdgePage/EditEdgePage.tsx b/web/src/pages/EditEdgePage/EditEdgePage.tsx index 48948cc049..d807d566db 100644 --- a/web/src/pages/EditEdgePage/EditEdgePage.tsx +++ b/web/src/pages/EditEdgePage/EditEdgePage.tsx @@ -13,6 +13,8 @@ import { Snackbar } from '../../shared/defguard-ui/providers/snackbar/snackbar'; import { ThemeSpacing } from '../../shared/defguard-ui/types'; import { useAppForm } from '../../shared/form'; import { formChangeLogic } from '../../shared/formLogic'; +import { openModal } from '../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../shared/hooks/modalControls/modalTypes'; import { getEdgeQueryOptions } from '../../shared/query'; export const EditEdgePage = () => { @@ -69,23 +71,6 @@ const EditEdgeForm = ({ edge }: { edge: Edge }) => { }, }); - const { mutate: deleteEdge, isPending: deletePending } = useMutation({ - mutationFn: () => api.edge.deleteEdge(edge.id), - meta: { - invalidate: ['edge'], - }, - onSuccess: () => { - navigate({ - to: '/edges', - replace: true, - }); - Snackbar.default(m.edge_delete_success()); - }, - onError: () => { - Snackbar.error(m.edge_delete_failed()); - }, - }); - const defaultValues = useMemo((): FormFields => ({ ...edge }), [edge]); const form = useAppForm({ @@ -142,9 +127,19 @@ const EditEdgeForm = ({ edge }: { edge: Edge }) => { deleteProps={{ text: m.edge_edit_delete(), onClick: () => { - deleteEdge(); + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_edge_title(), + contentMd: m.modal_delete_edge_body({ name: edge.name }), + actionPromise: () => api.edge.deleteEdge(edge.id), + invalidateKeys: [['edge']], + submitProps: { text: m.edge_edit_delete(), variant: 'critical' }, + onSuccess: () => { + navigate({ to: '/edges', replace: true }); + Snackbar.default(m.edge_delete_success()); + }, + onError: () => Snackbar.error(m.edge_delete_failed()), + }); }, - loading: deletePending, disabled: isSubmitting, }} submitProps={{ From b9dd6da6d144d051163933e38bdd42c452e5aa80 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:43:52 +0100 Subject: [PATCH 06/18] delete old modal and use generic one --- .../pages/EditGatewayPage/EditGatewayPage.tsx | 39 ++++----- .../components/GatewaysTable.tsx | 16 +++- .../DeleteGatewayModal/DeleteGatewayModal.tsx | 87 ------------------- .../pages/LocationsPage/tabs/GatewaysTab.tsx | 10 +-- .../shared/hooks/modalControls/modalTypes.ts | 6 -- web/src/shared/hooks/modalControls/types.ts | 6 -- 6 files changed, 34 insertions(+), 130 deletions(-) delete mode 100644 web/src/pages/LocationsPage/modals/DeleteGatewayModal/DeleteGatewayModal.tsx diff --git a/web/src/pages/EditGatewayPage/EditGatewayPage.tsx b/web/src/pages/EditGatewayPage/EditGatewayPage.tsx index 6a96e3cab1..ff21bc1fd4 100644 --- a/web/src/pages/EditGatewayPage/EditGatewayPage.tsx +++ b/web/src/pages/EditGatewayPage/EditGatewayPage.tsx @@ -13,7 +13,9 @@ import { Snackbar } from '../../shared/defguard-ui/providers/snackbar/snackbar'; import { ThemeSpacing } from '../../shared/defguard-ui/types'; import { useAppForm } from '../../shared/form'; import { formChangeLogic } from '../../shared/formLogic'; -import { getGatewayQueryOptions } from '../../shared/query'; +import { openModal } from '../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../shared/hooks/modalControls/modalTypes'; +import { getGatewayQueryOptions, getLocationQueryOptions } from '../../shared/query'; export const EditGatewayPage = () => { const { gatewayId } = useParams({ @@ -56,6 +58,7 @@ type FormFields = z.infer; const EditGatewayForm = ({ gateway }: { gateway: Gateway }) => { const navigate = useNavigate(); + const { data: location } = useSuspenseQuery(getLocationQueryOptions(gateway.location_id)); const { mutateAsync: editGateway } = useMutation({ mutationFn: api.gateway.editGateway, @@ -70,23 +73,6 @@ const EditGatewayForm = ({ gateway }: { gateway: Gateway }) => { }, }); - const { mutate: deleteGateway, isPending: deletePending } = useMutation({ - mutationFn: () => api.gateway.deleteGateway(gateway.id), - meta: { - invalidate: [['gateway'], ['network']], - }, - onSuccess: () => { - navigate({ - to: '/locations', - replace: true, - }); - Snackbar.default(m.gateway_delete_success()); - }, - onError: () => { - Snackbar.error(m.gateway_delete_failed()); - }, - }); - const defaultValues = useMemo((): FormFields => ({ ...gateway }), [gateway]); const form = useAppForm({ @@ -144,9 +130,22 @@ const EditGatewayForm = ({ gateway }: { gateway: Gateway }) => { deleteProps={{ text: m.gateway_edit_delete(), onClick: () => { - deleteGateway(); + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_gateway_title(), + contentMd: m.modal_delete_gateway_body({ + name: gateway.name, + locationName: location.name, + }), + actionPromise: () => api.gateway.deleteGateway(gateway.id), + invalidateKeys: [['gateway'], ['network']], + submitProps: { text: m.gateway_edit_delete(), variant: 'critical' }, + onSuccess: () => { + navigate({ to: '/locations', replace: true }); + Snackbar.default(m.gateway_delete_success()); + }, + onError: () => Snackbar.error(m.gateway_delete_failed()), + }); }, - loading: deletePending, disabled: isSubmitting, }} submitProps={{ diff --git a/web/src/pages/LocationsPage/components/GatewaysTable.tsx b/web/src/pages/LocationsPage/components/GatewaysTable.tsx index fa8276994b..b699433502 100644 --- a/web/src/pages/LocationsPage/components/GatewaysTable.tsx +++ b/web/src/pages/LocationsPage/components/GatewaysTable.tsx @@ -19,6 +19,7 @@ import { TableBody } from '../../../shared/defguard-ui/components/table/TableBod import { TableCell } from '../../../shared/defguard-ui/components/table/TableCell/TableCell'; import { TableEditCell } from '../../../shared/defguard-ui/components/table/TableEditCell/TableEditCell'; import { TableTop } from '../../../shared/defguard-ui/components/table/TableTop/TableTop'; +import { Snackbar } from '../../../shared/defguard-ui/providers/snackbar/snackbar'; import { openModal } from '../../../shared/hooks/modalControls/modalsSubjects'; import { ModalName } from '../../../shared/hooks/modalControls/modalTypes'; import { getGatewaysQueryOptions } from '../../../shared/query'; @@ -211,10 +212,17 @@ export const GatewaysTable = () => { icon: 'delete', variant: 'danger', onClick: () => { - openModal(ModalName.DeleteGateway, { - id: rowData.id, - name: rowData.name, - locationName: rowData.location_name, + openModal(ModalName.ConfirmAction, { + title: m.modal_delete_gateway_title(), + contentMd: m.modal_delete_gateway_body({ + name: rowData.name, + locationName: rowData.location_name, + }), + actionPromise: () => api.gateway.deleteGateway(rowData.id), + invalidateKeys: [['gateway'], ['network']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => Snackbar.default(m.gateway_delete_success()), + onError: () => Snackbar.error(m.gateway_delete_failed()), }); }, }, diff --git a/web/src/pages/LocationsPage/modals/DeleteGatewayModal/DeleteGatewayModal.tsx b/web/src/pages/LocationsPage/modals/DeleteGatewayModal/DeleteGatewayModal.tsx deleted file mode 100644 index 02b1b81550..0000000000 --- a/web/src/pages/LocationsPage/modals/DeleteGatewayModal/DeleteGatewayModal.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useEffect, useState } from 'react'; -import { m } from '../../../../paraglide/messages'; -import api from '../../../../shared/api/api'; -import { AppText } from '../../../../shared/defguard-ui/components/AppText/AppText'; -import { Modal } from '../../../../shared/defguard-ui/components/Modal/Modal'; -import { ModalControls } from '../../../../shared/defguard-ui/components/ModalControls/ModalControls'; -import { Snackbar } from '../../../../shared/defguard-ui/providers/snackbar/snackbar'; -import { TextStyle } from '../../../../shared/defguard-ui/types'; -import { - subscribeCloseModal, - subscribeOpenModal, -} from '../../../../shared/hooks/modalControls/modalsSubjects'; -import { ModalName } from '../../../../shared/hooks/modalControls/modalTypes'; -import type { OpenDeleteGatewayModal } from '../../../../shared/hooks/modalControls/types'; - -const modalNameValue = ModalName.DeleteGateway; - -type ModalData = OpenDeleteGatewayModal; - -export const DeleteGatewayModal = () => { - const [isOpen, setOpen] = useState(false); - const [modalData, setModalData] = useState(null); - - const { mutateAsync: deleteGateway, isPending } = useMutation({ - mutationFn: api.gateway.deleteGateway, - meta: { - invalidate: [['gateway'], ['network']], - }, - }); - - useEffect(() => { - const openSub = subscribeOpenModal(modalNameValue, (data) => { - setModalData(data); - setOpen(true); - }); - const closeSub = subscribeCloseModal(modalNameValue, () => setOpen(false)); - return () => { - openSub.unsubscribe(); - closeSub.unsubscribe(); - }; - }, []); - - const handleDelete = async () => { - if (!modalData) return; - try { - await deleteGateway(modalData.id); - Snackbar.default(m.gateway_delete_success()); - setOpen(false); - } catch { - Snackbar.error(m.gateway_delete_failed()); - } - }; - - return ( - setOpen(false)} - afterClose={() => setModalData(null)} - > - - {m.modal_delete_gateway_body({ - name: modalData?.name ?? '', - locationName: modalData?.locationName ?? '', - })} - - setOpen(false), - disabled: isPending, - }} - /> - - ); -}; diff --git a/web/src/pages/LocationsPage/tabs/GatewaysTab.tsx b/web/src/pages/LocationsPage/tabs/GatewaysTab.tsx index 81f5048b3a..04613d6e64 100644 --- a/web/src/pages/LocationsPage/tabs/GatewaysTab.tsx +++ b/web/src/pages/LocationsPage/tabs/GatewaysTab.tsx @@ -1,15 +1,11 @@ import { Suspense } from 'react'; import { TableSkeleton } from '../../../shared/components/skeleton/TableSkeleton/TableSkeleton'; import { GatewaysTable } from '../components/GatewaysTable'; -import { DeleteGatewayModal } from '../modals/DeleteGatewayModal/DeleteGatewayModal'; export const GatewaysTab = () => { return ( - <> - }> - - - - + }> + + ); }; diff --git a/web/src/shared/hooks/modalControls/modalTypes.ts b/web/src/shared/hooks/modalControls/modalTypes.ts index b52673c342..cf13cfe684 100644 --- a/web/src/shared/hooks/modalControls/modalTypes.ts +++ b/web/src/shared/hooks/modalControls/modalTypes.ts @@ -14,7 +14,6 @@ import type { OpenConfirmActionModal, OpenDeleteAliasDestinationBlockedModal, OpenDeleteAliasDestinationConfirmModal, - OpenDeleteGatewayModal, OpenDeleteLocationModal, OpenDeleteNetworkDeviceModal, OpenDeleteOpenIdClientModal, @@ -69,7 +68,6 @@ export const ModalName = { AddLogStreaming: 'addLogStreaming', EditLogStreaming: 'editLogStreaming', DeleteLogStreaming: 'deleteLogStreaming', - DeleteGateway: 'deleteGateway', DeleteLocation: 'deleteLocation', SelfEnrollmentToken: 'selfEnrollmentToken', AddNewDevice: 'addNewDevice', @@ -189,10 +187,6 @@ const modalOpenArgsSchema = z.discriminatedUnion('name', [ name: z.literal(ModalName.DeleteLogStreaming), data: z.custom(), }), - z.object({ - name: z.literal(ModalName.DeleteGateway), - data: z.custom(), - }), z.object({ name: z.literal(ModalName.DeleteLocation), data: z.custom(), diff --git a/web/src/shared/hooks/modalControls/types.ts b/web/src/shared/hooks/modalControls/types.ts index cf9f1f860d..dc963892e4 100644 --- a/web/src/shared/hooks/modalControls/types.ts +++ b/web/src/shared/hooks/modalControls/types.ts @@ -166,12 +166,6 @@ export interface OpenAssignUserDeviceIPModal { locationData: DeviceLocationIpsResponse; } -export interface OpenDeleteGatewayModal { - id: number; - name: string; - locationName: string; -} - export interface OpenDeleteLocationModal { id: number; name: string; From 978b72e130524f70e56c4231ca9446994c58b564 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:52:16 +0100 Subject: [PATCH 07/18] webhook delete --- web/messages/en/webhooks.json | 6 +++++- web/src/pages/WebhooksPage/WebhooksTable.tsx | 19 +++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/web/messages/en/webhooks.json b/web/messages/en/webhooks.json index ce4dd6061c..879e46886b 100644 --- a/web/messages/en/webhooks.json +++ b/web/messages/en/webhooks.json @@ -4,5 +4,9 @@ "webhooks_table_title": "All Webhooks", "webhooks_add": "Add new webhook", "webhooks_empty_title": "You don't have any Webhooks.", - "webhooks_empty_subtitle": "To add one, click the button below." + "webhooks_empty_subtitle": "To add one, click the button below.", + "webhooks_delete_confirm_title": "Delete Webhook", + "webhooks_delete_confirm_body": "Are you sure you want to delete this webhook? This action cannot be undone.", + "webhooks_delete_success": "Webhook deleted", + "webhooks_delete_failed": "Failed to delete webhook" } diff --git a/web/src/pages/WebhooksPage/WebhooksTable.tsx b/web/src/pages/WebhooksPage/WebhooksTable.tsx index dceaba291c..901ff841c5 100644 --- a/web/src/pages/WebhooksPage/WebhooksTable.tsx +++ b/web/src/pages/WebhooksPage/WebhooksTable.tsx @@ -18,6 +18,7 @@ import { TableBody } from '../../shared/defguard-ui/components/table/TableBody/T import { TableCell } from '../../shared/defguard-ui/components/table/TableCell/TableCell'; import { TableEditCell } from '../../shared/defguard-ui/components/table/TableEditCell/TableEditCell'; import { TableTop } from '../../shared/defguard-ui/components/table/TableTop/TableTop'; +import { Snackbar } from '../../shared/defguard-ui/providers/snackbar/snackbar'; import { openModal } from '../../shared/hooks/modalControls/modalsSubjects'; import { ModalName } from '../../shared/hooks/modalControls/modalTypes'; @@ -36,12 +37,6 @@ export const WebhooksTable = ({ webhooks }: Props) => { invalidate: ['webhook'], }, }); - const { mutate: deleteWebhook } = useMutation({ - mutationFn: api.webhook.deleteWebhook, - meta: { - invalidate: ['webhook'], - }, - }); const addButtonProps = useMemo( (): ButtonProps => ({ text: m.webhooks_add(), @@ -131,7 +126,15 @@ export const WebhooksTable = ({ webhooks }: Props) => { testId: 'delete', variant: 'danger', onClick: () => { - deleteWebhook(row.id); + openModal(ModalName.ConfirmAction, { + title: m.webhooks_delete_confirm_title(), + contentMd: m.webhooks_delete_confirm_body(), + actionPromise: () => api.webhook.deleteWebhook(row.id), + invalidateKeys: [['webhook']], + submitProps: { text: m.controls_delete(), variant: 'critical' }, + onSuccess: () => Snackbar.default(m.webhooks_delete_success()), + onError: () => Snackbar.error(m.webhooks_delete_failed()), + }); }, }, ], @@ -141,7 +144,7 @@ export const WebhooksTable = ({ webhooks }: Props) => { }, }), ], - [deleteWebhook, toggleWebhook], + [toggleWebhook], ); const table = useReactTable({ columns, From 80b2acb200413c529cb4975d9feec5deac3d108b Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:57:37 +0100 Subject: [PATCH 08/18] reset smtp settings --- web/messages/en/common.json | 1 + web/messages/en/settings.json | 6 ++++- .../SettingsSmtpPage/SettingsSmtpPage.tsx | 25 ++++++++++--------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/web/messages/en/common.json b/web/messages/en/common.json index 8af5a20a1d..d17c436882 100644 --- a/web/messages/en/common.json +++ b/web/messages/en/common.json @@ -26,6 +26,7 @@ "controls_close": "Close", "controls_back": "Back", "controls_delete": "Delete", + "controls_reset": "Reset", "controls_edit": "Edit", "controls_logout": "Logout", "controls_keep": "Keep", diff --git a/web/messages/en/settings.json b/web/messages/en/settings.json index 4d54ebb8d9..2b4bea55f9 100644 --- a/web/messages/en/settings.json +++ b/web/messages/en/settings.json @@ -48,5 +48,9 @@ "settings_activity_log_streaming_table_header_name": "Name", "settings_activity_log_streaming_table_stream_type_name": "Destination", "settings_msg_saved": "Settings saved", - "settings_msg_save_failed": "Failed to save settings" + "settings_msg_save_failed": "Failed to save settings", + "settings_smtp_reset_confirm_title": "Reset SMTP Settings", + "settings_smtp_reset_confirm_body": "Are you sure you want to reset SMTP settings? This action cannot be undone.", + "settings_smtp_reset_success": "SMTP settings reset", + "settings_smtp_reset_failed": "Failed to reset SMTP settings" } diff --git a/web/src/pages/settings/SettingsSmtpPage/SettingsSmtpPage.tsx b/web/src/pages/settings/SettingsSmtpPage/SettingsSmtpPage.tsx index c59cc385ca..e45637d99d 100644 --- a/web/src/pages/settings/SettingsSmtpPage/SettingsSmtpPage.tsx +++ b/web/src/pages/settings/SettingsSmtpPage/SettingsSmtpPage.tsx @@ -24,6 +24,7 @@ import { ThemeSpacing } from '../../../shared/defguard-ui/types'; import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; import { useAppForm } from '../../../shared/form'; import { formChangeLogic } from '../../../shared/formLogic'; +import { Snackbar } from '../../../shared/defguard-ui/providers/snackbar/snackbar'; import { openModal } from '../../../shared/hooks/modalControls/modalsSubjects'; import { ModalName } from '../../../shared/hooks/modalControls/modalTypes'; import { useApp } from '../../../shared/hooks/useApp'; @@ -155,16 +156,6 @@ const Content = ({ settings }: { settings: Settings }) => { }, }); - const { mutateAsync: deleteSmtp, isPending: deletePending } = useMutation({ - mutationFn: () => api.settings.patchSettings(emptyValues), - meta: { - invalidate: [['settings'], ['info']], - }, - onSuccess: () => { - form.reset(emptyValues); - }, - }); - const form = useAppForm({ defaultValues, validationLogic: formChangeLogic, @@ -230,9 +221,19 @@ const Content = ({ settings }: { settings: Settings }) => {