diff --git a/frontend/src/lang/translations/de.json b/frontend/src/lang/translations/de.json index 7d53e45..07c9f8b 100644 --- a/frontend/src/lang/translations/de.json +++ b/frontend/src/lang/translations/de.json @@ -129,6 +129,11 @@ "button-add-peers": "Mehrere Peers hinzufügen", "button-show-peer": "Peer anzeigen", "button-edit-peer": "Peer bearbeiten", + "button-bulk-delete": "Ausgewählte Peers löschen", + "button-bulk-enable": "Ausgewählte Peers aktivieren", + "button-bulk-disable": "Ausgewählte Peers deaktivieren", + "confirm-bulk-delete": "Sind Sie sicher, dass Sie {count} Peers löschen möchten?", + "confirm-bulk-disable": "Sind Sie sicher, dass Sie {count} Peers deaktivieren möchten?", "peer-disabled": "Peer ist deaktiviert, Grund:", "peer-expiring": "Peer läuft ab am", "peer-connected": "Verbunden", @@ -153,6 +158,14 @@ "button-add-user": "Benutzer hinzufügen", "button-show-user": "Benutzer anzeigen", "button-edit-user": "Benutzer bearbeiten", + "button-bulk-delete": "Ausgewählte Benutzer löschen", + "button-bulk-enable": "Ausgewählte Benutzer aktivieren", + "button-bulk-disable": "Ausgewählte Benutzer deaktivieren", + "button-bulk-lock": "Ausgewählte Benutzer sperren", + "button-bulk-unlock": "Ausgewählte Benutzer entsperren", + "confirm-bulk-delete": "Sind Sie sicher, dass Sie {count} Benutzer löschen möchten?", + "confirm-bulk-disable": "Sind Sie sicher, dass Sie {count} Benutzer deaktivieren möchten?", + "confirm-bulk-lock": "Sind Sie sicher, dass Sie {count} Benutzer sperren möchten?", "user-disabled": "Benutzer ist deaktiviert, Grund:", "user-locked": "Konto ist gesperrt, Grund:", "admin": "Benutzer hat Administratorrechte", diff --git a/frontend/src/lang/translations/en.json b/frontend/src/lang/translations/en.json index b9e14fc..58f6de7 100644 --- a/frontend/src/lang/translations/en.json +++ b/frontend/src/lang/translations/en.json @@ -129,6 +129,11 @@ "button-add-peers": "Add Multiple Peers", "button-show-peer": "Show Peer", "button-edit-peer": "Edit Peer", + "button-bulk-delete": "Delete selected peers", + "button-bulk-enable": "Enable selected peers", + "button-bulk-disable": "Disable selected peers", + "confirm-bulk-delete": "Are you sure you want to delete {count} peers?", + "confirm-bulk-disable": "Are you sure you want to disable {count} peers?", "peer-disabled": "Peer is disabled, reason:", "peer-expiring": "Peer is expiring at", "peer-connected": "Connected", @@ -153,6 +158,14 @@ "button-add-user": "Add User", "button-show-user": "Show User", "button-edit-user": "Edit User", + "button-bulk-delete": "Delete selected users", + "button-bulk-enable": "Enable selected users", + "button-bulk-disable": "Disable selected users", + "button-bulk-lock": "Lock selected users", + "button-bulk-unlock": "Unlock selected users", + "confirm-bulk-delete": "Are you sure you want to delete {count} users?", + "confirm-bulk-disable": "Are you sure you want to disable {count} users?", + "confirm-bulk-lock": "Are you sure you want to lock {count} users?", "user-disabled": "User is disabled, reason:", "user-locked": "Account is locked, reason:", "admin": "User has administrator privileges", diff --git a/frontend/src/stores/peers.js b/frontend/src/stores/peers.js index fe892ab..2f71656 100644 --- a/frontend/src/stores/peers.js +++ b/frontend/src/stores/peers.js @@ -222,6 +222,73 @@ export const peerStore = defineStore('peers', { throw new Error(error) }) }, + async BulkDelete(ids) { + this.fetching = true + return apiWrapper.post(`${baseUrl}/bulk-delete`, { Identifiers: ids }) + .then(() => { + this.peers = this.peers.filter(p => !ids.includes(p.Identifier)) + this.fetching = false + notify({ + title: "Peers deleted", + text: "Selected peers have been deleted!", + type: 'success', + }) + }) + .catch(error => { + this.fetching = false + console.log("Failed to delete peers: ", error) + notify({ + title: "Backend Connection Failure", + text: "Failed to delete selected peers!", + type: 'error', + }) + throw new Error(error) + }) + }, + async BulkEnable(ids) { + this.fetching = true + return apiWrapper.post(`${baseUrl}/bulk-enable`, { Identifiers: ids }) + .then(async () => { + await this.LoadPeers() + notify({ + title: "Peers enabled", + text: "Selected peers have been enabled!", + type: 'success', + }) + }) + .catch(error => { + this.fetching = false + console.log("Failed to enable peers: ", error) + notify({ + title: "Backend Connection Failure", + text: "Failed to enable selected peers!", + type: 'error', + }) + throw new Error(error) + }) + }, + async BulkDisable(ids, reason) { + this.fetching = true + return apiWrapper.post(`${baseUrl}/bulk-disable`, { Identifiers: ids, Reason: reason }) + .then(async () => { + await this.LoadPeers() + notify({ + title: "Peers disabled", + text: "Selected peers have been disabled!", + type: 'success', + }) + }) + .catch(error => { + this.fetching = false + console.log("Failed to disable peers: ", error) + notify({ + title: "Backend Connection Failure", + text: "Failed to disable selected peers!", + type: 'error', + }) + throw new Error(error) + }) + }, async UpdatePeer(id, formData) { this.fetching = true return apiWrapper.put(`${baseUrl}/${base64_url_encode(id)}`, formData) diff --git a/frontend/src/stores/profile.js b/frontend/src/stores/profile.js index cbb9864..8b5df8f 100644 --- a/frontend/src/stores/profile.js +++ b/frontend/src/stores/profile.js @@ -2,6 +2,7 @@ import { defineStore } from 'pinia' import {apiWrapper} from "@/helpers/fetch-wrapper"; import {notify} from "@kyvg/vue3-notification"; import {authStore} from "@/stores/auth"; +import {peerStore} from "@/stores/peers"; import { base64_url_encode } from '@/helpers/encoding'; import {freshStats} from "@/helpers/models"; import { ipToBigInt } from '@/helpers/utils'; @@ -218,5 +219,18 @@ export const profileStore = defineStore('profile', { }) }) }, + async BulkDelete(ids) { + this.fetching = true + const peers = peerStore() + return peers.BulkDelete(ids) + .then(() => { + this.peers = this.peers.filter(p => !ids.includes(p.Identifier)) + this.fetching = false + }) + .catch(error => { + this.fetching = false + throw new Error(error) + }) + }, } }) diff --git a/frontend/src/stores/users.js b/frontend/src/stores/users.js index 3c5eb55..8eb194a 100644 --- a/frontend/src/stores/users.js +++ b/frontend/src/stores/users.js @@ -142,5 +142,140 @@ export const userStore = defineStore('users', { }) }) }, + async BulkDelete(ids) { + this.fetching = true + return apiWrapper.post(`${baseUrl}/bulk-delete`, { Identifiers: ids }) + .then(() => { + this.users = this.users.filter(u => !ids.includes(u.Identifier)) + this.fetching = false + notify({ + title: "Users deleted", + text: "Selected users have been deleted!", + type: 'success', + }) + }) + .catch(error => { + this.fetching = false + console.log("Failed to delete users: ", error) + notify({ + title: "Backend Connection Failure", + text: "Failed to delete selected users!", + type: 'error', + }) + throw new Error(error) + }) + }, + async BulkEnable(ids) { + this.fetching = true + return apiWrapper.post(`${baseUrl}/bulk-enable`, { Identifiers: ids }) + .then(() => { + this.users.forEach(u => { + if (ids.includes(u.Identifier)) { + u.Disabled = false + u.DisabledReason = "" + } + }) + this.fetching = false + notify({ + title: "Users enabled", + text: "Selected users have been enabled!", + type: 'success', + }) + }) + .catch(error => { + this.fetching = false + console.log("Failed to enable users: ", error) + notify({ + title: "Backend Connection Failure", + text: "Failed to enable selected users!", + type: 'error', + }) + throw new Error(error) + }) + }, + async BulkDisable(ids, reason) { + this.fetching = true + return apiWrapper.post(`${baseUrl}/bulk-disable`, { Identifiers: ids, Reason: reason }) + .then(() => { + this.users.forEach(u => { + if (ids.includes(u.Identifier)) { + u.Disabled = true + u.DisabledReason = reason + } + }) + this.fetching = false + notify({ + title: "Users disabled", + text: "Selected users have been disabled!", + type: 'success', + }) + }) + .catch(error => { + this.fetching = false + console.log("Failed to disable users: ", error) + notify({ + title: "Backend Connection Failure", + text: "Failed to disable selected users!", + type: 'error', + }) + throw new Error(error) + }) + }, + async BulkLock(ids, reason) { + this.fetching = true + return apiWrapper.post(`${baseUrl}/bulk-lock`, { Identifiers: ids, Reason: reason }) + .then(() => { + this.users.forEach(u => { + if (ids.includes(u.Identifier)) { + u.Locked = true + u.LockedReason = reason + } + }) + this.fetching = false + notify({ + title: "Users locked", + text: "Selected users have been locked!", + type: 'success', + }) + }) + .catch(error => { + this.fetching = false + console.log("Failed to lock users: ", error) + notify({ + title: "Backend Connection Failure", + text: "Failed to lock selected users!", + type: 'error', + }) + throw new Error(error) + }) + }, + async BulkUnlock(ids) { + this.fetching = true + return apiWrapper.post(`${baseUrl}/bulk-unlock`, { Identifiers: ids }) + .then(() => { + this.users.forEach(u => { + if (ids.includes(u.Identifier)) { + u.Locked = false + u.LockedReason = "" + } + }) + this.fetching = false + notify({ + title: "Users unlocked", + text: "Selected users have been unlocked!", + type: 'success', + }) + }) + .catch(error => { + this.fetching = false + console.log("Failed to unlock users: ", error) + notify({ + title: "Backend Connection Failure", + text: "Failed to unlock selected users!", + type: 'error', + }) + throw new Error(error) + }) + }, } }) diff --git a/frontend/src/views/InterfaceView.vue b/frontend/src/views/InterfaceView.vue index d726e3e..ed9dc35 100644 --- a/frontend/src/views/InterfaceView.vue +++ b/frontend/src/views/InterfaceView.vue @@ -29,6 +29,10 @@ const sortKey = ref("") const sortOrder = ref(1) const selectAll = ref(false) +const selectedPeers = computed(() => { + return peers.All.filter(peer => peer.IsSelected).map(peer => peer.Identifier); +}) + function sortBy(key) { if (sortKey.value === key) { sortOrder.value = sortOrder.value * -1; // Toggle sort order @@ -111,6 +115,39 @@ async function saveConfig() { } } +async function bulkDelete() { + if (confirm(t('interfaces.confirm-bulk-delete', {count: selectedPeers.value.length}))) { + try { + await peers.BulkDelete(selectedPeers.value) + selectAll.value = false // reset selection + } catch (e) { + // notification is handled in store + } + } +} + +async function bulkEnable() { + try { + await peers.BulkEnable(selectedPeers.value) + selectAll.value = false + peers.All.forEach(p => p.IsSelected = false) // remove selection + } catch (e) { + // notification is handled in store + } +} + +async function bulkDisable() { + if (confirm(t('interfaces.confirm-bulk-disable', {count: selectedPeers.value.length}))) { + try { + await peers.BulkDisable(selectedPeers.value) + selectAll.value = false + peers.All.forEach(p => p.IsSelected = false) // remove selection + } catch (e) { + // notification is handled in store + } + } +} + function toggleSelectAll() { peers.FilteredAndPaged.forEach(peer => { peer.IsSelected = selectAll.value; @@ -353,6 +390,13 @@ onMounted(async () => { +