creation of multiple peers

This commit is contained in:
Christoph Haas 2023-07-07 15:36:07 +02:00
parent 4b05f1840c
commit 85438d1dce
16 changed files with 1182 additions and 87 deletions

View File

@ -0,0 +1,97 @@
<script setup>
import Modal from "./Modal.vue";
import {peerStore} from "@/stores/peers";
import {interfaceStore} from "@/stores/interfaces";
import {computed, ref, watch} from "vue";
import { useI18n } from 'vue-i18n';
import { notify } from "@kyvg/vue3-notification";
import Vue3TagsInput from "vue3-tags-input";
import { freshInterface } from '@/helpers/models';
const { t } = useI18n()
const peers = peerStore()
const interfaces = interfaceStore()
const props = defineProps({
visible: Boolean,
})
const emit = defineEmits(['close'])
const selectedInterface = computed(() => {
let i = interfaces.GetSelected;
if (!i) {
i = freshInterface() // dummy interface to avoid 'undefined' exceptions
}
return i
})
function freshForm() {
return {
Identifiers: [],
Suffix: "",
}
}
const formData = ref(freshForm())
function close() {
formData.value = freshForm()
emit('close')
}
function handleChangeUserIdentifiers(tags) {
formData.value.Identifiers = tags
}
async function save() {
if (formData.value.Identifiers.length === 0) {
notify({
title: "Missing Identifiers",
text: "At least one identifier is required to create a new peer.",
type: 'error',
})
return
}
try {
await peers.CreateMultiplePeers(selectedInterface.value.Identifier, formData.value)
close()
} catch (e) {
console.log(e)
notify({
title: "Backend Connection Failure",
text: "Failed to create peers!",
type: 'error',
})
}
}
</script>
<template>
<Modal :title="t('modals.peerscreate.title')" :visible="visible" @close="close">
<template #default>
<fieldset>
<div class="form-group">
<label class="form-label mt-4">{{ $t('modals.peerscreate.identifiers') }}</label>
<vue3-tags-input class="form-control" :tags="formData.Identifiers"
:placeholder="t('modals.peerscreate.identifiers.placeholder')"
:add-tag-on-keys="[13, 188, 32, 9]"
@on-tags-changed="handleChangeUserIdentifiers"/>
</div>
<div class="form-group">
<label class="form-label mt-4">{{ $t('modals.peerscreate.peernamesuffix') }}</label>
<input type="text" class="form-control" :placeholder="t('modals.peerscreate.peernamesuffix.placeholder')" v-model="formData.Suffix">
</div>
</fieldset>
</template>
<template #footer>
<button class="btn btn-primary me-1" type="button" @click.prevent="save">Create</button>
<button class="btn btn-secondary" type="button" @click.prevent="close">Cancel</button>
</template>
</Modal>
</template>

View File

