mirror of
https://github.com/h44z/wg-portal.git
synced 2026-02-23 02:46:23 +00:00
Compare commits
4 Commits
doc_update
...
default_pe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a9d5e3ea8 | ||
|
|
1b56acac87 | ||
|
|
015220dc7b | ||
|
|
4b49a55ea2 |
6
.github/workflows/docker-publish.yml
vendored
6
.github/workflows/docker-publish.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Get Version
|
||||
shell: bash
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Upload binaries
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: binaries
|
||||
path: binaries/wg-portal_linux*
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download binaries
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
name: binaries
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ func main() {
|
||||
rawDb, err := adapters.NewDatabase(cfg.Database)
|
||||
internal.AssertNoError(err)
|
||||
|
||||
database, err := adapters.NewSqlRepository(rawDb)
|
||||
database, err := adapters.NewSqlRepository(rawDb, cfg)
|
||||
internal.AssertNoError(err)
|
||||
|
||||
wireGuard, err := wireguard.NewControllerManager(cfg)
|
||||
|
||||
@@ -157,12 +157,14 @@ More advanced options are found in the subsequent `Advanced` section.
|
||||
### `create_default_peer`
|
||||
- **Default:** `false`
|
||||
- **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER`
|
||||
- **Description:** If a user logs in for the first time with no existing peers, automatically create a new WireGuard peer for **all** server interfaces.
|
||||
- **Description:** If a user logs in for the first time with no existing peers, automatically create a new WireGuard peer for all server interfaces where the "Create default peer" flag is set.
|
||||
- **Important:** This option is only effective for interfaces where the "Create default peer" flag is set (via the UI).
|
||||
|
||||
### `create_default_peer_on_creation`
|
||||
- **Default:** `false`
|
||||
- **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_CREATION`
|
||||
- **Description:** If an LDAP user is created (e.g., through LDAP sync) and has no peers, automatically create a new WireGuard peer for **all** server interfaces.
|
||||
- **Description:** If an LDAP user is created (e.g., through LDAP sync) and has no peers, automatically create a new WireGuard peer for all server interfaces where the "Create default peer" flag is set.
|
||||
- **Important:** This option requires [create_default_peer](#create_default_peer) to be enabled.
|
||||
|
||||
### `re_enable_peer_after_user_enable`
|
||||
- **Default:** `true`
|
||||
|
||||
@@ -83,6 +83,7 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
||||
formData.value.Identifier = interfaces.Prepared.Identifier
|
||||
formData.value.DisplayName = interfaces.Prepared.DisplayName
|
||||
formData.value.Mode = interfaces.Prepared.Mode
|
||||
formData.value.CreateDefaultPeer = interfaces.Prepared.CreateDefaultPeer
|
||||
formData.value.Backend = interfaces.Prepared.Backend
|
||||
|
||||
formData.value.PublicKey = interfaces.Prepared.PublicKey
|
||||
@@ -122,6 +123,7 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
||||
formData.value.Identifier = selectedInterface.value.Identifier
|
||||
formData.value.DisplayName = selectedInterface.value.DisplayName
|
||||
formData.value.Mode = selectedInterface.value.Mode
|
||||
formData.value.CreateDefaultPeer = selectedInterface.value.CreateDefaultPeer
|
||||
formData.value.Backend = selectedInterface.value.Backend
|
||||
|
||||
formData.value.PublicKey = selectedInterface.value.PublicKey
|
||||
@@ -487,6 +489,10 @@ async function del() {
|
||||
<input v-model="formData.Disabled" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label">{{ $t('modals.interface-edit.disabled.label') }}</label>
|
||||
</div>
|
||||
<div class="form-check form-switch" v-if="formData.Mode==='server' && settings.Setting('CreateDefaultPeer')">
|
||||
<input v-model="formData.CreateDefaultPeer" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label">{{ $t('modals.interface-edit.create-default-peer.label') }}</label>
|
||||
</div>
|
||||
<div class="form-check form-switch" v-if="formData.Backend==='local'">
|
||||
<input v-model="formData.SaveConfig" checked="" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label">{{ $t('modals.interface-edit.save-config.label') }}</label>
|
||||
|
||||
@@ -4,6 +4,7 @@ export function freshInterface() {
|
||||
Disabled: false,
|
||||
DisplayName: "",
|
||||
Identifier: "",
|
||||
CreateDefaultPeer: false,
|
||||
Mode: "server",
|
||||
Backend: "local",
|
||||
|
||||
|
||||
@@ -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",
|
||||
@@ -456,6 +469,9 @@
|
||||
"disabled": {
|
||||
"label": "Schnittstelle deaktiviert"
|
||||
},
|
||||
"create-default-peer": {
|
||||
"label": "Peer für neue Benutzer automatisch erstellen"
|
||||
},
|
||||
"save-config": {
|
||||
"label": "wg-quick Konfiguration automatisch speichern"
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
@@ -456,6 +469,9 @@
|
||||
"disabled": {
|
||||
"label": "Interface Disabled"
|
||||
},
|
||||
"create-default-peer": {
|
||||
"label": "Create default peer for new users"
|
||||
},
|
||||
"save-config": {
|
||||
"label": "Automatically save wg-quick config"
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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 () => {
|
||||
<a class="btn btn-primary ms-2" href="#" :title="$t('interfaces.button-add-peer')" @click.prevent="editPeerId='#NEW#'"><i class="fa fa-plus me-1"></i><i class="fa fa-user"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="selectedPeers.length > 0">
|
||||
<div class="col-12 text-lg-end">
|
||||
<a class="btn btn-outline-primary btn-sm ms-2" href="#" :title="$t('interfaces.button-bulk-enable')" @click.prevent="bulkEnable"><i class="fa-regular fa-circle-check"></i></a>
|
||||
<a class="btn btn-outline-primary btn-sm ms-2" href="#" :title="$t('interfaces.button-bulk-disable')" @click.prevent="bulkDisable"><i class="fa fa-ban"></i></a>
|
||||
<a class="btn btn-outline-danger btn-sm ms-2" href="#" :title="$t('interfaces.button-bulk-delete')" @click.prevent="bulkDelete"><i class="fa fa-trash-can"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="interfaces.Count!==0" class="mt-2 table-responsive">
|
||||
<div v-if="peers.Count===0">
|
||||
<h4>{{ $t('interfaces.no-peer.headline') }}</h4>
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<script setup>
|
||||
import PeerViewModal from "../components/PeerViewModal.vue";
|
||||
|
||||
import { onMounted, ref } from "vue";
|
||||
import { onMounted, ref, computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { profileStore } from "@/stores/profile";
|
||||
import { peerStore } from "@/stores/peers";
|
||||
import UserPeerEditModal from "@/components/UserPeerEditModal.vue";
|
||||
import { settingsStore } from "@/stores/settings";
|
||||
import { humanFileSize } from "@/helpers/utils";
|
||||
|
||||
const settings = settingsStore()
|
||||
const profile = profileStore()
|
||||
const peers = peerStore()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const viewedPeerId = ref("")
|
||||
const editPeerId = ref("")
|
||||
@@ -17,6 +22,10 @@ const sortKey = ref("")
|
||||
const sortOrder = ref(1)
|
||||
const selectAll = ref(false)
|
||||
|
||||
const selectedPeers = computed(() => {
|
||||
return profile.Peers.filter(peer => peer.IsSelected).map(peer => peer.Identifier);
|
||||
})
|
||||
|
||||
function sortBy(key) {
|
||||
if (sortKey.value === key) {
|
||||
sortOrder.value = sortOrder.value * -1; // Toggle sort order
|
||||
@@ -35,6 +44,17 @@ function friendlyInterfaceName(id, name) {
|
||||
return id
|
||||
}
|
||||
|
||||
async function bulkDelete() {
|
||||
if (confirm(t('interfaces.confirm-bulk-delete', {count: selectedPeers.value.length}))) {
|
||||
try {
|
||||
await profile.BulkDelete(selectedPeers.value)
|
||||
selectAll.value = false // reset selection
|
||||
} catch (e) {
|
||||
// notification is handled in store
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSelectAll() {
|
||||
profile.FilteredAndPagedPeers.forEach(peer => {
|
||||
peer.IsSelected = selectAll.value;
|
||||
@@ -84,6 +104,13 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="selectedPeers.length > 0">
|
||||
<div class="col-12 text-lg-end">
|
||||
<button class="btn btn-outline-danger btn-sm" :title="$t('interfaces.button-bulk-delete')" @click.prevent="bulkDelete">
|
||||
<i class="fa fa-trash-can"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 table-responsive">
|
||||
<div v-if="profile.CountPeers === 0">
|
||||
<h4>{{ $t('profile.no-peer.headline') }}</h4>
|
||||
|
||||
@@ -1,16 +1,77 @@
|
||||
<script setup>
|
||||
import {userStore} from "@/stores/users";
|
||||
import {ref,onMounted} from "vue";
|
||||
import {ref, onMounted, computed} from "vue";
|
||||
import UserEditModal from "../components/UserEditModal.vue";
|
||||
import UserViewModal from "../components/UserViewModal.vue";
|
||||
import {useI18n} from "vue-i18n";
|
||||
|
||||
const users = userStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const editUserId = ref("")
|
||||
const viewedUserId = ref("")
|
||||
|
||||
const selectAll = ref(false)
|
||||
|
||||
const selectedUsers = computed(() => {
|
||||
return users.All.filter(user => user.IsSelected).map(user => user.Identifier);
|
||||
})
|
||||
|
||||
async function bulkDelete() {
|
||||
if (confirm(t('users.confirm-bulk-delete', {count: selectedUsers.value.length}))) {
|
||||
try {
|
||||
await users.BulkDelete(selectedUsers.value)
|
||||
selectAll.value = false // reset selection
|
||||
} catch (e) {
|
||||
// notification is handled in store
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function bulkEnable() {
|
||||
try {
|
||||
await users.BulkEnable(selectedUsers.value)
|
||||
selectAll.value = false
|
||||
users.All.forEach(u => u.IsSelected = false) // remove selection
|
||||
} catch (e) {
|
||||
// notification is handled in store
|
||||
}
|
||||
}
|
||||
|
||||
async function bulkDisable() {
|
||||
if (confirm(t('users.confirm-bulk-disable', {count: selectedUsers.value.length}))) {
|
||||
try {
|
||||
await users.BulkDisable(selectedUsers.value)
|
||||
selectAll.value = false
|
||||
users.All.forEach(u => u.IsSelected = false) // remove selection
|
||||
} catch (e) {
|
||||
// notification is handled in store
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function bulkLock() {
|
||||
if (confirm(t('users.confirm-bulk-lock', {count: selectedUsers.value.length}))) {
|
||||
try {
|
||||
await users.BulkLock(selectedUsers.value)
|
||||
selectAll.value = false
|
||||
users.All.forEach(u => u.IsSelected = false) // remove selection
|
||||
} catch (e) {
|
||||
// notification is handled in store
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function bulkUnlock() {
|
||||
try {
|
||||
await users.BulkUnlock(selectedUsers.value)
|
||||
selectAll.value = false
|
||||
users.All.forEach(u => u.IsSelected = false) // remove selection
|
||||
} catch (e) {
|
||||
// notification is handled in store
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSelectAll() {
|
||||
users.FilteredAndPaged.forEach(user => {
|
||||
user.IsSelected = selectAll.value;
|
||||
@@ -45,6 +106,15 @@ onMounted(() => {
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" v-if="selectedUsers.length > 0">
|
||||
<div class="col-12 text-lg-end">
|
||||
<a class="btn btn-outline-primary btn-sm ms-2" href="#" :title="$t('users.button-bulk-enable')" @click.prevent="bulkEnable"><i class="fa-regular fa-circle-check"></i></a>
|
||||
<a class="btn btn-outline-primary btn-sm ms-2" href="#" :title="$t('users.button-bulk-disable')" @click.prevent="bulkDisable"><i class="fa fa-ban"></i></a>
|
||||
<a class="btn btn-outline-primary btn-sm ms-2" href="#" :title="$t('users.button-bulk-unlock')" @click.prevent="bulkUnlock"><i class="fa-solid fa-lock-open"></i></a>
|
||||
<a class="btn btn-outline-primary btn-sm ms-2" href="#" :title="$t('users.button-bulk-lock')" @click.prevent="bulkLock"><i class="fa-solid fa-lock"></i></a>
|
||||
<a class="btn btn-outline-danger btn-sm ms-2" href="#" :title="$t('users.button-bulk-delete')" @click.prevent="bulkDelete"><i class="fa fa-trash-can"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 table-responsive">
|
||||
<div v-if="users.Count===0">
|
||||
<h4>{{ $t('users.no-user.headline') }}</h4>
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
// SchemaVersion describes the current database schema version. It must be incremented if a manual migration is needed.
|
||||
var SchemaVersion uint64 = 1
|
||||
var SchemaVersion uint64 = 2
|
||||
|
||||
// SysStat stores the current database schema version and the timestamp when it was applied.
|
||||
type SysStat struct {
|
||||
@@ -180,12 +180,14 @@ func NewDatabase(cfg config.DatabaseConfig) (*gorm.DB, error) {
|
||||
// Currently, it supports MySQL, SQLite, Microsoft SQL and Postgresql database systems.
|
||||
type SqlRepo struct {
|
||||
db *gorm.DB
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
// NewSqlRepository creates a new SqlRepo instance.
|
||||
func NewSqlRepository(db *gorm.DB) (*SqlRepo, error) {
|
||||
func NewSqlRepository(db *gorm.DB, cfg *config.Config) (*SqlRepo, error) {
|
||||
repo := &SqlRepo{
|
||||
db: db,
|
||||
cfg: cfg,
|
||||
}
|
||||
|
||||
if err := repo.preCheck(); err != nil {
|
||||
@@ -232,7 +234,9 @@ func (r *SqlRepo) migrate() error {
|
||||
slog.Debug("running migration: audit data", "result", r.db.AutoMigrate(&domain.AuditEntry{}))
|
||||
|
||||
existingSysStat := SysStat{}
|
||||
r.db.Where("schema_version = ?", SchemaVersion).First(&existingSysStat)
|
||||
r.db.Order("schema_version desc").First(&existingSysStat) // get latest version
|
||||
|
||||
// Migration: 0 --> 1
|
||||
if existingSysStat.SchemaVersion == 0 {
|
||||
sysStat := SysStat{
|
||||
MigratedAt: time.Now(),
|
||||
@@ -244,6 +248,27 @@ func (r *SqlRepo) migrate() error {
|
||||
slog.Debug("sys-stat entry written", "schema_version", SchemaVersion)
|
||||
}
|
||||
|
||||
// Migration: 1 --> 2
|
||||
if existingSysStat.SchemaVersion == 1 {
|
||||
// Preserve existing behavior for installations that had default-peer-creation enabled.
|
||||
if r.cfg.Core.CreateDefaultPeer {
|
||||
err := r.db.Model(&domain.Interface{}).
|
||||
Where("type = ?", domain.InterfaceTypeServer).
|
||||
Update("create_default_peer", true).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to migrate interface flags for schema version %d: %w", SchemaVersion, err)
|
||||
}
|
||||
slog.Debug("migrated interface create_default_peer flags", "schema_version", SchemaVersion)
|
||||
}
|
||||
sysStat := SysStat{
|
||||
MigratedAt: time.Now(),
|
||||
SchemaVersion: SchemaVersion,
|
||||
}
|
||||
if err := r.db.Create(&sysStat).Error; err != nil {
|
||||
return fmt.Errorf("failed to write sysstat entry for schema version %d: %w", SchemaVersion, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -800,6 +800,126 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/peer/bulk-delete": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Peer"
|
||||
],
|
||||
"summary": "Bulk delete selected peers.",
|
||||
"operationId": "peers_handleBulkDelete",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A list of peer identifiers to delete",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.BulkPeerRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No content if deletion was successful"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/peer/bulk-disable": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Peer"
|
||||
],
|
||||
"summary": "Bulk disable selected peers.",
|
||||
"operationId": "peers_handleBulkDisable",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A list of peer identifiers to disable",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.BulkPeerRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No content if action was successful"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/peer/bulk-enable": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Peer"
|
||||
],
|
||||
"summary": "Bulk enable selected peers.",
|
||||
"operationId": "peers_handleBulkEnable",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A list of peer identifiers to enable",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.BulkPeerRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No content if action was successful"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/peer/config-mail": {
|
||||
"post": {
|
||||
"produces": [
|
||||
@@ -1324,6 +1444,206 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/bulk-delete": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Bulk delete selected users.",
|
||||
"operationId": "users_handleBulkDelete",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A list of user identifiers to delete",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.BulkPeerRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No content if deletion was successful"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/bulk-disable": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Bulk disable selected users.",
|
||||
"operationId": "users_handleBulkDisable",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A list of user identifiers to disable",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.BulkPeerRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No content if action was successful"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/bulk-enable": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Bulk enable selected users.",
|
||||
"operationId": "users_handleBulkEnable",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A list of user identifiers to enable",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.BulkPeerRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No content if action was successful"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/bulk-lock": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Bulk lock selected users.",
|
||||
"operationId": "users_handleBulkLock",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A list of user identifiers to lock",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.BulkPeerRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No content if action was successful"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/bulk-unlock": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Bulk unlock selected users.",
|
||||
"operationId": "users_handleBulkUnlock",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A list of user identifiers to unlock",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.BulkPeerRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No content if action was successful"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/new": {
|
||||
"post": {
|
||||
"produces": [
|
||||
@@ -1737,6 +2057,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.BulkPeerRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Identifiers"
|
||||
],
|
||||
"properties": {
|
||||
"Identifiers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"Reason": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.ConfigOption-array_string": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -16,6 +16,17 @@ definitions:
|
||||
Timestamp:
|
||||
type: string
|
||||
type: object
|
||||
model.BulkPeerRequest:
|
||||
properties:
|
||||
Identifiers:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
Reason:
|
||||
type: string
|
||||
required:
|
||||
- Identifiers
|
||||
type: object
|
||||
model.ConfigOption-array_string:
|
||||
properties:
|
||||
Overridable:
|
||||
@@ -1080,6 +1091,84 @@ paths:
|
||||
summary: Update the given peer record.
|
||||
tags:
|
||||
- Peer
|
||||
/peer/bulk-delete:
|
||||
post:
|
||||
operationId: peers_handleBulkDelete
|
||||
parameters:
|
||||
- description: A list of peer identifiers to delete
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/model.BulkPeerRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No content if deletion was successful
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
summary: Bulk delete selected peers.
|
||||
tags:
|
||||
- Peer
|
||||
/peer/bulk-disable:
|
||||
post:
|
||||
operationId: peers_handleBulkDisable
|
||||
parameters:
|
||||
- description: A list of peer identifiers to disable
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/model.BulkPeerRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No content if action was successful
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
summary: Bulk disable selected peers.
|
||||
tags:
|
||||
- Peer
|
||||
/peer/bulk-enable:
|
||||
post:
|
||||
operationId: peers_handleBulkEnable
|
||||
parameters:
|
||||
- description: A list of peer identifiers to enable
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/model.BulkPeerRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No content if action was successful
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
summary: Bulk enable selected peers.
|
||||
tags:
|
||||
- Peer
|
||||
/peer/config-mail:
|
||||
post:
|
||||
operationId: peers_handleEmailPost
|
||||
@@ -1571,6 +1660,136 @@ paths:
|
||||
summary: Get all user records.
|
||||
tags:
|
||||
- Users
|
||||
/user/bulk-delete:
|
||||
post:
|
||||
operationId: users_handleBulkDelete
|
||||
parameters:
|
||||
- description: A list of user identifiers to delete
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/model.BulkPeerRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No content if deletion was successful
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
summary: Bulk delete selected users.
|
||||
tags:
|
||||
- Users
|
||||
/user/bulk-disable:
|
||||
post:
|
||||
operationId: users_handleBulkDisable
|
||||
parameters:
|
||||
- description: A list of user identifiers to disable
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/model.BulkPeerRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No content if action was successful
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
summary: Bulk disable selected users.
|
||||
tags:
|
||||
- Users
|
||||
/user/bulk-enable:
|
||||
post:
|
||||
operationId: users_handleBulkEnable
|
||||
parameters:
|
||||
- description: A list of user identifiers to enable
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/model.BulkPeerRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No content if action was successful
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
summary: Bulk enable selected users.
|
||||
tags:
|
||||
- Users
|
||||
/user/bulk-lock:
|
||||
post:
|
||||
operationId: users_handleBulkLock
|
||||
parameters:
|
||||
- description: A list of user identifiers to lock
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/model.BulkPeerRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No content if action was successful
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
summary: Bulk lock selected users.
|
||||
tags:
|
||||
- Users
|
||||
/user/bulk-unlock:
|
||||
post:
|
||||
operationId: users_handleBulkUnlock
|
||||
parameters:
|
||||
- description: A list of user identifiers to unlock
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/model.BulkPeerRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: No content if action was successful
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/model.Error'
|
||||
summary: Bulk unlock selected users.
|
||||
tags:
|
||||
- Users
|
||||
/user/new:
|
||||
post:
|
||||
operationId: users_handleCreatePost
|
||||
|
||||
@@ -2,6 +2,7 @@ package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
@@ -118,3 +119,30 @@ func (p PeerService) SendPeerEmail(
|
||||
func (p PeerService) GetPeerStats(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.PeerStatus, error) {
|
||||
return p.peers.GetPeerStats(ctx, id)
|
||||
}
|
||||
|
||||
func (p PeerService) BulkDelete(ctx context.Context, ids []domain.PeerIdentifier) error {
|
||||
for _, id := range ids {
|
||||
if err := p.peers.DeletePeer(ctx, id); err != nil {
|
||||
return fmt.Errorf("failed to delete peer %s: %w", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p PeerService) BulkUpdate(ctx context.Context, ids []domain.PeerIdentifier, updateFn func(*domain.Peer)) error {
|
||||
for _, id := range ids {
|
||||
peer, err := p.peers.GetPeer(ctx, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get peer %s: %w", id, err)
|
||||
}
|
||||
|
||||
updateFn(peer)
|
||||
|
||||
if _, err := p.peers.UpdatePeer(ctx, peer); err != nil {
|
||||
return fmt.Errorf("failed to update peer %s: %w", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -72,7 +72,11 @@ func (u UserService) DeactivateApi(ctx context.Context, id domain.UserIdentifier
|
||||
return u.users.DeactivateApi(ctx, id)
|
||||
}
|
||||
|
||||
func (u UserService) ChangePassword(ctx context.Context, id domain.UserIdentifier, oldPassword, newPassword string) (*domain.User, error) {
|
||||
func (u UserService) ChangePassword(
|
||||
ctx context.Context,
|
||||
id domain.UserIdentifier,
|
||||
oldPassword, newPassword string,
|
||||
) (*domain.User, error) {
|
||||
oldPassword = strings.TrimSpace(oldPassword)
|
||||
newPassword = strings.TrimSpace(newPassword)
|
||||
|
||||
@@ -121,3 +125,30 @@ func (u UserService) GetUserPeerStats(ctx context.Context, id domain.UserIdentif
|
||||
func (u UserService) GetUserInterfaces(ctx context.Context, id domain.UserIdentifier) ([]domain.Interface, error) {
|
||||
return u.wg.GetUserInterfaces(ctx, id)
|
||||
}
|
||||
|
||||
func (u UserService) BulkDelete(ctx context.Context, ids []domain.UserIdentifier) error {
|
||||
for _, id := range ids {
|
||||
if err := u.users.DeleteUser(ctx, id); err != nil {
|
||||
return fmt.Errorf("failed to delete user %s: %w", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u UserService) BulkUpdate(ctx context.Context, ids []domain.UserIdentifier, updateFn func(*domain.User)) error {
|
||||
for _, id := range ids {
|
||||
user, err := u.users.GetUser(ctx, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get user %s: %w", id, err)
|
||||
}
|
||||
|
||||
updateFn(user)
|
||||
|
||||
if _, err := u.users.UpdateUser(ctx, user); err != nil {
|
||||
return fmt.Errorf("failed to update user %s: %w", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ func (e ConfigEndpoint) handleSettingsGet() http.HandlerFunc {
|
||||
MinPasswordLength: e.cfg.Auth.MinPasswordLength,
|
||||
AvailableBackends: controllerFn(),
|
||||
LoginFormVisible: !e.cfg.Auth.HideLoginForm || !hasSocialLogin,
|
||||
CreateDefaultPeer: e.cfg.Core.CreateDefaultPeer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-pkgz/routegroup"
|
||||
|
||||
@@ -41,6 +42,10 @@ type PeerService interface {
|
||||
SendPeerEmail(ctx context.Context, linkOnly bool, style string, peers ...domain.PeerIdentifier) error
|
||||
// GetPeerStats returns the peer stats for the given interface.
|
||||
GetPeerStats(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.PeerStatus, error)
|
||||
// BulkDelete deletes multiple peers.
|
||||
BulkDelete(context.Context, []domain.PeerIdentifier) error
|
||||
// BulkUpdate modifies multiple peers.
|
||||
BulkUpdate(context.Context, []domain.PeerIdentifier, func(*domain.Peer)) error
|
||||
}
|
||||
|
||||
type PeerEndpoint struct {
|
||||
@@ -84,6 +89,9 @@ func (e PeerEndpoint) RegisterRoutes(g *routegroup.Bundle) {
|
||||
apiGroup.HandleFunc("GET /{id}", e.handleSingleGet())
|
||||
apiGroup.HandleFunc("PUT /{id}", e.handleUpdatePut())
|
||||
apiGroup.HandleFunc("DELETE /{id}", e.handleDelete())
|
||||
apiGroup.HandleFunc("POST /bulk-delete", e.handleBulkDelete())
|
||||
apiGroup.HandleFunc("POST /bulk-enable", e.handleBulkEnable())
|
||||
apiGroup.HandleFunc("POST /bulk-disable", e.handleBulkDisable())
|
||||
}
|
||||
|
||||
// handleAllGet returns a gorm Handler function.
|
||||
@@ -521,3 +529,114 @@ func (e PeerEndpoint) getConfigStyle(r *http.Request) string {
|
||||
}
|
||||
return configStyle
|
||||
}
|
||||
|
||||
// handleBulkDelete returns a gorm Handler function.
|
||||
//
|
||||
// @ID peers_handleBulkDelete
|
||||
// @Tags Peer
|
||||
// @Summary Bulk delete selected peers.
|
||||
// @Produce json
|
||||
// @Param request body model.BulkPeerRequest true "A list of peer identifiers to delete"
|
||||
// @Success 204 "No content if deletion was successful"
|
||||
// @Failure 400 {object} model.Error
|
||||
// @Failure 500 {object} model.Error
|
||||
// @Router /peer/bulk-delete [post]
|
||||
func (e PeerEndpoint) handleBulkDelete() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.BulkPeerRequest
|
||||
if err := request.BodyJson(r, &req); err != nil {
|
||||
respond.JSON(w, http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ids := make([]domain.PeerIdentifier, len(req.Identifiers))
|
||||
for i, id := range req.Identifiers {
|
||||
ids[i] = domain.PeerIdentifier(id)
|
||||
}
|
||||
|
||||
err := e.peerService.BulkDelete(r.Context(), ids)
|
||||
if err != nil {
|
||||
respond.JSON(w, http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
respond.Status(w, http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// handleBulkEnable returns a gorm Handler function.
|
||||
//
|
||||
// @ID peers_handleBulkEnable
|
||||
// @Tags Peer
|
||||
// @Summary Bulk enable selected peers.
|
||||
// @Produce json
|
||||
// @Param request body model.BulkPeerRequest true "A list of peer identifiers to enable"
|
||||
// @Success 204 "No content if action was successful"
|
||||
// @Failure 400 {object} model.Error
|
||||
// @Failure 500 {object} model.Error
|
||||
// @Router /peer/bulk-enable [post]
|
||||
func (e PeerEndpoint) handleBulkEnable() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.BulkPeerRequest
|
||||
if err := request.BodyJson(r, &req); err != nil {
|
||||
respond.JSON(w, http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ids := make([]domain.PeerIdentifier, len(req.Identifiers))
|
||||
for i, id := range req.Identifiers {
|
||||
ids[i] = domain.PeerIdentifier(id)
|
||||
}
|
||||
|
||||
err := e.peerService.BulkUpdate(r.Context(), ids, func(p *domain.Peer) {
|
||||
p.Disabled = nil
|
||||
})
|
||||
if err != nil {
|
||||
respond.JSON(w, http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
respond.Status(w, http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// handleBulkDisable returns a gorm Handler function.
|
||||
//
|
||||
// @ID peers_handleBulkDisable
|
||||
// @Tags Peer
|
||||
// @Summary Bulk disable selected peers.
|
||||
// @Produce json
|
||||
// @Param request body model.BulkPeerRequest true "A list of peer identifiers to disable"
|
||||
// @Success 204 "No content if action was successful"
|
||||
// @Failure 400 {object} model.Error
|
||||
// @Failure 500 {object} model.Error
|
||||
// @Router /peer/bulk-disable [post]
|
||||
func (e PeerEndpoint) handleBulkDisable() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.BulkPeerRequest
|
||||
if err := request.BodyJson(r, &req); err != nil {
|
||||
respond.JSON(w, http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ids := make([]domain.PeerIdentifier, len(req.Identifiers))
|
||||
for i, id := range req.Identifiers {
|
||||
ids[i] = domain.PeerIdentifier(id)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
err := e.peerService.BulkUpdate(r.Context(), ids, func(p *domain.Peer) {
|
||||
p.Disabled = &now
|
||||
p.DisabledReason = domain.DisabledReasonAdmin
|
||||
})
|
||||
if err != nil {
|
||||
respond.JSON(w, http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
respond.Status(w, http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package handlers
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-pkgz/routegroup"
|
||||
|
||||
@@ -36,6 +37,10 @@ type UserService interface {
|
||||
GetUserPeerStats(ctx context.Context, id domain.UserIdentifier) ([]domain.PeerStatus, error)
|
||||
// GetUserInterfaces returns all interfaces for the given user.
|
||||
GetUserInterfaces(ctx context.Context, id domain.UserIdentifier) ([]domain.Interface, error)
|
||||
// BulkDelete deletes multiple users.
|
||||
BulkDelete(ctx context.Context, ids []domain.UserIdentifier) error
|
||||
// BulkUpdate modifies multiple users.
|
||||
BulkUpdate(ctx context.Context, ids []domain.UserIdentifier, updateFn func(*domain.User)) error
|
||||
}
|
||||
|
||||
type UserEndpoint struct {
|
||||
@@ -77,7 +82,13 @@ func (e UserEndpoint) RegisterRoutes(g *routegroup.Bundle) {
|
||||
apiGroup.With(e.authenticator.UserIdMatch("id")).HandleFunc("GET /{id}/interfaces", e.handleInterfacesGet())
|
||||
apiGroup.With(e.authenticator.UserIdMatch("id")).HandleFunc("POST /{id}/api/enable", e.handleApiEnablePost())
|
||||
apiGroup.With(e.authenticator.UserIdMatch("id")).HandleFunc("POST /{id}/api/disable", e.handleApiDisablePost())
|
||||
apiGroup.With(e.authenticator.UserIdMatch("id")).HandleFunc("POST /{id}/change-password", e.handleChangePasswordPost())
|
||||
apiGroup.With(e.authenticator.UserIdMatch("id")).HandleFunc("POST /{id}/change-password",
|
||||
e.handleChangePasswordPost())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("POST /bulk-delete", e.handleBulkDelete())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("POST /bulk-enable", e.handleBulkEnable())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("POST /bulk-disable", e.handleBulkDisable())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("POST /bulk-lock", e.handleBulkLock())
|
||||
apiGroup.With(e.authenticator.LoggedIn(ScopeAdmin)).HandleFunc("POST /bulk-unlock", e.handleBulkUnlock())
|
||||
}
|
||||
|
||||
// handleAllGet returns a gorm Handler function.
|
||||
@@ -459,3 +470,190 @@ func (e UserEndpoint) handleChangePasswordPost() http.HandlerFunc {
|
||||
respond.JSON(w, http.StatusOK, model.NewUser(user, false))
|
||||
}
|
||||
}
|
||||
|
||||
// handleBulkDelete returns a gorm Handler function.
|
||||
//
|
||||
// @ID users_handleBulkDelete
|
||||
// @Tags Users
|
||||
// @Summary Bulk delete selected users.
|
||||
// @Produce json
|
||||
// @Param request body model.BulkPeerRequest true "A list of user identifiers to delete"
|
||||
// @Success 204 "No content if deletion was successful"
|
||||
// @Failure 400 {object} model.Error
|
||||
// @Failure 500 {object} model.Error
|
||||
// @Router /user/bulk-delete [post]
|
||||
func (e UserEndpoint) handleBulkDelete() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.BulkUserRequest
|
||||
if err := request.BodyJson(r, &req); err != nil {
|
||||
respond.JSON(w, http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ids := make([]domain.UserIdentifier, len(req.Identifiers))
|
||||
for i, id := range req.Identifiers {
|
||||
ids[i] = domain.UserIdentifier(id)
|
||||
}
|
||||
|
||||
err := e.userService.BulkDelete(r.Context(), ids)
|
||||
if err != nil {
|
||||
respond.JSON(w, http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
respond.Status(w, http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// handleBulkEnable returns a gorm Handler function.
|
||||
//
|
||||
// @ID users_handleBulkEnable
|
||||
// @Tags Users
|
||||
// @Summary Bulk enable selected users.
|
||||
// @Produce json
|
||||
// @Param request body model.BulkPeerRequest true "A list of user identifiers to enable"
|
||||
// @Success 204 "No content if action was successful"
|
||||
// @Failure 400 {object} model.Error
|
||||
// @Failure 500 {object} model.Error
|
||||
// @Router /user/bulk-enable [post]
|
||||
func (e UserEndpoint) handleBulkEnable() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.BulkUserRequest
|
||||
if err := request.BodyJson(r, &req); err != nil {
|
||||
respond.JSON(w, http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ids := make([]domain.UserIdentifier, len(req.Identifiers))
|
||||
for i, id := range req.Identifiers {
|
||||
ids[i] = domain.UserIdentifier(id)
|
||||
}
|
||||
|
||||
err := e.userService.BulkUpdate(r.Context(), ids, func(user *domain.User) {
|
||||
user.Disabled = nil
|
||||
})
|
||||
if err != nil {
|
||||
respond.JSON(w, http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
respond.Status(w, http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// handleBulkDisable returns a gorm Handler function.
|
||||
//
|
||||
// @ID users_handleBulkDisable
|
||||
// @Tags Users
|
||||
// @Summary Bulk disable selected users.
|
||||
// @Produce json
|
||||
// @Param request body model.BulkPeerRequest true "A list of user identifiers to disable"
|
||||
// @Success 204 "No content if action was successful"
|
||||
// @Failure 400 {object} model.Error
|
||||
// @Failure 500 {object} model.Error
|
||||
// @Router /user/bulk-disable [post]
|
||||
func (e UserEndpoint) handleBulkDisable() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.BulkUserRequest
|
||||
if err := request.BodyJson(r, &req); err != nil {
|
||||
respond.JSON(w, http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ids := make([]domain.UserIdentifier, len(req.Identifiers))
|
||||
for i, id := range req.Identifiers {
|
||||
ids[i] = domain.UserIdentifier(id)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
err := e.userService.BulkUpdate(r.Context(), ids, func(user *domain.User) {
|
||||
user.Disabled = &now
|
||||
user.DisabledReason = domain.DisabledReasonAdmin
|
||||
})
|
||||
if err != nil {
|
||||
respond.JSON(w, http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
respond.Status(w, http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// handleBulkLock returns a gorm Handler function.
|
||||
//
|
||||
// @ID users_handleBulkLock
|
||||
// @Tags Users
|
||||
// @Summary Bulk lock selected users.
|
||||
// @Produce json
|
||||
// @Param request body model.BulkPeerRequest true "A list of user identifiers to lock"
|
||||
// @Success 204 "No content if action was successful"
|
||||
// @Failure 400 {object} model.Error
|
||||
// @Failure 500 {object} model.Error
|
||||
// @Router /user/bulk-lock [post]
|
||||
func (e UserEndpoint) handleBulkLock() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.BulkUserRequest
|
||||
if err := request.BodyJson(r, &req); err != nil {
|
||||
respond.JSON(w, http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ids := make([]domain.UserIdentifier, len(req.Identifiers))
|
||||
for i, id := range req.Identifiers {
|
||||
ids[i] = domain.UserIdentifier(id)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
err := e.userService.BulkUpdate(r.Context(), ids, func(user *domain.User) {
|
||||
user.Locked = &now
|
||||
user.LockedReason = domain.LockedReasonAdmin
|
||||
})
|
||||
if err != nil {
|
||||
respond.JSON(w, http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
respond.Status(w, http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// handleBulkUnlock returns a gorm Handler function.
|
||||
//
|
||||
// @ID users_handleBulkUnlock
|
||||
// @Tags Users
|
||||
// @Summary Bulk unlock selected users.
|
||||
// @Produce json
|
||||
// @Param request body model.BulkPeerRequest true "A list of user identifiers to unlock"
|
||||
// @Success 204 "No content if action was successful"
|
||||
// @Failure 400 {object} model.Error
|
||||
// @Failure 500 {object} model.Error
|
||||
// @Router /user/bulk-unlock [post]
|
||||
func (e UserEndpoint) handleBulkUnlock() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.BulkUserRequest
|
||||
if err := request.BodyJson(r, &req); err != nil {
|
||||
respond.JSON(w, http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
ids := make([]domain.UserIdentifier, len(req.Identifiers))
|
||||
for i, id := range req.Identifiers {
|
||||
ids[i] = domain.UserIdentifier(id)
|
||||
}
|
||||
|
||||
err := e.userService.BulkUpdate(r.Context(), ids, func(user *domain.User) {
|
||||
user.Locked = nil
|
||||
})
|
||||
if err != nil {
|
||||
respond.JSON(w, http.StatusInternalServerError,
|
||||
model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
respond.Status(w, http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ type Settings struct {
|
||||
MinPasswordLength int `json:"MinPasswordLength"`
|
||||
AvailableBackends []SettingsBackendNames `json:"AvailableBackends"`
|
||||
LoginFormVisible bool `json:"LoginFormVisible"`
|
||||
CreateDefaultPeer bool `json:"CreateDefaultPeer"`
|
||||
}
|
||||
|
||||
type SettingsBackendNames struct {
|
||||
|
||||
10
internal/app/api/v0/model/models_bulk.go
Normal file
10
internal/app/api/v0/model/models_bulk.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package model
|
||||
|
||||
type BulkPeerRequest struct {
|
||||
Identifiers []string `json:"Identifiers" binding:"required"`
|
||||
Reason string `json:"Reason"`
|
||||
}
|
||||
|
||||
type BulkUserRequest struct {
|
||||
Identifiers []string `json:"Identifiers" binding:"required"`
|
||||
}
|
||||
@@ -18,6 +18,7 @@ type Interface struct {
|
||||
Disabled bool `json:"Disabled"` // flag that specifies if the interface is enabled (up) or not (down)
|
||||
DisabledReason string `json:"DisabledReason"` // the reason why the interface has been disabled
|
||||
SaveConfig bool `json:"SaveConfig"` // automatically persist config changes to the wgX.conf file
|
||||
CreateDefaultPeer bool `json:"CreateDefaultPeer"` // if true, default peers will be created for this interface
|
||||
|
||||
ListenPort int `json:"ListenPort"` // the listening port, for example: 51820
|
||||
Addresses []string `json:"Addresses"` // the interface ip addresses
|
||||
@@ -65,6 +66,7 @@ func NewInterface(src *domain.Interface, peers []domain.Peer) *Interface {
|
||||
Disabled: src.IsDisabled(),
|
||||
DisabledReason: src.DisabledReason,
|
||||
SaveConfig: src.SaveConfig,
|
||||
CreateDefaultPeer: src.CreateDefaultPeer,
|
||||
ListenPort: src.ListenPort,
|
||||
Addresses: domain.CidrsToStringSlice(src.Addresses),
|
||||
Dns: internal.SliceString(src.DnsStr),
|
||||
@@ -151,6 +153,7 @@ func NewDomainInterface(src *Interface) *domain.Interface {
|
||||
PreDown: src.PreDown,
|
||||
PostDown: src.PostDown,
|
||||
SaveConfig: src.SaveConfig,
|
||||
CreateDefaultPeer: src.CreateDefaultPeer,
|
||||
DisplayName: src.DisplayName,
|
||||
Type: domain.InterfaceType(src.Mode),
|
||||
Backend: domain.InterfaceBackend(src.Backend),
|
||||
|
||||
@@ -374,6 +374,7 @@ func (m Manager) PrepareInterface(ctx context.Context) (*domain.Interface, error
|
||||
SaveConfig: m.cfg.Advanced.ConfigStoragePath != "",
|
||||
DisplayName: string(id),
|
||||
Type: domain.InterfaceTypeServer,
|
||||
CreateDefaultPeer: m.cfg.Core.CreateDefaultPeer,
|
||||
DriverType: "",
|
||||
Disabled: nil,
|
||||
DisabledReason: "",
|
||||
|
||||
@@ -35,6 +35,10 @@ func (m Manager) CreateDefaultPeer(ctx context.Context, userId domain.UserIdenti
|
||||
continue // only create default peers for server interfaces
|
||||
}
|
||||
|
||||
if !iface.CreateDefaultPeer {
|
||||
continue // only create default peers if the interface flag is set
|
||||
}
|
||||
|
||||
peerAlreadyCreated := slices.ContainsFunc(userPeers, func(peer domain.Peer) bool {
|
||||
return peer.InterfaceIdentifier == iface.Identifier
|
||||
})
|
||||
|
||||
@@ -78,7 +78,12 @@ func (f *mockDB) GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceId
|
||||
func (f *mockDB) GetPeersStats(ctx context.Context, ids ...domain.PeerIdentifier) ([]domain.PeerStatus, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f *mockDB) GetAllInterfaces(ctx context.Context) ([]domain.Interface, error) { return nil, nil }
|
||||
func (f *mockDB) GetAllInterfaces(ctx context.Context) ([]domain.Interface, error) {
|
||||
if f.iface != nil {
|
||||
return []domain.Interface{*f.iface}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
func (f *mockDB) GetInterfaceIps(ctx context.Context) (map[domain.InterfaceIdentifier][]domain.Cidr, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -192,3 +197,58 @@ func TestCreatePeer_SetsIdentifier_FromPublicKey(t *testing.T) {
|
||||
t.Fatalf("expected peer with identifier %q to be saved in DB", expectedId)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDefaultPeer_RespectsInterfaceFlag(t *testing.T) {
|
||||
// Arrange
|
||||
cfg := &config.Config{}
|
||||
cfg.Core.CreateDefaultPeer = true
|
||||
|
||||
bus := &mockBus{}
|
||||
ctrlMgr := &ControllerManager{
|
||||
controllers: map[domain.InterfaceBackend]backendInstance{
|
||||
config.LocalBackendName: {Implementation: &mockController{}},
|
||||
},
|
||||
}
|
||||
|
||||
db := &mockDB{
|
||||
iface: &domain.Interface{
|
||||
Identifier: "wg0",
|
||||
Type: domain.InterfaceTypeServer,
|
||||
CreateDefaultPeer: false, // Flag is disabled!
|
||||
},
|
||||
}
|
||||
|
||||
m := Manager{
|
||||
cfg: cfg,
|
||||
bus: bus,
|
||||
db: db,
|
||||
wg: ctrlMgr,
|
||||
}
|
||||
|
||||
userId := domain.UserIdentifier("user@example.com")
|
||||
ctx := domain.SetUserInfo(context.Background(), &domain.ContextUserInfo{Id: userId, IsAdmin: true})
|
||||
|
||||
// Act
|
||||
err := m.CreateDefaultPeer(ctx, userId)
|
||||
|
||||
// Assert
|
||||
if err != nil {
|
||||
t.Fatalf("CreateDefaultPeer returned error: %v", err)
|
||||
}
|
||||
|
||||
if len(db.savedPeers) != 0 {
|
||||
t.Fatalf("expected no peers to be created because interface flag is false, but got %d", len(db.savedPeers))
|
||||
}
|
||||
|
||||
// Now enable the flag and try again
|
||||
db.iface.CreateDefaultPeer = true
|
||||
err = m.CreateDefaultPeer(ctx, userId)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("CreateDefaultPeer returned error after enabling flag: %v", err)
|
||||
}
|
||||
|
||||
if len(db.savedPeers) != 1 {
|
||||
t.Fatalf("expected 1 peer to be created because interface flag is true, but got %d", len(db.savedPeers))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ type Interface struct {
|
||||
// WG Portal specific
|
||||
DisplayName string // a nice display name/ description for the interface
|
||||
Type InterfaceType // the interface type, either InterfaceTypeServer or InterfaceTypeClient
|
||||
CreateDefaultPeer bool // if true, default peers will be created for this interface
|
||||
Backend InterfaceBackend // the backend that is used to manage the interface (wgctrl, mikrotik, ...)
|
||||
DriverType string // the interface driver type (linux, software, ...)
|
||||
Disabled *time.Time `gorm:"index"` // flag that specifies if the interface is enabled (up) or not (down)
|
||||
|
||||
Reference in New Issue
Block a user