Skip to content

Commit f1a3556

Browse files
committed
fix(FR-2819): use BAIConfirmModalWithInput for irreversible delete actions
Resolves #7253(FR-2819) ## Summary - Replace `modal.confirm` with `BAIConfirmModalWithInput` for permanent keypair deletion in `UserCredentialList.tsx` — user must type the keypair's `access_key` to confirm - Replace `Popconfirm` with `BAIConfirmModalWithInput` for permanent auto-scaling rule deletion in `AutoScalingRuleListLegacy.tsx` — user must type the rule's `metric_name` to confirm - Migrate `Card` to `BAICard` in `AutoScalingRuleListLegacy.tsx` per project convention - Leave activate/deactivate actions untouched (reversible, handled by FR-2825) ## Files changed - `react/src/components/UserCredentialList.tsx` - `react/src/components/AutoScalingRuleListLegacy.tsx` ## Audited but no changes needed - `ResourceGroupList.tsx` — already uses `BAIConfirmModalWithInput` - `ShellScriptEditModal.tsx` — already uses `BAIConfirmModalWithInput` - `ContainerRegistryEditorModal.tsx` — `modal.confirm` is for a non-destructive save confirmation, not a delete
1 parent 82d550b commit f1a3556

23 files changed

Lines changed: 156 additions & 133 deletions

react/src/components/AutoScalingRuleListLegacy.tsx