@ -167,6 +167,19 @@ export const peerStore = defineStore({
throw new Error(error)
})
},
async CreateMultiplePeers(interfaceId, formData) {
this.fetching = true
return apiWrapper.post(`${baseUrl}/iface/${base64_url_encode(interfaceId)}/multiplenew`, formData)
.then(peers => {
this.peers.push(...peers)
this.fetching = false
})
.catch(error => {
this.fetching = false
console.log(error)
throw new Error(error)
})
},
async LoadPeers(interfaceId) {
// if no interfaceId is given, use the currently selected interface
if (!interfaceId) {

View File

@ -1,6 +1,7 @@
<script setup>
import PeerViewModal from "../components/PeerViewModal.vue";
import PeerEditModal from "../components/PeerEditModal.vue";
import PeerMultiCreateModal from "../components/PeerMultiCreateModal.vue";
import InterfaceEditModal from "../components/InterfaceEditModal.vue";
import InterfaceViewModal from "../components/InterfaceViewModal.vue";
@ -13,6 +14,7 @@ const peers = peerStore()
const viewedPeerId = ref("")
const editPeerId = ref("")
const multiCreatePeerId = ref("")
const editInterfaceId = ref("")
const viewedInterfaceId = ref("")
@ -51,6 +53,7 @@ onMounted(async () => {
<template>
<PeerViewModal :peerId="viewedPeerId" :visible="viewedPeerId!==''" @close="viewedPeerId=''"></PeerViewModal>
<PeerEditModal :peerId="editPeerId" :visible="editPeerId!==''" @close="editPeerId=''"></PeerEditModal>
<PeerMultiCreateModal :visible="multiCreatePeerId!==''" @close="multiCreatePeerId=''"></PeerMultiCreateModal>
<InterfaceEditModal :interfaceId="editInterfaceId" :visible="editInterfaceId!==''" @close="editInterfaceId=''"></InterfaceEditModal>
<InterfaceViewModal :interfaceId="viewedInterfaceId" :visible="viewedInterfaceId!==''" @close="viewedInterfaceId=''"></InterfaceViewModal>
@ -272,7 +275,7 @@ onMounted(async () => {
</div>
<div class="col-12 col-lg-3 text-lg-end">
<a v-if="interfaces.GetSelected.Mode==='server' && peers.Count!==0" class="btn btn-primary" href="#" title="Send mail to all peers"><i class="fa fa-paper-plane"></i></a>
<a class="btn btn-primary ms-2" href="#" title="Add multiple peers"><i class="fa fa-plus me-1"></i><i class="fa fa-users"></i></a>
<a class="btn btn-primary ms-2" href="#" title="Add multiple peers" @click.prevent="multiCreatePeerId='#NEW#'"><i class="fa fa-plus me-1"></i><i class="fa fa-users"></i></a>
<a class="btn btn-primary ms-2" href="#" title="Add a peer" @click.prevent="editPeerId='#NEW#'"><i class="fa fa-plus me-1"></i><i class="fa fa-user"></i></a>
</div>
</div>

View File

@ -136,6 +136,8 @@ func NewDatabase(cfg config.DatabaseConfig) (*gorm.DB, error) {
if err != nil {
return nil, fmt.Errorf("failed to open sqlite database: %w", err)
}
sqlDB, _ := gormDb.DB()
sqlDB.SetMaxOpenConns(1)
}
return gormDb, nil

View File

@ -251,6 +251,38 @@
}
}
},
"/interface/config/{id}": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Interface"
],
"summary": "Get interface configuration as string.",
"operationId": "interfaces_handleConfigGet",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/model.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/model.Error"
}
}
}
}
},
"/interface/get/{id}": {
"get": {
"produces": [
@ -429,6 +461,42 @@
}
}
}
},
"delete": {
"produces": [
"application/json"
],
"tags": [
"Interface"
],
"summary": "Delete the interface record.",
"operationId": "interfaces_handleDelete",
"parameters": [
{
"type": "string",
"description": "The interface identifier",
"name": "id",
"in": "path",
"required": true
}
],
"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"
}
}
}
}
},
"/now": {
@ -458,7 +526,7 @@
}
}
},
"/peer/all/{id}": {
"/peer/config-qr/{id}": {
"get": {
"produces": [
"application/json"
@ -466,16 +534,19 @@
"tags": [
"Peer"
],
"summary": "Get peers for the given interface.",
"operationId": "peers_handlePeersGet",
"summary": "Get peer configuration as qr code.",
"operationId": "peers_handleQrCodeGet",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/model.Peer"
}
"type": "string"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/model.Error"
}
},
"500": {
@ -487,7 +558,299 @@
}
}
},
"/users": {
"/peer/config/{id}": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Peer"
],
"summary": "Get peer configuration as string.",
"operationId": "peers_handleConfigGet",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/model.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/model.Error"
}
}
}
}
},
"/peer/iface/{iface}/all": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Peer"
],
"summary": "Get peers for the given interface.",
"operationId": "peers_handleAllGet",
"parameters": [
{
"type": "string",
"description": "The interface identifier",
"name": "iface",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/model.Peer"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/model.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/model.Error"
}
}
}
}
},
"/peer/iface/{iface}/new": {
"post": {
"produces": [
"application/json"
],
"tags": [
"Peer"
],
"summary": "Prepare a new peer for the given interface.",
"operationId": "peers_handleCreatePost",
"parameters": [
{
"type": "string",
"description": "The interface identifier",
"name": "iface",
"in": "path",
"required": true
},
{
"description": "The peer data",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/model.Peer"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/model.Peer"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/model.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/model.Error"
}
}
}
}
},
"/peer/iface/{iface}/prepare": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Peer"
],
"summary": "Prepare a new peer for the given interface.",
"operationId": "peers_handlePrepareGet",
"parameters": [
{
"type": "string",
"description": "The interface identifier",
"name": "iface",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/model.Peer"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/model.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/model.Error"
}
}
}
}
},
"/peer/{id}": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Peer"
],
"summary": "Get peer for the given identifier.",
"operationId": "peers_handleSingleGet",
"parameters": [
{
"type": "string",
"description": "The peer identifier",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/model.Peer"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/model.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/model.Error"
}
}
}
},
"put": {
"produces": [
"application/json"
],
"tags": [
"Peer"
],
"summary": "Update the given peer record.",
"operationId": "peers_handleUpdatePut",
"parameters": [
{
"type": "string",
"description": "The peer identifier",
"name": "id",
"in": "path",
"required": true
},
{
"description": "The peer data",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/model.Peer"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/model.Peer"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/model.Error"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/model.Error"
}
}
}
},
"delete": {
"produces": [
"application/json"
],
"tags": [
"Peer"
],
"summary": "Delete the peer record.",
"operationId": "peers_handleDelete",
"parameters": [
{
"type": "string",
"description": "The peer identifier",
"name": "id",
"in": "path",
"required": true
}
],
"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/all": {
"get": {
"produces": [
"application/json"
@ -516,7 +879,7 @@
}
}
},
"/users/new": {
"/user/new": {
"post": {
"produces": [
"application/json"
@ -559,7 +922,40 @@
}
}
},
"/users/{id}": {
"/user/{id}": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Get a single user record.",
"operationId": "users_handleSingleGet",
"parameters": [
{
"type": "string",
"description": "The user identifier",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/model.User"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/model.Error"
}
}
}
},
"put": {
"produces": [
"application/json"
@ -607,9 +1003,45 @@
}
}
}
},
"delete": {
"produces": [
"application/json"
],
"tags": [
"Users"
],
"summary": "Delete the user record.",
"operationId": "users_handleDelete",
"parameters": [
{
"type": "string",
"description": "The user identifier",
"name": "id",
"in": "path",
"required": true
}
],
"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"
}
}
}
}
},
"/users/{id}/peers": {
"/user/{id}/peers": {
"get": {
"produces": [
"application/json"
@ -643,14 +1075,36 @@
"model.Error": {
"type": "object",
"properties": {
"code": {
"Code": {
"type": "integer"
},
"message": {
"Message": {
"type": "string"
}
}
},
"model.Int32ConfigOption": {
"type": "object",
"properties": {
"Overridable": {
"type": "boolean"
},
"Value": {
"type": "integer"
}
}
},
"model.IntConfigOption": {
"type": "object",
"properties": {
"Overridable": {
"type": "boolean"
},
"Value": {
"type": "integer"
}
}
},
"model.Interface": {
"type": "object",
"properties": {
@ -848,6 +1302,14 @@
},
"AllowedIPs": {
"description": "all allowed ip subnets, comma seperated",
"allOf": [
{
"$ref": "#/definitions/model.StringSliceConfigOption"
}
]
},
"CheckAliveAddress": {
"description": "optional ip address or DNS name that is used for ping checks",
"type": "string"
},
"Disabled": {
@ -864,27 +1326,54 @@
},
"Dns": {
"description": "the dns server that should be set if the interface is up, comma separated",
"type": "string"
"allOf": [
{
"$ref": "#/definitions/model.StringSliceConfigOption"
}
]
},
"DnsSearch": {
"description": "the dns search option string that should be set if the interface is up, will be appended to DnsStr",
"type": "string"
"allOf": [
{
"$ref": "#/definitions/model.StringSliceConfigOption"
}
]
},
"Endpoint": {
"description": "the endpoint address",
"type": "string"
"allOf": [
{
"$ref": "#/definitions/model.StringConfigOption"
}
]
},
"EndpointPublicKey": {
"description": "the endpoint public key",
"allOf": [
{
"$ref": "#/definitions/model.StringConfigOption"
}
]
},
"ExpiresAt": {
"description": "expiry dates for peers",
"type": "string"
},
"ExtraAllowedIPs": {
"description": "all allowed ip subnets on the server side, comma seperated",
"type": "string"
"type": "array",
"items": {
"type": "string"
}
},
"FirewallMark": {
"description": "a firewall mark",
"type": "integer"
"allOf": [
{
"$ref": "#/definitions/model.Int32ConfigOption"
}
]
},
"Identifier": {
"description": "peer unique identifier",
@ -901,27 +1390,55 @@
},
"Mtu": {
"description": "the device MTU",
"type": "integer"
"allOf": [
{
"$ref": "#/definitions/model.IntConfigOption"
}
]
},
"Notes": {
"description": "a note field for peers",
"type": "string"
},
"PersistentKeepalive": {
"description": "the persistent keep-alive interval",
"type": "integer"
"allOf": [
{
"$ref": "#/definitions/model.IntConfigOption"
}
]
},
"PostDown": {
"description": "action that is executed after the device is down",
"type": "string"
"allOf": [
{
"$ref": "#/definitions/model.StringConfigOption"
}
]
},
"PostUp": {
"description": "action that is executed after the device is up",
"type": "string"
"allOf": [
{
"$ref": "#/definitions/model.StringConfigOption"
}
]
},
"PreDown": {
"description": "action that is executed before the device is down",
"type": "string"
"allOf": [
{
"$ref": "#/definitions/model.StringConfigOption"
}
]
},
"PreUp": {
"description": "action that is executed before the device is up",
"type": "string"
"allOf": [
{
"$ref": "#/definitions/model.StringConfigOption"
}
]
},
"PresharedKey": {
"description": "the pre-shared Key of the peer",
@ -939,7 +1456,11 @@
},
"RoutingTable": {
"description": "the routing table",
"type": "string"
"allOf": [
{
"$ref": "#/definitions/model.StringConfigOption"
}
]
},
"UserIdentifier": {
"description": "the owner",
@ -970,6 +1491,31 @@
}
}
},
"model.StringConfigOption": {
"type": "object",
"properties": {
"Overridable": {
"type": "boolean"
},
"Value": {
"type": "string"
}
}
},
"model.StringSliceConfigOption": {
"type": "object",
"properties": {
"Overridable": {
"type": "boolean"
},
"Value": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"model.User": {
"type": "object",
"properties": {

View File

@ -2,11 +2,25 @@ basePath: /api/v0
definitions:
model.Error:
properties:
code:
Code:
type: integer
message:
Message:
type: string
type: object
model.Int32ConfigOption:
properties:
Overridable:
type: boolean
Value:
type: integer
type: object
model.IntConfigOption:
properties:
Overridable:
type: boolean
Value:
type: integer
type: object
model.Interface:
properties:
Addresses:
@ -154,7 +168,11 @@ definitions:
type: string
type: array
AllowedIPs:
allOf:
- $ref: '#/definitions/model.StringSliceConfigOption'
description: all allowed ip subnets, comma seperated
CheckAliveAddress:
description: optional ip address or DNS name that is used for ping checks
type: string
Disabled:
description: flag that specifies if the peer is enabled (up) or not (down)
@ -166,25 +184,35 @@ definitions:
description: a nice display name/ description for the peer
type: string
Dns:
allOf:
- $ref: '#/definitions/model.StringSliceConfigOption'
description: the dns server that should be set if the interface is up, comma
separated
type: string
DnsSearch:
allOf:
- $ref: '#/definitions/model.StringSliceConfigOption'
description: the dns search option string that should be set if the interface
is up, will be appended to DnsStr
type: string
Endpoint:
allOf:
- $ref: '#/definitions/model.StringConfigOption'
description: the endpoint address
type: string
EndpointPublicKey:
allOf:
- $ref: '#/definitions/model.StringConfigOption'
description: the endpoint public key
ExpiresAt:
description: expiry dates for peers
type: string
ExtraAllowedIPs:
description: all allowed ip subnets on the server side, comma seperated
type: string
items:
type: string
type: array
FirewallMark:
allOf:
- $ref: '#/definitions/model.Int32ConfigOption'
description: a firewall mark
type: integer
Identifier:
description: peer unique identifier
example: super_nice_peer
@ -196,23 +224,32 @@ definitions:
description: the peer interface type (server, client, any)
type: string
Mtu:
allOf:
- $ref: '#/definitions/model.IntConfigOption'
description: the device MTU
type: integer
Notes:
description: a note field for peers
type: string
PersistentKeepalive:
allOf:
- $ref: '#/definitions/model.IntConfigOption'
description: the persistent keep-alive interval
type: integer
PostDown:
allOf:
- $ref: '#/definitions/model.StringConfigOption'
description: action that is executed after the device is down
type: string
PostUp:
allOf:
- $ref: '#/definitions/model.StringConfigOption'
description: action that is executed after the device is up
type: string
PreDown:
allOf:
- $ref: '#/definitions/model.StringConfigOption'
description: action that is executed before the device is down
type: string
PreUp:
allOf:
- $ref: '#/definitions/model.StringConfigOption'
description: action that is executed before the device is up
type: string
PresharedKey:
description: the pre-shared Key of the peer
type: string
@ -225,8 +262,9 @@ definitions:
example: abcdef==
type: string
RoutingTable:
allOf:
- $ref: '#/definitions/model.StringConfigOption'
description: the routing table
type: string
UserIdentifier:
description: the owner
type: string
@ -246,6 +284,22 @@ definitions:
UserLastname:
type: string
type: object
model.StringConfigOption:
properties:
Overridable:
type: boolean
Value:
type: string
type: object
model.StringSliceConfigOption:
properties:
Overridable:
type: boolean
Value:
items:
type: string
type: array
type: object
model.User:
properties:
Department:
@ -426,6 +480,30 @@ paths:
tags:
- Testing
/interface/{id}:
delete:
operationId: interfaces_handleDelete
parameters:
- description: The interface identifier
in: path
name: id
required: true
type: string
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: Delete the interface record.
tags:
- Interface
put:
operationId: interfaces_handleUpdatePut
parameters:
@ -477,6 +555,27 @@ paths:
summary: Get all available interfaces.
tags:
- Interface
/interface/config/{id}:
get:
operationId: interfaces_handleConfigGet
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"400":
description: Bad Request
schema:
$ref: '#/definitions/model.Error'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/model.Error'
summary: Get interface configuration as string.
tags:
- Interface
/interface/get/{id}:
get:
operationId: interfaces_handleSingleGet
@ -580,9 +679,140 @@ paths:
summary: Get the current local time.
tags:
- Testing
/peer/all/{id}:
/peer/{id}:
delete:
operationId: peers_handleDelete
parameters:
- description: The peer identifier
in: path
name: id
required: true
type: string
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: Delete the peer record.
tags:
- Peer
get:
operationId: peers_handlePeersGet
operationId: peers_handleSingleGet
parameters:
- description: The peer identifier
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/model.Peer'
"400":
description: Bad Request
schema:
$ref: '#/definitions/model.Error'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/model.Error'
summary: Get peer for the given identifier.
tags:
- Peer
put:
operationId: peers_handleUpdatePut
parameters:
- description: The peer identifier
in: path
name: id
required: true
type: string
- description: The peer data
in: body
name: request
required: true
schema:
$ref: '#/definitions/model.Peer'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/model.Peer'
"400":
description: Bad Request
schema:
$ref: '#/definitions/model.Error'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/model.Error'
summary: Update the given peer record.
tags:
- Peer
/peer/config-qr/{id}:
get:
operationId: peers_handleQrCodeGet
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"400":
description: Bad Request
schema:
$ref: '#/definitions/model.Error'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/model.Error'
summary: Get peer configuration as qr code.
tags:
- Peer
/peer/config/{id}:
get:
operationId: peers_handleConfigGet
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
"400":
description: Bad Request
schema:
$ref: '#/definitions/model.Error'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/model.Error'
summary: Get peer configuration as string.
tags:
- Peer
/peer/iface/{iface}/all:
get:
operationId: peers_handleAllGet
parameters:
- description: The interface identifier
in: path
name: iface
required: true
type: string
produces:
- application/json
responses:
@ -592,6 +822,10 @@ paths:
items:
$ref: '#/definitions/model.Peer'
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/model.Error'
"500":
description: Internal Server Error
schema:
@ -599,26 +833,113 @@ paths:
summary: Get peers for the given interface.
tags:
- Peer
/users:
get:
operationId: users_handleAllGet
/peer/iface/{iface}/new:
post:
operationId: peers_handleCreatePost
parameters:
- description: The interface identifier
in: path
name: iface
required: true
type: string
- description: The peer data
in: body
name: request
required: true
schema:
$ref: '#/definitions/model.Peer'
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/model.User'
type: array
$ref: '#/definitions/model.Peer'
"400":
description: Bad Request
schema:
$ref: '#/definitions/model.Error'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/model.Error'
summary: Get all user records.
summary: Prepare a new peer for the given interface.
tags:
- Peer
/peer/iface/{iface}/prepare:
get:
operationId: peers_handlePrepareGet
parameters:
- description: The interface identifier
in: path
name: iface
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/model.Peer'
"400":
description: Bad Request
schema:
$ref: '#/definitions/model.Error'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/model.Error'
summary: Prepare a new peer for the given interface.
tags:
- Peer
/user/{id}:
delete:
operationId: users_handleDelete
parameters:
- description: The user identifier
in: path
name: id
required: true
type: string
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: Delete the user record.
tags:
- Users
get:
operationId: users_handleSingleGet
parameters:
- description: The user identifier
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/model.User'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/model.Error'
summary: Get a single user record.
tags:
- Users
/users/{id}:
put:
operationId: users_handleUpdatePut
parameters:
@ -651,7 +972,7 @@ paths:
summary: Update the user record.
tags:
- Users
/users/{id}/peers:
/user/{id}/peers:
get:
operationId: users_handlePeersGet
produces:
@ -670,7 +991,26 @@ paths:
summary: Get peers for the given user.
tags:
- Users
/users/new:
/user/all:
get:
operationId: users_handleAllGet
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/model.User'
type: array
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/model.Error'
summary: Get all user records.
tags:
- Users
/user/new:
post:
operationId: users_handleCreatePost
parameters:

View File

@ -11,7 +11,7 @@
let WGPORTAL_BACKEND_BASE_URL="http://localhost:5000/api/v0";
</script>
<script src="/api/v0/config/frontend.js"></script>
<script type="module" crossorigin src="/app/assets/index-8f53e6dd.js"></script>
<script type="module" crossorigin src="/app/assets/index-b5bbe402.js"></script>
<link rel="stylesheet" href="/app/assets/index-a233ff7e.css">
</head>
<body class="d-flex flex-column min-vh-100">

View File

@ -7,7 +7,9 @@ import (
"github.com/gin-gonic/gin"
"github.com/h44z/wg-portal/internal/app"
"html/template"
"net"
"net/http"
"net/url"
)
//go:embed frontend_config.js.gotpl
@ -52,7 +54,14 @@ func (e configEndpoint) handleConfigJsGet() gin.HandlerFunc {
return func(c *gin.Context) {
backendUrl := fmt.Sprintf("%s/api/v0", e.app.Config.Web.ExternalUrl)
if c.GetHeader("x-wg-dev") != "" {
backendUrl = "http://localhost:5000/api/v0" // override if reqest comes from frontend started with npm run dev
referer := c.Request.Header.Get("Referer")
host := "localhost"
port := "5000"
parsedReferer, err := url.Parse(referer)
if err == nil {
host, port, _ = net.SplitHostPort(parsedReferer.Host)
}
backendUrl = fmt.Sprintf("http://%s:%s/api/v0", host, port) // override if request comes from frontend started with npm run dev
}
buf := &bytes.Buffer{}
err := e.tpl.ExecuteTemplate(buf, "frontend_config.js.gotpl", gin.H{

View File

@ -24,6 +24,7 @@ func (e peerEndpoint) RegisterRoutes(g *gin.RouterGroup, authenticator *authenti
apiGroup.GET("/iface/:iface/all", e.handleAllGet())
apiGroup.GET("/iface/:iface/prepare", e.handlePrepareGet())
apiGroup.POST("/iface/:iface/new", e.handleCreatePost())
apiGroup.POST("/iface/:iface/multiplenew", e.handleCreateMultiplePost())
apiGroup.GET("/config-qr/:id", e.handleQrCodeGet())
apiGroup.GET("/config/:id", e.handleConfigGet())
apiGroup.GET("/:id", e.handleSingleGet())
@ -168,6 +169,45 @@ func (e peerEndpoint) handleCreatePost() gin.HandlerFunc {
}
}
// handleCreateMultiplePost returns a gorm handler function.
//
// @ID peers_handleCreateMultiplePost
// @Tags Peer
// @Summary Create multiple new peers for the given interface.
// @Produce json
// @Param iface path string true "The interface identifier"
// @Param request body model.MultiPeerRequest true "The peer creation request data"
// @Success 200 {object} []model.Peer
// @Failure 400 {object} model.Error
// @Failure 500 {object} model.Error
// @Router /peer/iface/{iface}/multiplenew [post]
func (e peerEndpoint) handleCreateMultiplePost() gin.HandlerFunc {
return func(c *gin.Context) {
ctx := domain.SetUserInfoFromGin(c)
interfaceId := Base64UrlDecode(c.Param("iface"))
if interfaceId == "" {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: "missing iface parameter"})
return
}
var req model.MultiPeerRequest
err := c.BindJSON(&req)
if err != nil {
c.JSON(http.StatusBadRequest, model.Error{Code: http.StatusBadRequest, Message: err.Error()})
return
}
newPeers, err := e.app.CreateMultiplePeers(ctx, domain.InterfaceIdentifier(interfaceId), model.NewDomainPeerCreationRequest(&req))
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return
}
c.JSON(http.StatusOK, model.NewPeers(newPeers))
}
}
// handleUpdatePut returns a gorm handler function.
//
// @ID peers_handleUpdatePut

View File

@ -131,3 +131,15 @@ func NewDomainPeer(src *Peer) *domain.Peer {
return res
}
type MultiPeerRequest struct {
Identifiers []string `json:"Identifiers"`
Suffix string `json:"Suffix"`
}
func NewDomainPeerCreationRequest(src *MultiPeerRequest) *domain.PeerCreationRequest {
return &domain.PeerCreationRequest{
Identifiers: src.Identifiers,
Suffix: src.Suffix,
}
}

View File

@ -40,6 +40,7 @@ type WireGuardManager interface {
PreparePeer(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Peer, error)
GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error)
CreatePeer(ctx context.Context, p *domain.Peer) (*domain.Peer, error)
CreateMultiplePeers(ctx context.Context, id domain.InterfaceIdentifier, r *domain.PeerCreationRequest) ([]domain.Peer, error)
UpdatePeer(ctx context.Context, p *domain.Peer) (*domain.Peer, error)
DeletePeer(ctx context.Context, id domain.PeerIdentifier) error
}

View File

@ -775,6 +775,33 @@ func (m Manager) CreatePeer(ctx context.Context, peer *domain.Peer) (*domain.Pee
return peer, nil
}
func (m Manager) CreateMultiplePeers(ctx context.Context, interfaceId domain.InterfaceIdentifier, r *domain.PeerCreationRequest) ([]domain.Peer, error) {
var newPeers []domain.Peer
for _, id := range r.Identifiers {
freshPeer, err := m.PreparePeer(ctx, interfaceId)
if err != nil {
return nil, fmt.Errorf("failed to prepare peer for interface %s: %w", interfaceId, err)
}
freshPeer.UserIdentifier = domain.UserIdentifier(id) // use id as user identifier. peers are allowed to have invalid user identifiers
if r.Suffix != "" {
freshPeer.DisplayName += " " + r.Suffix
}
newPeers = append(newPeers, *freshPeer)
}
for i, peer := range newPeers {
_, err := m.CreatePeer(ctx, &newPeers[i])
if err != nil {
return nil, fmt.Errorf("failed to create peer %s (uid: %s) for interface %s: %w", peer.Identifier, peer.UserIdentifier, interfaceId, err)
}
}
return newPeers, nil
}
func (m Manager) UpdatePeer(ctx context.Context, peer *domain.Peer) (*domain.Peer, error) {
existingPeer, err := m.db.GetPeer(ctx, peer.Identifier)
if err != nil {

View File

@ -181,3 +181,8 @@ func MergeToPhysicalPeer(pp *PhysicalPeer, p *Peer) {
pp.PublicKey = p.Interface.PublicKey
pp.PersistentKeepalive = p.PersistentKeepalive.GetValue()
}
type PeerCreationRequest struct {
Identifiers []string
Suffix string
}