mirror of
https://github.com/h44z/wg-portal.git
synced 2026-02-23 10:56:22 +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
|
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- 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
|
- name: Get Version
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -96,7 +96,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Upload binaries
|
- name: Upload binaries
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: binaries
|
name: binaries
|
||||||
path: binaries/wg-portal_linux*
|
path: binaries/wg-portal_linux*
|
||||||
@@ -110,7 +110,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Download binaries
|
- name: Download binaries
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: binaries
|
name: binaries
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ func main() {
|
|||||||
rawDb, err := adapters.NewDatabase(cfg.Database)
|
rawDb, err := adapters.NewDatabase(cfg.Database)
|
||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
|
|
||||||
database, err := adapters.NewSqlRepository(rawDb)
|
database, err := adapters.NewSqlRepository(rawDb, cfg)
|
||||||
internal.AssertNoError(err)
|
internal.AssertNoError(err)
|
||||||
|
|
||||||
wireGuard, err := wireguard.NewControllerManager(cfg)
|
wireGuard, err := wireguard.NewControllerManager(cfg)
|
||||||
|
|||||||
@@ -157,12 +157,14 @@ More advanced options are found in the subsequent `Advanced` section.
|
|||||||
### `create_default_peer`
|
### `create_default_peer`
|
||||||
- **Default:** `false`
|
- **Default:** `false`
|
||||||
- **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER`
|
- **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`
|
### `create_default_peer_on_creation`
|
||||||
- **Default:** `false`
|
- **Default:** `false`
|
||||||
- **Environment Variable:** `WG_PORTAL_CORE_CREATE_DEFAULT_PEER_ON_CREATION`
|
- **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`
|
### `re_enable_peer_after_user_enable`
|
||||||
- **Default:** `true`
|
- **Default:** `true`
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
|||||||
formData.value.Identifier = interfaces.Prepared.Identifier
|
formData.value.Identifier = interfaces.Prepared.Identifier
|
||||||
formData.value.DisplayName = interfaces.Prepared.DisplayName
|
formData.value.DisplayName = interfaces.Prepared.DisplayName
|
||||||
formData.value.Mode = interfaces.Prepared.Mode
|
formData.value.Mode = interfaces.Prepared.Mode
|
||||||
|
formData.value.CreateDefaultPeer = interfaces.Prepared.CreateDefaultPeer
|
||||||
formData.value.Backend = interfaces.Prepared.Backend
|
formData.value.Backend = interfaces.Prepared.Backend
|
||||||
|
|
||||||
formData.value.PublicKey = interfaces.Prepared.PublicKey
|
formData.value.PublicKey = interfaces.Prepared.PublicKey
|
||||||
@@ -122,6 +123,7 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
|||||||
formData.value.Identifier = selectedInterface.value.Identifier
|
formData.value.Identifier = selectedInterface.value.Identifier
|
||||||
formData.value.DisplayName = selectedInterface.value.DisplayName
|
formData.value.DisplayName = selectedInterface.value.DisplayName
|
||||||
formData.value.Mode = selectedInterface.value.Mode
|
formData.value.Mode = selectedInterface.value.Mode
|
||||||
|
formData.value.CreateDefaultPeer = selectedInterface.value.CreateDefaultPeer
|
||||||
formData.value.Backend = selectedInterface.value.Backend
|
formData.value.Backend = selectedInterface.value.Backend
|
||||||
|
|
||||||
formData.value.PublicKey = selectedInterface.value.PublicKey
|
formData.value.PublicKey = selectedInterface.value.PublicKey
|
||||||
@@ -487,6 +489,10 @@ async function del() {
|
|||||||
<input v-model="formData.Disabled" class="form-check-input" type="checkbox">
|
<input v-model="formData.Disabled" class="form-check-input" type="checkbox">
|
||||||
<label class="form-check-label">{{ $t('modals.interface-edit.disabled.label') }}</label>
|
<label class="form-check-label">{{ $t('modals.interface-edit.disabled.label') }}</label>
|
||||||
</div>
|
</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'">
|
<div class="form-check form-switch" v-if="formData.Backend==='local'">
|
||||||
<input v-model="formData.SaveConfig" checked="" class="form-check-input" type="checkbox">
|
<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>
|
<label class="form-check-label">{{ $t('modals.interface-edit.save-config.label') }}</label>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export function freshInterface() {
|
|||||||
Disabled: false,
|
Disabled: false,
|
||||||
DisplayName: "",
|
DisplayName: "",
|
||||||
Identifier: "",
|
Identifier: "",
|
||||||
|
CreateDefaultPeer: false,
|
||||||
Mode: "server",
|
Mode: "server",
|
||||||
Backend: "local",
|
Backend: "local",
|
||||||
|
|
||||||
|
|||||||
@@ -129,6 +129,11 @@
|
|||||||
"button-add-peers": "Mehrere Peers hinzufügen",
|
"button-add-peers": "Mehrere Peers hinzufügen",
|
||||||
"button-show-peer": "Peer anzeigen",
|
"button-show-peer": "Peer anzeigen",
|
||||||
"button-edit-peer": "Peer bearbeiten",
|
"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-disabled": "Peer ist deaktiviert, Grund:",
|
||||||
"peer-expiring": "Peer läuft ab am",
|
"peer-expiring": "Peer läuft ab am",
|
||||||
"peer-connected": "Verbunden",
|
"peer-connected": "Verbunden",
|
||||||
@@ -153,6 +158,14 @@
|
|||||||
"button-add-user": "Benutzer hinzufügen",
|
"button-add-user": "Benutzer hinzufügen",
|
||||||
"button-show-user": "Benutzer anzeigen",
|
"button-show-user": "Benutzer anzeigen",
|
||||||
"button-edit-user": "Benutzer bearbeiten",
|
"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-disabled": "Benutzer ist deaktiviert, Grund:",
|
||||||
"user-locked": "Konto ist gesperrt, Grund:",
|
"user-locked": "Konto ist gesperrt, Grund:",
|
||||||
"admin": "Benutzer hat Administratorrechte",
|
"admin": "Benutzer hat Administratorrechte",
|
||||||
@@ -456,6 +469,9 @@
|
|||||||
"disabled": {
|
"disabled": {
|
||||||
"label": "Schnittstelle deaktiviert"
|
"label": "Schnittstelle deaktiviert"
|
||||||
},
|
},
|
||||||
|
"create-default-peer": {
|
||||||
|
"label": "Peer für neue Benutzer automatisch erstellen"
|
||||||
|
},
|
||||||
"save-config": {
|
"save-config": {
|
||||||
"label": "wg-quick Konfiguration automatisch speichern"
|
"label": "wg-quick Konfiguration automatisch speichern"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -129,6 +129,11 @@
|
|||||||
"button-add-peers": "Add Multiple Peers",
|
"button-add-peers": "Add Multiple Peers",
|
||||||
"button-show-peer": "Show Peer",
|
"button-show-peer": "Show Peer",
|
||||||
"button-edit-peer": "Edit 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-disabled": "Peer is disabled, reason:",
|
||||||
"peer-expiring": "Peer is expiring at",
|
"peer-expiring": "Peer is expiring at",
|
||||||
"peer-connected": "Connected",
|
"peer-connected": "Connected",
|
||||||
@@ -153,6 +158,14 @@
|
|||||||
"button-add-user": "Add User",
|
"button-add-user": "Add User",
|
||||||
"button-show-user": "Show User",
|
"button-show-user": "Show User",
|
||||||
"button-edit-user": "Edit 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-disabled": "User is disabled, reason:",
|
||||||
"user-locked": "Account is locked, reason:",
|
"user-locked": "Account is locked, reason:",
|
||||||
"admin": "User has administrator privileges",
|
"admin": "User has administrator privileges",
|
||||||
@@ -456,6 +469,9 @@
|
|||||||
"disabled": {
|
"disabled": {
|
||||||
"label": "Interface Disabled"
|
"label": "Interface Disabled"
|
||||||
},
|
},
|
||||||
|
"create-default-peer": {
|
||||||
|
"label": "Create default peer for new users"
|
||||||
|
},
|
||||||
"save-config": {
|
"save-config": {
|
||||||
"label": "Automatically save wg-quick config"
|
"label": "Automatically save wg-quick config"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -222,6 +222,73 @@ export const peerStore = defineStore('peers', {
|
|||||||
throw new Error(error)
|
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) {
|
async UpdatePeer(id, formData) {
|
||||||
this.fetching = true
|
this.fetching = true
|
||||||
return apiWrapper.put(`${baseUrl}/${base64_url_encode(id)}`, formData)
|
return apiWrapper.put(`${baseUrl}/${base64_url_encode(id)}`, formData)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
|
|||||||
import {apiWrapper} from "@/helpers/fetch-wrapper";
|
import {apiWrapper} from "@/helpers/fetch-wrapper";
|
||||||
import {notify} from "@kyvg/vue3-notification";
|
import {notify} from "@kyvg/vue3-notification";
|
||||||
import {authStore} from "@/stores/auth";
|
import {authStore} from "@/stores/auth";
|
||||||
|
import {peerStore} from "@/stores/peers";
|
||||||
import { base64_url_encode } from '@/helpers/encoding';
|
import { base64_url_encode } from '@/helpers/encoding';
|
||||||
import {freshStats} from "@/helpers/models";
|
import {freshStats} from "@/helpers/models";
|
||||||
import { ipToBigInt } from '@/helpers/utils';
|
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 sortOrder = ref(1)
|
||||||
const selectAll = ref(false)
|
const selectAll = ref(false)
|
||||||
|
|
||||||
|
const selectedPeers = computed(() => {
|
||||||
|
return peers.All.filter(peer => peer.IsSelected).map(peer => peer.Identifier);
|
||||||
|
})
|
||||||
|
|
||||||
function sortBy(key) {
|
function sortBy(key) {
|
||||||
if (sortKey.value === key) {
|
if (sortKey.value === key) {
|
||||||
sortOrder.value = sortOrder.value * -1; // Toggle sort order
|
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() {
|
function toggleSelectAll() {
|
||||||
peers.FilteredAndPaged.forEach(peer => {
|
peers.FilteredAndPaged.forEach(peer => {
|
||||||
peer.IsSelected = selectAll.value;
|
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>
|
<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>
|
</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="interfaces.Count!==0" class="mt-2 table-responsive">
|
||||||
<div v-if="peers.Count===0">
|
<div v-if="peers.Count===0">
|
||||||
<h4>{{ $t('interfaces.no-peer.headline') }}</h4>
|
<h4>{{ $t('interfaces.no-peer.headline') }}</h4>
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import PeerViewModal from "../components/PeerViewModal.vue";
|
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 { profileStore } from "@/stores/profile";
|
||||||
|
import { peerStore } from "@/stores/peers";
|
||||||
import UserPeerEditModal from "@/components/UserPeerEditModal.vue";
|
import UserPeerEditModal from "@/components/UserPeerEditModal.vue";
|
||||||
import { settingsStore } from "@/stores/settings";
|
import { settingsStore } from "@/stores/settings";
|
||||||
import { humanFileSize } from "@/helpers/utils";
|
import { humanFileSize } from "@/helpers/utils";
|
||||||
|
|
||||||
const settings = settingsStore()
|
const settings = settingsStore()
|
||||||
const profile = profileStore()
|
const profile = profileStore()
|
||||||
|
const peers = peerStore()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const viewedPeerId = ref("")
|
const viewedPeerId = ref("")
|
||||||
const editPeerId = ref("")
|
const editPeerId = ref("")
|
||||||
@@ -17,6 +22,10 @@ const sortKey = ref("")
|
|||||||
const sortOrder = ref(1)
|
const sortOrder = ref(1)
|
||||||
const selectAll = ref(false)
|
const selectAll = ref(false)
|
||||||
|
|
||||||
|
const selectedPeers = computed(() => {
|
||||||
|
return profile.Peers.filter(peer => peer.IsSelected).map(peer => peer.Identifier);
|
||||||
|
})
|
||||||
|
|
||||||
function sortBy(key) {
|
function sortBy(key) {
|
||||||
if (sortKey.value === key) {
|
if (sortKey.value === key) {
|
||||||
sortOrder.value = sortOrder.value * -1; // Toggle sort order
|
sortOrder.value = sortOrder.value * -1; // Toggle sort order
|
||||||
@@ -35,6 +44,17 @@ function friendlyInterfaceName(id, name) {
|
|||||||
return id
|
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() {
|
function toggleSelectAll() {
|
||||||
profile.FilteredAndPagedPeers.forEach(peer => {
|
profile.FilteredAndPagedPeers.forEach(peer => {
|
||||||
peer.IsSelected = selectAll.value;
|
peer.IsSelected = selectAll.value;
|
||||||
@@ -84,6 +104,13 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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 class="mt-2 table-responsive">
|
||||||
<div v-if="profile.CountPeers === 0">
|
<div v-if="profile.CountPeers === 0">
|
||||||
<h4>{{ $t('profile.no-peer.headline') }}</h4>
|
<h4>{{ $t('profile.no-peer.headline') }}</h4>
|
||||||
|
|||||||
@@ -1,16 +1,77 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {userStore} from "@/stores/users";
|
import {userStore} from "@/stores/users";
|
||||||
import {ref,onMounted} from "vue";
|
import {ref, onMounted, computed} from "vue";
|
||||||
import UserEditModal from "../components/UserEditModal.vue";
|
import UserEditModal from "../components/UserEditModal.vue";
|
||||||
import UserViewModal from "../components/UserViewModal.vue";
|
import UserViewModal from "../components/UserViewModal.vue";
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
|
|
||||||
const users = userStore()
|
const users = userStore()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const editUserId = ref("")
|
const editUserId = ref("")
|
||||||
const viewedUserId = ref("")
|
const viewedUserId = ref("")
|
||||||
|
|
||||||
const selectAll = ref(false)
|
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() {
|
function toggleSelectAll() {
|
||||||
users.FilteredAndPaged.forEach(user => {
|
users.FilteredAndPaged.forEach(user => {
|
||||||
user.IsSelected = selectAll.value;
|
user.IsSelected = selectAll.value;
|
||||||
@@ -45,6 +106,15 @@ onMounted(() => {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</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 class="mt-2 table-responsive">
|
||||||
<div v-if="users.Count===0">
|
<div v-if="users.Count===0">
|
||||||
<h4>{{ $t('users.no-user.headline') }}</h4>
|
<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.
|
// 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.
|
// SysStat stores the current database schema version and the timestamp when it was applied.
|
||||||
type SysStat struct {
|
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.
|
// Currently, it supports MySQL, SQLite, Microsoft SQL and Postgresql database systems.
|
||||||
type SqlRepo struct {
|
type SqlRepo struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
|
cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSqlRepository creates a new SqlRepo instance.
|
// 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{
|
repo := &SqlRepo{
|
||||||
db: db,
|
db: db,
|
||||||
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo.preCheck(); err != nil {
|
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{}))
|
slog.Debug("running migration: audit data", "result", r.db.AutoMigrate(&domain.AuditEntry{}))
|
||||||
|
|
||||||
existingSysStat := SysStat{}
|
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 {
|
if existingSysStat.SchemaVersion == 0 {
|
||||||
sysStat := SysStat{
|
sysStat := SysStat{
|
||||||
MigratedAt: time.Now(),
|
MigratedAt: time.Now(),
|
||||||
@@ -244,6 +248,27 @@ func (r *SqlRepo) migrate() error {
|
|||||||
slog.Debug("sys-stat entry written", "schema_version", SchemaVersion)
|
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
|
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": {
|
"/peer/config-mail": {
|
||||||
"post": {
|
"post": {
|
||||||
"produces": [
|
"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": {
|
"/user/new": {
|
||||||
"post": {
|
"post": {
|
||||||
"produces": [
|
"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": {
|
"model.ConfigOption-array_string": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -16,6 +16,17 @@ definitions:
|
|||||||
Timestamp:
|
Timestamp:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
model.BulkPeerRequest:
|
||||||
|
properties:
|
||||||
|
Identifiers:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
Reason:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- Identifiers
|
||||||
|
type: object
|
||||||
model.ConfigOption-array_string:
|
model.ConfigOption-array_string:
|
||||||
properties:
|
properties:
|
||||||
Overridable:
|
Overridable:
|
||||||
@@ -1080,6 +1091,84 @@ paths:
|
|||||||
summary: Update the given peer record.
|
summary: Update the given peer record.
|
||||||
tags:
|
tags:
|
||||||
- Peer
|
- 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:
|
/peer/config-mail:
|
||||||
post:
|
post:
|
||||||
operationId: peers_handleEmailPost
|
operationId: peers_handleEmailPost
|
||||||
@@ -1571,6 +1660,136 @@ paths:
|
|||||||
summary: Get all user records.
|
summary: Get all user records.
|
||||||
tags:
|
tags:
|
||||||
- Users
|
- 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:
|
/user/new:
|
||||||
post:
|
post:
|
||||||
operationId: users_handleCreatePost
|
operationId: users_handleCreatePost
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package backend
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/h44z/wg-portal/internal/config"
|
"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) {
|
func (p PeerService) GetPeerStats(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.PeerStatus, error) {
|
||||||
return p.peers.GetPeerStats(ctx, id)
|
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)
|
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)
|
oldPassword = strings.TrimSpace(oldPassword)
|
||||||
newPassword = strings.TrimSpace(newPassword)
|
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) {
|
func (u UserService) GetUserInterfaces(ctx context.Context, id domain.UserIdentifier) ([]domain.Interface, error) {
|
||||||
return u.wg.GetUserInterfaces(ctx, id)
|
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,
|
MinPasswordLength: e.cfg.Auth.MinPasswordLength,
|
||||||
AvailableBackends: controllerFn(),
|
AvailableBackends: controllerFn(),
|
||||||
LoginFormVisible: !e.cfg.Auth.HideLoginForm || !hasSocialLogin,
|
LoginFormVisible: !e.cfg.Auth.HideLoginForm || !hasSocialLogin,
|
||||||
|
CreateDefaultPeer: e.cfg.Core.CreateDefaultPeer,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-pkgz/routegroup"
|
"github.com/go-pkgz/routegroup"
|
||||||
|
|
||||||
@@ -41,6 +42,10 @@ type PeerService interface {
|
|||||||
SendPeerEmail(ctx context.Context, linkOnly bool, style string, peers ...domain.PeerIdentifier) error
|
SendPeerEmail(ctx context.Context, linkOnly bool, style string, peers ...domain.PeerIdentifier) error
|
||||||
// GetPeerStats returns the peer stats for the given interface.
|
// GetPeerStats returns the peer stats for the given interface.
|
||||||
GetPeerStats(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.PeerStatus, error)
|
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 {
|
type PeerEndpoint struct {
|
||||||
@@ -84,6 +89,9 @@ func (e PeerEndpoint) RegisterRoutes(g *routegroup.Bundle) {
|
|||||||
apiGroup.HandleFunc("GET /{id}", e.handleSingleGet())
|
apiGroup.HandleFunc("GET /{id}", e.handleSingleGet())
|
||||||
apiGroup.HandleFunc("PUT /{id}", e.handleUpdatePut())
|
apiGroup.HandleFunc("PUT /{id}", e.handleUpdatePut())
|
||||||
apiGroup.HandleFunc("DELETE /{id}", e.handleDelete())
|
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.
|
// handleAllGet returns a gorm Handler function.
|
||||||
@@ -521,3 +529,114 @@ func (e PeerEndpoint) getConfigStyle(r *http.Request) string {
|
|||||||
}
|
}
|
||||||
return configStyle
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-pkgz/routegroup"
|
"github.com/go-pkgz/routegroup"
|
||||||
|
|
||||||
@@ -36,6 +37,10 @@ type UserService interface {
|
|||||||
GetUserPeerStats(ctx context.Context, id domain.UserIdentifier) ([]domain.PeerStatus, error)
|
GetUserPeerStats(ctx context.Context, id domain.UserIdentifier) ([]domain.PeerStatus, error)
|
||||||
// GetUserInterfaces returns all interfaces for the given user.
|
// GetUserInterfaces returns all interfaces for the given user.
|
||||||
GetUserInterfaces(ctx context.Context, id domain.UserIdentifier) ([]domain.Interface, error)
|
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 {
|
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("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/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}/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.
|
// 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))
|
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"`
|
MinPasswordLength int `json:"MinPasswordLength"`
|
||||||
AvailableBackends []SettingsBackendNames `json:"AvailableBackends"`
|
AvailableBackends []SettingsBackendNames `json:"AvailableBackends"`
|
||||||
LoginFormVisible bool `json:"LoginFormVisible"`
|
LoginFormVisible bool `json:"LoginFormVisible"`
|
||||||
|
CreateDefaultPeer bool `json:"CreateDefaultPeer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingsBackendNames struct {
|
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)
|
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
|
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
|
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
|
ListenPort int `json:"ListenPort"` // the listening port, for example: 51820
|
||||||
Addresses []string `json:"Addresses"` // the interface ip addresses
|
Addresses []string `json:"Addresses"` // the interface ip addresses
|
||||||
@@ -65,6 +66,7 @@ func NewInterface(src *domain.Interface, peers []domain.Peer) *Interface {
|
|||||||
Disabled: src.IsDisabled(),
|
Disabled: src.IsDisabled(),
|
||||||
DisabledReason: src.DisabledReason,
|
DisabledReason: src.DisabledReason,
|
||||||
SaveConfig: src.SaveConfig,
|
SaveConfig: src.SaveConfig,
|
||||||
|
CreateDefaultPeer: src.CreateDefaultPeer,
|
||||||
ListenPort: src.ListenPort,
|
ListenPort: src.ListenPort,
|
||||||
Addresses: domain.CidrsToStringSlice(src.Addresses),
|
Addresses: domain.CidrsToStringSlice(src.Addresses),
|
||||||
Dns: internal.SliceString(src.DnsStr),
|
Dns: internal.SliceString(src.DnsStr),
|
||||||
@@ -151,6 +153,7 @@ func NewDomainInterface(src *Interface) *domain.Interface {
|
|||||||
PreDown: src.PreDown,
|
PreDown: src.PreDown,
|
||||||
PostDown: src.PostDown,
|
PostDown: src.PostDown,
|
||||||
SaveConfig: src.SaveConfig,
|
SaveConfig: src.SaveConfig,
|
||||||
|
CreateDefaultPeer: src.CreateDefaultPeer,
|
||||||
DisplayName: src.DisplayName,
|
DisplayName: src.DisplayName,
|
||||||
Type: domain.InterfaceType(src.Mode),
|
Type: domain.InterfaceType(src.Mode),
|
||||||
Backend: domain.InterfaceBackend(src.Backend),
|
Backend: domain.InterfaceBackend(src.Backend),
|
||||||
|
|||||||
@@ -374,6 +374,7 @@ func (m Manager) PrepareInterface(ctx context.Context) (*domain.Interface, error
|
|||||||
SaveConfig: m.cfg.Advanced.ConfigStoragePath != "",
|
SaveConfig: m.cfg.Advanced.ConfigStoragePath != "",
|
||||||
DisplayName: string(id),
|
DisplayName: string(id),
|
||||||
Type: domain.InterfaceTypeServer,
|
Type: domain.InterfaceTypeServer,
|
||||||
|
CreateDefaultPeer: m.cfg.Core.CreateDefaultPeer,
|
||||||
DriverType: "",
|
DriverType: "",
|
||||||
Disabled: nil,
|
Disabled: nil,
|
||||||
DisabledReason: "",
|
DisabledReason: "",
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ func (m Manager) CreateDefaultPeer(ctx context.Context, userId domain.UserIdenti
|
|||||||
continue // only create default peers for server interfaces
|
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 {
|
peerAlreadyCreated := slices.ContainsFunc(userPeers, func(peer domain.Peer) bool {
|
||||||
return peer.InterfaceIdentifier == iface.Identifier
|
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) {
|
func (f *mockDB) GetPeersStats(ctx context.Context, ids ...domain.PeerIdentifier) ([]domain.PeerStatus, error) {
|
||||||
return nil, nil
|
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) {
|
func (f *mockDB) GetInterfaceIps(ctx context.Context) (map[domain.InterfaceIdentifier][]domain.Cidr, error) {
|
||||||
return nil, nil
|
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)
|
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
|
// WG Portal specific
|
||||||
DisplayName string // a nice display name/ description for the interface
|
DisplayName string // a nice display name/ description for the interface
|
||||||
Type InterfaceType // the interface type, either InterfaceTypeServer or InterfaceTypeClient
|
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, ...)
|
Backend InterfaceBackend // the backend that is used to manage the interface (wgctrl, mikrotik, ...)
|
||||||
DriverType string // the interface driver type (linux, software, ...)
|
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)
|
Disabled *time.Time `gorm:"index"` // flag that specifies if the interface is enabled (up) or not (down)
|
||||||
|
|||||||
Reference in New Issue
Block a user