From 4d5152532cad6d6ad4dde1b0e27d443a9e019b4a Mon Sep 17 00:00:00 2001 From: LeC-D Date: Mon, 30 Mar 2026 13:19:13 -0400 Subject: [PATCH] feat(frontend): add confirmation dialog before deleting users, peers, and interfaces (#652) Add a browser confirm() dialog to the delete functions in UserEditModal, PeerEditModal, and InterfaceEditModal to prevent accidental deletions. The bulk-delete actions in UserView already had this protection; this change brings single-item deletion in line with that behavior. Translation keys (confirm-delete) added for all 10 supported locales: de, en, es, fr, ko, pt, ru, uk, vi, zh. Signed-off-by: LeC-D --- .../src/components/InterfaceEditModal.vue | 1 + frontend/src/components/PeerEditModal.vue | 1 + frontend/src/components/UserEditModal.vue | 1 + frontend/src/lang/translations/de.json | 9 +++-- frontend/src/lang/translations/en.json | 38 ++++++++++--------- frontend/src/lang/translations/es.json | 8 +++- frontend/src/lang/translations/fr.json | 13 ++++--- frontend/src/lang/translations/ko.json | 10 ++++- frontend/src/lang/translations/pt.json | 9 +++-- frontend/src/lang/translations/ru.json | 37 +++++++++--------- frontend/src/lang/translations/uk.json | 11 +++--- frontend/src/lang/translations/vi.json | 10 +++-- frontend/src/lang/translations/zh.json | 10 ++++- 13 files changed, 96 insertions(+), 62 deletions(-) diff --git a/frontend/src/components/InterfaceEditModal.vue b/frontend/src/components/InterfaceEditModal.vue index 4b3dc4d..65f5a94 100644 --- a/frontend/src/components/InterfaceEditModal.vue +++ b/frontend/src/components/InterfaceEditModal.vue @@ -315,6 +315,7 @@ async function applyPeerDefaults() { async function del() { if (isDeleting.value) return + if (!confirm(t('modals.interface-edit.confirm-delete', {id: selectedInterface.value.Identifier}))) return isDeleting.value = true try { await interfaces.DeleteInterface(selectedInterface.value.Identifier) diff --git a/frontend/src/components/PeerEditModal.vue b/frontend/src/components/PeerEditModal.vue index 10d0559..08dbf72 100644 --- a/frontend/src/components/PeerEditModal.vue +++ b/frontend/src/components/PeerEditModal.vue @@ -294,6 +294,7 @@ async function save() { async function del() { if (isDeleting.value) return + if (!confirm(t('modals.peer-edit.confirm-delete', {id: selectedPeer.value.Identifier}))) return isDeleting.value = true try { await peers.DeletePeer(selectedPeer.value.Identifier) diff --git a/frontend/src/components/UserEditModal.vue b/frontend/src/components/UserEditModal.vue index 2a2bf8d..01446a5 100644 --- a/frontend/src/components/UserEditModal.vue +++ b/frontend/src/components/UserEditModal.vue @@ -114,6 +114,7 @@ async function save() { async function del() { if (isDeleting.value) return + if (!confirm(t('modals.user-edit.confirm-delete', {id: selectedUser.value.Identifier}))) return isDeleting.value = true try { await users.DeleteUser(selectedUser.value.Identifier) diff --git a/frontend/src/lang/translations/de.json b/frontend/src/lang/translations/de.json index 7977809..8da8c11 100644 --- a/frontend/src/lang/translations/de.json +++ b/frontend/src/lang/translations/de.json @@ -382,7 +382,8 @@ "persist-local-changes": { "label": "Lokale Änderungen speichern" }, - "sync-warning": "Um diesen synchronisierten Benutzer zu bearbeiten, aktivieren Sie die lokale Änderungsspeicherung. Andernfalls werden Ihre Änderungen bei der nächsten Synchronisierung überschrieben." + "sync-warning": "Um diesen synchronisierten Benutzer zu bearbeiten, aktivieren Sie die lokale Änderungsspeicherung. Andernfalls werden Ihre Änderungen bei der nächsten Synchronisierung überschrieben.", + "confirm-delete": "Benutzer '{id}' wirklich loeschen?" }, "interface-view": { "headline": "Konfiguration für Schnittstelle:" @@ -503,7 +504,8 @@ "placeholder": "Persistentes Keepalive (0 = Standard)" } }, - "button-apply-defaults": "Peer-Standardeinstellungen anwenden" + "button-apply-defaults": "Peer-Standardeinstellungen anwenden", + "confirm-delete": "Interface '{id}' wirklich loeschen?" }, "peer-view": { "headline-peer": "Peer:", @@ -625,7 +627,8 @@ }, "expires-at": { "label": "Ablaufdatum" - } + }, + "confirm-delete": "Peer '{id}' wirklich loeschen?" }, "peer-multi-create": { "headline-peer": "Mehrere Peers erstellen", diff --git a/frontend/src/lang/translations/en.json b/frontend/src/lang/translations/en.json index f1c96ca..2e93614 100644 --- a/frontend/src/lang/translations/en.json +++ b/frontend/src/lang/translations/en.json @@ -271,16 +271,16 @@ "headline-preshared-key": "New Preshared Key", "button-generate": "Generate", "private-key": { - "label": "Private Key", - "placeholder": "The private key" + "label": "Private Key", + "placeholder": "The private key" }, "public-key": { - "label": "Public Key", - "placeholder": "The public key" + "label": "Public Key", + "placeholder": "The public key" }, "preshared-key": { - "label": "Preshared Key", - "placeholder": "The pre-shared key" + "label": "Preshared Key", + "placeholder": "The pre-shared key" } }, "calculator": { @@ -289,18 +289,18 @@ "headline-allowed-ip": "New Allowed IPs", "button-exclude-private": "Exclude Private IP Ranges", "allowed-ip": { - "label": "Allowed IPs", - "placeholder": "0.0.0.0/0, ::/0", - "empty": "Value cannot be empty" + "label": "Allowed IPs", + "placeholder": "0.0.0.0/0, ::/0", + "empty": "Value cannot be empty" }, "dissallowed-ip": { - "label": "Disallowed IPs", - "placeholder": "10.0.0.0/8, 192.168.0.0/16", - "invalid": "Invalid address: {addr}" + "label": "Disallowed IPs", + "placeholder": "10.0.0.0/8, 192.168.0.0/16", + "invalid": "Invalid address: {addr}" }, "new-allowed-ip": { - "label": "Allowed IPs", - "placeholder": "" + "label": "Allowed IPs", + "placeholder": "" } }, "modals": { @@ -382,7 +382,8 @@ "persist-local-changes": { "label": "Persist local changes" }, - "sync-warning": "To modify this synchronized user, enable local change persistence. Otherwise, your changes will be overwritten during the next synchronization." + "sync-warning": "To modify this synchronized user, enable local change persistence. Otherwise, your changes will be overwritten during the next synchronization.", + "confirm-delete": "Are you sure you want to delete user '{id}'?" }, "interface-view": { "headline": "Config for Interface:" @@ -503,8 +504,8 @@ "placeholder": "Persistent Keepalive (0 = default)" } }, - - "button-apply-defaults": "Apply Peer Defaults" + "button-apply-defaults": "Apply Peer Defaults", + "confirm-delete": "Are you sure you want to delete interface '{id}'?" }, "peer-view": { "headline-peer": "Peer:", @@ -626,7 +627,8 @@ }, "expires-at": { "label": "Expiry date" - } + }, + "confirm-delete": "Are you sure you want to delete peer '{id}'?" }, "peer-multi-create": { "headline-peer": "Create multiple peers", diff --git a/frontend/src/lang/translations/es.json b/frontend/src/lang/translations/es.json index c24795a..b268bc6 100644 --- a/frontend/src/lang/translations/es.json +++ b/frontend/src/lang/translations/es.json @@ -366,6 +366,8 @@ "admin": { "label": "Es administrador" } + }, + "confirm-delete": "Seguro que desea eliminar el usuario '{id}'?" }, "interface-view": { "headline": "Configuración de la interfaz:" @@ -493,7 +495,8 @@ "placeholder": "Keepalive Persistente (0 = por defecto)" } }, - "button-apply-defaults": "Aplicar Valores Predeterminados de peers" + "button-apply-defaults": "Aplicar Valores Predeterminados de peers", + "confirm-delete": "Seguro que desea eliminar la interfaz '{id}'?" }, "peer-view": { "headline-peer": "Peer:", @@ -613,7 +616,8 @@ }, "expires-at": { "label": "Fecha de expiración" - } + }, + "confirm-delete": "Seguro que desea eliminar el par '{id}'?" }, "peer-multi-create": { "headline-peer": "Crear múltiples peers", diff --git a/frontend/src/lang/translations/fr.json b/frontend/src/lang/translations/fr.json index acbe563..23f6a5b 100644 --- a/frontend/src/lang/translations/fr.json +++ b/frontend/src/lang/translations/fr.json @@ -126,9 +126,7 @@ "peer-expiring": "Le pair expire le", "peer-connected": "Connecté", "peer-not-connected": "Non connecté", - "peer-handshake": "Dernière négociation :", - "button-show-peer": "Afficher le pair", - "button-edit-peer": "Modifier le pair" + "peer-handshake": "Dernière négociation :" }, "users": { "headline": "Administration des utilisateurs", @@ -264,7 +262,8 @@ }, "admin": { "label": "Est Admin" - } + }, + "confirm-delete": "Voulez-vous vraiment supprimer l'utilisateur \"{id}\" ?" }, "interface-view": { "headline": "Configuration pour l'interface :" @@ -377,7 +376,8 @@ "placeholder": "Persistent Keepalive (0 = par défaut)" } }, - "button-apply-defaults": "Appliquer les valeurs par défaut des pairs" + "button-apply-defaults": "Appliquer les valeurs par défaut des pairs", + "confirm-delete": "Voulez-vous vraiment supprimer l'interface \"{id}\" ?" }, "peer-view": { "headline-peer": "Pair :", @@ -493,7 +493,8 @@ }, "expires-at": { "label": "Date d'expiration" - } + }, + "confirm-delete": "Voulez-vous vraiment supprimer le pair \"{id}\" ?" }, "peer-multi-create": { "headline-peer": "Créer plusieurs pairs", diff --git a/frontend/src/lang/translations/ko.json b/frontend/src/lang/translations/ko.json index 8150cf6..e3d0e51 100644 --- a/frontend/src/lang/translations/ko.json +++ b/frontend/src/lang/translations/ko.json @@ -282,6 +282,8 @@ "label": "관리자 여부" } }, + "confirm-delete": "사용자 '{id}'를 삭제하시겠습니까?" +}, "interface-view": { "headline": "인터페이스 구성:" }, @@ -393,7 +395,9 @@ "placeholder": "영구 Keepalive (0 = 기본값)" } }, - "button-apply-defaults": "피어 기본값 적용" + "button-apply-defaults": "피어 기본값 적용", + + "confirm-delete": "인터페이스 '{id}'를 삭제하시겠습니까?" }, "peer-view": { "headline-peer": "피어:", @@ -509,7 +513,9 @@ }, "expires-at": { "label": "만료 날짜" - } + }, + + "confirm-delete": "피어 '{id}'를 삭제하시겠습니까?" }, "peer-multi-create": { "headline-peer": "여러 피어 생성", diff --git a/frontend/src/lang/translations/pt.json b/frontend/src/lang/translations/pt.json index 500c5a7..c5a86b5 100644 --- a/frontend/src/lang/translations/pt.json +++ b/frontend/src/lang/translations/pt.json @@ -300,7 +300,8 @@ }, "admin": { "label": "É Administrador" - } + }, + "confirm-delete": "Tem certeza que deseja excluir o utilizador '{id}'?" }, "interface-view": { "headline": "Configuração para a Interface:" @@ -413,7 +414,8 @@ "placeholder": "Keepalive persistente (0 = padrão)" } }, - "button-apply-defaults": "Aplicar Padrões de Peer" + "button-apply-defaults": "Aplicar Padrões de Peer", + "confirm-delete": "Tem certeza que deseja excluir a interface '{id}'?" }, "peer-view": { "headline-peer": "Peer:", @@ -530,7 +532,8 @@ }, "expires-at": { "label": "Data de expiração" - } + }, + "confirm-delete": "Tem certeza que deseja excluir o par '{id}'?" }, "peer-multi-create": { "headline-peer": "Criar múltiplos peers", diff --git a/frontend/src/lang/translations/ru.json b/frontend/src/lang/translations/ru.json index 1776681..eeaa254 100644 --- a/frontend/src/lang/translations/ru.json +++ b/frontend/src/lang/translations/ru.json @@ -259,16 +259,16 @@ "headline-preshared-key": "Новый общий ключ", "button-generate": "Генерировать", "private-key": { - "label": "Приватный ключ", - "placeholder": "Приватный ключ" + "label": "Приватный ключ", + "placeholder": "Приватный ключ" }, "public-key": { - "label": "Публичный ключ", - "placeholder": "Публичный ключ" + "label": "Публичный ключ", + "placeholder": "Публичный ключ" }, "preshared-key": { - "label": "Общий ключ", - "placeholder": "Общий ключ" + "label": "Общий ключ", + "placeholder": "Общий ключ" } }, "calculator": { @@ -277,18 +277,18 @@ "headline-allowed-ip": "Новые разрешенные IP-адреса", "button-exclude-private": "Исключить частные диапазоны IP-адресов", "allowed-ip": { - "label": "Разрешенные IP-адреса", - "placeholder": "0.0.0.0/0, ::/0", - "empty": "Поле ввода не должно быть пустым" + "label": "Разрешенные IP-адреса", + "placeholder": "0.0.0.0/0, ::/0", + "empty": "Поле ввода не должно быть пустым" }, "dissallowed-ip": { - "label": "Запрещенные IP-адреса", - "placeholder": "10.0.0.0/8, 192.168.0.0/16", - "invalid": "Некорректный адрес: {addr}" + "label": "Запрещенные IP-адреса", + "placeholder": "10.0.0.0/8, 192.168.0.0/16", + "invalid": "Некорректный адрес: {addr}" }, "new-allowed-ip": { - "label": "Разрешенные IP-адреса", - "placeholder": "" + "label": "Разрешенные IP-адреса", + "placeholder": "" } }, "modals": { @@ -366,7 +366,8 @@ }, "admin": { "label": "Является администратором" - } + }, + "confirm-delete": "Вы уверены, что хотите удалить пользователя «{id}»?" }, "interface-view": { "headline": "Конфигурация интерфейса:" @@ -484,7 +485,8 @@ "placeholder": "Постоянное поддержание активности (0 = значение по умолчанию)" } }, - "button-apply-defaults": "Применить настройки пира по умолчанию" + "button-apply-defaults": "Применить настройки пира по умолчанию", + "confirm-delete": "Вы уверены, что хотите удалить интерфейс «{id}»?" }, "peer-view": { "headline-peer": "Пир:", @@ -605,7 +607,8 @@ }, "expires-at": { "label": "Дата истечения срока действия" - } + }, + "confirm-delete": "Вы уверены, что хотите удалить пир «{id}»?" }, "peer-multi-create": { "headline-peer": "Создать несколько узлов", diff --git a/frontend/src/lang/translations/uk.json b/frontend/src/lang/translations/uk.json index 9fd269a..1f0982a 100644 --- a/frontend/src/lang/translations/uk.json +++ b/frontend/src/lang/translations/uk.json @@ -151,7 +151,6 @@ "admin": "Користувач має адміністративні привілеї", "no-admin": "Користувач не має адміністративних привілеїв" }, - "profile": { "headline": "Мої VPN-піри", "table-heading": { @@ -189,7 +188,6 @@ "api-link": "Документація API" } }, - "modals": { "user-view": { "headline": "Обліковий запис користувача:", @@ -264,7 +262,8 @@ }, "admin": { "label": "Адміністратор" - } + }, + "confirm-delete": "Ви впевнені, що хочете видалити користувача «{id}»?" }, "interface-view": { "headline": "Конфігурація для інтерфейсу:" @@ -377,7 +376,8 @@ "placeholder": "Постійний Keepalive (0 = за замовчуванням)" } }, - "button-apply-defaults": "Застосувати значення за замовчуванням для пірів" + "button-apply-defaults": "Застосувати значення за замовчуванням для пірів", + "confirm-delete": "Ви впевнені, що хочете видалити інтерфейс «{id}»?" }, "peer-view": { "headline-peer": "Пір:", @@ -493,7 +493,8 @@ }, "expires-at": { "label": "Дата закінчення терміну дії" - } + }, + "confirm-delete": "Ви впевнені, що хочете видалити пір «{id}»?" }, "peer-multi-create": { "headline-peer": "Створити декілька пір", diff --git a/frontend/src/lang/translations/vi.json b/frontend/src/lang/translations/vi.json index 0ad146c..27a015f 100644 --- a/frontend/src/lang/translations/vi.json +++ b/frontend/src/lang/translations/vi.json @@ -240,7 +240,8 @@ }, "admin": { "label": "Là Quản trị viên" - } + }, + "confirm-delete": "Ban co chac muon xoa nguoi dung '{id}' khong?" }, "interface-view": { "headline": "Cấu hình cho Giao diện:" @@ -353,8 +354,8 @@ "placeholder": "Giữ kết nối liên tục (0 = mặc định)" } }, - - "button-apply-defaults": "Áp dụng Cài đặt Mặc định của Peer" + "button-apply-defaults": "Áp dụng Cài đặt Mặc định của Peer", + "confirm-delete": "Ban co chac muon xoa giao dien '{id}' khong?" }, "peer-view": { "headline-peer": "Peer:", @@ -470,7 +471,8 @@ }, "expires-at": { "label": "Ngày hết hạn" - } + }, + "confirm-delete": "Ban co chac muon xoa peer '{id}' khong?" }, "peer-multi-create": { "headline-peer": "Tạo nhiều peer", diff --git a/frontend/src/lang/translations/zh.json b/frontend/src/lang/translations/zh.json index 733ed36..4ba7818 100644 --- a/frontend/src/lang/translations/zh.json +++ b/frontend/src/lang/translations/zh.json @@ -242,6 +242,8 @@ "label": "管理员" } }, + "confirm-delete": "确定要删除用户“{id}”吗?" +}, "interface-view": { "headline": "接口配置: " }, @@ -353,7 +355,9 @@ "placeholder": "持久保持连接 (0 = 默认)" } }, - "button-apply-defaults": "应用节点默认值" + "button-apply-defaults": "应用节点默认值", + + "confirm-delete": "确定要删除接口“{id}”吗?" }, "peer-view": { "headline-peer": "节点: ", @@ -469,7 +473,9 @@ }, "expires-at": { "label": "过期日期" - } + }, + + "confirm-delete": "确定要删除对等点“{id}”吗?" }, "peer-multi-create": { "headline-peer": "创建多个节点",