mirror of
https://github.com/h44z/wg-portal.git
synced 2025-08-12 08:12:23 +00:00
fix peer creation/update on mikrotik, add loading spinner to frontend for long running actions
This commit is contained in:
parent
ed7761a918
commit
08373fa675
@ -50,6 +50,9 @@ const currentTags = ref({
|
|||||||
PeerDefDnsSearch: ""
|
PeerDefDnsSearch: ""
|
||||||
})
|
})
|
||||||
const formData = ref(freshInterface())
|
const formData = ref(freshInterface())
|
||||||
|
const isSaving = ref(false)
|
||||||
|
const isDeleting = ref(false)
|
||||||
|
const isApplyingDefaults = ref(false)
|
||||||
|
|
||||||
const isBackendValid = computed(() => {
|
const isBackendValid = computed(() => {
|
||||||
if (!props.visible || !selectedInterface.value) {
|
if (!props.visible || !selectedInterface.value) {
|
||||||
@ -258,6 +261,8 @@ function handleChangePeerDefDnsSearch(tags) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
|
if (isSaving.value) return
|
||||||
|
isSaving.value = true
|
||||||
try {
|
try {
|
||||||
if (props.interfaceId!=='#NEW#') {
|
if (props.interfaceId!=='#NEW#') {
|
||||||
await interfaces.UpdateInterface(selectedInterface.value.Identifier, formData.value)
|
await interfaces.UpdateInterface(selectedInterface.value.Identifier, formData.value)
|
||||||
@ -272,6 +277,8 @@ async function save() {
|
|||||||
text: e.toString(),
|
text: e.toString(),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
isSaving.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,6 +287,8 @@ async function applyPeerDefaults() {
|
|||||||
return; // do nothing for new interfaces
|
return; // do nothing for new interfaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isApplyingDefaults.value) return
|
||||||
|
isApplyingDefaults.value = true
|
||||||
try {
|
try {
|
||||||
await interfaces.ApplyPeerDefaults(selectedInterface.value.Identifier, formData.value)
|
await interfaces.ApplyPeerDefaults(selectedInterface.value.Identifier, formData.value)
|
||||||
|
|
||||||
@ -297,10 +306,14 @@ async function applyPeerDefaults() {
|
|||||||
text: e.toString(),
|
text: e.toString(),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
isApplyingDefaults.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function del() {
|
async function del() {
|
||||||
|
if (isDeleting.value) return
|
||||||
|
isDeleting.value = true
|
||||||
try {
|
try {
|
||||||
await interfaces.DeleteInterface(selectedInterface.value.Identifier)
|
await interfaces.DeleteInterface(selectedInterface.value.Identifier)
|
||||||
close()
|
close()
|
||||||
@ -311,6 +324,8 @@ async function del() {
|
|||||||
text: e.toString(),
|
text: e.toString(),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
isDeleting.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -562,16 +577,25 @@ async function del() {
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset v-if="props.interfaceId!=='#NEW#'" class="text-end">
|
<fieldset v-if="props.interfaceId!=='#NEW#'" class="text-end">
|
||||||
<hr class="mt-4">
|
<hr class="mt-4">
|
||||||
<button class="btn btn-primary me-1" type="button" @click.prevent="applyPeerDefaults">{{ $t('modals.interface-edit.button-apply-defaults') }}</button>
|
<button class="btn btn-primary me-1" type="button" @click.prevent="applyPeerDefaults" :disabled="isApplyingDefaults">
|
||||||
|
<span v-if="isApplyingDefaults" class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
||||||
|
{{ $t('modals.interface-edit.button-apply-defaults') }}
|
||||||
|
</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex-fill text-start">
|
<div class="flex-fill text-start">
|
||||||
<button v-if="props.interfaceId!=='#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del">{{ $t('general.delete') }}</button>
|
<button v-if="props.interfaceId!=='#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del" :disabled="isDeleting">
|
||||||
|
<span v-if="isDeleting" class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
||||||
|
{{ $t('general.delete') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary me-1" type="button" @click.prevent="save">{{ $t('general.save') }}</button>
|
<button class="btn btn-primary me-1" type="button" @click.prevent="save" :disabled="isSaving">
|
||||||
|
<span v-if="isSaving" class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
||||||
|
{{ $t('general.save') }}
|
||||||
|
</button>
|
||||||
<button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
|
<button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -73,6 +73,8 @@ const currentTags = ref({
|
|||||||
DnsSearch: ""
|
DnsSearch: ""
|
||||||
})
|
})
|
||||||
const formData = ref(freshPeer())
|
const formData = ref(freshPeer())
|
||||||
|
const isSaving = ref(false)
|
||||||
|
const isDeleting = ref(false)
|
||||||
|
|
||||||
// functions
|
// functions
|
||||||
|
|
||||||
@ -270,6 +272,8 @@ function handleChangeDnsSearch(tags) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
|
if (isSaving.value) return
|
||||||
|
isSaving.value = true
|
||||||
try {
|
try {
|
||||||
if (props.peerId !== '#NEW#') {
|
if (props.peerId !== '#NEW#') {
|
||||||
await peers.UpdatePeer(selectedPeer.value.Identifier, formData.value)
|
await peers.UpdatePeer(selectedPeer.value.Identifier, formData.value)
|
||||||
@ -278,26 +282,30 @@ async function save() {
|
|||||||
}
|
}
|
||||||
close()
|
close()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// console.log(e)
|
|
||||||
notify({
|
notify({
|
||||||
title: "Failed to save peer!",
|
title: "Failed to save peer!",
|
||||||
text: e.toString(),
|
text: e.toString(),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
isSaving.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function del() {
|
async function del() {
|
||||||
|
if (isDeleting.value) return
|
||||||
|
isDeleting.value = true
|
||||||
try {
|
try {
|
||||||
await peers.DeletePeer(selectedPeer.value.Identifier)
|
await peers.DeletePeer(selectedPeer.value.Identifier)
|
||||||
close()
|
close()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// console.log(e)
|
|
||||||
notify({
|
notify({
|
||||||
title: "Failed to delete peer!",
|
title: "Failed to delete peer!",
|
||||||
text: e.toString(),
|
text: e.toString(),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
isDeleting.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,10 +478,15 @@ async function del() {
|
|||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex-fill text-start">
|
<div class="flex-fill text-start">
|
||||||
<button v-if="props.peerId !== '#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del">{{
|
<button v-if="props.peerId !== '#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del" :disabled="isDeleting">
|
||||||
$t('general.delete') }}</button>
|
<span v-if="isDeleting" class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
||||||
|
{{ $t('general.delete') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary me-1" type="button" @click.prevent="save">{{ $t('general.save') }}</button>
|
<button class="btn btn-primary me-1" type="button" @click.prevent="save" :disabled="isSaving">
|
||||||
|
<span v-if="isSaving" class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
||||||
|
{{ $t('general.save') }}
|
||||||
|
</button>
|
||||||
<button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
|
<button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -38,6 +38,7 @@ function freshForm() {
|
|||||||
|
|
||||||
const currentTag = ref("")
|
const currentTag = ref("")
|
||||||
const formData = ref(freshForm())
|
const formData = ref(freshForm())
|
||||||
|
const isSaving = ref(false)
|
||||||
|
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
if (!props.visible) {
|
if (!props.visible) {
|
||||||
@ -60,12 +61,15 @@ function handleChangeUserIdentifiers(tags) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
|
if (isSaving.value) return
|
||||||
|
isSaving.value = true
|
||||||
if (formData.value.Identifiers.length === 0) {
|
if (formData.value.Identifiers.length === 0) {
|
||||||
notify({
|
notify({
|
||||||
title: "Missing Identifiers",
|
title: "Missing Identifiers",
|
||||||
text: "At least one identifier is required to create a new peer.",
|
text: "At least one identifier is required to create a new peer.",
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
isSaving.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +83,8 @@ async function save() {
|
|||||||
text: e.toString(),
|
text: e.toString(),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
isSaving.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +114,10 @@ async function save() {
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<button class="btn btn-primary me-1" type="button" @click.prevent="save">{{ $t('general.save') }}</button>
|
<button class="btn btn-primary me-1" type="button" @click.prevent="save" :disabled="isSaving">
|
||||||
|
<span v-if="isSaving" class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
||||||
|
{{ $t('general.save') }}
|
||||||
|
</button>
|
||||||
<button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
|
<button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -34,6 +34,8 @@ const title = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const formData = ref(freshUser())
|
const formData = ref(freshUser())
|
||||||
|
const isSaving = ref(false)
|
||||||
|
const isDeleting = ref(false)
|
||||||
|
|
||||||
const passwordWeak = computed(() => {
|
const passwordWeak = computed(() => {
|
||||||
return formData.value.Password && formData.value.Password.length > 0 && formData.value.Password.length < settings.Setting('MinPasswordLength')
|
return formData.value.Password && formData.value.Password.length > 0 && formData.value.Password.length < settings.Setting('MinPasswordLength')
|
||||||
@ -89,6 +91,8 @@ function close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
|
if (isSaving.value) return
|
||||||
|
isSaving.value = true
|
||||||
try {
|
try {
|
||||||
if (props.userId!=='#NEW#') {
|
if (props.userId!=='#NEW#') {
|
||||||
await users.UpdateUser(selectedUser.value.Identifier, formData.value)
|
await users.UpdateUser(selectedUser.value.Identifier, formData.value)
|
||||||
@ -102,10 +106,14 @@ async function save() {
|
|||||||
text: e.toString(),
|
text: e.toString(),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
isSaving.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function del() {
|
async function del() {
|
||||||
|
if (isDeleting.value) return
|
||||||
|
isDeleting.value = true
|
||||||
try {
|
try {
|
||||||
await users.DeleteUser(selectedUser.value.Identifier)
|
await users.DeleteUser(selectedUser.value.Identifier)
|
||||||
close()
|
close()
|
||||||
@ -115,6 +123,8 @@ async function del() {
|
|||||||
text: e.toString(),
|
text: e.toString(),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
isDeleting.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,9 +203,15 @@ async function del() {
|
|||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex-fill text-start">
|
<div class="flex-fill text-start">
|
||||||
<button v-if="props.userId!=='#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del">{{ $t('general.delete') }}</button>
|
<button v-if="props.userId!=='#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del" :disabled="isDeleting">
|
||||||
|
<span v-if="isDeleting" class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
||||||
|
{{ $t('general.delete') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary me-1" type="button" @click.prevent="save" :disabled="!formValid">{{ $t('general.save') }}</button>
|
<button class="btn btn-primary me-1" type="button" @click.prevent="save" :disabled="!formValid || isSaving">
|
||||||
|
<span v-if="isSaving" class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
||||||
|
{{ $t('general.save') }}
|
||||||
|
</button>
|
||||||
<button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
|
<button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -55,6 +55,8 @@ const title = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const formData = ref(freshPeer())
|
const formData = ref(freshPeer())
|
||||||
|
const isSaving = ref(false)
|
||||||
|
const isDeleting = ref(false)
|
||||||
|
|
||||||
// functions
|
// functions
|
||||||
|
|
||||||
@ -163,6 +165,8 @@ function close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
|
if (isSaving.value) return
|
||||||
|
isSaving.value = true
|
||||||
try {
|
try {
|
||||||
if (props.peerId !== '#NEW#') {
|
if (props.peerId !== '#NEW#') {
|
||||||
await peers.UpdatePeer(selectedPeer.value.Identifier, formData.value)
|
await peers.UpdatePeer(selectedPeer.value.Identifier, formData.value)
|
||||||
@ -171,26 +175,30 @@ async function save() {
|
|||||||
}
|
}
|
||||||
close()
|
close()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// console.log(e)
|
|
||||||
notify({
|
notify({
|
||||||
title: "Failed to save peer!",
|
title: "Failed to save peer!",
|
||||||
text: e.toString(),
|
text: e.toString(),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
isSaving.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function del() {
|
async function del() {
|
||||||
|
if (isDeleting.value) return
|
||||||
|
isDeleting.value = true
|
||||||
try {
|
try {
|
||||||
await peers.DeletePeer(selectedPeer.value.Identifier)
|
await peers.DeletePeer(selectedPeer.value.Identifier)
|
||||||
close()
|
close()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// console.log(e)
|
|
||||||
notify({
|
notify({
|
||||||
title: "Failed to delete peer!",
|
title: "Failed to delete peer!",
|
||||||
text: e.toString(),
|
text: e.toString(),
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
} finally {
|
||||||
|
isDeleting.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,10 +291,15 @@ async function del() {
|
|||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex-fill text-start">
|
<div class="flex-fill text-start">
|
||||||
<button v-if="props.peerId !== '#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del">{{
|
<button v-if="props.peerId !== '#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del" :disabled="isDeleting">
|
||||||
$t('general.delete') }}</button>
|
<span v-if="isDeleting" class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
||||||
|
{{ $t('general.delete') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary me-1" type="button" @click.prevent="save">{{ $t('general.save') }}</button>
|
<button class="btn btn-primary me-1" type="button" @click.prevent="save" :disabled="isSaving">
|
||||||
|
<span v-if="isSaving" class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
||||||
|
{{ $t('general.save') }}
|
||||||
|
</button>
|
||||||
<button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
|
<button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -204,6 +204,11 @@ func (c LocalController) convertWireGuardPeer(peer *wgtypes.Peer) (domain.Physic
|
|||||||
ImportSource: domain.ControllerTypeLocal,
|
ImportSource: domain.ControllerTypeLocal,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set local extras - local peers are never disabled in the kernel
|
||||||
|
peerModel.SetExtras(domain.LocalPeerExtras{
|
||||||
|
Disabled: false,
|
||||||
|
})
|
||||||
|
|
||||||
for _, addr := range peer.AllowedIPs {
|
for _, addr := range peer.AllowedIPs {
|
||||||
peerModel.AllowedIPs = append(peerModel.AllowedIPs, domain.CidrFromIpNet(addr))
|
peerModel.AllowedIPs = append(peerModel.AllowedIPs, domain.CidrFromIpNet(addr))
|
||||||
}
|
}
|
||||||
@ -410,6 +415,18 @@ func (c LocalController) SavePeer(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the peer is disabled by looking at the backend extras
|
||||||
|
// For local controller, disabled peers should be deleted
|
||||||
|
if physicalPeer.GetExtras() != nil {
|
||||||
|
switch extras := physicalPeer.GetExtras().(type) {
|
||||||
|
case domain.LocalPeerExtras:
|
||||||
|
if extras.Disabled {
|
||||||
|
// Delete the peer instead of updating it
|
||||||
|
return c.deletePeer(deviceId, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := c.updatePeer(deviceId, physicalPeer); err != nil {
|
if err := c.updatePeer(deviceId, physicalPeer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,11 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
"github.com/h44z/wg-portal/internal/config"
|
"github.com/h44z/wg-portal/internal/config"
|
||||||
"github.com/h44z/wg-portal/internal/domain"
|
"github.com/h44z/wg-portal/internal/domain"
|
||||||
"github.com/h44z/wg-portal/internal/lowlevel"
|
"github.com/h44z/wg-portal/internal/lowlevel"
|
||||||
@ -18,6 +21,10 @@ type MikrotikController struct {
|
|||||||
cfg *config.BackendMikrotik
|
cfg *config.BackendMikrotik
|
||||||
|
|
||||||
client *lowlevel.MikrotikApiClient
|
client *lowlevel.MikrotikApiClient
|
||||||
|
|
||||||
|
// Add mutexes to prevent race conditions
|
||||||
|
interfaceMutexes sync.Map // map[domain.InterfaceIdentifier]*sync.Mutex
|
||||||
|
peerMutexes sync.Map // map[domain.PeerIdentifier]*sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMikrotikController(coreCfg *config.Config, cfg *config.BackendMikrotik) (*MikrotikController, error) {
|
func NewMikrotikController(coreCfg *config.Config, cfg *config.BackendMikrotik) (*MikrotikController, error) {
|
||||||
@ -31,16 +38,31 @@ func NewMikrotikController(coreCfg *config.Config, cfg *config.BackendMikrotik)
|
|||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
|
||||||
client: client,
|
client: client,
|
||||||
|
|
||||||
|
interfaceMutexes: sync.Map{},
|
||||||
|
peerMutexes: sync.Map{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) GetId() domain.InterfaceBackend {
|
func (c *MikrotikController) GetId() domain.InterfaceBackend {
|
||||||
return domain.InterfaceBackend(c.cfg.Id)
|
return domain.InterfaceBackend(c.cfg.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getInterfaceMutex returns a mutex for the given interface to prevent concurrent modifications
|
||||||
|
func (c *MikrotikController) getInterfaceMutex(id domain.InterfaceIdentifier) *sync.Mutex {
|
||||||
|
mutex, _ := c.interfaceMutexes.LoadOrStore(id, &sync.Mutex{})
|
||||||
|
return mutex.(*sync.Mutex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPeerMutex returns a mutex for the given peer to prevent concurrent modifications
|
||||||
|
func (c *MikrotikController) getPeerMutex(id domain.PeerIdentifier) *sync.Mutex {
|
||||||
|
mutex, _ := c.peerMutexes.LoadOrStore(id, &sync.Mutex{})
|
||||||
|
return mutex.(*sync.Mutex)
|
||||||
|
}
|
||||||
|
|
||||||
// region wireguard-related
|
// region wireguard-related
|
||||||
|
|
||||||
func (c MikrotikController) GetInterfaces(ctx context.Context) ([]domain.PhysicalInterface, error) {
|
func (c *MikrotikController) GetInterfaces(ctx context.Context) ([]domain.PhysicalInterface, error) {
|
||||||
wgReply := c.client.Query(ctx, "/interface/wireguard", &lowlevel.MikrotikRequestOptions{
|
wgReply := c.client.Query(ctx, "/interface/wireguard", &lowlevel.MikrotikRequestOptions{
|
||||||
PropList: []string{
|
PropList: []string{
|
||||||
".id", "name", "public-key", "private-key", "listen-port", "mtu", "disabled", "running", "comment",
|
".id", "name", "public-key", "private-key", "listen-port", "mtu", "disabled", "running", "comment",
|
||||||
@ -62,7 +84,7 @@ func (c MikrotikController) GetInterfaces(ctx context.Context) ([]domain.Physica
|
|||||||
return interfaces, nil
|
return interfaces, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) GetInterface(ctx context.Context, id domain.InterfaceIdentifier) (
|
func (c *MikrotikController) GetInterface(ctx context.Context, id domain.InterfaceIdentifier) (
|
||||||
*domain.PhysicalInterface,
|
*domain.PhysicalInterface,
|
||||||
error,
|
error,
|
||||||
) {
|
) {
|
||||||
@ -85,7 +107,7 @@ func (c MikrotikController) GetInterface(ctx context.Context, id domain.Interfac
|
|||||||
return c.loadInterfaceData(ctx, wgReply.Data[0])
|
return c.loadInterfaceData(ctx, wgReply.Data[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) loadInterfaceData(
|
func (c *MikrotikController) loadInterfaceData(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
wireGuardObj lowlevel.GenericJsonObject,
|
wireGuardObj lowlevel.GenericJsonObject,
|
||||||
) (*domain.PhysicalInterface, error) {
|
) (*domain.PhysicalInterface, error) {
|
||||||
@ -113,7 +135,7 @@ func (c MikrotikController) loadInterfaceData(
|
|||||||
return &interfaceModel, nil
|
return &interfaceModel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) loadIpAddresses(
|
func (c *MikrotikController) loadIpAddresses(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
deviceName string,
|
deviceName string,
|
||||||
) (ipv4 []lowlevel.GenericJsonObject, ipv6 []lowlevel.GenericJsonObject, err error) {
|
) (ipv4 []lowlevel.GenericJsonObject, ipv6 []lowlevel.GenericJsonObject, err error) {
|
||||||
@ -150,7 +172,7 @@ func (c MikrotikController) loadIpAddresses(
|
|||||||
return addrV4Reply.Data, addrV6Reply.Data, nil
|
return addrV4Reply.Data, addrV6Reply.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) convertIpAddresses(
|
func (c *MikrotikController) convertIpAddresses(
|
||||||
ipv4, ipv6 []lowlevel.GenericJsonObject,
|
ipv4, ipv6 []lowlevel.GenericJsonObject,
|
||||||
) []domain.Cidr {
|
) []domain.Cidr {
|
||||||
addresses := make([]domain.Cidr, 0, len(ipv4)+len(ipv6))
|
addresses := make([]domain.Cidr, 0, len(ipv4)+len(ipv6))
|
||||||
@ -170,7 +192,7 @@ func (c MikrotikController) convertIpAddresses(
|
|||||||
return addresses
|
return addresses
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) convertWireGuardInterface(
|
func (c *MikrotikController) convertWireGuardInterface(
|
||||||
wg, iface lowlevel.GenericJsonObject,
|
wg, iface lowlevel.GenericJsonObject,
|
||||||
addresses []domain.Cidr,
|
addresses []domain.Cidr,
|
||||||
) (
|
) (
|
||||||
@ -203,7 +225,7 @@ func (c MikrotikController) convertWireGuardInterface(
|
|||||||
return pi, nil
|
return pi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) GetPeers(ctx context.Context, deviceId domain.InterfaceIdentifier) (
|
func (c *MikrotikController) GetPeers(ctx context.Context, deviceId domain.InterfaceIdentifier) (
|
||||||
[]domain.PhysicalPeer,
|
[]domain.PhysicalPeer,
|
||||||
error,
|
error,
|
||||||
) {
|
) {
|
||||||
@ -237,7 +259,7 @@ func (c MikrotikController) GetPeers(ctx context.Context, deviceId domain.Interf
|
|||||||
return peers, nil
|
return peers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) convertWireGuardPeer(peer lowlevel.GenericJsonObject) (
|
func (c *MikrotikController) convertWireGuardPeer(peer lowlevel.GenericJsonObject) (
|
||||||
domain.PhysicalPeer,
|
domain.PhysicalPeer,
|
||||||
error,
|
error,
|
||||||
) {
|
) {
|
||||||
@ -300,11 +322,16 @@ func (c MikrotikController) convertWireGuardPeer(peer lowlevel.GenericJsonObject
|
|||||||
return peerModel, nil
|
return peerModel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) SaveInterface(
|
func (c *MikrotikController) SaveInterface(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
id domain.InterfaceIdentifier,
|
id domain.InterfaceIdentifier,
|
||||||
updateFunc func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error),
|
updateFunc func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error),
|
||||||
) error {
|
) error {
|
||||||
|
// Lock the interface to prevent concurrent modifications
|
||||||
|
mutex := c.getInterfaceMutex(id)
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
physicalInterface, err := c.getOrCreateInterface(ctx, id)
|
physicalInterface, err := c.getOrCreateInterface(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -328,7 +355,7 @@ func (c MikrotikController) SaveInterface(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) getOrCreateInterface(
|
func (c *MikrotikController) getOrCreateInterface(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
id domain.InterfaceIdentifier,
|
id domain.InterfaceIdentifier,
|
||||||
) (*domain.PhysicalInterface, error) {
|
) (*domain.PhysicalInterface, error) {
|
||||||
@ -355,7 +382,7 @@ func (c MikrotikController) getOrCreateInterface(
|
|||||||
return nil, fmt.Errorf("failed to create interface %s: %v", id, createReply.Error)
|
return nil, fmt.Errorf("failed to create interface %s: %v", id, createReply.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) updateInterface(ctx context.Context, pi *domain.PhysicalInterface) error {
|
func (c *MikrotikController) updateInterface(ctx context.Context, pi *domain.PhysicalInterface) error {
|
||||||
extras := pi.GetExtras().(domain.MikrotikInterfaceExtras)
|
extras := pi.GetExtras().(domain.MikrotikInterfaceExtras)
|
||||||
interfaceId := extras.Id
|
interfaceId := extras.Id
|
||||||
wgReply := c.client.Update(ctx, "/interface/wireguard/"+interfaceId, lowlevel.GenericJsonObject{
|
wgReply := c.client.Update(ctx, "/interface/wireguard/"+interfaceId, lowlevel.GenericJsonObject{
|
||||||
@ -403,7 +430,7 @@ func (c MikrotikController) updateInterface(ctx context.Context, pi *domain.Phys
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) updateIpAddresses(
|
func (c *MikrotikController) updateIpAddresses(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
deviceName string,
|
deviceName string,
|
||||||
currentV4, currentV6 []lowlevel.GenericJsonObject,
|
currentV4, currentV6 []lowlevel.GenericJsonObject,
|
||||||
@ -459,7 +486,12 @@ func (c MikrotikController) updateIpAddresses(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error {
|
func (c *MikrotikController) DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error {
|
||||||
|
// Lock the interface to prevent concurrent modifications
|
||||||
|
mutex := c.getInterfaceMutex(id)
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
// delete the interface's addresses
|
// delete the interface's addresses
|
||||||
currentV4, currentV6, err := c.loadIpAddresses(ctx, string(id))
|
currentV4, currentV6, err := c.loadIpAddresses(ctx, string(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -494,8 +526,8 @@ func (c MikrotikController) DeleteInterface(ctx context.Context, id domain.Inter
|
|||||||
return nil // interface does not exist, nothing to delete
|
return nil // interface does not exist, nothing to delete
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceId := wgReply.Data[0].GetString(".id")
|
interfaceId := wgReply.Data[0].GetString(".id")
|
||||||
deleteReply := c.client.Delete(ctx, "/interface/wireguard/"+deviceId)
|
deleteReply := c.client.Delete(ctx, "/interface/wireguard/"+interfaceId)
|
||||||
if deleteReply.Status != lowlevel.MikrotikApiStatusOk {
|
if deleteReply.Status != lowlevel.MikrotikApiStatusOk {
|
||||||
return fmt.Errorf("failed to delete WireGuard interface %s: %v", id, deleteReply.Error)
|
return fmt.Errorf("failed to delete WireGuard interface %s: %v", id, deleteReply.Error)
|
||||||
}
|
}
|
||||||
@ -503,12 +535,17 @@ func (c MikrotikController) DeleteInterface(ctx context.Context, id domain.Inter
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) SavePeer(
|
func (c *MikrotikController) SavePeer(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
deviceId domain.InterfaceIdentifier,
|
deviceId domain.InterfaceIdentifier,
|
||||||
id domain.PeerIdentifier,
|
id domain.PeerIdentifier,
|
||||||
updateFunc func(pp *domain.PhysicalPeer) (*domain.PhysicalPeer, error),
|
updateFunc func(pp *domain.PhysicalPeer) (*domain.PhysicalPeer, error),
|
||||||
) error {
|
) error {
|
||||||
|
// Lock the peer to prevent concurrent modifications
|
||||||
|
mutex := c.getPeerMutex(id)
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
physicalPeer, err := c.getOrCreatePeer(ctx, deviceId, id)
|
physicalPeer, err := c.getOrCreatePeer(ctx, deviceId, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -530,7 +567,7 @@ func (c MikrotikController) SavePeer(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) getOrCreatePeer(
|
func (c *MikrotikController) getOrCreatePeer(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
deviceId domain.InterfaceIdentifier,
|
deviceId domain.InterfaceIdentifier,
|
||||||
id domain.PeerIdentifier,
|
id domain.PeerIdentifier,
|
||||||
@ -546,6 +583,7 @@ func (c MikrotikController) getOrCreatePeer(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
if wgReply.Status == lowlevel.MikrotikApiStatusOk && len(wgReply.Data) > 0 {
|
if wgReply.Status == lowlevel.MikrotikApiStatusOk && len(wgReply.Data) > 0 {
|
||||||
|
slog.Debug("found existing Mikrotik peer", "peer", id, "interface", deviceId)
|
||||||
existingPeer, err := c.convertWireGuardPeer(wgReply.Data[0])
|
existingPeer, err := c.convertWireGuardPeer(wgReply.Data[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -554,24 +592,26 @@ func (c MikrotikController) getOrCreatePeer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create a new peer if it does not exist
|
// create a new peer if it does not exist
|
||||||
|
slog.Debug("creating new Mikrotik peer", "peer", id, "interface", deviceId)
|
||||||
createReply := c.client.Create(ctx, "/interface/wireguard/peers", lowlevel.GenericJsonObject{
|
createReply := c.client.Create(ctx, "/interface/wireguard/peers", lowlevel.GenericJsonObject{
|
||||||
"name": fmt.Sprintf("tmp-wg-%s", id[0:8]),
|
"name": fmt.Sprintf("tmp-wg-%s", id[0:8]),
|
||||||
"interface": string(deviceId),
|
"interface": string(deviceId),
|
||||||
"public-key": string(id), // public key will be set later
|
"public-key": string(id),
|
||||||
"allowed-address": "169.254.254.254/32", // allowed addresses will be set later
|
"allowed-address": "0.0.0.0/0", // Use 0.0.0.0/0 as default, will be updated by updatePeer
|
||||||
})
|
})
|
||||||
if createReply.Status == lowlevel.MikrotikApiStatusOk {
|
if createReply.Status == lowlevel.MikrotikApiStatusOk {
|
||||||
newPeer, err := c.convertWireGuardPeer(createReply.Data)
|
newPeer, err := c.convertWireGuardPeer(createReply.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
slog.Debug("successfully created Mikrotik peer", "peer", id, "interface", deviceId)
|
||||||
return &newPeer, nil
|
return &newPeer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("failed to create peer %s for interface %s: %v", id, deviceId, createReply.Error)
|
return nil, fmt.Errorf("failed to create peer %s for interface %s: %v", id, deviceId, createReply.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) updatePeer(
|
func (c *MikrotikController) updatePeer(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
deviceId domain.InterfaceIdentifier,
|
deviceId domain.InterfaceIdentifier,
|
||||||
pp *domain.PhysicalPeer,
|
pp *domain.PhysicalPeer,
|
||||||
@ -586,6 +626,14 @@ func (c MikrotikController) updatePeer(
|
|||||||
endpointPort = s[1]
|
endpointPort = s[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allowedAddressStr := domain.CidrsToString(pp.AllowedIPs)
|
||||||
|
slog.Debug("updating Mikrotik peer",
|
||||||
|
"peer", pp.Identifier,
|
||||||
|
"interface", deviceId,
|
||||||
|
"allowed-address", allowedAddressStr,
|
||||||
|
"allowed-ips-count", len(pp.AllowedIPs),
|
||||||
|
"disabled", extras.Disabled)
|
||||||
|
|
||||||
wgReply := c.client.Update(ctx, "/interface/wireguard/peers/"+peerId, lowlevel.GenericJsonObject{
|
wgReply := c.client.Update(ctx, "/interface/wireguard/peers/"+peerId, lowlevel.GenericJsonObject{
|
||||||
"name": extras.Name,
|
"name": extras.Name,
|
||||||
"comment": extras.Comment,
|
"comment": extras.Comment,
|
||||||
@ -601,19 +649,31 @@ func (c MikrotikController) updatePeer(
|
|||||||
"client-dns": extras.ClientDns,
|
"client-dns": extras.ClientDns,
|
||||||
"endpoint-address": endpoint,
|
"endpoint-address": endpoint,
|
||||||
"endpoint-port": endpointPort,
|
"endpoint-port": endpointPort,
|
||||||
|
"allowed-address": allowedAddressStr, // Add the missing allowed-address field
|
||||||
})
|
})
|
||||||
if wgReply.Status != lowlevel.MikrotikApiStatusOk {
|
if wgReply.Status != lowlevel.MikrotikApiStatusOk {
|
||||||
return fmt.Errorf("failed to update peer %s on interface %s: %v", pp.Identifier, deviceId, wgReply.Error)
|
return fmt.Errorf("failed to update peer %s on interface %s: %v", pp.Identifier, deviceId, wgReply.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if extras.Disabled {
|
||||||
|
slog.Debug("successfully disabled Mikrotik peer", "peer", pp.Identifier, "interface", deviceId)
|
||||||
|
} else {
|
||||||
|
slog.Debug("successfully updated Mikrotik peer", "peer", pp.Identifier, "interface", deviceId)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) DeletePeer(
|
func (c *MikrotikController) DeletePeer(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
deviceId domain.InterfaceIdentifier,
|
deviceId domain.InterfaceIdentifier,
|
||||||
id domain.PeerIdentifier,
|
id domain.PeerIdentifier,
|
||||||
) error {
|
) error {
|
||||||
|
// Lock the peer to prevent concurrent modifications
|
||||||
|
mutex := c.getPeerMutex(id)
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
wgReply := c.client.Query(ctx, "/interface/wireguard/peers", &lowlevel.MikrotikRequestOptions{
|
wgReply := c.client.Query(ctx, "/interface/wireguard/peers", &lowlevel.MikrotikRequestOptions{
|
||||||
PropList: []string{".id"},
|
PropList: []string{".id"},
|
||||||
Filters: map[string]string{
|
Filters: map[string]string{
|
||||||
@ -641,17 +701,17 @@ func (c MikrotikController) DeletePeer(
|
|||||||
|
|
||||||
// region wg-quick-related
|
// region wg-quick-related
|
||||||
|
|
||||||
func (c MikrotikController) ExecuteInterfaceHook(id domain.InterfaceIdentifier, hookCmd string) error {
|
func (c *MikrotikController) ExecuteInterfaceHook(id domain.InterfaceIdentifier, hookCmd string) error {
|
||||||
// TODO implement me
|
// TODO implement me
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) SetDNS(id domain.InterfaceIdentifier, dnsStr, dnsSearchStr string) error {
|
func (c *MikrotikController) SetDNS(id domain.InterfaceIdentifier, dnsStr, dnsSearchStr string) error {
|
||||||
// TODO implement me
|
// TODO implement me
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) UnsetDNS(id domain.InterfaceIdentifier) error {
|
func (c *MikrotikController) UnsetDNS(id domain.InterfaceIdentifier) error {
|
||||||
// TODO implement me
|
// TODO implement me
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
@ -660,12 +720,12 @@ func (c MikrotikController) UnsetDNS(id domain.InterfaceIdentifier) error {
|
|||||||
|
|
||||||
// region routing-related
|
// region routing-related
|
||||||
|
|
||||||
func (c MikrotikController) SyncRouteRules(_ context.Context, rules []domain.RouteRule) error {
|
func (c *MikrotikController) SyncRouteRules(_ context.Context, rules []domain.RouteRule) error {
|
||||||
// TODO implement me
|
// TODO implement me
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) DeleteRouteRules(_ context.Context, rules []domain.RouteRule) error {
|
func (c *MikrotikController) DeleteRouteRules(_ context.Context, rules []domain.RouteRule) error {
|
||||||
// TODO implement me
|
// TODO implement me
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
@ -674,7 +734,7 @@ func (c MikrotikController) DeleteRouteRules(_ context.Context, rules []domain.R
|
|||||||
|
|
||||||
// region statistics-related
|
// region statistics-related
|
||||||
|
|
||||||
func (c MikrotikController) PingAddresses(
|
func (c *MikrotikController) PingAddresses(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
addr string,
|
addr string,
|
||||||
) (*domain.PingerResult, error) {
|
) (*domain.PingerResult, error) {
|
||||||
|
@ -188,29 +188,28 @@ func (m Manager) CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Pee
|
|||||||
|
|
||||||
sessionUser := domain.GetUserInfo(ctx)
|
sessionUser := domain.GetUserInfo(ctx)
|
||||||
|
|
||||||
// Enforce peer limit for non-admin users if LimitAdditionalUserPeers is set
|
// Enforce peer limit for non-admin users if LimitAdditionalUserPeers is set
|
||||||
if m.cfg.Core.SelfProvisioningAllowed && !sessionUser.IsAdmin && m.cfg.Advanced.LimitAdditionalUserPeers > 0 {
|
if m.cfg.Core.SelfProvisioningAllowed && !sessionUser.IsAdmin && m.cfg.Advanced.LimitAdditionalUserPeers > 0 {
|
||||||
peers, err := m.db.GetUserPeers(ctx, peer.UserIdentifier)
|
peers, err := m.db.GetUserPeers(ctx, peer.UserIdentifier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to fetch peers for user %s: %w", peer.UserIdentifier, err)
|
return nil, fmt.Errorf("failed to fetch peers for user %s: %w", peer.UserIdentifier, err)
|
||||||
}
|
}
|
||||||
// Count enabled peers (disabled IS NULL)
|
// Count enabled peers (disabled IS NULL)
|
||||||
peerCount := 0
|
peerCount := 0
|
||||||
for _, p := range peers {
|
for _, p := range peers {
|
||||||
if !p.IsDisabled() {
|
if !p.IsDisabled() {
|
||||||
peerCount++
|
peerCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
totalAllowedPeers := 1 + m.cfg.Advanced.LimitAdditionalUserPeers // 1 default peer + x additional peers
|
totalAllowedPeers := 1 + m.cfg.Advanced.LimitAdditionalUserPeers // 1 default peer + x additional peers
|
||||||
if peerCount >= totalAllowedPeers {
|
if peerCount >= totalAllowedPeers {
|
||||||
slog.WarnContext(ctx, "peer creation blocked due to limit",
|
slog.WarnContext(ctx, "peer creation blocked due to limit",
|
||||||
"user", peer.UserIdentifier,
|
"user", peer.UserIdentifier,
|
||||||
"current_count", peerCount,
|
"current_count", peerCount,
|
||||||
"allowed_count", totalAllowedPeers)
|
"allowed_count", totalAllowedPeers)
|
||||||
return nil, fmt.Errorf("peer limit reached (%d peers allowed): %w", totalAllowedPeers, domain.ErrNoPermission)
|
return nil, fmt.Errorf("peer limit reached (%d peers allowed): %w", totalAllowedPeers, domain.ErrNoPermission)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
existingPeer, err := m.db.GetPeer(ctx, peer.Identifier)
|
existingPeer, err := m.db.GetPeer(ctx, peer.Identifier)
|
||||||
if err != nil && !errors.Is(err, domain.ErrNotFound) {
|
if err != nil && !errors.Is(err, domain.ErrNotFound) {
|
||||||
@ -449,33 +448,22 @@ func (m Manager) savePeers(ctx context.Context, peers ...*domain.Peer) error {
|
|||||||
return fmt.Errorf("unable to find interface %s: %w", peer.InterfaceIdentifier, err)
|
return fmt.Errorf("unable to find interface %s: %w", peer.InterfaceIdentifier, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if peer.IsDisabled() || peer.IsExpired() {
|
// Always save the peer to the backend, regardless of disabled/expired state
|
||||||
err = m.db.SavePeer(ctx, peer.Identifier, func(p *domain.Peer) (*domain.Peer, error) {
|
// The backend will handle the disabled state appropriately
|
||||||
peer.CopyCalculatedAttributes(p)
|
err = m.db.SavePeer(ctx, peer.Identifier, func(p *domain.Peer) (*domain.Peer, error) {
|
||||||
|
peer.CopyCalculatedAttributes(p)
|
||||||
|
|
||||||
if err := m.wg.GetController(*iface).DeletePeer(ctx, peer.InterfaceIdentifier,
|
err := m.wg.GetController(*iface).SavePeer(ctx, peer.InterfaceIdentifier, peer.Identifier,
|
||||||
peer.Identifier); err != nil {
|
func(pp *domain.PhysicalPeer) (*domain.PhysicalPeer, error) {
|
||||||
return nil, fmt.Errorf("failed to delete wireguard peer %s: %w", peer.Identifier, err)
|
domain.MergeToPhysicalPeer(pp, peer)
|
||||||
}
|
return pp, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to save wireguard peer %s: %w", peer.Identifier, err)
|
||||||
|
}
|
||||||
|
|
||||||
return peer, nil
|
return peer, nil
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
err = m.db.SavePeer(ctx, peer.Identifier, func(p *domain.Peer) (*domain.Peer, error) {
|
|
||||||
peer.CopyCalculatedAttributes(p)
|
|
||||||
|
|
||||||
err := m.wg.GetController(*iface).SavePeer(ctx, peer.InterfaceIdentifier, peer.Identifier,
|
|
||||||
func(pp *domain.PhysicalPeer) (*domain.PhysicalPeer, error) {
|
|
||||||
domain.MergeToPhysicalPeer(pp, peer)
|
|
||||||
return pp, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to save wireguard peer %s: %w", peer.Identifier, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return peer, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("save failure for peer %s: %w", peer.Identifier, err)
|
return fmt.Errorf("save failure for peer %s: %w", peer.Identifier, err)
|
||||||
}
|
}
|
||||||
|
@ -26,3 +26,7 @@ type MikrotikPeerExtras struct {
|
|||||||
ClientDns string
|
ClientDns string
|
||||||
ClientKeepalive int
|
ClientKeepalive int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LocalPeerExtras struct {
|
||||||
|
Disabled bool
|
||||||
|
}
|
||||||
|
@ -236,7 +236,8 @@ func (p *PhysicalPeer) GetExtras() any {
|
|||||||
func (p *PhysicalPeer) SetExtras(extras any) {
|
func (p *PhysicalPeer) SetExtras(extras any) {
|
||||||
switch extras.(type) {
|
switch extras.(type) {
|
||||||
case MikrotikPeerExtras: // OK
|
case MikrotikPeerExtras: // OK
|
||||||
default: // we only support MikrotikPeerExtras for now
|
case LocalPeerExtras: // OK
|
||||||
|
default: // we only support MikrotikPeerExtras and LocalPeerExtras for now
|
||||||
panic(fmt.Sprintf("unsupported peer backend extras type %T", extras))
|
panic(fmt.Sprintf("unsupported peer backend extras type %T", extras))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,6 +289,15 @@ func ConvertPhysicalPeer(pp *PhysicalPeer) *Peer {
|
|||||||
peer.Disabled = nil
|
peer.Disabled = nil
|
||||||
peer.DisabledReason = ""
|
peer.DisabledReason = ""
|
||||||
}
|
}
|
||||||
|
case ControllerTypeLocal:
|
||||||
|
extras := pp.GetExtras().(LocalPeerExtras)
|
||||||
|
if extras.Disabled {
|
||||||
|
peer.Disabled = &now
|
||||||
|
peer.DisabledReason = "Disabled by Local controller"
|
||||||
|
} else {
|
||||||
|
peer.Disabled = nil
|
||||||
|
peer.DisabledReason = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return peer
|
return peer
|
||||||
@ -326,6 +336,11 @@ func MergeToPhysicalPeer(pp *PhysicalPeer, p *Peer) {
|
|||||||
ClientKeepalive: p.PersistentKeepalive.GetValue(),
|
ClientKeepalive: p.PersistentKeepalive.GetValue(),
|
||||||
}
|
}
|
||||||
pp.SetExtras(extras)
|
pp.SetExtras(extras)
|
||||||
|
case ControllerTypeLocal:
|
||||||
|
extras := LocalPeerExtras{
|
||||||
|
Disabled: p.IsDisabled(),
|
||||||
|
}
|
||||||
|
pp.SetExtras(extras)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user