refactor(frontend): migrate confirm() to ConfirmDialog (8 files)

Replaced native window.confirm() with styled <ConfirmDialog> from $lib/ui:
- RepositoryDashboardGrid (delete repo)
- ConnectionsTab (delete connection)
- EnvironmentsTab (delete environment)
- admin/roles (delete role)
- admin/users (delete user)
- tools/storage (delete file)
- ProviderConfig (delete provider)
- MaintenanceEventsTable (remove event + remove all)
This commit is contained in:
2026-06-17 14:58:23 +03:00
parent c73246db85
commit 144a56893e
8 changed files with 236 additions and 60 deletions

View File

@@ -16,11 +16,14 @@
<script lang="ts">
import { maintenanceStore } from "$lib/stores/maintenance.svelte.js";
import Button from "$lib/ui/Button.svelte";
import { EmptyState } from "$lib/ui";
import { EmptyState, ConfirmDialog } from "$lib/ui";
import { t } from "$lib/i18n/index.svelte.js";
let removingEventId = $state(null);
let isRemovingAll = $state(false);
let showRemoveConfirm = $state(false);
let showRemoveAllConfirm = $state(false);
let removeTarget = $state(null);
let isLoading = $derived(maintenanceStore.isLoading);
let activeEvents = $derived(maintenanceStore.activeEvents);
@@ -50,13 +53,20 @@
}
/**
* Handle removing a single event.
* Handle removing a single event — opens confirmation dialog.
* @param {object} event
*/
async function handleRemove(event) {
const confirmed = confirm($t.maintenance?.remove_event_confirm || "Remove banners for this event?");
if (!confirmed) return;
function promptRemove(event) {
removeTarget = event;
showRemoveConfirm = true;
}
/**
* Handle confirmed removal of a single event.
*/
async function onConfirmRemove() {
const event = removeTarget;
removeTarget = null;
removingEventId = event.id;
try {
await maintenanceStore.endEvent(event.id);
@@ -68,12 +78,16 @@
}
/**
* Handle removing all events.
* Handle removing all events — opens confirmation dialog.
*/
async function handleRemoveAll() {
const confirmed = confirm($t.maintenance?.remove_all_confirm || "Remove all active maintenance banners?");
if (!confirmed) return;
function promptRemoveAll() {
showRemoveAllConfirm = true;
}
/**
* Handle confirmed removal of all events.
*/
async function onConfirmRemoveAll() {
isRemovingAll = true;
try {
await maintenanceStore.endAllEvents();
@@ -113,7 +127,7 @@
<Button
variant="danger"
size="sm"
onclick={handleRemoveAll}
onclick={promptRemoveAll}
isLoading={isRemovingAll}
disabled={isRemovingAll}
>
@@ -211,7 +225,7 @@
<Button
variant="danger"
size="sm"
onclick={() => handleRemove(event)}
onclick={() => promptRemove(event)}
isLoading={removingEventId === event.id}
disabled={isRemovingAll || removingEventId !== null}
>
@@ -278,4 +292,26 @@
</details>
{/if}
</div>
<ConfirmDialog
bind:show={showRemoveConfirm}
title="Remove event?"
message={$t.maintenance?.remove_event_confirm || "Remove banners for this event?"}
variant="destructive"
confirmLabel="Remove"
cancelLabel="Cancel"
onConfirm={onConfirmRemove}
onCancel={() => {}}
/>
<ConfirmDialog
bind:show={showRemoveAllConfirm}
title="Remove all events?"
message={$t.maintenance?.remove_all_confirm || "Remove all active maintenance banners?"}
variant="destructive"
confirmLabel="Remove All"
cancelLabel="Cancel"
onConfirm={onConfirmRemoveAll}
onCancel={() => {}}
/>
<!-- #endregion MaintenanceEventsTable -->

View File

@@ -13,7 +13,7 @@
import { untrack } from "svelte";
import type { DashboardMetadata } from "../../types/dashboard";
import { t } from '$lib/i18n/index.svelte.js';
import { Button, Input } from "$lib/ui";
import { Button, Input, ConfirmDialog } from "$lib/ui";
import GitManager from "$lib/components/git/GitManager.svelte";
import { gitService } from "../../../services/gitService";
import { addToast } from "$lib/toasts.svelte.js";
@@ -45,6 +45,7 @@
let repositoryStatusByDashboardId = $state<Record<number, string>>({});
let repositoryStatusRequestId = $state(0);
let bulkActionRunning = $state(false);
let showBulkDeleteConfirm = $state(false);
// [/SECTION]
// [SECTION: DERIVED]
@@ -377,8 +378,11 @@
// #region handleBulkDelete:Function [TYPE Function]
// @ingroup Dashboard
// @PURPOSE: Removes selected repositories from storage and binding table.
async function handleBulkDelete(): Promise<void> {
if (!confirm($t.git?.confirm_delete_repo || "Удалить выбранные репозитории?")) return;
function handleBulkDelete(): void {
showBulkDeleteConfirm = true;
}
async function onConfirmBulkDelete(): Promise<void> {
const idsToDelete = [...selectedIds];
await runBulkGitAction("delete", (dashboardId) =>
gitService.deleteRepository(dashboardId, envId),
@@ -714,6 +718,17 @@
/>
{/if}
<ConfirmDialog
bind:show={showBulkDeleteConfirm}
title="Delete repositories?"
message={$t.git?.confirm_delete_repo || "Удалить выбранные репозитории?"}
variant="destructive"
confirmLabel="Delete"
cancelLabel="Cancel"
onConfirm={onConfirmBulkDelete}
onCancel={() => {}}
/>
<!-- [/SECTION] -->
<!-- #endregion Dashboard.RepositoryGrid -->

View File

@@ -16,6 +16,7 @@ import { SvelteSet } from "svelte/reactivity";
import { requestApi } from "$lib/api";
import { addToast } from "$lib/toasts.svelte.js";
import HelpTooltip from "$lib/ui/HelpTooltip.svelte";
import { ConfirmDialog } from "$lib/ui";
let {
providers = [],
@@ -49,6 +50,8 @@ import { SvelteSet } from "svelte/reactivity";
let isTesting = $state(false);
let togglingProviderIds = new SvelteSet();
let deletingProviderIds = new SvelteSet();
let showDeleteProviderConfirm = $state(false);
let deleteProviderTarget = $state(null);
// Model fetching state
let availableModels = $state([]);
@@ -346,15 +349,16 @@ import { SvelteSet } from "svelte/reactivity";
}
}
async function handleDelete(provider) {
function promptDeleteProvider(provider) {
if (deletingProviderIds.has(provider.id)) return;
deleteProviderTarget = provider;
showDeleteProviderConfirm = true;
}
async function onConfirmDeleteProvider() {
const provider = deleteProviderTarget;
deleteProviderTarget = null;
if (deletingProviderIds.has(provider.id)) return;
if (
!confirm(
$t.llm.delete_confirm.replace("{name}", provider.name || provider.id),
)
) {
return;
}
deletingProviderIds.add(provider.id);
@@ -729,7 +733,7 @@ import { SvelteSet } from "svelte/reactivity";
<button
type="button"
class="text-sm text-destructive hover:underline"
onclick={() => handleDelete(provider)}
onclick={() => promptDeleteProvider(provider)}
disabled={deletingProviderIds.has(provider.id)}
>
{#if deletingProviderIds.has(provider.id)}
@@ -762,4 +766,14 @@ import { SvelteSet } from "svelte/reactivity";
</div>
</div>
<ConfirmDialog
bind:show={showDeleteProviderConfirm}
title="Delete provider?"
message={deleteProviderTarget ? $t.llm.delete_confirm.replace("{name}", deleteProviderTarget.name || deleteProviderTarget.id) : ""}
variant="destructive"
confirmLabel="Delete"
cancelLabel="Cancel"
onConfirm={onConfirmDeleteProvider}
onCancel={() => {}}
/>
<!-- #endregion ProviderConfig -->

View File

@@ -18,7 +18,7 @@
// [SECTION: IMPORTS]
import { onMount } from 'svelte';
import { t } from '$lib/i18n/index.svelte.js';
import { Button } from '$lib/ui';
import { Button, ConfirmDialog } from '$lib/ui';
import ProtectedRoute from '$lib/components/auth/ProtectedRoute.svelte';
import { adminService } from '../../../services/adminService';
// [/SECTION: IMPORTS]
@@ -31,6 +31,8 @@
let showModal = false;
let isEditing = false;
let currentRoleId = null;
let showDeleteRoleConfirm = $state(false);
let deleteRoleTarget = $state(null);
let roleForm = {
name: '',
description: '',
@@ -123,16 +125,29 @@
}
// #endregion handleSaveRole:Function
// #region handleDeleteRole:Function [TYPE Function]
// #region promptDeleteRole:Function [TYPE Function]
// @ingroup Routes
/**
* @purpose Deletes a role after confirmation.
* @purpose Opens confirmation dialog before deleting a role.
* @pre role object is provided.
* @post Role is deleted if confirmed, data reloaded.
* @post Confirmation dialog shown, deletes on confirm.
*/
async function handleDeleteRole(role) {
if (!confirm($t.admin.roles.confirm_delete.replace('{name}', role.name))) return;
function promptDeleteRole(role) {
deleteRoleTarget = role;
showDeleteRoleConfirm = true;
}
// #endregion promptDeleteRole:Function
// #region onConfirmDeleteRole:Function [TYPE Function]
// @ingroup Routes
/**
* @purpose Deletes a role after dialog confirmation.
* @pre deleteRoleTarget is set.
* @post Role is deleted, data reloaded.
*/
async function onConfirmDeleteRole() {
const role = deleteRoleTarget;
deleteRoleTarget = null;
console.log('[AdminRolesPage][handleDeleteRole][Entry]');
try {
await adminService.deleteRole(role.id);
@@ -143,7 +158,7 @@
console.error('[AdminRolesPage][handleDeleteRole][Coherence:Failed]', e);
}
}
// #endregion handleDeleteRole:Function
// #endregion onConfirmDeleteRole:Function
onMount(loadData);
</script>
@@ -192,7 +207,7 @@
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<Button onclick={() => openEditModal(role)} variant="ghost" size="sm" class="text-primary mr-3">{$t.common.edit}</Button>
<Button onclick={() => handleDeleteRole(role)} variant="ghost" size="sm" class="text-destructive">{$t.common.delete}</Button>
<Button onclick={() => promptDeleteRole(role)} variant="ghost" size="sm" class="text-destructive">{$t.common.delete}</Button>
</td>
</tr>
{/each}
@@ -241,6 +256,17 @@
</div>
{/if}
</div>
<ConfirmDialog
bind:show={showDeleteRoleConfirm}
title="Delete role?"
message={deleteRoleTarget ? $t.admin.roles.confirm_delete.replace('{name}', deleteRoleTarget.name) : ""}
variant="destructive"
confirmLabel="Delete"
cancelLabel="Cancel"
onConfirm={onConfirmDeleteRole}
onCancel={() => {}}
/>
<!-- [/SECTION: TEMPLATE] -->
</ProtectedRoute>

View File

@@ -22,7 +22,7 @@
// [SECTION: IMPORTS]
import { onMount } from 'svelte';
import { t } from '$lib/i18n/index.svelte.js';
import { Button } from '$lib/ui';
import { Button, ConfirmDialog } from '$lib/ui';
import ProtectedRoute from '$lib/components/auth/ProtectedRoute.svelte';
import { adminService } from '../../../services/adminService';
// [/SECTION: IMPORTS]
@@ -36,6 +36,8 @@
let showModal = false;
let isEditing = false;
let currentUserId = null;
let showDeleteUserConfirm = $state(false);
let deleteUserTarget = $state(null);
let userForm = {
username: '',
email: '',
@@ -135,20 +137,34 @@
}
// #endregion handleSaveUser:Function
// #region handleDeleteUser:Function [TYPE Function]
// #region promptDeleteUser:Function [TYPE Function]
// @ingroup Routes
/**
* @purpose Deletes a user after confirmation.
* @purpose Opens confirmation dialog before deleting a user.
* @pre user object must be valid.
* @post User deleted if confirmed, data reloaded.
* @side_effect Triggers API call to adminService.
* @relation CALLS -> [EXT:method:adminService.deleteUser]
* @post Confirmation dialog shown, deletes on confirm.
* @param {Object} user - The user to delete.
*/
async function handleDeleteUser(user) {
function promptDeleteUser(user) {
if (deletingUserId) return;
deleteUserTarget = user;
showDeleteUserConfirm = true;
}
// #endregion promptDeleteUser:Function
// #region onConfirmDeleteUser:Function [TYPE Function]
// @ingroup Routes
/**
* @purpose Deletes a user after dialog confirmation.
* @pre deleteUserTarget is set.
* @post User deleted, data reloaded.
* @side_effect Triggers API call to adminService.
* @relation CALLS -> [EXT:method:adminService.deleteUser]
*/
async function onConfirmDeleteUser() {
const user = deleteUserTarget;
deleteUserTarget = null;
if (deletingUserId) return;
if (!confirm($t.admin.users.confirm_delete.replace('{username}', user.username))) return;
console.log('[AdminUsersPage][handleDeleteUser][Entry]');
deletingUserId = user.id;
try {
@@ -162,7 +178,7 @@
deletingUserId = null;
}
}
// #endregion handleDeleteUser:Function
// #endregion onConfirmDeleteUser:Function
onMount(loadData);
</script>
@@ -230,7 +246,7 @@
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<Button onclick={() => openEditModal(user)} variant="ghost" size="sm" class="text-primary mr-3" disabled={deletingUserId === user.id}>{$t.common.edit}</Button>
<Button
onclick={() => handleDeleteUser(user)}
onclick={() => promptDeleteUser(user)}
variant="ghost" size="sm" class="text-destructive"
disabled={deletingUserId === user.id}
>
@@ -293,6 +309,17 @@
</div>
{/if}
</div>
<ConfirmDialog
bind:show={showDeleteUserConfirm}
title="Delete user?"
message={deleteUserTarget ? $t.admin.users.confirm_delete.replace('{username}', deleteUserTarget.username) : ""}
variant="destructive"
confirmLabel="Delete"
cancelLabel="Cancel"
onConfirm={onConfirmDeleteUser}
onCancel={() => {}}
/>
<!-- [/SECTION: TEMPLATE] -->
</ProtectedRoute>

View File

@@ -7,7 +7,7 @@
<!-- @UX_TEST idle -> {click: "Add Connection"} -> adding -> fill form -> {click: "Test"} -> testing -> test_success -> {click: "Save"} -> populated -->
<script lang="ts">
import { getT } from "$lib/i18n/index.svelte.js";
import { Button, EmptyState } from "$lib/ui";
import { Button, EmptyState, ConfirmDialog } from "$lib/ui";
import { api } from "$lib/api.js";
import { addToast } from "$lib/toasts.svelte.js";
@@ -37,6 +37,8 @@
let editingId = $state<string | null>(null);
let testResult = $state<{ success: boolean; latency_ms?: number; db_version?: string; error?: string } | null>(null);
let blockDeleteJobs = $state<string[]>([]);
let showDeleteConfirm = $state(false);
let deleteTargetId: string | null = $state(null);
// Form state
let form = $state({
@@ -210,8 +212,14 @@
}
function confirmDelete(id: string) {
if (window.confirm("Are you sure you want to delete this connection?")) {
handleDelete(id);
deleteTargetId = id;
showDeleteConfirm = true;
}
async function onConfirmDelete(): Promise<void> {
if (deleteTargetId) {
await handleDelete(deleteTargetId);
deleteTargetId = null;
}
}
@@ -375,4 +383,15 @@
</div>
{/if}
</div>
<ConfirmDialog
bind:show={showDeleteConfirm}
title="Delete connection?"
message="Are you sure you want to delete this connection?"
variant="destructive"
confirmLabel="Delete"
cancelLabel="Cancel"
onConfirm={onConfirmDelete}
onCancel={() => {}}
/>
<!-- #endregion Settings.ConnectionsTab -->

View File

@@ -15,7 +15,7 @@
* @UX_FEEDBACK: Toast notifications on success/failure of CRUD or test operations.
*/
import { t } from "$lib/i18n/index.svelte.js";
import { Button } from "$lib/ui";
import { Button, ConfirmDialog } from "$lib/ui";
import { api } from "$lib/api.js";
import { addToast } from "$lib/toasts.svelte.js";
import { resolveEnvStage, normalizeSupersetBaseUrl } from "./settings-utils.js";
@@ -24,6 +24,8 @@
let editingEnvId = $state(null);
let isAddingEnv = $state(false);
let showDeleteEnvConfirm = $state(false);
let deleteEnvTargetId = $state(null);
let newEnv = $state({
id: "",
name: "",
@@ -131,17 +133,22 @@
}
}
async function handleDeleteEnv(id) {
if (confirm($t.settings?.env_delete_confirm)) {
console.log(`[EXT:frontend:EnvironmentsTab][Action] Delete environment ${id}`);
try {
await api.deleteEnvironment(id);
addToast($t.settings?.env_deleted, "success");
if (onSave) await onSave();
} catch (error) {
console.error("[EXT:frontend:EnvironmentsTab][Coherence:Failed] Failed to delete environment:", error);
addToast($t.settings?.env_delete_failed, "error");
}
function promptDeleteEnv(id) {
deleteEnvTargetId = id;
showDeleteEnvConfirm = true;
}
async function onConfirmDeleteEnv() {
const id = deleteEnvTargetId;
deleteEnvTargetId = null;
console.log(`[EXT:frontend:EnvironmentsTab][Action] Delete environment ${id}`);
try {
await api.deleteEnvironment(id);
addToast($t.settings?.env_deleted, "success");
if (onSave) await onSave();
} catch (error) {
console.error("[EXT:frontend:EnvironmentsTab][Coherence:Failed] Failed to delete environment:", error);
addToast(error.message || $t.settings?.env_delete_failed, "error");
}
}
</script>
@@ -332,7 +339,7 @@
<Button variant="ghost" size="sm" class="text-primary mr-4" onclick={() => editEnv(env)}>
{$t.common.edit}
</Button>
<Button variant="ghost" size="sm" class="text-destructive" onclick={() => handleDeleteEnv(env.id)}>
<Button variant="ghost" size="sm" class="text-destructive" onclick={() => promptDeleteEnv(env.id)}>
{$t.settings?.env_delete}
</Button>
</td>
@@ -350,4 +357,15 @@
</div>
{/if}
</div>
<ConfirmDialog
bind:show={showDeleteEnvConfirm}
title="Delete environment?"
message={$t.settings?.env_delete_confirm || "Are you sure you want to delete this environment?"}
variant="destructive"
confirmLabel="Delete"
cancelLabel="Cancel"
onConfirm={onConfirmDeleteEnv}
onCancel={() => {}}
/>
<!-- #endregion EnvironmentsTab -->

View File

@@ -22,6 +22,7 @@
import { listFiles, deleteFile } from '../../../services/storageService';
import { addToast } from '$lib/toasts.svelte.js';
import { t } from '$lib/i18n/index.svelte.js';
import { ConfirmDialog } from "$lib/ui";
import FileList from '$lib/components/storage/FileList.svelte';
import FileUpload from '$lib/components/storage/FileUpload.svelte';
// [/SECTION: IMPORTS]
@@ -36,6 +37,8 @@
let files = $state([]);
let isLoading = $state(false);
let currentPath = $state('');
let showDeleteConfirm = $state(false);
let deletePayload = $state(null);
// #region resolveStorageQueryFromPath:Function [TYPE Function]
// @ingroup Routes
@@ -89,8 +92,15 @@
console.warn('[STORAGE-PAGE][DELETE_SKIP] invalid payload', payload);
return;
}
deletePayload = payload;
showDeleteConfirm = true;
}
async function onConfirmDelete() {
const payload = deletePayload;
const { category, path, name } = payload || {};
deletePayload = null;
console.log('[STORAGE-PAGE][DELETE_START] category=%s path=%s', category, path);
if (!confirm($t.storage.messages.delete_confirm.replace('{name}', name))) return;
try {
await deleteFile(category, path);
@@ -229,6 +239,17 @@
</div>
</div>
</div>
<ConfirmDialog
bind:show={showDeleteConfirm}
title="Delete file?"
message={deletePayload ? $t.storage.messages.delete_confirm.replace('{name}', deletePayload.name) : ""}
variant="destructive"
confirmLabel="Delete"
cancelLabel="Cancel"
onConfirm={onConfirmDelete}
onCancel={() => {}}
/>
<!-- [/SECTION: TEMPLATE] -->