Lines changed: 88 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,14 @@ import AutoScalingRuleEditorModalLegacy, {
88
COMPARATOR_LABELS,
99
} from './AutoScalingRuleEditorModalLegacy';
1010
import { DeleteFilled, PlusOutlined, SettingOutlined } from '@ant-design/icons';
11+
import { App, Button, Tag, Tooltip, Typography, theme } from 'antd';
1112
import {
12-
App,
13-
Button,
14-
Card,
15-
Popconfirm,
16-
Tag,
17-
Tooltip,
18-
Typography,
19-
theme,
20-
} from 'antd';
21-
import { BAIFlex, BAITable, BAIUnmountAfterClose } from 'backend.ai-ui';
13+
BAICard,
14+
BAIConfirmModalWithInput,
15+
BAIFlex,
16+
BAITable,
17+
BAIUnmountAfterClose,
18+
} from 'backend.ai-ui';
2219
import { default as dayjs } from 'dayjs';
2320
import * as _ from 'lodash-es';
2421
import { CircleArrowDownIcon, CircleArrowUpIcon } from 'lucide-react';
@@ -102,6 +99,9 @@ const AutoScalingRuleListLegacy: React.FC<AutoScalingRuleListLegacyProps> = ({
10299
useState<AutoScalingRuleEditorModalLegacyFragment$key | null>(null);
103100
const [isOpenAutoScalingRuleModal, setIsOpenAutoScalingRuleModal] =
104101
useState(false);
102+
const [deletingRule, setDeletingRule] = useState<Record<string, any> | null>(
103+
null,
104+
);
105105

106106
const [
107107
commitDeleteAutoScalingRuleMutation,
@@ -117,7 +117,7 @@ const AutoScalingRuleListLegacy: React.FC<AutoScalingRuleListLegacyProps> = ({
117117

118118
return (
119119
<>
120-
<Card
120+
<BAICard
121121
title={t('modelService.AutoScalingRules')}
122122
extra={
123123
<Button
@@ -131,6 +131,7 @@ const AutoScalingRuleListLegacy: React.FC<AutoScalingRuleListLegacyProps> = ({
131131
{t('modelService.AddRules')}
132132
</Button>
133133
}
134+
styles={{ body: { paddingTop: 0 } }}
134135
>
135136
<BAITable
136137
scroll={{ x: 'max-content' }}
@@ -180,79 +181,26 @@ const AutoScalingRuleListLegacy: React.FC<AutoScalingRuleListLegacyProps> = ({
180181
}
181182
}}
182183
/>
183-
<Popconfirm
184-
title={t('dialog.warning.CannotBeUndone')}
185-
okText={t('button.Delete')}
186-
okButtonProps={{
187-
danger: true,
188-
}}
189-
disabled={isInFlightDeleteAutoScalingRuleMutation}
190-
onConfirm={() => {
191-
if (autoScalingRules) {
192-
commitDeleteAutoScalingRuleMutation({
193-
variables: {
194-
id: row?.id as string,
195-
},
196-
onCompleted: (res, errors) => {
197-
if (
198-
!res?.delete_endpoint_auto_scaling_rule_node?.ok
199-
) {
200-
message.error(
201-
res?.delete_endpoint_auto_scaling_rule_node
202-
?.msg,
203-
);
204-
} else if (errors && errors.length > 0) {
205-
const errorMsgList = _.map(
206-
errors,
207-
(error) =>
208-
error.message || t('dialog.ErrorOccurred'),
209-
);
210-
for (const error of errorMsgList) {
211-
message.error(error);
184+
<Button
185+
type="text"
186+
icon={
187+
<DeleteFilled
188+
style={
189+
isEndpointDestroying
190+
? undefined
191+
: {
192+
color: token.colorError,
212193
}
213-
} else {
214-
setEditingAutoScalingRule(null);
215-
startRefetchTransition(() => {
216-
onRefetch();
217-
});
218-
message.success({
219-
key: 'autoscaling-rule-deleted',
220-
content: t(
221-
'autoScalingRule.SuccessfullyDeleted',
222-
),
223-
});
224-
}
225-
},
226-
onError: (error) => {
227-
message.error(
228-
error?.message || t('dialog.ErrorOccurred'),
229-
);
230-
},
231-
});
194+
}
195+
/>
196+
}
197+
disabled={isEndpointDestroying || !isOwnedByCurrentUser}
198+
onClick={() => {
199+
if (row) {
200+
setDeletingRule(row);
232201
}
233202
}}
234-
>
235-
<Button
236-
type="text"
237-
icon={
238-
<DeleteFilled
239-
style={
240-
isEndpointDestroying
241-
? undefined
242-
: {
243-
color: token.colorError,
244-
}
245-
}
246-
/>
247-
}
248-
disabled={false}
249-
onClick={() => {
250-
if (row) {
251-
setEditingAutoScalingRule(row);
252-
}
253-
}}
254-
/>
255-
</Popconfirm>
203+
/>
256204
</BAIFlex>
257205
),
258206
},
@@ -322,7 +270,7 @@ const AutoScalingRuleListLegacy: React.FC<AutoScalingRuleListLegacyProps> = ({
322270
showSorterTooltip={false}
323271
dataSource={autoScalingRules}
324272
></BAITable>
325-
</Card>
273+
</BAICard>
326274
<BAIUnmountAfterClose>
327275
<AutoScalingRuleEditorModalLegacy
328276
open={isOpenAutoScalingRuleModal}
@@ -339,6 +287,64 @@ const AutoScalingRuleListLegacy: React.FC<AutoScalingRuleListLegacyProps> = ({
339287
}}
340288
/>
341289
</BAIUnmountAfterClose>
290+
<BAIConfirmModalWithInput
291+
open={!!deletingRule}
292+
title={t('autoScalingRule.ConfirmDeleteAutoScalingRule', {
293+
autoScalingRule: deletingRule?.metric_name ?? '',
294+
})}
295+
content={
296+
<Typography.Text type="danger">
297+
{t('dialog.warning.CannotBeUndone')}
298+
</Typography.Text>
299+
}
300+
confirmText={deletingRule?.metric_name ?? ''}
301+
inputLabel={t('autoScalingRule.TypeMetricNameToDelete', {
302+
metricName: deletingRule?.metric_name ?? '',
303+
})}
304+
okText={t('button.Delete')}
305+
okButtonProps={{ loading: isInFlightDeleteAutoScalingRuleMutation }}
306+
onOk={() => {
307+
if (deletingRule && autoScalingRules) {
308+
commitDeleteAutoScalingRuleMutation({
309+
variables: {
310+
id: deletingRule.id as string,
311+
},
312+
onCompleted: (res, errors) => {
313+
if (!res?.delete_endpoint_auto_scaling_rule_node?.ok) {
314+
message.error(
315+
res?.delete_endpoint_auto_scaling_rule_node?.msg,
316+
);
317+
setDeletingRule(null);
318+
} else if (errors && errors.length > 0) {
319+
const errorMsgList = _.map(
320+
errors,
321+
(error) => error.message || t('dialog.ErrorOccurred'),
322+
);
323+
for (const error of errorMsgList) {
324+
message.error(error);
325+
}
326+
setDeletingRule(null);
327+
} else {
328+
setDeletingRule(null);
329+
setEditingAutoScalingRule(null);
330+
startRefetchTransition(() => {
331+
onRefetch();
332+
});
333+
message.success({
334+
key: 'autoscaling-rule-deleted',
335+
content: t('autoScalingRule.SuccessfullyDeleted'),
336+
});
337+
}
338+
},
339+
onError: (error) => {
340+
message.error(error?.message || t('dialog.ErrorOccurred'));
341+
setDeletingRule(null);
342+
},
343+
});
344+
}
345+
}}
346+
onCancel={() => setDeletingRule(null)}
347+
/>
342348
</>
343349
);
344350
};

react/src/components/UserCredentialList.tsx

Lines changed: 47 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
BAIFlex,
2929
BAIPropertyFilter,
3030
BAINameActionCell,
31+
BAIConfirmModalWithInput,
3132
useBAILogger,
3233
useUpdatableState,
3334
BAIText,
@@ -74,6 +75,7 @@ const UserCredentialList: React.FC = () => {
7475
useTransition();
7576
const [isPendingSettingModalOpen, startSettingModalOpenTransition] =
7677
useTransition();
78+
const [deletingKeypair, setDeletingKeypair] = useState<Keypair | null>(null);
7779

7880
const {
7981
baiPaginationOption,
@@ -410,57 +412,7 @@ const UserCredentialList: React.FC = () => {
410412
icon: <DeleteFilled />,
411413
type: 'danger' as const,
412414
onClick: () => {
413-
modal.confirm({
414-
title: t('credential.DeleteCredential'),
415-
content: (
416-
<BAIFlex direction="column" align="stretch">
417-
<Typography.Text>
418-
{t(
419-
'credential.YouAreAboutToDeleteCredential',
420-
)}
421-
</Typography.Text>
422-
<Typography.Text strong>
423-
{record.user_id}
424-
</Typography.Text>
425-
<br />
426-
<Typography.Text type="danger">
427-
{t('dialog.warning.CannotBeUndone')}
428-
</Typography.Text>
429-
</BAIFlex>
430-
),
431-
onOk: () => {
432-
return new Promise<void>((resolve) => {
433-
commitDeleteKeypair({
434-
variables: {
435-
access_key: record.access_key ?? '',
436-
},
437-
onCompleted: (res, errors) => {
438-
if (!res?.delete_keypair?.ok || errors) {
439-
message.error(res?.delete_keypair?.msg);
440-
resolve();
441-
return;
442-
}
443-
message.success(
444-
t(
445-
'credential.KeypairSuccessfullyDeleted',
446-
),
447-
);
448-
updateFetchKey();
449-
resolve();
450-
},
451-
onError: (error) => {
452-
message.error(error?.message);
453-
logger.error(error);
454-
resolve();
455-
},
456-
});
457-
});
458-
},
459-
okButtonProps: {
460-
danger: true,
461-
},
462-
okText: t('button.Delete'),
463-
});
415+
setDeletingKeypair(record);
464416
},
465417
},
466418
]),
@@ -609,6 +561,50 @@ const UserCredentialList: React.FC = () => {
609561
}
610562
}}
611563
/>
564+
<BAIConfirmModalWithInput
565+
open={!!deletingKeypair}
566+
title={t('credential.DeleteCredential')}
567+
content={
568+
<BAIFlex direction="column" align="stretch">
569+
<Typography.Text>
570+
{t('credential.YouAreAboutToDeleteCredential')}
571+
</Typography.Text>
572+
<Typography.Text strong>{deletingKeypair?.user_id}</Typography.Text>
573+
<br />
574+
<Typography.Text type="danger">
575+
{t('dialog.warning.CannotBeUndone')}
576+
</Typography.Text>
577+
</BAIFlex>
578+
}
579+
confirmText={deletingKeypair?.access_key ?? ''}
580+
inputLabel={t('credential.TypeAccessKeyToDelete')}
581+
okText={t('button.Delete')}
582+
onOk={() => {
583+
if (deletingKeypair) {
584+
commitDeleteKeypair({
585+
variables: {
586+
access_key: deletingKeypair.access_key ?? '',
587+
},
588+
onCompleted: (res, errors) => {
589+
if (!res?.delete_keypair?.ok || errors) {
590+
message.error(res?.delete_keypair?.msg);
591+
setDeletingKeypair(null);
592+
return;
593+
}
594+
message.success(t('credential.KeypairSuccessfullyDeleted'));
595+
setDeletingKeypair(null);
596+
updateFetchKey();
597+
},
598+
onError: (error) => {
599+
message.error(error?.message);
600+
logger.error(error);
601+
setDeletingKeypair(null);
602+
},
603+
});
604+
}
605+
}}
606+
onCancel={() => setDeletingKeypair(null)}
607+
/>
612608
</BAIFlex>
613609
);
614610
};

resources/i18n/de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@
560560
"StatusInfo": "Statusinfo",
561561
"StatusUpdatedSuccessfully": "Der Benutzerstatus hat sich geändert.",
562562
"TooltipForRequirePasswordChange": "Wenn Sie diese Option aktivieren, muss der Benutzer bei der nächsten Anmeldung sein Passwort ändern.",
563+
"TypeAccessKeyToDelete": "Type the access key to confirm deletion",
563564
"TypePermanentlyDelete": "Bitte geben Sie \"{{text}}\" ein.",
564565
"UpdateUsers": "Benutzer aktualisieren",
565566
"UpdateUsersWarningAlertTitle": "Das Festlegen von UID oder GID(s) kann die Verwendung zuvor erstellter Ordner-Mounts einschränken.",

resources/i18n/el.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@
560560
"StatusInfo": "Πληροφορίες κατάστασης",
561561
"StatusUpdatedSuccessfully": "Η κατάσταση του χρήστη έχει αλλάξει.",
562562
"TooltipForRequirePasswordChange": "Εάν ενεργοποιηθεί αυτή η επιλογή, ο χρήστης θα πρέπει να αλλάξει τον κωδικό πρόσβασης κατά την επόμενη σύνδεση.",
563+
"TypeAccessKeyToDelete": "Type the access key to confirm deletion",
563564
"TypePermanentlyDelete": "Παρακαλώ πληκτρολογήστε \"{{text}}\".",
564565
"UpdateUsers": "Ενημέρωση χρηστών",
565566
"UpdateUsersWarningAlertTitle": "Η ρύθμιση του UID ή των GID(s) μπορεί να περιορίσει τη χρήση ήδη δημιουργημένων προσαρτήσεων φακέλων.",

resources/i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@
577577
"StatusInfo": "Status Info",
578578
"StatusUpdatedSuccessfully": "The user status has changed.",
579579
"TooltipForRequirePasswordChange": "When enabled, the user must change their password at next login.",
580+
"TypeAccessKeyToDelete": "Type the access key to confirm deletion",
580581
"TypePermanentlyDelete": "Please type \"{{text}}\".",
581582
"UpdateUsers": "Update Users",
582583
"UpdateUsersWarningAlertTitle": "Setting UID or GID(s) may restrict the use of previously created folder mounts.",

resources/i18n/es.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@
560560
"StatusInfo": "Información de estado",
561561
"StatusUpdatedSuccessfully": "El estado del usuario ha cambiado.",
562562
"TooltipForRequirePasswordChange": "Si activa esta opción, los usuarios deberán cambiar su contraseña en el próximo inicio de sesión.",
563+
"TypeAccessKeyToDelete": "Type the access key to confirm deletion",
563564
"TypePermanentlyDelete": "Por favor, escriba \"{{text}}\".",
564565
"UpdateUsers": "Actualizar usuarios",
565566
"UpdateUsersWarningAlertTitle": "Establecer UID o GID(s) puede restringir el uso de montajes de carpetas creados previamente.",

resources/i18n/fi.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@
560560
"StatusInfo": "Tilatiedot",
561561
"StatusUpdatedSuccessfully": "Käyttäjän tila on muuttunut.",
562562
"TooltipForRequirePasswordChange": "Kun tämä asetus on päällä, käyttäjän on vaihdettava salasanansa seuraavalla kirjautumiskerralla.",
563+
"TypeAccessKeyToDelete": "Type the access key to confirm deletion",
563564
"TypePermanentlyDelete": "Kirjoita \"{{text}}\".",
564565
"UpdateUsers": "Päivitä käyttäjät",
565566
"UpdateUsersWarningAlertTitle": "UID:n tai GID:ien asettaminen saattaa rajoittaa aiemmin liitettyjen kansioiden käyttöä.",

resources/i18n/fr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@
560560
"StatusInfo": "Informations sur le statut",
561561
"StatusUpdatedSuccessfully": "Le statut de l'utilisateur a changé.",
562562
"TooltipForRequirePasswordChange": "Si cette option est activée, l'utilisateur devra changer son mot de passe lors de sa prochaine connexion.",
563+
"TypeAccessKeyToDelete": "Type the access key to confirm deletion",
563564
"TypePermanentlyDelete": "Veuillez saisir \"{{text}}\".",
564565
"UpdateUsers": "Mettre à jour les utilisateurs",
565566
"UpdateUsersWarningAlertTitle": "La définition de l'UID ou des GID peut restreindre l'utilisation des points de montage de dossiers créés précédemment.",

0 commit comments

Comments
 (0)