Add shared critical confirmation popup for destructive actions
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 23s
Build and Deploy Gateway / deploy (push) Successful in 7s

This commit is contained in:
2026-07-11 22:50:51 +03:00
parent 387cc273e8
commit 65bf88bf41
8 changed files with 256 additions and 127 deletions

View File

@@ -13,6 +13,7 @@ import {
import { formatBytes } from '../utils/format.js';
import { instructionBlocks } from '../instructions.js';
import { createLatestRequest, operationBlocked } from '../state/operations.js';
import { ConfirmationPopup } from './ConfirmationPopup.jsx';
import { canAppendRouteRule } from '../../shared/routingRules.js';
import {
HARBOR_VERSIONS,
@@ -326,7 +327,6 @@ function LocalRulesPanel({
blocked,
dirty,
restartPending,
confirmingClose,
error,
operations,
panelRef,
@@ -334,8 +334,6 @@ function LocalRulesPanel({
onChange,
onRemove,
onRemoveComplete,
onKeepEditing,
onDiscard,
onClose,
onSave,
}) {
@@ -373,16 +371,6 @@ function LocalRulesPanel({
</header>
<form id="client-local-rules-form" className="client-local-rules-form" onSubmit={onSave}>
{confirmingClose && (
<section className="client-local-rules-discard" role="alert">
<strong>Есть несохранённые настройки</strong>
<span>Закрыть редактор и потерять изменения?</span>
<div>
<button type="button" onClick={onKeepEditing}>Остаться</button>
<button type="button" className="is-danger" onClick={onDiscard}>Закрыть без сохранения</button>
</div>
</section>
)}
<section className="client-local-rules-group" aria-labelledby="local-rules-list-title">
<span id="local-rules-list-title">Правила</span>
<div className="client-local-rules-list">
@@ -1014,7 +1002,9 @@ export function ClientOverviewPage({
}
return (
<div className={`client-shell${hasSubscription ? '' : ' is-first-run'}${showIntro && !hasSubscription ? ' is-intro' : ''}`}>
<div
className={`client-shell${hasSubscription ? '' : ' is-first-run'}${showIntro && !hasSubscription ? ' is-intro' : ''}`}
>
<VersionDisplay isGateway={isGateway} versionInfo={versionInfo} />
<HarborBrand
isGateway={isGateway}
@@ -1200,28 +1190,12 @@ export function ClientOverviewPage({
)}
<div
className={`client-form${subscriptionWaiting ? ' is-waiting' : ''}${confirmingDelete ? ' is-confirming-delete' : ''}`}
className={`client-form${subscriptionWaiting ? ' is-waiting' : ''}`}
aria-hidden={subscriptionWaiting}
aria-disabled={gatewayDirect}
inert={subscriptionWaiting || gatewayDirect ? true : undefined}
>
{confirmingDelete && <section
className="client-delete-confirmation"
role="group"
aria-labelledby="delete-subscription-title"
aria-describedby="delete-subscription-description"
>
<span>Необратимое действие</span>
<h2 id="delete-subscription-title">Удалить подписку?</h2>
<p id="delete-subscription-description">
Harbor остановит VPN и удалит сохранённую подписку. Приложения, настроенные на локальный прокси, не смогут выходить в сеть до добавления новой подписки.
</p>
<div className="client-delete-actions">
<button type="button" onClick={() => setConfirmingDelete(false)}>Отмена</button>
<button className="is-danger" type="button" disabled={subscriptionDeleteBlocked} onClick={forgetSubscription}>Удалить</button>
</div>
</section>}
<div className="client-form-content" inert={confirmingDelete ? true : undefined}>
<div className="client-form-content">
<div
ref={subscriptionRef}
className={`client-subscription ${editingSubscription ? 'is-editing' : ''}${editingSubscription && state?.hasSubscription && !subscriptionUrl ? ' is-timing-out' : ''}`}
@@ -1434,7 +1408,6 @@ export function ClientOverviewPage({
blocked={routeRulesBlocked || localRulesDraft.some((rule) => rule.removing)}
dirty={localRulesDirty}
restartPending={state?.route?.localRulesPendingRestart === true}
confirmingClose={confirmingLocalRulesClose}
error={error}
operations={operations}
panelRef={localRulesPanelRef}
@@ -1445,12 +1418,31 @@ export function ClientOverviewPage({
onChange={changeLocalRule}
onRemove={(index) => removeLocalRule(localRulesDraft[index]._key)}
onRemoveComplete={finishRemoveLocalRule}
onKeepEditing={() => setConfirmingLocalRulesClose(false)}
onDiscard={discardLocalRules}
onClose={requestCloseLocalRules}
onSave={saveLocalRules}
/>}
<ConfirmationPopup
open={confirmingLocalRulesClose}
id="discard-local-rules"
title="Есть несохранённые настройки"
description="Закрыть редактор и потерять изменения?"
cancelLabel="Остаться"
confirmLabel="Закрыть без сохранения"
onCancel={() => setConfirmingLocalRulesClose(false)}
onConfirm={discardLocalRules}
/>
<ConfirmationPopup
open={confirmingDelete}
id="delete-subscription"
kicker="Необратимое действие"
title="Удалить подписку?"
description="Harbor остановит VPN и удалит сохранённую подписку. Приложения с локальным прокси потеряют соединение до добавления новой подписки."
cancelLabel="Отмена"
confirmLabel="Удалить"
busy={subscriptionDeleteBlocked}
onCancel={() => setConfirmingDelete(false)}
onConfirm={forgetSubscription}
/>
</div>
);
}