mirror of
https://github.com/h44z/wg-portal
synced 2025-02-26 05:49:14 +00:00
Brought into working condition for LDAP authentication.
This commit is contained in:
parent
1b4b5ff161
commit
0ade556e80
@ -54,7 +54,7 @@ func generateApi(basePath, apiPath, version string) error {
|
|||||||
OutputDir: filepath.Join(basePath, "core/assets/doc"),
|
OutputDir: filepath.Join(basePath, "core/assets/doc"),
|
||||||
OutputTypes: []string{"json", "yaml"},
|
OutputTypes: []string{"json", "yaml"},
|
||||||
ParseVendor: false,
|
ParseVendor: false,
|
||||||
ParseDependency: true,
|
ParseDependency: 3,
|
||||||
MarkdownFilesDir: "",
|
MarkdownFilesDir: "",
|
||||||
ParseInternal: true,
|
ParseInternal: true,
|
||||||
GeneratedTime: false,
|
GeneratedTime: false,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { RouterLink, RouterView } from 'vue-router';
|
import { RouterLink, RouterView } from 'vue-router';
|
||||||
import {computed, getCurrentInstance, onMounted, ref} from "vue";
|
import { computed, getCurrentInstance, onMounted, ref } from "vue";
|
||||||
import {authStore} from "./stores/auth";
|
import { authStore } from "./stores/auth";
|
||||||
import {securityStore} from "./stores/security";
|
import { securityStore } from "./stores/security";
|
||||||
import {settingsStore} from "@/stores/settings";
|
import { settingsStore } from "@/stores/settings";
|
||||||
|
|
||||||
const appGlobal = getCurrentInstance().appContext.config.globalProperties
|
const appGlobal = getCurrentInstance().appContext.config.globalProperties
|
||||||
const auth = authStore()
|
const auth = authStore()
|
||||||
@ -80,10 +80,11 @@ const currentYear = ref(new Date().getFullYear())
|
|||||||
|
|
||||||
<div class="navbar-nav d-flex justify-content-end">
|
<div class="navbar-nav d-flex justify-content-end">
|
||||||
<div v-if="auth.IsAuthenticated" class="nav-item dropdown">
|
<div v-if="auth.IsAuthenticated" class="nav-item dropdown">
|
||||||
<a aria-expanded="false" aria-haspopup="true" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#"
|
<a aria-expanded="false" aria-haspopup="true" class="nav-link dropdown-toggle" data-bs-toggle="dropdown"
|
||||||
role="button">{{ auth.User.Firstname }} {{ auth.User.Lastname }}</a>
|
href="#" role="button">{{ auth.User.Firstname }} {{ auth.User.Lastname }}</a>
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<RouterLink :to="{ name: 'profile' }" class="dropdown-item"><i class="fas fa-user"></i> {{ $t('menu.profile') }}</RouterLink>
|
<RouterLink :to="{ name: 'profile' }" class="dropdown-item"><i class="fas fa-user"></i> {{
|
||||||
|
$t('menu.profile') }}</RouterLink>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<a class="dropdown-item" href="#" @click.prevent="auth.Logout">
|
<a class="dropdown-item" href="#" @click.prevent="auth.Logout">
|
||||||
<i class="fas fa-sign-out-alt"></i> {{ $t('menu.logout') }}
|
<i class="fas fa-sign-out-alt"></i> {{ $t('menu.logout') }}
|
||||||
@ -107,22 +108,24 @@ const currentYear = ref(new Date().getFullYear())
|
|||||||
<footer class="page-footer mt-auto">
|
<footer class="page-footer mt-auto">
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col-6">Copyright © {{ companyName }} {{ currentYear }} <span v-if="auth.IsAuthenticated"> - version {{ wgVersion }}</span></div>
|
<div class="col-6">Copyright © {{ companyName }} {{ currentYear }} <span v-if="auth.IsAuthenticated"> - version {{
|
||||||
|
wgVersion }}</span></div>
|
||||||
<div class="col-6 text-end">
|
<div class="col-6 text-end">
|
||||||
<div :aria-label="$t('menu.lang')" class="btn-group" role="group">
|
<div :aria-label="$t('menu.lang')" class="btn-group" role="group">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<button aria-expanded="false" aria-haspopup="true" class="btn btn btn-secondary pe-0" data-bs-toggle="dropdown" type="button"><span :class="languageFlag" class="fi"></span></button>
|
<button aria-expanded="false" aria-haspopup="true" class="btn btn btn-secondary pe-0"
|
||||||
|
data-bs-toggle="dropdown" type="button"><span :class="languageFlag" class="fi"></span></button>
|
||||||
<div aria-labelledby="btnGroupDrop3" class="dropdown-menu" style="">
|
<div aria-labelledby="btnGroupDrop3" class="dropdown-menu" style="">
|
||||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('en')"><span class="fi fi-us"></span> English</a>
|
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('en')"><span class="fi fi-us"></span>
|
||||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('de')"><span class="fi fi-de"></span> Deutsch</a>
|
English</a>
|
||||||
|
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('ru')"><span class="fi fi-ru"></span>
|
||||||
|
Русский</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer></template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
<style></style>
|
||||||
</style>
|
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Modal from "./Modal.vue";
|
import Modal from "./Modal.vue";
|
||||||
import {peerStore} from "@/stores/peers";
|
import { peerStore } from "@/stores/peers";
|
||||||
import {interfaceStore} from "@/stores/interfaces";
|
import { interfaceStore } from "@/stores/interfaces";
|
||||||
import {computed, ref, watch} from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { notify } from "@kyvg/vue3-notification";
|
import { notify } from "@kyvg/vue3-notification";
|
||||||
import Vue3TagsInput from "vue3-tags-input";
|
import Vue3TagsInput from "vue3-tags-input";
|
||||||
import { validateCIDR, validateIP, validateDomain } from '@/helpers/validators';
|
import { validateCIDR, validateIP, validateDomain } from '@/helpers/validators';
|
||||||
import isCidr from "is-cidr";
|
import isCidr from "is-cidr";
|
||||||
import {isIP} from 'is-ip';
|
import { isIP } from 'is-ip';
|
||||||
import { freshPeer, freshInterface } from '@/helpers/models';
|
import { freshPeer, freshInterface } from '@/helpers/models';
|
||||||
|
import { profileStore } from "@/stores/profile";
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const peers = peerStore()
|
const peers = peerStore()
|
||||||
const interfaces = interfaceStore()
|
const interfaces = interfaceStore()
|
||||||
|
const profile = profileStore()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
peerId: String,
|
peerId: String,
|
||||||
@ -24,7 +26,16 @@ const props = defineProps({
|
|||||||
const emit = defineEmits(['close'])
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
const selectedPeer = computed(() => {
|
const selectedPeer = computed(() => {
|
||||||
return peers.Find(props.peerId)
|
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(() => {
|
const selectedInterface = computed(() => {
|
||||||
@ -60,8 +71,6 @@ const formData = ref(freshPeer())
|
|||||||
|
|
||||||
watch(() => props.visible, async (newValue, oldValue) => {
|
watch(() => props.visible, async (newValue, oldValue) => {
|
||||||
if (oldValue === false && newValue === true) { // if modal is shown
|
if (oldValue === false && newValue === true) { // if modal is shown
|
||||||
console.log(selectedInterface.value)
|
|
||||||
console.log(selectedPeer.value)
|
|
||||||
if (!selectedPeer.value) {
|
if (!selectedPeer.value) {
|
||||||
await peers.PreparePeer(selectedInterface.value.Identifier)
|
await peers.PreparePeer(selectedInterface.value.Identifier)
|
||||||
|
|
||||||
@ -149,7 +158,7 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(() => formData.value.IgnoreGlobalSettings, async (newValue, oldValue) => {
|
watch(() => formData.value.IgnoreGlobalSettings, async (newValue, oldValue) => {
|
||||||
@ -166,14 +175,14 @@ watch(() => formData.value.IgnoreGlobalSettings, async (newValue, oldValue) => {
|
|||||||
formData.value.PostUp.Overridable = !newValue
|
formData.value.PostUp.Overridable = !newValue
|
||||||
formData.value.PreDown.Overridable = !newValue
|
formData.value.PreDown.Overridable = !newValue
|
||||||
formData.value.PostDown.Overridable = !newValue
|
formData.value.PostDown.Overridable = !newValue
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(() => formData.value.Disabled, async (newValue, oldValue) => {
|
watch(() => formData.value.Disabled, async (newValue, oldValue) => {
|
||||||
if (oldValue && !newValue && formData.value.ExpiresAt) {
|
if (oldValue && !newValue && formData.value.ExpiresAt) {
|
||||||
formData.value.ExpiresAt = "" // reset expiry date
|
formData.value.ExpiresAt = "" // reset expiry date
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
@ -184,7 +193,7 @@ function close() {
|
|||||||
function handleChangeAddresses(tags) {
|
function handleChangeAddresses(tags) {
|
||||||
let validInput = true
|
let validInput = true
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
if(isCidr(tag) === 0) {
|
if (isCidr(tag) === 0) {
|
||||||
validInput = false
|
validInput = false
|
||||||
notify({
|
notify({
|
||||||
title: "Invalid CIDR",
|
title: "Invalid CIDR",
|
||||||
@ -193,7 +202,7 @@ function handleChangeAddresses(tags) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if(validInput) {
|
if (validInput) {
|
||||||
formData.value.Addresses = tags
|
formData.value.Addresses = tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,7 +210,7 @@ function handleChangeAddresses(tags) {
|
|||||||
function handleChangeAllowedIPs(tags) {
|
function handleChangeAllowedIPs(tags) {
|
||||||
let validInput = true
|
let validInput = true
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
if(isCidr(tag) === 0) {
|
if (isCidr(tag) === 0) {
|
||||||
validInput = false
|
validInput = false
|
||||||
notify({
|
notify({
|
||||||
title: "Invalid CIDR",
|
title: "Invalid CIDR",
|
||||||
@ -210,7 +219,7 @@ function handleChangeAllowedIPs(tags) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if(validInput) {
|
if (validInput) {
|
||||||
formData.value.AllowedIPs.Value = tags
|
formData.value.AllowedIPs.Value = tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,7 +227,7 @@ function handleChangeAllowedIPs(tags) {
|
|||||||
function handleChangeExtraAllowedIPs(tags) {
|
function handleChangeExtraAllowedIPs(tags) {
|
||||||
let validInput = true
|
let validInput = true
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
if(isCidr(tag) === 0) {
|
if (isCidr(tag) === 0) {
|
||||||
validInput = false
|
validInput = false
|
||||||
notify({
|
notify({
|
||||||
title: "Invalid CIDR",
|
title: "Invalid CIDR",
|
||||||
@ -227,7 +236,7 @@ function handleChangeExtraAllowedIPs(tags) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if(validInput) {
|
if (validInput) {
|
||||||
formData.value.ExtraAllowedIPs = tags
|
formData.value.ExtraAllowedIPs = tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -235,7 +244,7 @@ function handleChangeExtraAllowedIPs(tags) {
|
|||||||
function handleChangeDns(tags) {
|
function handleChangeDns(tags) {
|
||||||
let validInput = true
|
let validInput = true
|
||||||
tags.forEach(tag => {
|
tags.forEach(tag => {
|
||||||
if(!isIP(tag)) {
|
if (!isIP(tag)) {
|
||||||
validInput = false
|
validInput = false
|
||||||
notify({
|
notify({
|
||||||
title: "Invalid IP",
|
title: "Invalid IP",
|
||||||
@ -244,7 +253,7 @@ function handleChangeDns(tags) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if(validInput) {
|
if (validInput) {
|
||||||
formData.value.Dns.Value = tags
|
formData.value.Dns.Value = tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -255,14 +264,14 @@ function handleChangeDnsSearch(tags) {
|
|||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
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)
|
||||||
} else {
|
} else {
|
||||||
await peers.CreatePeer(selectedInterface.value.Identifier, formData.value)
|
await peers.CreatePeer(selectedInterface.value.Identifier, formData.value)
|
||||||
}
|
}
|
||||||
close()
|
close()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
// console.log(e)
|
||||||
notify({
|
notify({
|
||||||
title: "Failed to save peer!",
|
title: "Failed to save peer!",
|
||||||
text: e.toString(),
|
text: e.toString(),
|
||||||
@ -276,7 +285,7 @@ async function del() {
|
|||||||
await peers.DeletePeer(selectedPeer.value.Identifier)
|
await peers.DeletePeer(selectedPeer.value.Identifier)
|
||||||
close()
|
close()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
// console.log(e)
|
||||||
notify({
|
notify({
|
||||||
title: "Failed to delete peer!",
|
title: "Failed to delete peer!",
|
||||||
text: e.toString(),
|
text: e.toString(),
|
||||||
@ -294,87 +303,86 @@ async function del() {
|
|||||||
<legend class="mt-4">{{ $t('modals.peer-edit.header-general') }}</legend>
|
<legend class="mt-4">{{ $t('modals.peer-edit.header-general') }}</legend>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.display-name.label') }}</label>
|
<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">
|
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.display-name.placeholder')"
|
||||||
|
v-model="formData.DisplayName">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.linked-user.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peer-edit.linked-user.label') }}</label>
|
||||||
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.linked-user.placeholder')" v-model="formData.UserIdentifier">
|
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.linked-user.placeholder')"
|
||||||
|
v-model="formData.UserIdentifier">
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="mt-4">{{ $t('modals.peer-edit.header-crypto') }}</legend>
|
<legend class="mt-4">{{ $t('modals.peer-edit.header-crypto') }}</legend>
|
||||||
<div class="form-group" v-if="selectedInterface.Mode==='server'">
|
<div class="form-group" v-if="selectedInterface.Mode === 'server'">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.private-key.label') }}</label>
|
<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">
|
<input type="email" class="form-control" :placeholder="$t('modals.peer-edit.private-key.placeholder')" required
|
||||||
|
v-model="formData.PrivateKey">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.public-key.label') }}</label>
|
<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">
|
<input type="email" class="form-control" :placeholder="$t('modals.peer-edit.public-key.placeholder')" required
|
||||||
|
v-model="formData.PublicKey">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.preshared-key.label') }}</label>
|
<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">
|
<input type="email" class="form-control" :placeholder="$t('modals.peer-edit.preshared-key.placeholder')"
|
||||||
|
v-model="formData.PresharedKey">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" v-if="formData.Mode==='client'">
|
<div class="form-group" v-if="formData.Mode === 'client'">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.endpoint-public-key.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peer-edit.endpoint-public-key.label') }}</label>
|
||||||
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.endpoint-public-key.placeholder')" v-model="formData.EndpointPublicKey.Value">
|
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.endpoint-public-key.placeholder')"
|
||||||
|
v-model="formData.EndpointPublicKey.Value">
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend class="mt-4">{{ $t('modals.peer-edit.header-network') }}</legend>
|
<legend class="mt-4">{{ $t('modals.peer-edit.header-network') }}</legend>
|
||||||
<div class="form-group" v-if="selectedInterface.Mode==='client'">
|
<div class="form-group" v-if="selectedInterface.Mode === 'client'">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.endpoint.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peer-edit.endpoint.label') }}</label>
|
||||||
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.endpoint.placeholder')" v-model="formData.Endpoint.Value">
|
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.endpoint.placeholder')"
|
||||||
|
v-model="formData.Endpoint.Value">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.ip.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peer-edit.ip.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.Addresses"
|
<vue3-tags-input class="form-control" :tags="formData.Addresses"
|
||||||
:placeholder="$t('modals.peer-edit.ip.placeholder')"
|
:placeholder="$t('modals.peer-edit.ip.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
:validate="validateCIDR" @on-tags-changed="handleChangeAddresses" />
|
||||||
:validate="validateCIDR"
|
|
||||||
@on-tags-changed="handleChangeAddresses"/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.allowed-ip.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peer-edit.allowed-ip.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.AllowedIPs.Value"
|
<vue3-tags-input class="form-control" :tags="formData.AllowedIPs.Value"
|
||||||
:placeholder="$t('modals.peer-edit.allowed-ip.placeholder')"
|
:placeholder="$t('modals.peer-edit.allowed-ip.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
:validate="validateCIDR" @on-tags-changed="handleChangeAllowedIPs" />
|
||||||
:validate="validateCIDR"
|
|
||||||
@on-tags-changed="handleChangeAllowedIPs"/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.extra-allowed-ip.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peer-edit.extra-allowed-ip.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.ExtraAllowedIPs"
|
<vue3-tags-input class="form-control" :tags="formData.ExtraAllowedIPs"
|
||||||
:placeholder="$t('modals.peer-edit.extra-allowed-ip.placeholder')"
|
:placeholder="$t('modals.peer-edit.extra-allowed-ip.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
:validate="validateCIDR" @on-tags-changed="handleChangeExtraAllowedIPs" />
|
||||||
:validate="validateCIDR"
|
|
||||||
@on-tags-changed="handleChangeExtraAllowedIPs"/>
|
|
||||||
<small class="form-text text-muted">{{ $t('modals.peer-edit.extra-allowed-ip.description') }}</small>
|
<small class="form-text text-muted">{{ $t('modals.peer-edit.extra-allowed-ip.description') }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.dns.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peer-edit.dns.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.Dns.Value"
|
<vue3-tags-input class="form-control" :tags="formData.Dns.Value"
|
||||||
:placeholder="$t('modals.peer-edit.dns.placeholder')"
|
:placeholder="$t('modals.peer-edit.dns.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
:validate="validateIP" @on-tags-changed="handleChangeDns" />
|
||||||
:validate="validateIP"
|
|
||||||
@on-tags-changed="handleChangeDns"/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div hidden class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.dns-search.label') }}</label>
|
<label class="form-label mt-4">{{ $t('modals.peer-edit.dns-search.label') }}</label>
|
||||||
<vue3-tags-input class="form-control" :tags="formData.DnsSearch.Value"
|
<vue3-tags-input class="form-control" :tags="formData.DnsSearch.Value"
|
||||||
:placeholder="$t('modals.peer-edit.dns-search.label')"
|
:placeholder="$t('modals.peer-edit.dns-search.label')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
:validate="validateDomain" @on-tags-changed="handleChangeDnsSearch" />
|
||||||
:validate="validateDomain"
|
|
||||||
@on-tags-changed="handleChangeDnsSearch"/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group col-md-6">
|
<div class="form-group col-md-6">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.keep-alive.label') }}</label>
|
<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">
|
<input type="number" class="form-control" :placeholder="$t('modals.peer-edit.keep-alive.label')"
|
||||||
|
v-model="formData.PersistentKeepalive.Value">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col-md-6">
|
<div class="form-group col-md-6">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.mtu.label') }}</label>
|
<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">
|
<input type="number" class="form-control" :placeholder="$t('modals.peer-edit.mtu.label')"
|
||||||
|
v-model="formData.Mtu.Value">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@ -382,19 +390,23 @@ async function del() {
|
|||||||
<legend class="mt-4">{{ $t('modals.peer-edit.header-hooks') }}</legend>
|
<legend class="mt-4">{{ $t('modals.peer-edit.header-hooks') }}</legend>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.pre-up.label') }}</label>
|
<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>
|
<textarea v-model="formData.PreUp.Value" class="form-control" rows="2"
|
||||||
|
:placeholder="$t('modals.peer-edit.pre-up.placeholder')"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.post-up.label') }}</label>
|
<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>
|
<textarea v-model="formData.PostUp.Value" class="form-control" rows="2"
|
||||||
|
:placeholder="$t('modals.peer-edit.post-up.placeholder')"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.pre-down.label') }}</label>
|
<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>
|
<textarea v-model="formData.PreDown.Value" class="form-control" rows="2"
|
||||||
|
:placeholder="$t('modals.peer-edit.pre-down.placeholder')"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.post-down.label') }}</label>
|
<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>
|
<textarea v-model="formData.PostDown.Value" class="form-control" rows="2"
|
||||||
|
:placeholder="$t('modals.peer-edit.post-down.placeholder')"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@ -403,7 +415,7 @@ async function del() {
|
|||||||
<div class="form-group col-md-6">
|
<div class="form-group col-md-6">
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox" v-model="formData.Disabled">
|
<input class="form-check-input" type="checkbox" v-model="formData.Disabled">
|
||||||
<label class="form-check-label" >{{ $t('modals.peer-edit.disabled.label') }}</label>
|
<label class="form-check-label">{{ $t('modals.peer-edit.disabled.label') }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox" v-model="formData.IgnoreGlobalSettings">
|
<input class="form-check-input" type="checkbox" v-model="formData.IgnoreGlobalSettings">
|
||||||
@ -412,14 +424,16 @@ async function del() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group col-md-6">
|
<div class="form-group col-md-6">
|
||||||
<label class="form-label">{{ $t('modals.peer-edit.expires-at.label') }}</label>
|
<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">
|
<input type="date" pattern="\d{4}-\d{2}-\d{2}" class="form-control" min="2023-01-01"
|
||||||
|
v-model="formData.ExpiresAt">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</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">{{ $t('general.delete') }}</button>
|
<button v-if="props.peerId !== '#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del">{{
|
||||||
|
$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">{{ $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>
|
||||||
@ -427,5 +441,4 @@ async function del() {
|
|||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style></style>
|
||||||
</style>
|
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Modal from "./Modal.vue";
|
import Modal from "./Modal.vue";
|
||||||
import {peerStore} from "@/stores/peers";
|
import { peerStore } from "@/stores/peers";
|
||||||
import {interfaceStore} from "@/stores/interfaces";
|
import { interfaceStore } from "@/stores/interfaces";
|
||||||
import {computed, ref, watch} from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import {useI18n} from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import {freshInterface, freshPeer, freshStats} from '@/helpers/models';
|
import { freshInterface, freshPeer, freshStats } from '@/helpers/models';
|
||||||
import Prism from "vue-prism-component";
|
import Prism from "vue-prism-component";
|
||||||
import {notify} from "@kyvg/vue3-notification";
|
import { notify } from "@kyvg/vue3-notification";
|
||||||
import {settingsStore} from "@/stores/settings";
|
import { settingsStore } from "@/stores/settings";
|
||||||
|
import { profileStore } from "@/stores/profile";
|
||||||
|
import { base64_url_encode } from '@/helpers/encoding';
|
||||||
|
import { apiWrapper } from "@/helpers/fetch-wrapper";
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const settings = settingsStore()
|
const settings = settingsStore()
|
||||||
const peers = peerStore()
|
const peers = peerStore()
|
||||||
const interfaces = interfaceStore()
|
const interfaces = interfaceStore()
|
||||||
|
const profile = profileStore()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
peerId: String,
|
peerId: String,
|
||||||
@ -32,9 +36,12 @@ const selectedPeer = computed(() => {
|
|||||||
let p = peers.Find(props.peerId)
|
let p = peers.Find(props.peerId)
|
||||||
|
|
||||||
if (!p) {
|
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
|
p = freshPeer() // dummy peer to avoid 'undefined' exceptions
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return p
|
return p
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -42,9 +49,13 @@ const selectedStats = computed(() => {
|
|||||||
let s = peers.Statistics(props.peerId)
|
let s = peers.Statistics(props.peerId)
|
||||||
|
|
||||||
if (!s) {
|
if (!s) {
|
||||||
|
if (!!props.peerId || props.peerId.length) {
|
||||||
|
p = profile.Statistics(props.peerId)
|
||||||
|
} else {
|
||||||
s = freshStats() // dummy peer to avoid 'undefined' exceptions
|
s = freshStats() // dummy peer to avoid 'undefined' exceptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
return s
|
return s
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -54,7 +65,6 @@ const selectedInterface = computed(() => {
|
|||||||
if (!i) {
|
if (!i) {
|
||||||
i = freshInterface() // dummy interface to avoid 'undefined' exceptions
|
i = freshInterface() // dummy interface to avoid 'undefined' exceptions
|
||||||
}
|
}
|
||||||
|
|
||||||
return i
|
return i
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -74,7 +84,7 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
|||||||
await peers.LoadPeerConfig(selectedPeer.value.Identifier)
|
await peers.LoadPeerConfig(selectedPeer.value.Identifier)
|
||||||
configString.value = peers.configuration
|
configString.value = peers.configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
function download() {
|
function download() {
|
||||||
@ -82,8 +92,8 @@ function download() {
|
|||||||
let filename = 'WireGuard-Tunnel.conf'
|
let filename = 'WireGuard-Tunnel.conf'
|
||||||
if (selectedPeer.value.DisplayName) {
|
if (selectedPeer.value.DisplayName) {
|
||||||
filename = selectedPeer.value.DisplayName
|
filename = selectedPeer.value.DisplayName
|
||||||
.replace(/ /g,"_")
|
.replace(/ /g, "_")
|
||||||
.replace(/[^a-zA-Z0-9-_]/g,"")
|
.replace(/[^a-zA-Z0-9-_]/g, "")
|
||||||
.substring(0, 16)
|
.substring(0, 16)
|
||||||
+ ".conf"
|
+ ".conf"
|
||||||
}
|
}
|
||||||
@ -110,6 +120,13 @@ function email() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ConfigQrUrl() {
|
||||||
|
if (props.peerId.length) {
|
||||||
|
return apiWrapper.url(`/peer/config-qr/${base64_url_encode(props.peerId)}`)
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -118,25 +135,30 @@ function email() {
|
|||||||
<div class="accordion" id="peerInformation">
|
<div class="accordion" id="peerInformation">
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header">
|
<h2 class="accordion-header">
|
||||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseDetails" aria-expanded="true" aria-controls="collapseDetails">
|
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseDetails"
|
||||||
|
aria-expanded="true" aria-controls="collapseDetails">
|
||||||
{{ $t('modals.peer-view.section-info') }}
|
{{ $t('modals.peer-view.section-info') }}
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="collapseDetails" class="accordion-collapse collapse show" aria-labelledby="headingDetails" data-bs-parent="#peerInformation" style="">
|
<div id="collapseDetails" class="accordion-collapse collapse show" aria-labelledby="headingDetails"
|
||||||
|
data-bs-parent="#peerInformation" style="">
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<ul>
|
<ul>
|
||||||
<li>{{ $t('modals.peer-view.identifier') }}: {{ selectedPeer.PublicKey }}</li>
|
<li>{{ $t('modals.peer-view.identifier') }}: {{ selectedPeer.PublicKey }}</li>
|
||||||
<li>{{ $t('modals.peer-view.ip') }}: <span v-for="ip in selectedPeer.Addresses" :key="ip" class="badge rounded-pill bg-light">{{ ip }}</span></li>
|
<li>{{ $t('modals.peer-view.ip') }}: <span v-for="ip in selectedPeer.Addresses" :key="ip"
|
||||||
|
class="badge rounded-pill bg-light">{{ ip }}</span></li>
|
||||||
<li>{{ $t('modals.peer-view.user') }}: {{ selectedPeer.UserIdentifier }}</li>
|
<li>{{ $t('modals.peer-view.user') }}: {{ selectedPeer.UserIdentifier }}</li>
|
||||||
<li v-if="selectedPeer.Notes">{{ $t('modals.peer-view.notes') }}: {{ selectedPeer.Notes }}</li>
|
<li v-if="selectedPeer.Notes">{{ $t('modals.peer-view.notes') }}: {{ selectedPeer.Notes }}</li>
|
||||||
<li v-if="selectedPeer.ExpiresAt">{{ $t('modals.peer-view.expiry-status') }}: {{ selectedPeer.ExpiresAt }}</li>
|
<li v-if="selectedPeer.ExpiresAt">{{ $t('modals.peer-view.expiry-status') }}: {{
|
||||||
<li v-if="selectedPeer.Disabled">{{ $t('modals.peer-view.disabled-status') }}: {{ selectedPeer.DisabledReason }}</li>
|
selectedPeer.ExpiresAt }}</li>
|
||||||
|
<li v-if="selectedPeer.Disabled">{{ $t('modals.peer-view.disabled-status') }}: {{
|
||||||
|
selectedPeer.DisabledReason }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<img class="config-qr-img" :src="peers.ConfigQrUrl(props.peerId)" loading="lazy" alt="Configuration QR Code">
|
<img class="config-qr-img" :src="ConfigQrUrl()" loading="lazy" alt="Configuration QR Code">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -144,16 +166,20 @@ function email() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header" id="headingStatus">
|
<h2 class="accordion-header" id="headingStatus">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseStatus" aria-expanded="false" aria-controls="collapseStatus">
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseStatus" aria-expanded="false" aria-controls="collapseStatus">
|
||||||
{{ $t('modals.peer-view.section-status') }}
|
{{ $t('modals.peer-view.section-status') }}
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="collapseStatus" class="accordion-collapse collapse" aria-labelledby="headingStatus" data-bs-parent="#peerInformation" style="">
|
<div id="collapseStatus" class="accordion-collapse collapse" aria-labelledby="headingStatus"
|
||||||
|
data-bs-parent="#peerInformation" style="">
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<h4>{{ $t('modals.peer-view.traffic') }}</h4>
|
<h4>{{ $t('modals.peer-view.traffic') }}</h4>
|
||||||
<p><i class="fas fa-long-arrow-alt-down" :title="$t('modals.peer-view.download')"></i> {{ selectedStats.BytesReceived }} Bytes / <i class="fas fa-long-arrow-alt-up" :title="$t('modals.peer-view.upload')"></i> {{ selectedStats.BytesTransmitted }} Bytes</p>
|
<p><i class="fas fa-long-arrow-alt-down" :title="$t('modals.peer-view.download')"></i> {{
|
||||||
|
selectedStats.BytesReceived }} Bytes / <i class="fas fa-long-arrow-alt-up"
|
||||||
|
:title="$t('modals.peer-view.upload')"></i> {{ selectedStats.BytesTransmitted }} Bytes</p>
|
||||||
<h4>{{ $t('modals.peer-view.connection-status') }}</h4>
|
<h4>{{ $t('modals.peer-view.connection-status') }}</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li>{{ $t('modals.peer-view.pingable') }}: {{ selectedStats.IsPingable }}</li>
|
<li>{{ $t('modals.peer-view.pingable') }}: {{ selectedStats.IsPingable }}</li>
|
||||||
@ -166,13 +192,15 @@ function email() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="selectedInterface.Mode==='server'" class="accordion-item">
|
<div v-if="selectedInterface.Mode === 'server'" class="accordion-item">
|
||||||
<h2 class="accordion-header" id="headingConfig">
|
<h2 class="accordion-header" id="headingConfig">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseConfig" aria-expanded="false" aria-controls="collapseConfig">
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseConfig" aria-expanded="false" aria-controls="collapseConfig">
|
||||||
{{ $t('modals.peer-view.section-config') }}
|
{{ $t('modals.peer-view.section-config') }}
|
||||||
</button>
|
</button>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="collapseConfig" class="accordion-collapse collapse" aria-labelledby="headingConfig" data-bs-parent="#peerInformation" style="">
|
<div id="collapseConfig" class="accordion-collapse collapse" aria-labelledby="headingConfig"
|
||||||
|
data-bs-parent="#peerInformation" style="">
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
<Prism language="ini" :code="configString"></Prism>
|
<Prism language="ini" :code="configString"></Prism>
|
||||||
</div>
|
</div>
|
||||||
@ -182,18 +210,17 @@ function email() {
|
|||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex-fill text-start">
|
<div class="flex-fill text-start">
|
||||||
<button @click.prevent="download" type="button" class="btn btn-primary me-1">{{ $t('modals.peer-view.button-download') }}</button>
|
<button @click.prevent="download" type="button" class="btn btn-primary me-1">{{
|
||||||
<button @click.prevent="email" type="button" class="btn btn-primary me-1">{{ $t('modals.peer-view.button-email') }}</button>
|
$t('modals.peer-view.button-download') }}</button>
|
||||||
|
<button @click.prevent="email" hidden type="button" class="btn btn-primary me-1">{{
|
||||||
|
$t('modals.peer-view.button-email') }}</button>
|
||||||
</div>
|
</div>
|
||||||
<button @click.prevent="close" type="button" class="btn btn-secondary">{{ $t('general.close') }}</button>
|
<button @click.prevent="close" type="button" class="btn btn-secondary">{{ $t('general.close') }}</button>
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal></template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
<style>.config-qr-img {
|
||||||
.config-qr-img {
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}</style>
|
||||||
</style>
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// src/lang/index.js
|
// src/lang/index.js
|
||||||
import de from './translations/de.json';
|
import de from './translations/de.json';
|
||||||
|
import ru from './translations/ru.json';
|
||||||
import en from './translations/en.json';
|
import en from './translations/en.json';
|
||||||
import {createI18n} from "vue-i18n";
|
import {createI18n} from "vue-i18n";
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ const i18n = createI18n({
|
|||||||
fallbackLocale: "en", // set fallback locale
|
fallbackLocale: "en", // set fallback locale
|
||||||
messages: {
|
messages: {
|
||||||
"de": de,
|
"de": de,
|
||||||
|
"ru": ru,
|
||||||
"en": en
|
"en": en
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -45,7 +45,8 @@
|
|||||||
"box-header": "WireGuard Installation",
|
"box-header": "WireGuard Installation",
|
||||||
"headline": "Installation",
|
"headline": "Installation",
|
||||||
"content": "Installation instructions for client software can be found on the official WireGuard website.",
|
"content": "Installation instructions for client software can be found on the official WireGuard website.",
|
||||||
"btn": "Open Instructions"
|
"btn": "Open Instructions",
|
||||||
|
"button": "Open Instructions"
|
||||||
},
|
},
|
||||||
"about-wg": {
|
"about-wg": {
|
||||||
"box-header": "About WireGuard",
|
"box-header": "About WireGuard",
|
||||||
|
489
frontend/src/lang/translations/ru.json
Normal file
489
frontend/src/lang/translations/ru.json
Normal file
@ -0,0 +1,489 @@
|
|||||||
|
{
|
||||||
|
"general": {
|
||||||
|
"pagination": {
|
||||||
|
"size": "Количество элементов",
|
||||||
|
"all": "Все (медленно)"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Поиск...",
|
||||||
|
"button": "Поиск"
|
||||||
|
},
|
||||||
|
"select-all": "Выбрать все",
|
||||||
|
"yes": "Да",
|
||||||
|
"no": "Нет",
|
||||||
|
"cancel": "Отмена",
|
||||||
|
"close": "Закрыть",
|
||||||
|
"save": "Сохранить",
|
||||||
|
"delete": "Удалить"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"headline": "Пожалуйста, войдите в систему",
|
||||||
|
"username": {
|
||||||
|
"label": "Имя пользователя",
|
||||||
|
"placeholder": "Пожалуйста, введите ваше имя пользователя"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"label": "Пароль",
|
||||||
|
"placeholder": "Пожалуйста, введите ваш пароль"
|
||||||
|
},
|
||||||
|
"button": "Войти"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"home": "Главная",
|
||||||
|
"interfaces": "Интерфейсы",
|
||||||
|
"users": "Пользователи",
|
||||||
|
"lang": "Сменить язык",
|
||||||
|
"profile": "Мой профиль",
|
||||||
|
"login": "Вход",
|
||||||
|
"logout": "Выход"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"headline": "Портал VPN WireGuard®",
|
||||||
|
"info-headline": "Дополнительная информация",
|
||||||
|
"abstract": "WireGuard® - это чрезвычайно простой, но быстрый и современный VPN, использующий передовую криптографию. Он стремится быть быстрее, проще, компактнее и полезнее, чем IPsec, избегая при этом значительных сложностей. Он предназначен для значительного повышения производительности по сравнению с OpenVPN.",
|
||||||
|
"installation": {
|
||||||
|
"box-header": "Установка WireGuard",
|
||||||
|
"headline": "Установка",
|
||||||
|
"content": "Инструкции по установке клиентского программного обеспечения можно найти на официальном сайте WireGuard.",
|
||||||
|
"btn": "Открыть инструкции",
|
||||||
|
"button": "Открыть инструкции"
|
||||||
|
},
|
||||||
|
"about-wg": {
|
||||||
|
"box-header": "О WireGuard",
|
||||||
|
"headline": "О программе",
|
||||||
|
"content": "WireGuard® - это чрезвычайно простой, но быстрый и современный VPN, использующий передовую криптографию.",
|
||||||
|
"button": "Подробнее"
|
||||||
|
},
|
||||||
|
"about-portal": {
|
||||||
|
"box-header": "О портале WireGuard",
|
||||||
|
"headline": "Портал WireGuard",
|
||||||
|
"content": "Портал WireGuard - это простой веб-портал для настройки WireGuard.",
|
||||||
|
"button": "Подробнее"
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"headline": "VPN Профили",
|
||||||
|
"abstract": "Вы можете получить доступ и загрузить свои личные конфигурации VPN через свой пользовательский профиль.",
|
||||||
|
"content": "Чтобы найти все сконфигурированные профили, нажмите на кнопку ниже.",
|
||||||
|
"button": "Открыть мой профиль"
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"headline": "Административная зона",
|
||||||
|
"abstract": "В административной зоне вы можете управлять узлами и серверным интерфейсом WireGuard, а также пользователями, которым разрешен вход в портал WireGuard.",
|
||||||
|
"content": "",
|
||||||
|
"button-admin": "Открыть администрирование сервера",
|
||||||
|
"button-user": "Открыть администрирование пользователей"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interfaces": {
|
||||||
|
"headline": "Администрирование интерфейсов",
|
||||||
|
"headline-peers": "Текущие VPN пиры",
|
||||||
|
"headline-endpoints": "Текущие конечные точки",
|
||||||
|
"no-interface": {
|
||||||
|
"default-selection": "Интерфейсы отсутствуют",
|
||||||
|
"headline": "Интерфейсы не найдены...",
|
||||||
|
"abstract": "Нажмите на кнопку со знаком плюса выше, чтобы создать новый интерфейс WireGuard."
|
||||||
|
},
|
||||||
|
"no-peer": {
|
||||||
|
"headline": "Пиры отсутствуют",
|
||||||
|
"abstract": "В настоящее время для выбранного интерфейса WireGuard нет доступных пиров."
|
||||||
|
},
|
||||||
|
"table-heading": {
|
||||||
|
"name": "Имя",
|
||||||
|
"user": "Пользователь",
|
||||||
|
"ip": "IP-адреса",
|
||||||
|
"endpoint": "Конечная точка",
|
||||||
|
"status": "Статус"
|
||||||
|
},
|
||||||
|
"interface": {
|
||||||
|
"headline": "Статус интерфейса для",
|
||||||
|
"mode": "режим",
|
||||||
|
"key": "Публичный ключ",
|
||||||
|
"endpoint": "Публичная конечная точка",
|
||||||
|
"port": "Порт прослушивания",
|
||||||
|
"peers": "Активные пиры",
|
||||||
|
"total-peers": "Всего пиров",
|
||||||
|
"endpoints": "Активные конечные точки",
|
||||||
|
"total-endpoints": "Всего конечных точек",
|
||||||
|
"ip": "IP-адрес",
|
||||||
|
"default-allowed-ip": "Разрешенные IP по умолчанию",
|
||||||
|
"dns": "DNS-серверы",
|
||||||
|
"mtu": "MTU",
|
||||||
|
"default-keep-alive": "Интервал поддержания активности по умолчанию",
|
||||||
|
"button-show-config": "Показать конфигурацию",
|
||||||
|
"button-download-config": "Скачать конфигурацию",
|
||||||
|
"button-store-config": "Сохранить конфигурацию для wg-quick",
|
||||||
|
"button-edit": "Редактировать интерфейс"
|
||||||
|
},
|
||||||
|
"button-add-interface": "Добавить интерфейс",
|
||||||
|
"button-add-peer": "Добавить пира",
|
||||||
|
"button-add-peers": "Добавить несколько пиров",
|
||||||
|
"button-show-peer": "Показать пира",
|
||||||
|
"button-edit-peer": "Редактировать пира",
|
||||||
|
"peer-disabled": "Пир отключен, причина:",
|
||||||
|
"peer-expiring": "Пир истекает в",
|
||||||
|
"peer-connected": "Подключено",
|
||||||
|
"peer-not-connected": "Не подключено",
|
||||||
|
"peer-handshake": "Последнее рукопожатие:"
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"headline": "Администрирование пользователей",
|
||||||
|
"table-heading": {
|
||||||
|
"id": "ID",
|
||||||
|
"email": "Электронная почта",
|
||||||
|
"firstname": "Имя",
|
||||||
|
"lastname": "Фамилия",
|
||||||
|
"source": "Источник",
|
||||||
|
"peers": "Пиры",
|
||||||
|
"admin": "Админ"
|
||||||
|
},
|
||||||
|
"no-user": {
|
||||||
|
"headline": "Пользователи отсутствуют",
|
||||||
|
"abstract": "В настоящее время в портале WireGuard не зарегистрировано ни одного пользователя."
|
||||||
|
},
|
||||||
|
"button-add-user": "Добавить пользователя",
|
||||||
|
"button-show-user": "Показать пользователя",
|
||||||
|
"button-edit-user": "Редактировать пользователя",
|
||||||
|
"user-disabled": "Пользователь отключен, причина:",
|
||||||
|
"user-locked": "Учетная запись заблокирована, причина:",
|
||||||
|
"admin": "Пользователь имеет права администратора",
|
||||||
|
"no-admin": "Пользователь не имеет прав администратора"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"headline": "Мои VPN пиры",
|
||||||
|
"table-heading": {
|
||||||
|
"name": "Имя",
|
||||||
|
"ip": "IP-адреса",
|
||||||
|
"stats": "Статус",
|
||||||
|
"interface": "Интерфейс сервера"
|
||||||
|
},
|
||||||
|
"no-peer": {
|
||||||
|
"headline": "Пиров нет",
|
||||||
|
"abstract": "В настоящее время у вашего профиля пользователя нет связанных пиров."
|
||||||
|
},
|
||||||
|
"peer-connected": "Подключено",
|
||||||
|
"button-add-peer": "Добавить пира",
|
||||||
|
"button-show-peer": "Показать пира",
|
||||||
|
"button-edit-peer": "Редактировать пира"
|
||||||
|
},
|
||||||
|
"modals": {
|
||||||
|
"user-view": {
|
||||||
|
"headline": "Учетная запись пользователя:",
|
||||||
|
"tab-user": "Информация",
|
||||||
|
"tab-peers": "Пиры",
|
||||||
|
"headline-info": "Информация о пользователе:",
|
||||||
|
"headline-notes": "Заметки:",
|
||||||
|
"email": "Электронная почта",
|
||||||
|
"firstname": "Имя",
|
||||||
|
"lastname": "Фамилия",
|
||||||
|
"phone": "Номер телефона",
|
||||||
|
"department": "Отдел",
|
||||||
|
"disabled": "Учетная запись отключена",
|
||||||
|
"locked": "Учетная запись заблокирована",
|
||||||
|
"no-peers": "У пользователя нет связанных пиров.",
|
||||||
|
"peers": {
|
||||||
|
"name": "Имя",
|
||||||
|
"interface": "Интерфейс",
|
||||||
|
"ip": "IP-адреса"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user-edit": {
|
||||||
|
"headline-edit": "Редактировать пользователя:",
|
||||||
|
"headline-new": "Новый пользователь",
|
||||||
|
"header-general": "Общее",
|
||||||
|
"header-personal": "Информация о пользователе",
|
||||||
|
"header-notes": "Заметки",
|
||||||
|
"header-state": "Состояние",
|
||||||
|
"identifier": {
|
||||||
|
"label": "Идентификатор",
|
||||||
|
"placeholder": "Уникальный идентификатор пользователя"
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"label": "Источник",
|
||||||
|
"placeholder": "Источник пользователя"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"label": "Пароль",
|
||||||
|
"placeholder": "Надежный пароль",
|
||||||
|
"description": "Оставьте это поле пустым, чтобы сохранить текущий пароль."
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"label": "Электронная почта",
|
||||||
|
"placeholder": "Адрес электронной почты"
|
||||||
|
},
|
||||||
|
"phone": {
|
||||||
|
"label": "Телефон",
|
||||||
|
"placeholder": "Номер телефона"
|
||||||
|
},
|
||||||
|
"department": {
|
||||||
|
"label": "Отдел",
|
||||||
|
"placeholder": "Отдел"
|
||||||
|
},
|
||||||
|
"firstname": {
|
||||||
|
"label": "Имя",
|
||||||
|
"placeholder": "Имя"
|
||||||
|
},
|
||||||
|
"lastname": {
|
||||||
|
"label": "Фамилия",
|
||||||
|
"placeholder": "Фамилия"
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"label": "Заметки",
|
||||||
|
"placeholder": ""
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"label": "Отключен (нет возможности подключения к WireGuard и входа в систему)"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"label": "Заблокирован (вход в систему невозможен, подключения WireGuard работают)"
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"label": "Является администратором"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interface-view": {
|
||||||
|
"headline": "Конфигурация интерфейса:"
|
||||||
|
},
|
||||||
|
"interface-edit": {
|
||||||
|
"headline-edit": "Редактировать интерфейс:",
|
||||||
|
"headline-new": "Новый интерфейс",
|
||||||
|
"tab-interface": "Интерфейс",
|
||||||
|
"tab-peerdef": "Настройки пира по умолчанию",
|
||||||
|
"header-general": "Общие",
|
||||||
|
"header-network": "Сеть",
|
||||||
|
"header-crypto": "Криптография",
|
||||||
|
"header-hooks": "Хуки интерфейса",
|
||||||
|
"header-peer-hooks": "Хуки",
|
||||||
|
"header-state": "Состояние",
|
||||||
|
"identifier": {
|
||||||
|
"label": "Идентификатор",
|
||||||
|
"placeholder": "Уникальный идентификатор интерфейса"
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"label": "Режим интерфейса",
|
||||||
|
"server": "Режим сервера",
|
||||||
|
"client": "Режим клиента",
|
||||||
|
"any": "Неизвестный режим"
|
||||||
|
},
|
||||||
|
"display-name": {
|
||||||
|
"label": "Отображаемое имя",
|
||||||
|
"placeholder": "Описательное имя для интерфейса"
|
||||||
|
},
|
||||||
|
"private-key": {
|
||||||
|
"label": "Приватный ключ",
|
||||||
|
"placeholder": "Приватный ключ"
|
||||||
|
},
|
||||||
|
"public-key": {
|
||||||
|
"label": "Публичный ключ",
|
||||||
|
"placeholder": "Публичный ключ"
|
||||||
|
},
|
||||||
|
"ip": {
|
||||||
|
"label": "IP-адреса",
|
||||||
|
"placeholder": "IP-адреса (в формате CIDR)"
|
||||||
|
},
|
||||||
|
"listen-port": {
|
||||||
|
"label": "Порт прослушивания",
|
||||||
|
"placeholder": "Порт для прослушивания"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"label": "DNS-сервер",
|
||||||
|
"placeholder": "Используемые DNS-серверы"
|
||||||
|
},
|
||||||
|
"dns-search": {
|
||||||
|
"label": "Поисковые домены DNS",
|
||||||
|
"placeholder": "Префиксы поиска DNS"
|
||||||
|
},
|
||||||
|
"mtu": {
|
||||||
|
"label": "MTU",
|
||||||
|
"placeholder": "MTU интерфейса (0 = использовать значение по умолчанию)"
|
||||||
|
},
|
||||||
|
"firewall-mark": {
|
||||||
|
"label": "Метка брандмауэра",
|
||||||
|
"placeholder": "Метка брандмауэра, применяемая к исходящему трафику (0 = автоматически)"
|
||||||
|
},
|
||||||
|
"routing-table": {
|
||||||
|
"label": "Таблица маршрутизации",
|
||||||
|
"placeholder": "ID таблицы маршрутизации",
|
||||||
|
"description": "Особые случаи: off = не управлять маршрутами, 0 = автоматически"
|
||||||
|
},
|
||||||
|
"pre-up": {
|
||||||
|
"label": "Pre-Up",
|
||||||
|
"placeholder": "Одна или несколько команд bash, разделенных ;"
|
||||||
|
},
|
||||||
|
"post-up": {
|
||||||
|
"label": "Post-Up",
|
||||||
|
"placeholder": "Одна или несколько команд bash, разделенных ;"
|
||||||
|
},
|
||||||
|
"pre-down": {
|
||||||
|
"label": "Pre-Down",
|
||||||
|
"placeholder": "Одна или несколько команд bash, разделенных ;"
|
||||||
|
},
|
||||||
|
"post-down": {
|
||||||
|
"label": "Post-Down",
|
||||||
|
"placeholder": "Одна или несколько команд bash, разделенных ;"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"label": "Интерфейс отключен"
|
||||||
|
},
|
||||||
|
"save-config": {
|
||||||
|
"label": "Автоматически сохранять конфигурацию wg-quick"
|
||||||
|
},
|
||||||
|
"defaults": {
|
||||||
|
"endpoint": {
|
||||||
|
"label": "Адрес конечной точки",
|
||||||
|
"placeholder": "Адрес конечной точки",
|
||||||
|
"description": "Адрес конечной точки, к которой будут подключаться пиры."
|
||||||
|
},
|
||||||
|
"networks": {
|
||||||
|
"label": "IP-сети",
|
||||||
|
"placeholder": "Сетевые адреса",
|
||||||
|
"description": "Пиры будут получать IP-адреса из этих подсетей."
|
||||||
|
},
|
||||||
|
"allowed-ip": {
|
||||||
|
"label": "Разрешенные IP-адреса",
|
||||||
|
"placeholder": "Разрешенные IP-адреса по умолчанию"
|
||||||
|
},
|
||||||
|
"mtu": {
|
||||||
|
"label": "MTU",
|
||||||
|
"placeholder": "MTU клиента (0 = использовать значение по умолчанию)"
|
||||||
|
},
|
||||||
|
"keep-alive": {
|
||||||
|
"label": "Интервал поддержания активности",
|
||||||
|
"placeholder": "Постоянное поддержание активности (0 = значение по умолчанию)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"button-apply-defaults": "Применить настройки пира по умолчанию"
|
||||||
|
},
|
||||||
|
"peer-view": {
|
||||||
|
"headline-peer": "Пир:",
|
||||||
|
"headline-endpoint": "Конечная точка:",
|
||||||
|
"section-info": "Информация о пире",
|
||||||
|
"section-status": "Текущий статус",
|
||||||
|
"section-config": "Конфигурация",
|
||||||
|
"identifier": "Идентификатор",
|
||||||
|
"ip": "IP-адреса",
|
||||||
|
"user": "Связанный пользователь",
|
||||||
|
"notes": "Заметки",
|
||||||
|
"expiry-status": "Истекает в",
|
||||||
|
"disabled-status": "Отключено в",
|
||||||
|
"traffic": "Трафик",
|
||||||
|
"connection-status": "Статус соединения",
|
||||||
|
"upload": "Загружено байт (от сервера к пиру)",
|
||||||
|
"download": "Скачано байт (от пира к серверу)",
|
||||||
|
"pingable": "Доступность пинга",
|
||||||
|
"handshake": "Последнее рукопожатие",
|
||||||
|
"connected-since": "Подключен с",
|
||||||
|
"endpoint": "Конечная точка",
|
||||||
|
"button-download": "Скачать конфигурацию",
|
||||||
|
"button-email": "Отправить конфигурацию по электронной почте"
|
||||||
|
},
|
||||||
|
"peer-edit": {
|
||||||
|
"headline-edit-peer": "Редактировать пира:",
|
||||||
|
"headline-edit-endpoint": "Редактировать конечную точку:",
|
||||||
|
"headline-new-peer": "Создать пира",
|
||||||
|
"headline-new-endpoint": "Создать конечную точку",
|
||||||
|
"header-general": "Общее",
|
||||||
|
"header-network": "Сеть",
|
||||||
|
"header-crypto": "Криптография",
|
||||||
|
"header-hooks": "Хуки (Выполняются на пире)",
|
||||||
|
"header-state": "Состояние",
|
||||||
|
"display-name": {
|
||||||
|
"label": "Отображаемое имя",
|
||||||
|
"placeholder": "Описательное имя для пира"
|
||||||
|
},
|
||||||
|
"linked-user": {
|
||||||
|
"label": "Связанный пользователь",
|
||||||
|
"placeholder": "Учетная запись пользователя, которой принадлежит этот пир"
|
||||||
|
},
|
||||||
|
"private-key": {
|
||||||
|
"label": "Приватный ключ",
|
||||||
|
"placeholder": "Приватный ключ"
|
||||||
|
},
|
||||||
|
"public-key": {
|
||||||
|
"label": "Публичный ключ",
|
||||||
|
"placeholder": "Публичный ключ"
|
||||||
|
},
|
||||||
|
"preshared-key": {
|
||||||
|
"label": "Предварительно разделяемый ключ",
|
||||||
|
"placeholder": "Необязательный предварительно разделяемый ключ"
|
||||||
|
},
|
||||||
|
"endpoint-public-key": {
|
||||||
|
"label": "Публичный ключ конечной точки",
|
||||||
|
"placeholder": "Публичный ключ удаленной конечной точки"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"label": "Адрес конечной точки",
|
||||||
|
"placeholder": "Адрес удаленной конечной точки"
|
||||||
|
},
|
||||||
|
"ip": {
|
||||||
|
"label": "IP-адреса",
|
||||||
|
"placeholder": "IP-адреса (в формате CIDR)"
|
||||||
|
},
|
||||||
|
"allowed-ip": {
|
||||||
|
"label": "Разрешенные IP-адреса",
|
||||||
|
"placeholder": "Разрешенные IP-адреса (в формате CIDR)"
|
||||||
|
},
|
||||||
|
"extra-allowed-ip": {
|
||||||
|
"label": "Дополнительно разрешенные IP-адреса",
|
||||||
|
"placeholder": "Дополнительные разрешенные IP-адреса (на стороне сервера)",
|
||||||
|
"description": "Эти IP-адреса будут добавлены в удаленный интерфейс WireGuard как разрешенные IP-адреса."
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"label": "DNS Server",
|
||||||
|
"placeholder": "The DNS servers that should be used"
|
||||||
|
},
|
||||||
|
"dns-search": {
|
||||||
|
"label": "DNS Search Domains",
|
||||||
|
"placeholder": "DNS search prefixes"
|
||||||
|
},
|
||||||
|
"keep-alive": {
|
||||||
|
"label": "Keep Alive Interval",
|
||||||
|
"placeholder": "Persistent Keepalive (0 = default)"
|
||||||
|
},
|
||||||
|
"mtu": {
|
||||||
|
"label": "MTU",
|
||||||
|
"placeholder": "The client MTU (0 = keep default)"
|
||||||
|
},
|
||||||
|
"pre-up": {
|
||||||
|
"label": "Pre-Up",
|
||||||
|
"placeholder": "One or multiple bash commands separated by ;"
|
||||||
|
},
|
||||||
|
"post-up": {
|
||||||
|
"label": "Post-Up",
|
||||||
|
"placeholder": "One or multiple bash commands separated by ;"
|
||||||
|
},
|
||||||
|
"pre-down": {
|
||||||
|
"label": "Pre-Down",
|
||||||
|
"placeholder": "One or multiple bash commands separated by ;"
|
||||||
|
},
|
||||||
|
"post-down": {
|
||||||
|
"label": "Post-Down",
|
||||||
|
"placeholder": "One or multiple bash commands separated by ;"
|
||||||
|
},
|
||||||
|
"disabled": {
|
||||||
|
"label": "Peer Disabled"
|
||||||
|
},
|
||||||
|
"ignore-global": {
|
||||||
|
"label": "Ignore global settings"
|
||||||
|
},
|
||||||
|
"expires-at": {
|
||||||
|
"label": "Expiry date"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"peer-multi-create": {
|
||||||
|
"headline-peer": "Create multiple peers",
|
||||||
|
"headline-endpoint": "Create multiple endpoints",
|
||||||
|
"identifiers": {
|
||||||
|
"label": "User Identifiers",
|
||||||
|
"placeholder": "User Identifiers",
|
||||||
|
"description": "A user identifier (the username) for which a peer should be created."
|
||||||
|
},
|
||||||
|
"prefix": {
|
||||||
|
"headline-peer": "Peer:",
|
||||||
|
"headline-endpoint": "Endpoint:",
|
||||||
|
"label": "Display Name Prefix",
|
||||||
|
"placeholder": "The prefix",
|
||||||
|
"description": "A prefix that is added to the peers display name."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {authStore} from "@/stores/auth";
|
import { authStore } from "@/stores/auth";
|
||||||
import {RouterLink} from "vue-router";
|
import { RouterLink } from "vue-router";
|
||||||
|
|
||||||
const auth = authStore()
|
const auth = authStore()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -29,7 +29,8 @@
|
|||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
<p>{{ $t('home.admin.content') }}</p>
|
<p>{{ $t('home.admin.content') }}</p>
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
<RouterLink :to="{ name: 'interfaces' }" class="btn btn-primary btn-lg me-2">{{ $t('home.admin.button-admin') }}</RouterLink>
|
<RouterLink :to="{ name: 'interfaces' }" class="btn btn-primary btn-lg me-2">{{ $t('home.admin.button-admin') }}
|
||||||
|
</RouterLink>
|
||||||
<RouterLink :to="{ name: 'users' }" class="btn btn-primary btn-lg">{{ $t('home.admin.button-user') }}</RouterLink>
|
<RouterLink :to="{ name: 'users' }" class="btn btn-primary btn-lg">{{ $t('home.admin.button-user') }}</RouterLink>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import PeerViewModal from "../components/PeerViewModal.vue";
|
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 PeerEditModal from "@/components/PeerEditModal.vue";
|
||||||
import {settingsStore} from "@/stores/settings";
|
import { settingsStore } from "@/stores/settings";
|
||||||
|
|
||||||
const settings = settingsStore()
|
const settings = settingsStore()
|
||||||
const profile = profileStore()
|
const profile = profileStore()
|
||||||
@ -17,11 +17,12 @@ onMounted(async () => {
|
|||||||
await profile.LoadPeers()
|
await profile.LoadPeers()
|
||||||
await profile.LoadStats()
|
await profile.LoadStats()
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<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>
|
<PeerEditModal :peerId="editPeerId" :visible="editPeerId !== ''" @close="editPeerId = ''"></PeerEditModal>
|
||||||
|
|
||||||
<!-- Peer list -->
|
<!-- Peer list -->
|
||||||
<div class="mt-4 row">
|
<div class="mt-4 row">
|
||||||
@ -31,25 +32,30 @@ onMounted(async () => {
|
|||||||
<div class="col-12 col-lg-4 text-lg-end">
|
<div class="col-12 col-lg-4 text-lg-end">
|
||||||
<div class="form-group d-inline">
|
<div class="form-group d-inline">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<input v-model="profile.filter" class="form-control" :placeholder="$t('general.search.placeholder')" type="text" @keyup="profile.afterPageSizeChange">
|
<input v-model="profile.filter" class="form-control" :placeholder="$t('general.search.placeholder')" type="text"
|
||||||
<button class="input-group-text btn btn-primary" :title="$t('general.search.button')"><i class="fa-solid fa-search"></i></button>
|
@keyup="profile.afterPageSizeChange">
|
||||||
|
<button class="input-group-text btn btn-primary" :title="$t('general.search.button')"><i
|
||||||
|
class="fa-solid fa-search"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</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="#" :title="$t('general.search.button-add-peer')" @click.prevent="editPeerId='#NEW#'"><i class="fa fa-plus me-1"></i><i class="fa fa-user"></i></a>
|
<a v-if="settings.Setting('SelfProvisioning')" class="btn btn-primary ms-2" href="#"
|
||||||
|
:title="$t('general.search.button-add-peer')" @click.prevent="editPeerId = '#NEW#'"><i
|
||||||
|
class="fa fa-plus me-1"></i><i class="fa fa-user"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 table-responsive">
|
<div class="mt-2 table-responsive">
|
||||||
<div v-if="profile.CountPeers===0">
|
<div v-if="profile.CountPeers === 0">
|
||||||
<h4>{{ $t('profile.no-peer.headline') }}</h4>
|
<h4>{{ $t('profile.no-peer.headline') }}</h4>
|
||||||
<p>{{ $t('profile.no-peer.abstract') }}</p>
|
<p>{{ $t('profile.no-peer.abstract') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<table v-if="profile.CountPeers!==0" id="peerTable" class="table table-sm">
|
<table v-if="profile.CountPeers !== 0" id="peerTable" class="table table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">
|
<th scope="col">
|
||||||
<input id="flexCheckDefault" class="form-check-input" :title="$t('general.select-all')" type="checkbox" value="">
|
<input id="flexCheckDefault" class="form-check-input" :title="$t('general.select-all')" type="checkbox"
|
||||||
|
value="">
|
||||||
</th><!-- select -->
|
</th><!-- select -->
|
||||||
<th scope="col"></th><!-- status -->
|
<th scope="col"></th><!-- status -->
|
||||||
<th scope="col">{{ $t('profile.table-heading.name') }}</th>
|
<th scope="col">{{ $t('profile.table-heading.name') }}</th>
|
||||||
@ -65,25 +71,31 @@ onMounted(async () => {
|
|||||||
<input id="flexCheckDefault" class="form-check-input" type="checkbox" value="">
|
<input id="flexCheckDefault" class="form-check-input" type="checkbox" value="">
|
||||||
</th>
|
</th>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<span v-if="peer.Disabled" class="text-danger"><i class="fa fa-circle-xmark" :title="peer.DisabledReason"></i></span>
|
<span v-if="peer.Disabled" class="text-danger"><i class="fa fa-circle-xmark"
|
||||||
<span v-if="!peer.Disabled && peer.ExpiresAt" class="text-warning"><i class="fas fa-hourglass-end" :title="peer.ExpiresAt"></i></span>
|
:title="peer.DisabledReason"></i></span>
|
||||||
|
<span v-if="!peer.Disabled && peer.ExpiresAt" class="text-warning"><i class="fas fa-hourglass-end"
|
||||||
|
:title="peer.ExpiresAt"></i></span>
|
||||||
</td>
|
</td>
|
||||||
<td><span v-if="peer.DisplayName" :title="peer.Identifier">{{peer.DisplayName}}</span><span v-else :title="peer.Identifier">{{$filters.truncate(peer.Identifier, 10)}}</span></td>
|
<td><span v-if="peer.DisplayName" :title="peer.Identifier">{{ peer.DisplayName }}</span><span v-else
|
||||||
|
:title="peer.Identifier">{{ $filters.truncate(peer.Identifier, 10) }}</span></td>
|
||||||
<td>
|
<td>
|
||||||
<span v-for="ip in peer.Addresses" :key="ip" class="badge rounded-pill bg-light">{{ ip }}</span>
|
<span v-for="ip in peer.Addresses" :key="ip" class="badge rounded-pill bg-light">{{ ip }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="profile.hasStatistics">
|
<td v-if="profile.hasStatistics">
|
||||||
<div v-if="profile.Statistics(peer.Identifier).IsConnected">
|
<div v-if="profile.Statistics(peer.Identifier).IsConnected">
|
||||||
<span class="badge rounded-pill bg-success"><i class="fa-solid fa-link"></i></span> <span :title="peers.Statistics(peer.Identifier).LastHandshake">{{ $t('profile.peer-connected') }}</span>
|
<span class="badge rounded-pill bg-success"><i class="fa-solid fa-link"></i></span>
|
||||||
|
<span :title="profile.Statistics(peer.Identifier).LastHandshake">{{ $t('profile.peer-connected') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<span class="badge rounded-pill bg-light"><i class="fa-solid fa-link-slash"></i></span>
|
<span class="badge rounded-pill bg-light"><i class="fa-solid fa-link-slash"></i></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{{peer.InterfaceIdentifier}}</td>
|
<td>{{ peer.InterfaceIdentifier }}</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="#" :title="$t('profile.button-show-peer')" @click.prevent="viewedPeerId=peer.Identifier"><i class="fas fa-eye me-2"></i></a>
|
<a href="#" :title="$t('profile.button-show-peer')" @click.prevent="viewedPeerId = peer.Identifier"><i
|
||||||
<a href="#" :title="$t('profile.button-edit-peer')" @click.prevent="editPeerId=peer.Identifier"><i class="fas fa-cog"></i></a>
|
class="fas fa-eye me-2"></i></a>
|
||||||
|
<a href="#" :title="$t('profile.button-edit-peer')" @click.prevent="editPeerId = peer.Identifier"><i
|
||||||
|
class="fas fa-cog"></i></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -94,22 +106,24 @@ onMounted(async () => {
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<ul class="pagination pagination-sm">
|
<ul class="pagination pagination-sm">
|
||||||
<li :class="{disabled:profile.pageOffset===0}" class="page-item">
|
<li :class="{ disabled: profile.pageOffset === 0 }" class="page-item">
|
||||||
<a class="page-link" @click="profile.previousPage">«</a>
|
<a class="page-link" @click="profile.previousPage">«</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li v-for="page in profile.pages" :key="page" :class="{active:profile.currentPage===page}" class="page-item">
|
<li v-for="page in profile.pages" :key="page" :class="{ active: profile.currentPage === page }" class="page-item">
|
||||||
<a class="page-link" @click="profile.gotoPage(page)">{{page}}</a>
|
<a class="page-link" @click="profile.gotoPage(page)">{{ page }}</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li :class="{disabled:!profile.hasNextPage}" class="page-item">
|
<li :class="{ disabled: !profile.hasNextPage }" class="page-item">
|
||||||
<a class="page-link" @click="profile.nextPage">»</a>
|
<a class="page-link" @click="profile.nextPage">»</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-sm-6 col-form-label text-end" for="paginationSelector">{{ $t('general.pagination.size') }}:</label>
|
<label class="col-sm-6 col-form-label text-end" for="paginationSelector">
|
||||||
|
{{ $t('general.pagination.size')}}:
|
||||||
|
</label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<select v-model.number="profile.pageSize" class="form-select" @click="profile.afterPageSizeChange()">
|
<select v-model.number="profile.pageSize" class="form-select" @click="profile.afterPageSizeChange()">
|
||||||
<option value="10">10</option>
|
<option value="10">10</option>
|
||||||
@ -122,5 +136,4 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div></template>
|
||||||
</template>
|
|
||||||
|
@ -4,11 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"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/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
evbus "github.com/vardius/message-bus"
|
evbus "github.com/vardius/message-bus"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
@ -59,6 +60,7 @@ func New(cfg *config.Config, bus evbus.MessageBus, authenticator Authenticator,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Startup(ctx context.Context) error {
|
func (a *App) Startup(ctx context.Context) error {
|
||||||
|
|
||||||
a.UserManager.StartBackgroundJobs(ctx)
|
a.UserManager.StartBackgroundJobs(ctx)
|
||||||
a.StatisticsCollector.StartBackgroundJobs(ctx)
|
a.StatisticsCollector.StartBackgroundJobs(ctx)
|
||||||
a.WireGuardManager.StartBackgroundJobs(ctx)
|
a.WireGuardManager.StartBackgroundJobs(ctx)
|
||||||
|
@ -4,11 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/h44z/wg-portal/internal/app"
|
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/h44z/wg-portal/internal/app"
|
||||||
|
|
||||||
"github.com/h44z/wg-portal/internal"
|
"github.com/h44z/wg-portal/internal"
|
||||||
|
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
@ -87,7 +88,9 @@ func (m Manager) NewUser(ctx context.Context, user *domain.User) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) StartBackgroundJobs(ctx context.Context) {
|
func (m Manager) StartBackgroundJobs(ctx context.Context) {
|
||||||
|
|
||||||
go m.runLdapSynchronizationService(ctx)
|
go m.runLdapSynchronizationService(ctx)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Manager) GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) {
|
func (m Manager) GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) {
|
||||||
@ -322,7 +325,7 @@ func (m Manager) runLdapSynchronizationService(ctx context.Context) {
|
|||||||
if !ldapCfg.Synchronize {
|
if !ldapCfg.Synchronize {
|
||||||
continue // sync disabled
|
continue // sync disabled
|
||||||
}
|
}
|
||||||
|
//logrus.Tracef(&ldapCfg)
|
||||||
err := m.synchronizeLdapUsers(ctx, &ldapCfg)
|
err := m.synchronizeLdapUsers(ctx, &ldapCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("failed to synchronize LDAP users for %s: %v", ldapCfg.ProviderName, err)
|
logrus.Errorf("failed to synchronize LDAP users for %s: %v", ldapCfg.ProviderName, err)
|
||||||
@ -382,15 +385,20 @@ func (m Manager) updateLdapUsers(ctx context.Context, providerName string, rawUs
|
|||||||
return fmt.Errorf("find error for user id %s: %w", user.Identifier, err)
|
return fmt.Errorf("find error for user id %s: %w", user.Identifier, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
tctx = domain.SetUserInfo(tctx, domain.SystemAdminContextUserInfo())
|
||||||
|
|
||||||
if existingUser == nil {
|
if existingUser == nil {
|
||||||
err := m.NewUser(ctx, user)
|
err := m.NewUser(tctx, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create error for user id %s: %w", user.Identifier, err)
|
return fmt.Errorf("create error for user id %s: %w", user.Identifier, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if existingUser != nil && existingUser.Source == domain.UserSourceLdap && userChangedInLdap(existingUser, user) {
|
if existingUser != nil && existingUser.Source == domain.UserSourceLdap && userChangedInLdap(existingUser, user) {
|
||||||
err := m.users.SaveUser(ctx, user.Identifier, func(u *domain.User) (*domain.User, error) {
|
|
||||||
|
err := m.users.SaveUser(tctx, user.Identifier, func(u *domain.User) (*domain.User, error) {
|
||||||
u.UpdatedAt = time.Now()
|
u.UpdatedAt = time.Now()
|
||||||
u.UpdatedBy = "ldap_sync"
|
u.UpdatedBy = "ldap_sync"
|
||||||
u.Email = user.Email
|
u.Email = user.Email
|
||||||
|
@ -3,9 +3,10 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
"github.com/h44z/wg-portal/internal/config"
|
"github.com/h44z/wg-portal/internal/config"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user