mirror of https://github.com/h44z/wg-portal.git
				
				
				
			fix self provisioning feature (#272)
This commit is contained in:
		
							parent
							
								
									1b8cdc3417
								
							
						
					
					
						commit
						d01d865b4d
					
				|  | @ -0,0 +1,294 @@ | ||||||
|  | <script setup> | ||||||
|  | import Modal from "./Modal.vue"; | ||||||
|  | import { peerStore } from "@/stores/peers"; | ||||||
|  | import { computed, ref, watch } from "vue"; | ||||||
|  | import { useI18n } from 'vue-i18n'; | ||||||
|  | import { notify } from "@kyvg/vue3-notification"; | ||||||
|  | import { freshPeer, freshInterface } from '@/helpers/models'; | ||||||
|  | import { profileStore } from "@/stores/profile"; | ||||||
|  | 
 | ||||||
|  | const { t } = useI18n() | ||||||
|  | 
 | ||||||
|  | const peers = peerStore() | ||||||
|  | const profile = profileStore() | ||||||
|  | 
 | ||||||
|  | const props = defineProps({ | ||||||
|  |   peerId: String, | ||||||
|  |   visible: Boolean, | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['close']) | ||||||
|  | 
 | ||||||
|  | const selectedPeer = computed(() => { | ||||||
|  |   let p = peers.Find(props.peerId) | ||||||
|  | 
 | ||||||
|  |   if (!p) { | ||||||
|  |     if (!!props.peerId || props.peerId.length) { | ||||||
|  |       p = profile.peers.find((p) => p.Identifier === props.peerId) | ||||||
|  |     } else { | ||||||
|  |       p = freshPeer() // dummy peer to avoid 'undefined' exceptions | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return p | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | const selectedInterface = computed(() => { | ||||||
|  |   let iId = profile.selectedInterfaceId; | ||||||
|  | 
 | ||||||
|  |   let i = freshInterface() // dummy interface to avoid 'undefined' exceptions | ||||||
|  |   if (iId) { | ||||||
|  |     i = profile.interfaces.find((i) => i.Identifier === iId) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return i | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | const title = computed(() => { | ||||||
|  |   if (!props.visible) { | ||||||
|  |     return "" | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (selectedPeer.value) { | ||||||
|  |     return t("modals.peer-edit.headline-edit-peer") + " " + selectedPeer.value.Identifier | ||||||
|  |   } | ||||||
|  |   return t("modals.peer-edit.headline-new-peer") | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | const formData = ref(freshPeer()) | ||||||
|  | 
 | ||||||
|  | // functions | ||||||
|  | 
 | ||||||
|  | watch(() => props.visible, async (newValue, oldValue) => { | ||||||
|  |   if (oldValue === false && newValue === true) { // if modal is shown | ||||||
|  |     if (!selectedPeer.value) { | ||||||
|  |       await peers.PreparePeer(selectedInterface.value.Identifier) | ||||||
|  | 
 | ||||||
|  |       formData.value.Identifier = peers.Prepared.Identifier | ||||||
|  |       formData.value.DisplayName = peers.Prepared.DisplayName | ||||||
|  |       formData.value.UserIdentifier = peers.Prepared.UserIdentifier | ||||||
|  |       formData.value.InterfaceIdentifier = peers.Prepared.InterfaceIdentifier | ||||||
|  |       formData.value.Disabled = peers.Prepared.Disabled | ||||||
|  |       formData.value.ExpiresAt = peers.Prepared.ExpiresAt | ||||||
|  |       formData.value.Notes = peers.Prepared.Notes | ||||||
|  | 
 | ||||||
|  |       formData.value.Endpoint = peers.Prepared.Endpoint | ||||||
|  |       formData.value.EndpointPublicKey = peers.Prepared.EndpointPublicKey | ||||||
|  |       formData.value.AllowedIPs = peers.Prepared.AllowedIPs | ||||||
|  |       formData.value.ExtraAllowedIPs = peers.Prepared.ExtraAllowedIPs | ||||||
|  |       formData.value.PresharedKey = peers.Prepared.PresharedKey | ||||||
|  |       formData.value.PersistentKeepalive = peers.Prepared.PersistentKeepalive | ||||||
|  | 
 | ||||||
|  |       formData.value.PrivateKey = peers.Prepared.PrivateKey | ||||||
|  |       formData.value.PublicKey = peers.Prepared.PublicKey | ||||||
|  | 
 | ||||||
|  |       formData.value.Mode = peers.Prepared.Mode | ||||||
|  | 
 | ||||||
|  |       formData.value.Addresses = peers.Prepared.Addresses | ||||||
|  |       formData.value.CheckAliveAddress = peers.Prepared.CheckAliveAddress | ||||||
|  |       formData.value.Dns = peers.Prepared.Dns | ||||||
|  |       formData.value.DnsSearch = peers.Prepared.DnsSearch | ||||||
|  |       formData.value.Mtu = peers.Prepared.Mtu | ||||||
|  |       formData.value.FirewallMark = peers.Prepared.FirewallMark | ||||||
|  |       formData.value.RoutingTable = peers.Prepared.RoutingTable | ||||||
|  | 
 | ||||||
|  |       formData.value.PreUp = peers.Prepared.PreUp | ||||||
|  |       formData.value.PostUp = peers.Prepared.PostUp | ||||||
|  |       formData.value.PreDown = peers.Prepared.PreDown | ||||||
|  |       formData.value.PostDown = peers.Prepared.PostDown | ||||||
|  | 
 | ||||||
|  |     } else { // fill existing data | ||||||
|  |       formData.value.Identifier = selectedPeer.value.Identifier | ||||||
|  |       formData.value.DisplayName = selectedPeer.value.DisplayName | ||||||
|  |       formData.value.UserIdentifier = selectedPeer.value.UserIdentifier | ||||||
|  |       formData.value.InterfaceIdentifier = selectedPeer.value.InterfaceIdentifier | ||||||
|  |       formData.value.Disabled = selectedPeer.value.Disabled | ||||||
|  |       formData.value.ExpiresAt = selectedPeer.value.ExpiresAt | ||||||
|  |       formData.value.Notes = selectedPeer.value.Notes | ||||||
|  | 
 | ||||||
|  |       formData.value.Endpoint = selectedPeer.value.Endpoint | ||||||
|  |       formData.value.EndpointPublicKey = selectedPeer.value.EndpointPublicKey | ||||||
|  |       formData.value.AllowedIPs = selectedPeer.value.AllowedIPs | ||||||
|  |       formData.value.ExtraAllowedIPs = selectedPeer.value.ExtraAllowedIPs | ||||||
|  |       formData.value.PresharedKey = selectedPeer.value.PresharedKey | ||||||
|  |       formData.value.PersistentKeepalive = selectedPeer.value.PersistentKeepalive | ||||||
|  | 
 | ||||||
|  |       formData.value.PrivateKey = selectedPeer.value.PrivateKey | ||||||
|  |       formData.value.PublicKey = selectedPeer.value.PublicKey | ||||||
|  | 
 | ||||||
|  |       formData.value.Mode = selectedPeer.value.Mode | ||||||
|  | 
 | ||||||
|  |       formData.value.Addresses = selectedPeer.value.Addresses | ||||||
|  |       formData.value.CheckAliveAddress = selectedPeer.value.CheckAliveAddress | ||||||
|  |       formData.value.Dns = selectedPeer.value.Dns | ||||||
|  |       formData.value.DnsSearch = selectedPeer.value.DnsSearch | ||||||
|  |       formData.value.Mtu = selectedPeer.value.Mtu | ||||||
|  |       formData.value.FirewallMark = selectedPeer.value.FirewallMark | ||||||
|  |       formData.value.RoutingTable = selectedPeer.value.RoutingTable | ||||||
|  | 
 | ||||||
|  |       formData.value.PreUp = selectedPeer.value.PreUp | ||||||
|  |       formData.value.PostUp = selectedPeer.value.PostUp | ||||||
|  |       formData.value.PreDown = selectedPeer.value.PreDown | ||||||
|  |       formData.value.PostDown = selectedPeer.value.PostDown | ||||||
|  | 
 | ||||||
|  |       if (!formData.value.Endpoint.Overridable || | ||||||
|  |         !formData.value.EndpointPublicKey.Overridable || | ||||||
|  |         !formData.value.AllowedIPs.Overridable || | ||||||
|  |         !formData.value.PersistentKeepalive.Overridable || | ||||||
|  |         !formData.value.Dns.Overridable || | ||||||
|  |         !formData.value.DnsSearch.Overridable || | ||||||
|  |         !formData.value.Mtu.Overridable || | ||||||
|  |         !formData.value.FirewallMark.Overridable || | ||||||
|  |         !formData.value.RoutingTable.Overridable || | ||||||
|  |         !formData.value.PreUp.Overridable || | ||||||
|  |         !formData.value.PostUp.Overridable || | ||||||
|  |         !formData.value.PreDown.Overridable || | ||||||
|  |         !formData.value.PostDown.Overridable) { | ||||||
|  |         formData.value.IgnoreGlobalSettings = true | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | watch(() => formData.value.Disabled, async (newValue, oldValue) => { | ||||||
|  |   if (oldValue && !newValue && formData.value.ExpiresAt) { | ||||||
|  |     formData.value.ExpiresAt = "" // reset expiry date | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | function close() { | ||||||
|  |   formData.value = freshPeer() | ||||||
|  |   emit('close') | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function save() { | ||||||
|  |   try { | ||||||
|  |     if (props.peerId !== '#NEW#') { | ||||||
|  |       await peers.UpdatePeer(selectedPeer.value.Identifier, formData.value) | ||||||
|  |     } else { | ||||||
|  |       await peers.CreatePeer(selectedInterface.value.Identifier, formData.value) | ||||||
|  |     } | ||||||
|  |     close() | ||||||
|  |   } catch (e) { | ||||||
|  |     // console.log(e) | ||||||
|  |     notify({ | ||||||
|  |       title: "Failed to save peer!", | ||||||
|  |       text: e.toString(), | ||||||
|  |       type: 'error', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function del() { | ||||||
|  |   try { | ||||||
|  |     await peers.DeletePeer(selectedPeer.value.Identifier) | ||||||
|  |     close() | ||||||
|  |   } catch (e) { | ||||||
|  |     // console.log(e) | ||||||
|  |     notify({ | ||||||
|  |       title: "Failed to delete peer!", | ||||||
|  |       text: e.toString(), | ||||||
|  |       type: 'error', | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <Modal :title="title" :visible="visible" @close="close"> | ||||||
|  |     <template #default> | ||||||
|  |       <fieldset> | ||||||
|  |         <legend class="mt-4">{{ $t('modals.peer-edit.header-general') }}</legend> | ||||||
|  |         <div class="form-group"> | ||||||
|  |           <label class="form-label mt-4">{{ $t('modals.peer-edit.display-name.label') }}</label> | ||||||
|  |           <input type="text" class="form-control" :placeholder="$t('modals.peer-edit.display-name.placeholder')" | ||||||
|  |             v-model="formData.DisplayName"> | ||||||
|  |         </div> | ||||||
|  |       </fieldset> | ||||||
|  |       <fieldset> | ||||||
|  |         <legend class="mt-4">{{ $t('modals.peer-edit.header-crypto') }}</legend> | ||||||
|  |         <div class="form-group"> | ||||||
|  |           <label class="form-label mt-4">{{ $t('modals.peer-edit.private-key.label') }}</label> | ||||||
|  |           <input type="email" class="form-control" :placeholder="$t('modals.peer-edit.private-key.placeholder')" required | ||||||
|  |             v-model="formData.PrivateKey"> | ||||||
|  |         </div> | ||||||
|  |         <div class="form-group"> | ||||||
|  |           <label class="form-label mt-4">{{ $t('modals.peer-edit.public-key.label') }}</label> | ||||||
|  |           <input type="email" class="form-control" :placeholder="$t('modals.peer-edit.public-key.placeholder')" required | ||||||
|  |             v-model="formData.PublicKey"> | ||||||
|  |         </div> | ||||||
|  |         <div class="form-group"> | ||||||
|  |           <label class="form-label mt-4">{{ $t('modals.peer-edit.preshared-key.label') }}</label> | ||||||
|  |           <input type="email" class="form-control" :placeholder="$t('modals.peer-edit.preshared-key.placeholder')" | ||||||
|  |             v-model="formData.PresharedKey"> | ||||||
|  |         </div> | ||||||
|  |       </fieldset> | ||||||
|  |       <fieldset> | ||||||
|  |         <legend class="mt-4">{{ $t('modals.peer-edit.header-network') }}</legend> | ||||||
|  |         <div class="row"> | ||||||
|  |           <div class="form-group col-md-6"> | ||||||
|  |             <label class="form-label mt-4">{{ $t('modals.peer-edit.keep-alive.label') }}</label> | ||||||
|  |             <input type="number" class="form-control" :placeholder="$t('modals.peer-edit.keep-alive.label')" | ||||||
|  |               v-model="formData.PersistentKeepalive.Value"> | ||||||
|  |           </div> | ||||||
|  |           <div class="form-group col-md-6"> | ||||||
|  |             <label class="form-label mt-4">{{ $t('modals.peer-edit.mtu.label') }}</label> | ||||||
|  |             <input type="number" class="form-control" :placeholder="$t('modals.peer-edit.mtu.label')" | ||||||
|  |               v-model="formData.Mtu.Value"> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </fieldset> | ||||||
|  |       <fieldset> | ||||||
|  |         <legend class="mt-4">{{ $t('modals.peer-edit.header-hooks') }}</legend> | ||||||
|  |         <div class="form-group"> | ||||||
|  |           <label class="form-label mt-4">{{ $t('modals.peer-edit.pre-up.label') }}</label> | ||||||
|  |           <textarea v-model="formData.PreUp.Value" class="form-control" rows="2" | ||||||
|  |             :placeholder="$t('modals.peer-edit.pre-up.placeholder')"></textarea> | ||||||
|  |         </div> | ||||||
|  |         <div class="form-group"> | ||||||
|  |           <label class="form-label mt-4">{{ $t('modals.peer-edit.post-up.label') }}</label> | ||||||
|  |           <textarea v-model="formData.PostUp.Value" class="form-control" rows="2" | ||||||
|  |             :placeholder="$t('modals.peer-edit.post-up.placeholder')"></textarea> | ||||||
|  |         </div> | ||||||
|  |         <div class="form-group"> | ||||||
|  |           <label class="form-label mt-4">{{ $t('modals.peer-edit.pre-down.label') }}</label> | ||||||
|  |           <textarea v-model="formData.PreDown.Value" class="form-control" rows="2" | ||||||
|  |             :placeholder="$t('modals.peer-edit.pre-down.placeholder')"></textarea> | ||||||
|  |         </div> | ||||||
|  |         <div class="form-group"> | ||||||
|  |           <label class="form-label mt-4">{{ $t('modals.peer-edit.post-down.label') }}</label> | ||||||
|  |           <textarea v-model="formData.PostDown.Value" class="form-control" rows="2" | ||||||
|  |             :placeholder="$t('modals.peer-edit.post-down.placeholder')"></textarea> | ||||||
|  |         </div> | ||||||
|  |       </fieldset> | ||||||
|  |       <fieldset> | ||||||
|  |         <legend class="mt-4">{{ $t('modals.peer-edit.header-state') }}</legend> | ||||||
|  |         <div class="row"> | ||||||
|  |           <div class="form-group col-md-6"> | ||||||
|  |             <div class="form-check form-switch"> | ||||||
|  |               <input class="form-check-input" type="checkbox" v-model="formData.Disabled"> | ||||||
|  |               <label class="form-check-label">{{ $t('modals.peer-edit.disabled.label') }}</label> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |           <div class="form-group col-md-6"> | ||||||
|  |             <label class="form-label">{{ $t('modals.peer-edit.expires-at.label') }}</label> | ||||||
|  |             <input type="date" pattern="\d{4}-\d{2}-\d{2}" class="form-control" min="2023-01-01" | ||||||
|  |               v-model="formData.ExpiresAt"> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </fieldset> | ||||||
|  |     </template> | ||||||
|  |     <template #footer> | ||||||
|  |       <div class="flex-fill text-start"> | ||||||
|  |         <button v-if="props.peerId !== '#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del">{{ | ||||||
|  |           $t('general.delete') }}</button> | ||||||
|  |       </div> | ||||||
|  |       <button class="btn btn-primary me-1" type="button" @click.prevent="save">{{ $t('general.save') }}</button> | ||||||
|  |       <button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button> | ||||||
|  |     </template> | ||||||
|  |   </Modal> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <style></style> | ||||||
|  | @ -12,6 +12,8 @@ export const profileStore = defineStore({ | ||||||
|   id: 'profile', |   id: 'profile', | ||||||
|   state: () => ({ |   state: () => ({ | ||||||
|     peers: [], |     peers: [], | ||||||
|  |     interfaces: [], | ||||||
|  |     selectedInterfaceId: "", | ||||||
|     stats: {}, |     stats: {}, | ||||||
|     statsEnabled: false, |     statsEnabled: false, | ||||||
|     user: {}, |     user: {}, | ||||||
|  | @ -71,6 +73,7 @@ export const profileStore = defineStore({ | ||||||
|       return (id) => state.statsEnabled && (id in state.stats) ? state.stats[id] : freshStats() |       return (id) => state.statsEnabled && (id in state.stats) ? state.stats[id] : freshStats() | ||||||
|     }, |     }, | ||||||
|     hasStatistics: (state) => state.statsEnabled, |     hasStatistics: (state) => state.statsEnabled, | ||||||
|  |     CountInterfaces: (state) => state.interfaces.length, | ||||||
|   }, |   }, | ||||||
|   actions: { |   actions: { | ||||||
|     afterPageSizeChange() { |     afterPageSizeChange() { | ||||||
|  | @ -116,6 +119,11 @@ export const profileStore = defineStore({ | ||||||
|       this.stats = statsResponse.Stats |       this.stats = statsResponse.Stats | ||||||
|       this.statsEnabled = statsResponse.Enabled |       this.statsEnabled = statsResponse.Enabled | ||||||
|     }, |     }, | ||||||
|  |     setInterfaces(interfaces) { | ||||||
|  |       this.interfaces = interfaces | ||||||
|  |       this.selectedInterfaceId = interfaces.length > 0 ? interfaces[0].Identifier : "" | ||||||
|  |       this.fetching = false | ||||||
|  |     }, | ||||||
|     async enableApi() { |     async enableApi() { | ||||||
|       this.fetching = true |       this.fetching = true | ||||||
|       let currentUser = authStore().user.Identifier |       let currentUser = authStore().user.Identifier | ||||||
|  | @ -186,5 +194,19 @@ export const profileStore = defineStore({ | ||||||
|           }) |           }) | ||||||
|         }) |         }) | ||||||
|     }, |     }, | ||||||
|  |     async LoadInterfaces() { | ||||||
|  |       this.fetching = true | ||||||
|  |       let currentUser = authStore().user.Identifier | ||||||
|  |       return apiWrapper.get(`${baseUrl}/${base64_url_encode(currentUser)}/interfaces`) | ||||||
|  |           .then(this.setInterfaces) | ||||||
|  |           .catch(error => { | ||||||
|  |             this.setInterfaces([]) | ||||||
|  |             console.log("Failed to load interfaces for ", currentUser, ": ", error) | ||||||
|  |             notify({ | ||||||
|  |               title: "Backend Connection Failure", | ||||||
|  |               text: "Failed to load interfaces!", | ||||||
|  |             }) | ||||||
|  |           }) | ||||||
|  |     }, | ||||||
|   } |   } | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import PeerViewModal from "../components/PeerViewModal.vue"; | ||||||
| 
 | 
 | ||||||
| import { onMounted, ref } from "vue"; | import { onMounted, ref } from "vue"; | ||||||
| import { profileStore } from "@/stores/profile"; | import { profileStore } from "@/stores/profile"; | ||||||
| import PeerEditModal from "@/components/PeerEditModal.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"; | ||||||
| 
 | 
 | ||||||
|  | @ -27,10 +27,18 @@ function sortBy(key) { | ||||||
|   profile.sortOrder = sortOrder.value; |   profile.sortOrder = sortOrder.value; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function friendlyInterfaceName(id, name) { | ||||||
|  |   if (name) { | ||||||
|  |     return name | ||||||
|  |   } | ||||||
|  |   return id | ||||||
|  | } | ||||||
|  | 
 | ||||||
| onMounted(async () => { | onMounted(async () => { | ||||||
|   await profile.LoadUser() |   await profile.LoadUser() | ||||||
|   await profile.LoadPeers() |   await profile.LoadPeers() | ||||||
|   await profile.LoadStats() |   await profile.LoadStats() | ||||||
|  |   await profile.LoadInterfaces() | ||||||
|   await profile.calculatePages(); // Forces to show initial page number |   await profile.calculatePages(); // Forces to show initial page number | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  | @ -38,7 +46,7 @@ onMounted(async () => { | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <PeerViewModal :peerId="viewedPeerId" :visible="viewedPeerId !== ''" @close="viewedPeerId = ''"></PeerViewModal> |   <PeerViewModal :peerId="viewedPeerId" :visible="viewedPeerId !== ''" @close="viewedPeerId = ''"></PeerViewModal> | ||||||
|   <PeerEditModal :peerId="editPeerId" :visible="editPeerId !== ''" @close="editPeerId = ''"></PeerEditModal> |   <UserPeerEditModal :peerId="editPeerId" :visible="editPeerId !== ''" @close="editPeerId = ''; profile.LoadPeers()"></UserPeerEditModal> | ||||||
| 
 | 
 | ||||||
|   <!-- Peer list --> |   <!-- Peer list --> | ||||||
|   <div class="mt-4 row"> |   <div class="mt-4 row"> | ||||||
|  | @ -56,9 +64,17 @@ onMounted(async () => { | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|     <div class="col-12 col-lg-3 text-lg-end"> |     <div class="col-12 col-lg-3 text-lg-end"> | ||||||
|       <a v-if="settings.Setting('SelfProvisioning')" class="btn btn-primary ms-2" href="#" |       <div class="form-group" v-if="settings.Setting('SelfProvisioning')"> | ||||||
|         :title="$t('interfaces.button-add-peer')" @click.prevent="editPeerId = '#NEW#'"><i |         <div class="input-group mb-3"> | ||||||
|           class="fa fa-plus me-1"></i><i class="fa fa-user"></i></a> |           <button class="input-group-text btn btn-primary" :title="$t('interfaces.button-add-peer')" @click.prevent="editPeerId = '#NEW#'"> | ||||||
|  |             <i class="fa fa-plus me-1"></i><i class="fa fa-user"></i> | ||||||
|  |           </button> | ||||||
|  |           <select v-model="profile.selectedInterfaceId" :disabled="profile.CountInterfaces===0" class="form-select"> | ||||||
|  |             <option v-if="profile.CountInterfaces===0" value="nothing">{{ $t('interfaces.no-interface.default-selection') }}</option> | ||||||
|  |             <option v-for="iface in profile.interfaces" :key="iface.Identifier" :value="iface.Identifier">{{ friendlyInterfaceName(iface.Identifier,iface.DisplayName) }}</option> | ||||||
|  |           </select> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|   <div class="mt-2 table-responsive"> |   <div class="mt-2 table-responsive"> | ||||||
|  |  | ||||||
|  | @ -24,8 +24,8 @@ func (e peerEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenti | ||||||
| 
 | 
 | ||||||
| 	apiGroup.GET("/iface/:iface/all", e.authenticator.LoggedIn(ScopeAdmin), e.handleAllGet()) | 	apiGroup.GET("/iface/:iface/all", e.authenticator.LoggedIn(ScopeAdmin), e.handleAllGet()) | ||||||
| 	apiGroup.GET("/iface/:iface/stats", e.authenticator.LoggedIn(ScopeAdmin), e.handleStatsGet()) | 	apiGroup.GET("/iface/:iface/stats", e.authenticator.LoggedIn(ScopeAdmin), e.handleStatsGet()) | ||||||
| 	apiGroup.GET("/iface/:iface/prepare", e.authenticator.LoggedIn(ScopeAdmin), e.handlePrepareGet()) | 	apiGroup.GET("/iface/:iface/prepare", e.authenticator.LoggedIn(), e.handlePrepareGet()) | ||||||
| 	apiGroup.POST("/iface/:iface/new", e.authenticator.LoggedIn(ScopeAdmin), e.handleCreatePost()) | 	apiGroup.POST("/iface/:iface/new", e.authenticator.LoggedIn(), e.handleCreatePost()) | ||||||
| 	apiGroup.POST("/iface/:iface/multiplenew", e.authenticator.LoggedIn(ScopeAdmin), e.handleCreateMultiplePost()) | 	apiGroup.POST("/iface/:iface/multiplenew", e.authenticator.LoggedIn(ScopeAdmin), e.handleCreateMultiplePost()) | ||||||
| 	apiGroup.GET("/config-qr/:id", e.handleQrCodeGet()) | 	apiGroup.GET("/config-qr/:id", e.handleQrCodeGet()) | ||||||
| 	apiGroup.POST("/config-mail", e.handleEmailPost()) | 	apiGroup.POST("/config-mail", e.handleEmailPost()) | ||||||
|  |  | ||||||
|  | @ -28,6 +28,7 @@ func (e userEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenti | ||||||
| 	apiGroup.POST("/new", e.authenticator.LoggedIn(ScopeAdmin), e.handleCreatePost()) | 	apiGroup.POST("/new", e.authenticator.LoggedIn(ScopeAdmin), e.handleCreatePost()) | ||||||
| 	apiGroup.GET("/:id/peers", e.authenticator.UserIdMatch("id"), e.handlePeersGet()) | 	apiGroup.GET("/:id/peers", e.authenticator.UserIdMatch("id"), e.handlePeersGet()) | ||||||
| 	apiGroup.GET("/:id/stats", e.authenticator.UserIdMatch("id"), e.handleStatsGet()) | 	apiGroup.GET("/:id/stats", e.authenticator.UserIdMatch("id"), e.handleStatsGet()) | ||||||
|  | 	apiGroup.GET("/:id/interfaces", e.authenticator.UserIdMatch("id"), e.handleInterfacesGet()) | ||||||
| 	apiGroup.POST("/:id/api/enable", e.authenticator.UserIdMatch("id"), e.handleApiEnablePost()) | 	apiGroup.POST("/:id/api/enable", e.authenticator.UserIdMatch("id"), e.handleApiEnablePost()) | ||||||
| 	apiGroup.POST("/:id/api/disable", e.authenticator.UserIdMatch("id"), e.handleApiDisablePost()) | 	apiGroup.POST("/:id/api/disable", e.authenticator.UserIdMatch("id"), e.handleApiDisablePost()) | ||||||
| } | } | ||||||
|  | @ -170,6 +171,7 @@ func (e userEndpoint) handleCreatePost() gin.HandlerFunc { | ||||||
| // @ID users_handlePeersGet
 | // @ID users_handlePeersGet
 | ||||||
| // @Tags Users
 | // @Tags Users
 | ||||||
| // @Summary Get peers for the given user.
 | // @Summary Get peers for the given user.
 | ||||||
|  | // @Param id path string true "The user identifier"
 | ||||||
| // @Produce json
 | // @Produce json
 | ||||||
| // @Success 200 {object} []model.Peer
 | // @Success 200 {object} []model.Peer
 | ||||||
| // @Failure 400 {object} model.Error
 | // @Failure 400 {object} model.Error
 | ||||||
|  | @ -179,14 +181,14 @@ func (e userEndpoint) handlePeersGet() gin.HandlerFunc { | ||||||
| 	return func(c *gin.Context) { | 	return func(c *gin.Context) { | ||||||
| 		ctx := domain.SetUserInfoFromGin(c) | 		ctx := domain.SetUserInfoFromGin(c) | ||||||
| 
 | 
 | ||||||
| 		interfaceId := Base64UrlDecode(c.Param("id")) | 		userId := Base64UrlDecode(c.Param("id")) | ||||||
| 		if interfaceId == "" { | 		if userId == "" { | ||||||
| 			c.JSON(http.StatusBadRequest, | 			c.JSON(http.StatusBadRequest, | ||||||
| 				model.Error{Code: http.StatusInternalServerError, Message: "missing id parameter"}) | 				model.Error{Code: http.StatusInternalServerError, Message: "missing id parameter"}) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		peers, err := e.app.GetUserPeers(ctx, domain.UserIdentifier(interfaceId)) | 		peers, err := e.app.GetUserPeers(ctx, domain.UserIdentifier(userId)) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			c.JSON(http.StatusInternalServerError, | 			c.JSON(http.StatusInternalServerError, | ||||||
| 				model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) | 				model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) | ||||||
|  | @ -202,6 +204,7 @@ func (e userEndpoint) handlePeersGet() gin.HandlerFunc { | ||||||
| // @ID users_handleStatsGet
 | // @ID users_handleStatsGet
 | ||||||
| // @Tags Users
 | // @Tags Users
 | ||||||
| // @Summary Get peer stats for the given user.
 | // @Summary Get peer stats for the given user.
 | ||||||
|  | // @Param id path string true "The user identifier"
 | ||||||
| // @Produce json
 | // @Produce json
 | ||||||
| // @Success 200 {object} model.PeerStats
 | // @Success 200 {object} model.PeerStats
 | ||||||
| // @Failure 400 {object} model.Error
 | // @Failure 400 {object} model.Error
 | ||||||
|  | @ -229,6 +232,39 @@ func (e userEndpoint) handleStatsGet() gin.HandlerFunc { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // handleInterfacesGet returns a gorm handler function.
 | ||||||
|  | //
 | ||||||
|  | // @ID users_handleInterfacesGet
 | ||||||
|  | // @Tags Users
 | ||||||
|  | // @Summary Get interfaces for the given user. Returns an empty list if self provisioning is disabled.
 | ||||||
|  | // @Param id path string true "The user identifier"
 | ||||||
|  | // @Produce json
 | ||||||
|  | // @Success 200 {object} []model.Interface
 | ||||||
|  | // @Failure 400 {object} model.Error
 | ||||||
|  | // @Failure 500 {object} model.Error
 | ||||||
|  | // @Router /user/{id}/interfaces [get]
 | ||||||
|  | func (e userEndpoint) handleInterfacesGet() gin.HandlerFunc { | ||||||
|  | 	return func(c *gin.Context) { | ||||||
|  | 		ctx := domain.SetUserInfoFromGin(c) | ||||||
|  | 
 | ||||||
|  | 		userId := Base64UrlDecode(c.Param("id")) | ||||||
|  | 		if userId == "" { | ||||||
|  | 			c.JSON(http.StatusBadRequest, | ||||||
|  | 				model.Error{Code: http.StatusInternalServerError, Message: "missing id parameter"}) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		peers, err := e.app.GetUserInterfaces(ctx, domain.UserIdentifier(userId)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			c.JSON(http.StatusInternalServerError, | ||||||
|  | 				model.Error{Code: http.StatusInternalServerError, Message: err.Error()}) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		c.JSON(http.StatusOK, model.NewInterfaces(peers, nil)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // handleDelete returns a gorm handler function.
 | // handleDelete returns a gorm handler function.
 | ||||||
| //
 | //
 | ||||||
| // @ID users_handleDelete
 | // @ID users_handleDelete
 | ||||||
|  |  | ||||||
|  | @ -109,7 +109,11 @@ func NewInterface(src *domain.Interface, peers []domain.Peer) *Interface { | ||||||
| func NewInterfaces(src []domain.Interface, srcPeers [][]domain.Peer) []Interface { | func NewInterfaces(src []domain.Interface, srcPeers [][]domain.Peer) []Interface { | ||||||
| 	results := make([]Interface, len(src)) | 	results := make([]Interface, len(src)) | ||||||
| 	for i := range src { | 	for i := range src { | ||||||
| 		results[i] = *NewInterface(&src[i], srcPeers[i]) | 		if srcPeers == nil { | ||||||
|  | 			results[i] = *NewInterface(&src[i], nil) | ||||||
|  | 		} else { | ||||||
|  | 			results[i] = *NewInterface(&src[i], srcPeers[i]) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return results | 	return results | ||||||
|  |  | ||||||
|  | @ -39,6 +39,7 @@ type WireGuardManager interface { | ||||||
| 	GetUserPeerStats(ctx context.Context, id domain.UserIdentifier) ([]domain.PeerStatus, error) | 	GetUserPeerStats(ctx context.Context, id domain.UserIdentifier) ([]domain.PeerStatus, error) | ||||||
| 	GetAllInterfacesAndPeers(ctx context.Context) ([]domain.Interface, [][]domain.Peer, error) | 	GetAllInterfacesAndPeers(ctx context.Context) ([]domain.Interface, [][]domain.Peer, error) | ||||||
| 	GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error) | 	GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error) | ||||||
|  | 	GetUserInterfaces(ctx context.Context, id domain.UserIdentifier) ([]domain.Interface, error) | ||||||
| 	PrepareInterface(ctx context.Context) (*domain.Interface, error) | 	PrepareInterface(ctx context.Context) (*domain.Interface, error) | ||||||
| 	CreateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, error) | 	CreateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, error) | ||||||
| 	UpdateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, []domain.Peer, error) | 	UpdateInterface(ctx context.Context, in *domain.Interface) (*domain.Interface, []domain.Peer, error) | ||||||
|  |  | ||||||
|  | @ -68,6 +68,34 @@ func (m Manager) GetAllInterfacesAndPeers(ctx context.Context) ([]domain.Interfa | ||||||
| 	return interfaces, allPeers, nil | 	return interfaces, allPeers, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // GetUserInterfaces returns all interfaces that are available for users to create new peers.
 | ||||||
|  | // If self-provisioning is disabled, this function will return an empty list.
 | ||||||
|  | func (m Manager) GetUserInterfaces(ctx context.Context, id domain.UserIdentifier) ([]domain.Interface, error) { | ||||||
|  | 	if !m.cfg.Core.SelfProvisioningAllowed { | ||||||
|  | 		return nil, nil // self-provisioning is disabled - no interfaces for users
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	interfaces, err := m.db.GetAllInterfaces(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("unable to load all interfaces: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// strip sensitive data, users only need very limited information
 | ||||||
|  | 	userInterfaces := make([]domain.Interface, 0, len(interfaces)) | ||||||
|  | 	for _, iface := range interfaces { | ||||||
|  | 		if iface.IsDisabled() { | ||||||
|  | 			continue // skip disabled interfaces
 | ||||||
|  | 		} | ||||||
|  | 		if iface.Type != domain.InterfaceTypeServer { | ||||||
|  | 			continue // skip client interfaces
 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		userInterfaces = append(userInterfaces, iface.PublicInfo()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return userInterfaces, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (m Manager) ImportNewInterfaces(ctx context.Context, filter ...domain.InterfaceIdentifier) (int, error) { | func (m Manager) ImportNewInterfaces(ctx context.Context, filter ...domain.InterfaceIdentifier) (int, error) { | ||||||
| 	if err := domain.ValidateAdminAccessRights(ctx); err != nil { | 	if err := domain.ValidateAdminAccessRights(ctx); err != nil { | ||||||
| 		return 0, err | 		return 0, err | ||||||
|  |  | ||||||
|  | @ -62,8 +62,10 @@ func (m Manager) GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m Manager) PreparePeer(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Peer, error) { | func (m Manager) PreparePeer(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Peer, error) { | ||||||
| 	if err := domain.ValidateAdminAccessRights(ctx); err != nil { | 	if !m.cfg.Core.SelfProvisioningAllowed { | ||||||
| 		return nil, err // TODO: self provisioning?
 | 		if err := domain.ValidateAdminAccessRights(ctx); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	currentUser := domain.GetUserInfo(ctx) | 	currentUser := domain.GetUserInfo(ctx) | ||||||
|  | @ -73,6 +75,10 @@ func (m Manager) PreparePeer(ctx context.Context, id domain.InterfaceIdentifier) | ||||||
| 		return nil, fmt.Errorf("unable to find interface %s: %w", id, err) | 		return nil, fmt.Errorf("unable to find interface %s: %w", id, err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if m.cfg.Core.SelfProvisioningAllowed && iface.Type != domain.InterfaceTypeServer { | ||||||
|  | 		return nil, fmt.Errorf("self provisioning is only allowed for server interfaces: %w", domain.ErrNoPermission) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	ips, err := m.getFreshPeerIpConfig(ctx, iface) | 	ips, err := m.getFreshPeerIpConfig(ctx, iface) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("unable to get fresh ip addresses: %w", err) | 		return nil, fmt.Errorf("unable to get fresh ip addresses: %w", err) | ||||||
|  | @ -149,10 +155,18 @@ func (m Manager) GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m Manager) CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Peer, error) { | func (m Manager) CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Peer, error) { | ||||||
| 	if err := domain.ValidateUserAccessRights(ctx, peer.UserIdentifier); err != nil { | 	if !m.cfg.Core.SelfProvisioningAllowed { | ||||||
| 		return nil, err | 		if err := domain.ValidateAdminAccessRights(ctx); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		if err := domain.ValidateUserAccessRights(ctx, peer.UserIdentifier); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	sessionUser := domain.GetUserInfo(ctx) | ||||||
|  | 
 | ||||||
| 	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) { | ||||||
| 		return nil, fmt.Errorf("unable to load existing peer %s: %w", peer.Identifier, err) | 		return nil, fmt.Errorf("unable to load existing peer %s: %w", peer.Identifier, err) | ||||||
|  | @ -161,6 +175,18 @@ func (m Manager) CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Pee | ||||||
| 		return nil, fmt.Errorf("peer %s already exists: %w", peer.Identifier, domain.ErrDuplicateEntry) | 		return nil, fmt.Errorf("peer %s already exists: %w", peer.Identifier, domain.ErrDuplicateEntry) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// if a peer is self provisioned, ensure that only allowed fields are set from the request
 | ||||||
|  | 	if !sessionUser.IsAdmin { | ||||||
|  | 		preparedPeer, err := m.PreparePeer(ctx, peer.InterfaceIdentifier) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("failed to prepare peer for interface %s: %w", peer.InterfaceIdentifier, err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		preparedPeer.OverwriteUserEditableFields(peer) | ||||||
|  | 
 | ||||||
|  | 		peer = preparedPeer | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if err := m.validatePeerCreation(ctx, existingPeer, peer); err != nil { | 	if err := m.validatePeerCreation(ctx, existingPeer, peer); err != nil { | ||||||
| 		return nil, fmt.Errorf("creation not allowed: %w", err) | 		return nil, fmt.Errorf("creation not allowed: %w", err) | ||||||
| 	} | 	} | ||||||
|  | @ -229,6 +255,19 @@ func (m Manager) UpdatePeer(ctx context.Context, peer *domain.Peer) (*domain.Pee | ||||||
| 		return nil, fmt.Errorf("update not allowed: %w", err) | 		return nil, fmt.Errorf("update not allowed: %w", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	sessionUser := domain.GetUserInfo(ctx) | ||||||
|  | 
 | ||||||
|  | 	// if a peer is self provisioned, ensure that only allowed fields are set from the request
 | ||||||
|  | 	if !sessionUser.IsAdmin { | ||||||
|  | 		originalPeer, err := m.db.GetPeer(ctx, peer.Identifier) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("unable to load existing peer %s: %w", peer.Identifier, err) | ||||||
|  | 		} | ||||||
|  | 		originalPeer.OverwriteUserEditableFields(peer) | ||||||
|  | 
 | ||||||
|  | 		peer = originalPeer | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// handle peer identifier change (new public key)
 | 	// handle peer identifier change (new public key)
 | ||||||
| 	if existingPeer.Identifier != domain.PeerIdentifier(peer.Interface.PublicKey) { | 	if existingPeer.Identifier != domain.PeerIdentifier(peer.Interface.PublicKey) { | ||||||
| 		peer.Identifier = domain.PeerIdentifier(peer.Interface.PublicKey) // set new identifier
 | 		peer.Identifier = domain.PeerIdentifier(peer.Interface.PublicKey) // set new identifier
 | ||||||
|  | @ -438,7 +477,7 @@ func (m Manager) getFreshPeerIpConfig(ctx context.Context, iface *domain.Interfa | ||||||
| func (m Manager) validatePeerModifications(ctx context.Context, old, new *domain.Peer) error { | func (m Manager) validatePeerModifications(ctx context.Context, old, new *domain.Peer) error { | ||||||
| 	currentUser := domain.GetUserInfo(ctx) | 	currentUser := domain.GetUserInfo(ctx) | ||||||
| 
 | 
 | ||||||
| 	if !currentUser.IsAdmin { | 	if !currentUser.IsAdmin && !m.cfg.Core.SelfProvisioningAllowed { | ||||||
| 		return domain.ErrNoPermission | 		return domain.ErrNoPermission | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -452,7 +491,7 @@ func (m Manager) validatePeerCreation(ctx context.Context, old, new *domain.Peer | ||||||
| 		return fmt.Errorf("invalid peer identifier: %w", domain.ErrInvalidData) | 		return fmt.Errorf("invalid peer identifier: %w", domain.ErrInvalidData) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !currentUser.IsAdmin { | 	if !currentUser.IsAdmin && !m.cfg.Core.SelfProvisioningAllowed { | ||||||
| 		return domain.ErrNoPermission | 		return domain.ErrNoPermission | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -467,7 +506,7 @@ func (m Manager) validatePeerCreation(ctx context.Context, old, new *domain.Peer | ||||||
| func (m Manager) validatePeerDeletion(ctx context.Context, del *domain.Peer) error { | func (m Manager) validatePeerDeletion(ctx context.Context, del *domain.Peer) error { | ||||||
| 	currentUser := domain.GetUserInfo(ctx) | 	currentUser := domain.GetUserInfo(ctx) | ||||||
| 
 | 
 | ||||||
| 	if !currentUser.IsAdmin { | 	if !currentUser.IsAdmin && !m.cfg.Core.SelfProvisioningAllowed { | ||||||
| 		return domain.ErrNoPermission | 		return domain.ErrNoPermission | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -72,6 +72,17 @@ type Interface struct { | ||||||
| 	PeerDefPostDown string // default action that is executed after the device is down
 | 	PeerDefPostDown string // default action that is executed after the device is down
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // PublicInfo returns a copy of the interface with only the public information.
 | ||||||
|  | // Sensible information like keys are not included.
 | ||||||
|  | func (i *Interface) PublicInfo() Interface { | ||||||
|  | 	return Interface{ | ||||||
|  | 		Identifier:  i.Identifier, | ||||||
|  | 		DisplayName: i.DisplayName, | ||||||
|  | 		Type:        i.Type, | ||||||
|  | 		Disabled:    i.Disabled, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Validate performs checks to ensure that the interface is valid.
 | // Validate performs checks to ensure that the interface is valid.
 | ||||||
| func (i *Interface) Validate() error { | func (i *Interface) Validate() error { | ||||||
| 	// validate peer default endpoint, add port if needed
 | 	// validate peer default endpoint, add port if needed
 | ||||||
|  |  | ||||||
|  | @ -127,6 +127,19 @@ func (p *Peer) GenerateDisplayName(prefix string) { | ||||||
| 	p.DisplayName = fmt.Sprintf("%sPeer %s", prefix, internal.TruncateString(string(p.Identifier), 8)) | 	p.DisplayName = fmt.Sprintf("%sPeer %s", prefix, internal.TruncateString(string(p.Identifier), 8)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // OverwriteUserEditableFields overwrites the user editable fields of the peer with the values from the userPeer
 | ||||||
|  | func (p *Peer) OverwriteUserEditableFields(userPeer *Peer) { | ||||||
|  | 	p.DisplayName = userPeer.DisplayName | ||||||
|  | 	p.Interface.PublicKey = userPeer.Interface.PublicKey | ||||||
|  | 	p.Interface.PrivateKey = userPeer.Interface.PrivateKey | ||||||
|  | 	p.Interface.Mtu = userPeer.Interface.Mtu | ||||||
|  | 	p.PersistentKeepalive = userPeer.PersistentKeepalive | ||||||
|  | 	p.ExpiresAt = userPeer.ExpiresAt | ||||||
|  | 	p.Disabled = userPeer.Disabled | ||||||
|  | 	p.DisabledReason = userPeer.DisabledReason | ||||||
|  | 	p.PresharedKey = userPeer.PresharedKey | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type PeerInterfaceConfig struct { | type PeerInterfaceConfig struct { | ||||||
| 	KeyPair // private/public Key of the peer
 | 	KeyPair // private/public Key of the peer
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue