mirror of
https://github.com/h44z/wg-portal.git
synced 2025-04-19 00:45:17 +00:00
Merge branch 'pr214'
This commit is contained in:
commit
e565e26c65
@ -54,7 +54,7 @@ func generateApi(basePath, apiPath, version string) error {
|
||||
OutputDir: filepath.Join(basePath, "core/assets/doc"),
|
||||
OutputTypes: []string{"json", "yaml"},
|
||||
ParseVendor: false,
|
||||
ParseDependency: true,
|
||||
ParseDependency: 3,
|
||||
MarkdownFilesDir: "",
|
||||
ParseInternal: true,
|
||||
GeneratedTime: false,
|
||||
|
14
frontend/package-lock.json
generated
14
frontend/package-lock.json
generated
@ -1046,9 +1046,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.32",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
|
||||
"integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
|
||||
"version": "8.4.35",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
|
||||
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@ -1148,13 +1148,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.0.10",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz",
|
||||
"integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==",
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.4.tgz",
|
||||
"integrity": "sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.19.3",
|
||||
"postcss": "^8.4.32",
|
||||
"postcss": "^8.4.35",
|
||||
"rollup": "^4.2.0"
|
||||
},
|
||||
"bin": {
|
||||
|
@ -1,9 +1,9 @@
|
||||
<script setup>
|
||||
import { RouterLink, RouterView } from 'vue-router';
|
||||
import {computed, getCurrentInstance, onMounted, ref} from "vue";
|
||||
import {authStore} from "./stores/auth";
|
||||
import {securityStore} from "./stores/security";
|
||||
import {settingsStore} from "@/stores/settings";
|
||||
import { computed, getCurrentInstance, onMounted, ref } from "vue";
|
||||
import { authStore } from "./stores/auth";
|
||||
import { securityStore } from "./stores/security";
|
||||
import { settingsStore } from "@/stores/settings";
|
||||
|
||||
const appGlobal = getCurrentInstance().appContext.config.globalProperties
|
||||
const auth = authStore()
|
||||
@ -80,20 +80,16 @@ const currentYear = ref(new Date().getFullYear())
|
||||
|
||||
<div class="navbar-nav d-flex justify-content-end">
|
||||
<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="#"
|
||||
role="button">{{ auth.User.Firstname }} {{ auth.User.Lastname }}</a>
|
||||
<a aria-expanded="false" aria-haspopup="true" class="nav-link dropdown-toggle" data-bs-toggle="dropdown"
|
||||
href="#" role="button">{{ auth.User.Firstname }} {{ auth.User.Lastname }}</a>
|
||||
<div class="dropdown-menu">
|
||||
<RouterLink :to="{ name: 'profile' }" class="dropdown-item"><i class="fas fa-user"></i> {{ $t('menu.profile') }}</RouterLink>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="#" @click.prevent="auth.Logout">
|
||||
<i class="fas fa-sign-out-alt"></i> {{ $t('menu.logout') }}
|
||||
</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="auth.Logout"><i class="fas fa-sign-out-alt"></i> {{ $t('menu.logout') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!auth.IsAuthenticated" class="nav-item">
|
||||
<RouterLink :to="{ name: 'login' }" class="nav-link">
|
||||
<i class="fas fa-sign-in-alt fa-sm fa-fw me-2"></i>{{ $t('menu.login') }}
|
||||
</RouterLink>
|
||||
<RouterLink :to="{ name: 'login' }" class="nav-link"><i class="fas fa-sign-in-alt fa-sm fa-fw me-2"></i>{{ $t('menu.login') }}</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -111,18 +107,18 @@ const currentYear = ref(new Date().getFullYear())
|
||||
<div class="col-6 text-end">
|
||||
<div :aria-label="$t('menu.lang')" 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="">
|
||||
<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('de')"><span class="fi fi-de"></span> Deutsch</a>
|
||||
<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('de')"><span class="fi fi-de"></span>Deutsch</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('ru')"><span class="fi fi-ru"></span>Русский</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
</footer></template>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
@ -1,20 +1,22 @@
|
||||
<script setup>
|
||||
import Modal from "./Modal.vue";
|
||||
import {peerStore} from "@/stores/peers";
|
||||
import {interfaceStore} from "@/stores/interfaces";
|
||||
import {computed, ref, watch} from "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 { validateCIDR, validateIP, validateDomain } from '@/helpers/validators';
|
||||
import isCidr from "is-cidr";
|
||||
import {isIP} from 'is-ip';
|
||||
import { isIP } from 'is-ip';
|
||||
import { freshPeer, freshInterface } from '@/helpers/models';
|
||||
import { profileStore } from "@/stores/profile";
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const peers = peerStore()
|
||||
const interfaces = interfaceStore()
|
||||
const profile = profileStore()
|
||||
|
||||
const props = defineProps({
|
||||
peerId: String,
|
||||
@ -24,7 +26,16 @@ const props = defineProps({
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
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(() => {
|
||||
@ -59,121 +70,119 @@ const formData = ref(freshPeer())
|
||||
// functions
|
||||
|
||||
watch(() => props.visible, async (newValue, oldValue) => {
|
||||
if (oldValue === false && newValue === true) { // if modal is shown
|
||||
console.log(selectedInterface.value)
|
||||
console.log(selectedPeer.value)
|
||||
if (!selectedPeer.value) {
|
||||
await peers.PreparePeer(selectedInterface.value.Identifier)
|
||||
if (oldValue === false && newValue === true) { // if modal is shown
|
||||
if (!selectedPeer.value) {
|
||||
await peers.PreparePeer(selectedInterface.value.Identifier)
|
||||
|
||||
formData.value.Identifier = peers.Prepared.Identifier
|
||||
formData.value.DisplayName = peers.Prepared.DisplayName
|
||||
formData.value.UserIdentifier = peers.Prepared.UserIdentifier
|
||||
formData.value.InterfaceIdentifier = peers.Prepared.InterfaceIdentifier
|
||||
formData.value.Disabled = peers.Prepared.Disabled
|
||||
formData.value.ExpiresAt = peers.Prepared.ExpiresAt
|
||||
formData.value.Notes = peers.Prepared.Notes
|
||||
formData.value.Identifier = peers.Prepared.Identifier
|
||||
formData.value.DisplayName = peers.Prepared.DisplayName
|
||||
formData.value.UserIdentifier = peers.Prepared.UserIdentifier
|
||||
formData.value.InterfaceIdentifier = peers.Prepared.InterfaceIdentifier
|
||||
formData.value.Disabled = peers.Prepared.Disabled
|
||||
formData.value.ExpiresAt = peers.Prepared.ExpiresAt
|
||||
formData.value.Notes = peers.Prepared.Notes
|
||||
|
||||
formData.value.Endpoint = peers.Prepared.Endpoint
|
||||
formData.value.EndpointPublicKey = peers.Prepared.EndpointPublicKey
|
||||
formData.value.AllowedIPs = peers.Prepared.AllowedIPs
|
||||
formData.value.ExtraAllowedIPs = peers.Prepared.ExtraAllowedIPs
|
||||
formData.value.PresharedKey = peers.Prepared.PresharedKey
|
||||
formData.value.PersistentKeepalive = peers.Prepared.PersistentKeepalive
|
||||
formData.value.Endpoint = peers.Prepared.Endpoint
|
||||
formData.value.EndpointPublicKey = peers.Prepared.EndpointPublicKey
|
||||
formData.value.AllowedIPs = peers.Prepared.AllowedIPs
|
||||
formData.value.ExtraAllowedIPs = peers.Prepared.ExtraAllowedIPs
|
||||
formData.value.PresharedKey = peers.Prepared.PresharedKey
|
||||
formData.value.PersistentKeepalive = peers.Prepared.PersistentKeepalive
|
||||
|
||||
formData.value.PrivateKey = peers.Prepared.PrivateKey
|
||||
formData.value.PublicKey = peers.Prepared.PublicKey
|
||||
formData.value.PrivateKey = peers.Prepared.PrivateKey
|
||||
formData.value.PublicKey = peers.Prepared.PublicKey
|
||||
|
||||
formData.value.Mode = peers.Prepared.Mode
|
||||
formData.value.Mode = peers.Prepared.Mode
|
||||
|
||||
formData.value.Addresses = peers.Prepared.Addresses
|
||||
formData.value.CheckAliveAddress = peers.Prepared.CheckAliveAddress
|
||||
formData.value.Dns = peers.Prepared.Dns
|
||||
formData.value.DnsSearch = peers.Prepared.DnsSearch
|
||||
formData.value.Mtu = peers.Prepared.Mtu
|
||||
formData.value.FirewallMark = peers.Prepared.FirewallMark
|
||||
formData.value.RoutingTable = peers.Prepared.RoutingTable
|
||||
formData.value.Addresses = peers.Prepared.Addresses
|
||||
formData.value.CheckAliveAddress = peers.Prepared.CheckAliveAddress
|
||||
formData.value.Dns = peers.Prepared.Dns
|
||||
formData.value.DnsSearch = peers.Prepared.DnsSearch
|
||||
formData.value.Mtu = peers.Prepared.Mtu
|
||||
formData.value.FirewallMark = peers.Prepared.FirewallMark
|
||||
formData.value.RoutingTable = peers.Prepared.RoutingTable
|
||||
|
||||
formData.value.PreUp = peers.Prepared.PreUp
|
||||
formData.value.PostUp = peers.Prepared.PostUp
|
||||
formData.value.PreDown = peers.Prepared.PreDown
|
||||
formData.value.PostDown = peers.Prepared.PostDown
|
||||
formData.value.PreUp = peers.Prepared.PreUp
|
||||
formData.value.PostUp = peers.Prepared.PostUp
|
||||
formData.value.PreDown = peers.Prepared.PreDown
|
||||
formData.value.PostDown = peers.Prepared.PostDown
|
||||
|
||||
} else { // fill existing data
|
||||
formData.value.Identifier = selectedPeer.value.Identifier
|
||||
formData.value.DisplayName = selectedPeer.value.DisplayName
|
||||
formData.value.UserIdentifier = selectedPeer.value.UserIdentifier
|
||||
formData.value.InterfaceIdentifier = selectedPeer.value.InterfaceIdentifier
|
||||
formData.value.Disabled = selectedPeer.value.Disabled
|
||||
formData.value.ExpiresAt = selectedPeer.value.ExpiresAt
|
||||
formData.value.Notes = selectedPeer.value.Notes
|
||||
} else { // fill existing data
|
||||
formData.value.Identifier = selectedPeer.value.Identifier
|
||||
formData.value.DisplayName = selectedPeer.value.DisplayName
|
||||
formData.value.UserIdentifier = selectedPeer.value.UserIdentifier
|
||||
formData.value.InterfaceIdentifier = selectedPeer.value.InterfaceIdentifier
|
||||
formData.value.Disabled = selectedPeer.value.Disabled
|
||||
formData.value.ExpiresAt = selectedPeer.value.ExpiresAt
|
||||
formData.value.Notes = selectedPeer.value.Notes
|
||||
|
||||
formData.value.Endpoint = selectedPeer.value.Endpoint
|
||||
formData.value.EndpointPublicKey = selectedPeer.value.EndpointPublicKey
|
||||
formData.value.AllowedIPs = selectedPeer.value.AllowedIPs
|
||||
formData.value.ExtraAllowedIPs = selectedPeer.value.ExtraAllowedIPs
|
||||
formData.value.PresharedKey = selectedPeer.value.PresharedKey
|
||||
formData.value.PersistentKeepalive = selectedPeer.value.PersistentKeepalive
|
||||
formData.value.Endpoint = selectedPeer.value.Endpoint
|
||||
formData.value.EndpointPublicKey = selectedPeer.value.EndpointPublicKey
|
||||
formData.value.AllowedIPs = selectedPeer.value.AllowedIPs
|
||||
formData.value.ExtraAllowedIPs = selectedPeer.value.ExtraAllowedIPs
|
||||
formData.value.PresharedKey = selectedPeer.value.PresharedKey
|
||||
formData.value.PersistentKeepalive = selectedPeer.value.PersistentKeepalive
|
||||
|
||||
formData.value.PrivateKey = selectedPeer.value.PrivateKey
|
||||
formData.value.PublicKey = selectedPeer.value.PublicKey
|
||||
formData.value.PrivateKey = selectedPeer.value.PrivateKey
|
||||
formData.value.PublicKey = selectedPeer.value.PublicKey
|
||||
|
||||
formData.value.Mode = selectedPeer.value.Mode
|
||||
formData.value.Mode = selectedPeer.value.Mode
|
||||
|
||||
formData.value.Addresses = selectedPeer.value.Addresses
|
||||
formData.value.CheckAliveAddress = selectedPeer.value.CheckAliveAddress
|
||||
formData.value.Dns = selectedPeer.value.Dns
|
||||
formData.value.DnsSearch = selectedPeer.value.DnsSearch
|
||||
formData.value.Mtu = selectedPeer.value.Mtu
|
||||
formData.value.FirewallMark = selectedPeer.value.FirewallMark
|
||||
formData.value.RoutingTable = selectedPeer.value.RoutingTable
|
||||
formData.value.Addresses = selectedPeer.value.Addresses
|
||||
formData.value.CheckAliveAddress = selectedPeer.value.CheckAliveAddress
|
||||
formData.value.Dns = selectedPeer.value.Dns
|
||||
formData.value.DnsSearch = selectedPeer.value.DnsSearch
|
||||
formData.value.Mtu = selectedPeer.value.Mtu
|
||||
formData.value.FirewallMark = selectedPeer.value.FirewallMark
|
||||
formData.value.RoutingTable = selectedPeer.value.RoutingTable
|
||||
|
||||
formData.value.PreUp = selectedPeer.value.PreUp
|
||||
formData.value.PostUp = selectedPeer.value.PostUp
|
||||
formData.value.PreDown = selectedPeer.value.PreDown
|
||||
formData.value.PostDown = selectedPeer.value.PostDown
|
||||
formData.value.PreUp = selectedPeer.value.PreUp
|
||||
formData.value.PostUp = selectedPeer.value.PostUp
|
||||
formData.value.PreDown = selectedPeer.value.PreDown
|
||||
formData.value.PostDown = selectedPeer.value.PostDown
|
||||
|
||||
if (!formData.value.Endpoint.Overridable ||
|
||||
!formData.value.EndpointPublicKey.Overridable ||
|
||||
!formData.value.AllowedIPs.Overridable ||
|
||||
!formData.value.PersistentKeepalive.Overridable ||
|
||||
!formData.value.Dns.Overridable ||
|
||||
!formData.value.DnsSearch.Overridable ||
|
||||
!formData.value.Mtu.Overridable ||
|
||||
!formData.value.FirewallMark.Overridable ||
|
||||
!formData.value.RoutingTable.Overridable ||
|
||||
!formData.value.PreUp.Overridable ||
|
||||
!formData.value.PostUp.Overridable ||
|
||||
!formData.value.PreDown.Overridable ||
|
||||
!formData.value.PostDown.Overridable) {
|
||||
formData.value.IgnoreGlobalSettings = true
|
||||
}
|
||||
}
|
||||
if (!formData.value.Endpoint.Overridable ||
|
||||
!formData.value.EndpointPublicKey.Overridable ||
|
||||
!formData.value.AllowedIPs.Overridable ||
|
||||
!formData.value.PersistentKeepalive.Overridable ||
|
||||
!formData.value.Dns.Overridable ||
|
||||
!formData.value.DnsSearch.Overridable ||
|
||||
!formData.value.Mtu.Overridable ||
|
||||
!formData.value.FirewallMark.Overridable ||
|
||||
!formData.value.RoutingTable.Overridable ||
|
||||
!formData.value.PreUp.Overridable ||
|
||||
!formData.value.PostUp.Overridable ||
|
||||
!formData.value.PreDown.Overridable ||
|
||||
!formData.value.PostDown.Overridable) {
|
||||
formData.value.IgnoreGlobalSettings = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(() => formData.value.IgnoreGlobalSettings, async (newValue, oldValue) => {
|
||||
formData.value.Endpoint.Overridable = !newValue
|
||||
formData.value.EndpointPublicKey.Overridable = !newValue
|
||||
formData.value.AllowedIPs.Overridable = !newValue
|
||||
formData.value.PersistentKeepalive.Overridable = !newValue
|
||||
formData.value.Dns.Overridable = !newValue
|
||||
formData.value.DnsSearch.Overridable = !newValue
|
||||
formData.value.Mtu.Overridable = !newValue
|
||||
formData.value.FirewallMark.Overridable = !newValue
|
||||
formData.value.RoutingTable.Overridable = !newValue
|
||||
formData.value.PreUp.Overridable = !newValue
|
||||
formData.value.PostUp.Overridable = !newValue
|
||||
formData.value.PreDown.Overridable = !newValue
|
||||
formData.value.PostDown.Overridable = !newValue
|
||||
}
|
||||
formData.value.Endpoint.Overridable = !newValue
|
||||
formData.value.EndpointPublicKey.Overridable = !newValue
|
||||
formData.value.AllowedIPs.Overridable = !newValue
|
||||
formData.value.PersistentKeepalive.Overridable = !newValue
|
||||
formData.value.Dns.Overridable = !newValue
|
||||
formData.value.DnsSearch.Overridable = !newValue
|
||||
formData.value.Mtu.Overridable = !newValue
|
||||
formData.value.FirewallMark.Overridable = !newValue
|
||||
formData.value.RoutingTable.Overridable = !newValue
|
||||
formData.value.PreUp.Overridable = !newValue
|
||||
formData.value.PostUp.Overridable = !newValue
|
||||
formData.value.PreDown.Overridable = !newValue
|
||||
formData.value.PostDown.Overridable = !newValue
|
||||
}
|
||||
)
|
||||
|
||||
watch(() => formData.value.Disabled, async (newValue, oldValue) => {
|
||||
if (oldValue && !newValue && formData.value.ExpiresAt) {
|
||||
formData.value.ExpiresAt = "" // reset expiry date
|
||||
}
|
||||
}
|
||||
if (oldValue && !newValue && formData.value.ExpiresAt) {
|
||||
formData.value.ExpiresAt = "" // reset expiry date
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
function close() {
|
||||
@ -184,7 +193,7 @@ function close() {
|
||||
function handleChangeAddresses(tags) {
|
||||
let validInput = true
|
||||
tags.forEach(tag => {
|
||||
if(isCidr(tag) === 0) {
|
||||
if (isCidr(tag) === 0) {
|
||||
validInput = false
|
||||
notify({
|
||||
title: "Invalid CIDR",
|
||||
@ -193,7 +202,7 @@ function handleChangeAddresses(tags) {
|
||||
})
|
||||
}
|
||||
})
|
||||
if(validInput) {
|
||||
if (validInput) {
|
||||
formData.value.Addresses = tags
|
||||
}
|
||||
}
|
||||
@ -201,7 +210,7 @@ function handleChangeAddresses(tags) {
|
||||
function handleChangeAllowedIPs(tags) {
|
||||
let validInput = true
|
||||
tags.forEach(tag => {
|
||||
if(isCidr(tag) === 0) {
|
||||
if (isCidr(tag) === 0) {
|
||||
validInput = false
|
||||
notify({
|
||||
title: "Invalid CIDR",
|
||||
@ -210,7 +219,7 @@ function handleChangeAllowedIPs(tags) {
|
||||
})
|
||||
}
|
||||
})
|
||||
if(validInput) {
|
||||
if (validInput) {
|
||||
formData.value.AllowedIPs.Value = tags
|
||||
}
|
||||
}
|
||||
@ -218,7 +227,7 @@ function handleChangeAllowedIPs(tags) {
|
||||
function handleChangeExtraAllowedIPs(tags) {
|
||||
let validInput = true
|
||||
tags.forEach(tag => {
|
||||
if(isCidr(tag) === 0) {
|
||||
if (isCidr(tag) === 0) {
|
||||
validInput = false
|
||||
notify({
|
||||
title: "Invalid CIDR",
|
||||
@ -227,7 +236,7 @@ function handleChangeExtraAllowedIPs(tags) {
|
||||
})
|
||||
}
|
||||
})
|
||||
if(validInput) {
|
||||
if (validInput) {
|
||||
formData.value.ExtraAllowedIPs = tags
|
||||
}
|
||||
}
|
||||
@ -235,7 +244,7 @@ function handleChangeExtraAllowedIPs(tags) {
|
||||
function handleChangeDns(tags) {
|
||||
let validInput = true
|
||||
tags.forEach(tag => {
|
||||
if(!isIP(tag)) {
|
||||
if (!isIP(tag)) {
|
||||
validInput = false
|
||||
notify({
|
||||
title: "Invalid IP",
|
||||
@ -244,7 +253,7 @@ function handleChangeDns(tags) {
|
||||
})
|
||||
}
|
||||
})
|
||||
if(validInput) {
|
||||
if (validInput) {
|
||||
formData.value.Dns.Value = tags
|
||||
}
|
||||
}
|
||||
@ -255,14 +264,14 @@ function handleChangeDnsSearch(tags) {
|
||||
|
||||
async function save() {
|
||||
try {
|
||||
if (props.peerId!=='#NEW#') {
|
||||
if (props.peerId !== '#NEW#') {
|
||||
await peers.UpdatePeer(selectedPeer.value.Identifier, formData.value)
|
||||
} else {
|
||||
await peers.CreatePeer(selectedInterface.value.Identifier, formData.value)
|
||||
}
|
||||
close()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
// console.log(e)
|
||||
notify({
|
||||
title: "Failed to save peer!",
|
||||
text: e.toString(),
|
||||
@ -276,7 +285,7 @@ async function del() {
|
||||
await peers.DeletePeer(selectedPeer.value.Identifier)
|
||||
close()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
// console.log(e)
|
||||
notify({
|
||||
title: "Failed to delete peer!",
|
||||
text: e.toString(),
|
||||
@ -294,87 +303,86 @@ async function del() {
|
||||
<legend class="mt-4">{{ $t('modals.peer-edit.header-general') }}</legend>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.display-name.label') }}</label>
|
||||
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.display-name.placeholder')" v-model="formData.DisplayName">
|
||||
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.display-name.placeholder')"
|
||||
v-model="formData.DisplayName">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<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>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<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>
|
||||
<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 class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.public-key.label') }}</label>
|
||||
<input type="email" class="form-control" :placeholder="$t('modals.peer-edit.public-key.placeholder')" required v-model="formData.PublicKey">
|
||||
<input type="email" class="form-control" :placeholder="$t('modals.peer-edit.public-key.placeholder')" required
|
||||
v-model="formData.PublicKey">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.preshared-key.label') }}</label>
|
||||
<input type="email" class="form-control" :placeholder="$t('modals.peer-edit.preshared-key.placeholder')" v-model="formData.PresharedKey">
|
||||
<input type="email" class="form-control" :placeholder="$t('modals.peer-edit.preshared-key.placeholder')"
|
||||
v-model="formData.PresharedKey">
|
||||
</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>
|
||||
<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>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<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>
|
||||
<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 class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.ip.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.Addresses"
|
||||
:placeholder="$t('modals.peer-edit.ip.placeholder')"
|
||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateCIDR"
|
||||
@on-tags-changed="handleChangeAddresses"/>
|
||||
:placeholder="$t('modals.peer-edit.ip.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateCIDR" @on-tags-changed="handleChangeAddresses" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.allowed-ip.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.AllowedIPs.Value"
|
||||
:placeholder="$t('modals.peer-edit.allowed-ip.placeholder')"
|
||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateCIDR"
|
||||
@on-tags-changed="handleChangeAllowedIPs"/>
|
||||
:placeholder="$t('modals.peer-edit.allowed-ip.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateCIDR" @on-tags-changed="handleChangeAllowedIPs" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.extra-allowed-ip.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.ExtraAllowedIPs"
|
||||
:placeholder="$t('modals.peer-edit.extra-allowed-ip.placeholder')"
|
||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateCIDR"
|
||||
@on-tags-changed="handleChangeExtraAllowedIPs"/>
|
||||
:placeholder="$t('modals.peer-edit.extra-allowed-ip.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateCIDR" @on-tags-changed="handleChangeExtraAllowedIPs" />
|
||||
<small class="form-text text-muted">{{ $t('modals.peer-edit.extra-allowed-ip.description') }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.dns.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.Dns.Value"
|
||||
:placeholder="$t('modals.peer-edit.dns.placeholder')"
|
||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateIP"
|
||||
@on-tags-changed="handleChangeDns"/>
|
||||
:placeholder="$t('modals.peer-edit.dns.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateIP" @on-tags-changed="handleChangeDns" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div hidden class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.dns-search.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.DnsSearch.Value"
|
||||
:placeholder="$t('modals.peer-edit.dns-search.label')"
|
||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateDomain"
|
||||
@on-tags-changed="handleChangeDnsSearch"/>
|
||||
:placeholder="$t('modals.peer-edit.dns-search.label')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateDomain" @on-tags-changed="handleChangeDnsSearch" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.keep-alive.label') }}</label>
|
||||
<input type="number" class="form-control" :placeholder="$t('modals.peer-edit.keep-alive.label')" v-model="formData.PersistentKeepalive.Value">
|
||||
<input type="number" class="form-control" :placeholder="$t('modals.peer-edit.keep-alive.label')"
|
||||
v-model="formData.PersistentKeepalive.Value">
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.mtu.label') }}</label>
|
||||
<input type="number" class="form-control" :placeholder="$t('modals.peer-edit.mtu.label')" v-model="formData.Mtu.Value">
|
||||
<input type="number" class="form-control" :placeholder="$t('modals.peer-edit.mtu.label')"
|
||||
v-model="formData.Mtu.Value">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
@ -382,19 +390,23 @@ async function del() {
|
||||
<legend class="mt-4">{{ $t('modals.peer-edit.header-hooks') }}</legend>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.pre-up.label') }}</label>
|
||||
<textarea v-model="formData.PreUp.Value" class="form-control" rows="2" :placeholder="$t('modals.peer-edit.pre-up.placeholder')"></textarea>
|
||||
<textarea v-model="formData.PreUp.Value" class="form-control" rows="2"
|
||||
:placeholder="$t('modals.peer-edit.pre-up.placeholder')"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.post-up.label') }}</label>
|
||||
<textarea v-model="formData.PostUp.Value" class="form-control" rows="2" :placeholder="$t('modals.peer-edit.post-up.placeholder')"></textarea>
|
||||
<textarea v-model="formData.PostUp.Value" class="form-control" rows="2"
|
||||
:placeholder="$t('modals.peer-edit.post-up.placeholder')"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.pre-down.label') }}</label>
|
||||
<textarea v-model="formData.PreDown.Value" class="form-control" rows="2" :placeholder="$t('modals.peer-edit.pre-down.placeholder')"></textarea>
|
||||
<textarea v-model="formData.PreDown.Value" class="form-control" rows="2"
|
||||
:placeholder="$t('modals.peer-edit.pre-down.placeholder')"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.post-down.label') }}</label>
|
||||
<textarea v-model="formData.PostDown.Value" class="form-control" rows="2" :placeholder="$t('modals.peer-edit.post-down.placeholder')"></textarea>
|
||||
<textarea v-model="formData.PostDown.Value" class="form-control" rows="2"
|
||||
:placeholder="$t('modals.peer-edit.post-down.placeholder')"></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
@ -403,7 +415,7 @@ async function del() {
|
||||
<div class="form-group col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" v-model="formData.Disabled">
|
||||
<label class="form-check-label" >{{ $t('modals.peer-edit.disabled.label') }}</label>
|
||||
<label class="form-check-label">{{ $t('modals.peer-edit.disabled.label') }}</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" v-model="formData.IgnoreGlobalSettings">
|
||||
@ -412,14 +424,16 @@ async function del() {
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label class="form-label">{{ $t('modals.peer-edit.expires-at.label') }}</label>
|
||||
<input type="date" pattern="\d{4}-\d{2}-\d{2}" class="form-control" min="2023-01-01" v-model="formData.ExpiresAt">
|
||||
<input type="date" pattern="\d{4}-\d{2}-\d{2}" class="form-control" min="2023-01-01"
|
||||
v-model="formData.ExpiresAt">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="flex-fill text-start">
|
||||
<button v-if="props.peerId!=='#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del">{{ $t('general.delete') }}</button>
|
||||
<button v-if="props.peerId !== '#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del">{{
|
||||
$t('general.delete') }}</button>
|
||||
</div>
|
||||
<button class="btn btn-primary me-1" type="button" @click.prevent="save">{{ $t('general.save') }}</button>
|
||||
<button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
|
||||
@ -427,5 +441,4 @@ async function del() {
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<style></style>
|
||||
|
@ -1,19 +1,23 @@
|
||||
<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 {freshInterface, freshPeer, freshStats} from '@/helpers/models';
|
||||
import { peerStore } from "@/stores/peers";
|
||||
import { interfaceStore } from "@/stores/interfaces";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { freshInterface, freshPeer, freshStats } from '@/helpers/models';
|
||||
import Prism from "vue-prism-component";
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
import {settingsStore} from "@/stores/settings";
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
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 settings = settingsStore()
|
||||
const peers = peerStore()
|
||||
const interfaces = interfaceStore()
|
||||
const profile = profileStore()
|
||||
|
||||
const props = defineProps({
|
||||
peerId: String,
|
||||
@ -32,9 +36,12 @@ const selectedPeer = computed(() => {
|
||||
let p = peers.Find(props.peerId)
|
||||
|
||||
if (!p) {
|
||||
p = freshPeer() // dummy peer to avoid 'undefined' exceptions
|
||||
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
|
||||
})
|
||||
|
||||
@ -42,9 +49,13 @@ const selectedStats = computed(() => {
|
||||
let s = peers.Statistics(props.peerId)
|
||||
|
||||
if (!s) {
|
||||
s = freshStats() // dummy peer to avoid 'undefined' exceptions
|
||||
}
|
||||
if (!!props.peerId || props.peerId.length) {
|
||||
p = profile.Statistics(props.peerId)
|
||||
} else {
|
||||
s = freshStats() // dummy stats to avoid 'undefined' exceptions
|
||||
}
|
||||
|
||||
}
|
||||
return s
|
||||
})
|
||||
|
||||
@ -54,7 +65,6 @@ const selectedInterface = computed(() => {
|
||||
if (!i) {
|
||||
i = freshInterface() // dummy interface to avoid 'undefined' exceptions
|
||||
}
|
||||
|
||||
return i
|
||||
})
|
||||
|
||||
@ -70,11 +80,11 @@ const title = computed(() => {
|
||||
})
|
||||
|
||||
watch(() => props.visible, async (newValue, oldValue) => {
|
||||
if (oldValue === false && newValue === true) { // if modal is shown
|
||||
await peers.LoadPeerConfig(selectedPeer.value.Identifier)
|
||||
configString.value = peers.configuration
|
||||
}
|
||||
}
|
||||
if (oldValue === false && newValue === true) { // if modal is shown
|
||||
await peers.LoadPeerConfig(selectedPeer.value.Identifier)
|
||||
configString.value = peers.configuration
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
function download() {
|
||||
@ -82,10 +92,10 @@ function download() {
|
||||
let filename = 'WireGuard-Tunnel.conf'
|
||||
if (selectedPeer.value.DisplayName) {
|
||||
filename = selectedPeer.value.DisplayName
|
||||
.replace(/ /g,"_")
|
||||
.replace(/[^a-zA-Z0-9-_]/g,"")
|
||||
.substring(0, 16)
|
||||
+ ".conf"
|
||||
.replace(/ /g, "_")
|
||||
.replace(/[^a-zA-Z0-9-_]/g, "")
|
||||
.substring(0, 16)
|
||||
+ ".conf"
|
||||
}
|
||||
let text = configString.value
|
||||
|
||||
@ -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>
|
||||
|
||||
<template>
|
||||
@ -118,25 +135,30 @@ function email() {
|
||||
<div class="accordion" id="peerInformation">
|
||||
<div class="accordion-item">
|
||||
<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') }}
|
||||
</button>
|
||||
</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="row">
|
||||
<div class="col-md-8">
|
||||
<ul>
|
||||
<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 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.Disabled">{{ $t('modals.peer-view.disabled-status') }}: {{ selectedPeer.DisabledReason }}</li>
|
||||
<li v-if="selectedPeer.ExpiresAt">{{ $t('modals.peer-view.expiry-status') }}: {{
|
||||
selectedPeer.ExpiresAt }}</li>
|
||||
<li v-if="selectedPeer.Disabled">{{ $t('modals.peer-view.disabled-status') }}: {{
|
||||
selectedPeer.DisabledReason }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<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>
|
||||
@ -144,16 +166,20 @@ function email() {
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<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') }}
|
||||
</button>
|
||||
</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="row">
|
||||
<div class="col-md-12">
|
||||
<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>
|
||||
<ul>
|
||||
<li>{{ $t('modals.peer-view.pingable') }}: {{ selectedStats.IsPingable }}</li>
|
||||
@ -166,13 +192,15 @@ function email() {
|
||||
</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">
|
||||
<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') }}
|
||||
</button>
|
||||
</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">
|
||||
<Prism language="ini" :code="configString"></Prism>
|
||||
</div>
|
||||
@ -182,18 +210,17 @@ function email() {
|
||||
</template>
|
||||
<template #footer>
|
||||
<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="email" type="button" class="btn btn-primary me-1">{{ $t('modals.peer-view.button-email') }}</button>
|
||||
<button @click.prevent="download" type="button" class="btn btn-primary me-1">{{
|
||||
$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>
|
||||
<button @click.prevent="close" type="button" class="btn btn-secondary">{{ $t('general.close') }}</button>
|
||||
|
||||
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
</template>
|
||||
</Modal></template>
|
||||
|
||||
<style>
|
||||
.config-qr-img {
|
||||
<style>.config-qr-img {
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
}</style>
|
||||
|
@ -1,5 +1,6 @@
|
||||
// src/lang/index.js
|
||||
import de from './translations/de.json';
|
||||
import ru from './translations/ru.json';
|
||||
import en from './translations/en.json';
|
||||
import {createI18n} from "vue-i18n";
|
||||
|
||||
@ -20,6 +21,7 @@ const i18n = createI18n({
|
||||
fallbackLocale: "en", // set fallback locale
|
||||
messages: {
|
||||
"de": de,
|
||||
"ru": ru,
|
||||
"en": en
|
||||
}
|
||||
});
|
||||
|
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>
|
||||
import {authStore} from "@/stores/auth";
|
||||
import {RouterLink} from "vue-router";
|
||||
import { authStore } from "@/stores/auth";
|
||||
import { RouterLink } from "vue-router";
|
||||
|
||||
const auth = authStore()
|
||||
const auth = authStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -29,7 +29,8 @@
|
||||
<hr class="my-4">
|
||||
<p>{{ $t('home.admin.content') }}</p>
|
||||
<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>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<script setup>
|
||||
import PeerViewModal from "../components/PeerViewModal.vue";
|
||||
|
||||
import {onMounted, ref} from "vue";
|
||||
import {profileStore} from "@/stores/profile";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { profileStore } from "@/stores/profile";
|
||||
import PeerEditModal from "@/components/PeerEditModal.vue";
|
||||
import {settingsStore} from "@/stores/settings";
|
||||
import { settingsStore } from "@/stores/settings";
|
||||
|
||||
const settings = settingsStore()
|
||||
const profile = profileStore()
|
||||
@ -17,11 +17,12 @@ onMounted(async () => {
|
||||
await profile.LoadPeers()
|
||||
await profile.LoadStats()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PeerViewModal :peerId="viewedPeerId" :visible="viewedPeerId!==''" @close="viewedPeerId=''"></PeerViewModal>
|
||||
<PeerEditModal :peerId="editPeerId" :visible="editPeerId!==''" @close="editPeerId=''"></PeerEditModal>
|
||||
<PeerViewModal :peerId="viewedPeerId" :visible="viewedPeerId !== ''" @close="viewedPeerId = ''"></PeerViewModal>
|
||||
<PeerEditModal :peerId="editPeerId" :visible="editPeerId !== ''" @close="editPeerId = ''"></PeerEditModal>
|
||||
|
||||
<!-- Peer list -->
|
||||
<div class="mt-4 row">
|
||||
@ -31,33 +32,38 @@ onMounted(async () => {
|
||||
<div class="col-12 col-lg-4 text-lg-end">
|
||||
<div class="form-group d-inline">
|
||||
<div class="input-group mb-3">
|
||||
<input v-model="profile.filter" class="form-control" :placeholder="$t('general.search.placeholder')" type="text" @keyup="profile.afterPageSizeChange">
|
||||
<button class="input-group-text btn btn-primary" :title="$t('general.search.button')"><i class="fa-solid fa-search"></i></button>
|
||||
<input v-model="profile.filter" class="form-control" :placeholder="$t('general.search.placeholder')" type="text"
|
||||
@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 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 class="mt-2 table-responsive">
|
||||
<div v-if="profile.CountPeers===0">
|
||||
<h4>{{ $t('profile.no-peer.headline') }}</h4>
|
||||
<p>{{ $t('profile.no-peer.abstract') }}</p>
|
||||
<div v-if="profile.CountPeers === 0">
|
||||
<h4>{{ $t('profile.no-peer.headline') }}</h4>
|
||||
<p>{{ $t('profile.no-peer.abstract') }}</p>
|
||||
</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>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<input id="flexCheckDefault" class="form-check-input" :title="$t('general.select-all')" type="checkbox" value="">
|
||||
</th><!-- select -->
|
||||
<th scope="col"></th><!-- status -->
|
||||
<th scope="col">{{ $t('profile.table-heading.name') }}</th>
|
||||
<th scope="col">{{ $t('profile.table-heading.ip') }}</th>
|
||||
<th v-if="profile.hasStatistics" scope="col">{{ $t('profile.table-heading.stats') }}</th>
|
||||
<th scope="col">{{ $t('profile.table-heading.interface') }}</th>
|
||||
<th scope="col"></th><!-- Actions -->
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<input id="flexCheckDefault" class="form-check-input" :title="$t('general.select-all')" type="checkbox"
|
||||
value="">
|
||||
</th><!-- select -->
|
||||
<th scope="col"></th><!-- status -->
|
||||
<th scope="col">{{ $t('profile.table-heading.name') }}</th>
|
||||
<th scope="col">{{ $t('profile.table-heading.ip') }}</th>
|
||||
<th v-if="profile.hasStatistics" scope="col">{{ $t('profile.table-heading.stats') }}</th>
|
||||
<th scope="col">{{ $t('profile.table-heading.interface') }}</th>
|
||||
<th scope="col"></th><!-- Actions -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="peer in profile.FilteredAndPagedPeers" :key="peer.Identifier">
|
||||
@ -65,25 +71,31 @@ onMounted(async () => {
|
||||
<input id="flexCheckDefault" class="form-check-input" type="checkbox" value="">
|
||||
</th>
|
||||
<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 && peer.ExpiresAt" class="text-warning"><i class="fas fa-hourglass-end" :title="peer.ExpiresAt"></i></span>
|
||||
<span v-if="peer.Disabled" class="text-danger"><i class="fa fa-circle-xmark"
|
||||
: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><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>
|
||||
<span v-for="ip in peer.Addresses" :key="ip" class="badge rounded-pill bg-light">{{ ip }}</span>
|
||||
</td>
|
||||
<td v-if="profile.hasStatistics">
|
||||
<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 v-else>
|
||||
<span class="badge rounded-pill bg-light"><i class="fa-solid fa-link-slash"></i></span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{peer.InterfaceIdentifier}}</td>
|
||||
<td>{{ peer.InterfaceIdentifier }}</td>
|
||||
<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-edit-peer')" @click.prevent="editPeerId=peer.Identifier"><i class="fas fa-cog"></i></a>
|
||||
<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-edit-peer')" @click.prevent="editPeerId = peer.Identifier"><i
|
||||
class="fas fa-cog"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@ -94,33 +106,34 @@ onMounted(async () => {
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<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>
|
||||
</li>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
</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>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<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">
|
||||
<select v-model.number="profile.pageSize" class="form-select" @click="profile.afterPageSizeChange()">
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
<option value="999999999">{{ $t('general.pagination.all') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<option value="100">100</option>
|
||||
<option value="999999999">{{ $t('general.pagination.all') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div></template>
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -14,7 +14,7 @@
|
||||
let WGPORTAL_SITE_COMPANY_NAME="WireGuard Portal";
|
||||
</script>
|
||||
<script src="/api/v0/config/frontend.js"></script>
|
||||
<script type="module" crossorigin src="/app/assets/index-0ieWfcrp.js"></script>
|
||||
<script type="module" crossorigin src="/app/assets/index--mSsjyvF.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/app/assets/index-OMIWgeM9.css">
|
||||
</head>
|
||||
<body class="d-flex flex-column min-vh-100">
|
||||
|
@ -4,11 +4,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
"github.com/sirupsen/logrus"
|
||||
evbus "github.com/vardius/message-bus"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 {
|
||||
|
||||
a.UserManager.StartBackgroundJobs(ctx)
|
||||
a.StatisticsCollector.StartBackgroundJobs(ctx)
|
||||
a.WireGuardManager.StartBackgroundJobs(ctx)
|
||||
|
@ -4,11 +4,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/h44z/wg-portal/internal/app"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/app"
|
||||
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
|
||||
"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) {
|
||||
|
||||
go m.runLdapSynchronizationService(ctx)
|
||||
|
||||
}
|
||||
|
||||
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 {
|
||||
continue // sync disabled
|
||||
}
|
||||
|
||||
//logrus.Tracef(&ldapCfg)
|
||||
err := m.synchronizeLdapUsers(ctx, &ldapCfg)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
tctx = domain.SetUserInfo(tctx, domain.SystemAdminContextUserInfo())
|
||||
|
||||
if existingUser == nil {
|
||||
err := m.NewUser(ctx, user)
|
||||
err := m.NewUser(tctx, user)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create error for user id %s: %w", user.Identifier, err)
|
||||
}
|
||||
}
|
||||
|
||||
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.UpdatedBy = "ldap_sync"
|
||||
u.Email = user.Email
|
||||
|
@ -3,9 +3,10 @@ package internal
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user