wip: english translation

This commit is contained in:
Christoph Haas 2023-07-25 21:37:12 +02:00
parent 984818c393
commit c934d7ecd3
17 changed files with 459 additions and 337 deletions

View File

@ -109,13 +109,12 @@ const currentYear = ref(new Date().getFullYear())
<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 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">
<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('es')"><span class="fi fi-es"></span> Español</a>
</div>
</div>
</div>

View File

@ -1,31 +1,8 @@
<template>
<Teleport to="#dialogs">
<div v-if="visible" class="modal-backdrop fade show">
<div class="modal fade show" tabindex="-1">
<div class="modal-dialog modal-dialog-scrollable" @click.stop="">
<div class="modal-content" ref="body">
<div class="modal-header">
<h5 class="modal-title">{{ title }}</h5>
</div>
<div class="modal-body">
{{ question }}
</div>
<div class="modal-footer pt-0 border-top-0">
<button type="button" class="btn btn-primary" @click="no">No</button>
<button type="button" class="btn btn-success" @click="yes">Yes</button>
</div>
</div>
</div>
</div>
</div>
</Teleport>
</template>
<style>
</style>
<script setup>
import {ref} from "vue";
import {useI18n} from "vue-i18n";
const { t } = useI18n()
const title = ref("Default Title")
const question = ref("Default Question")
@ -41,13 +18,37 @@ function showDialog(titleStr, questionStr) {
function yes() {
visible.value = false
console.log("Chosen yes")
emit('yes')
}
function no() {
visible.value = false
console.log("Chosen no")
emit('no')
}
</script>
</script>
<template>
<Teleport to="#dialogs">
<div v-if="visible" class="modal-backdrop fade show">
<div class="modal fade show" tabindex="-1">
<div class="modal-dialog modal-dialog-scrollable" @click.stop="">
<div class="modal-content" ref="body">
<div class="modal-header">
<h5 class="modal-title">{{ title }}</h5>
</div>
<div class="modal-body">
{{ question }}
</div>
<div class="modal-footer pt-0 border-top-0">
<button type="button" class="btn btn-primary" @click="no">{{ $t('general.no') }}</button>
<button type="button" class="btn btn-success" @click="yes">{{ $t('general.yes') }}</button>
</div>
</div>
</div>
</div>
</div>
</Teleport>
</template>
<style>
</style>

View File

@ -28,7 +28,7 @@ const title = computed(() => {
return "" // otherwise interfaces.GetSelected will die...
}
return t("interfaces.interface.show") + ": " + selectedInterface.value.Identifier
return t("modals.interface-view.headline") + " " + selectedInterface.value.Identifier
})
// functions
@ -54,7 +54,7 @@ function close() {
<Prism language="ini" :code="configString"></Prism>
</template>
<template #footer>
<button class="btn btn-primary" type="button" @click.prevent="close">Close</button>
<button class="btn btn-primary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
</template>
</Modal>
</template>

View File

@ -2,7 +2,7 @@
<Teleport to="#modals">
<div v-show="visible" class="modal-backdrop fade show" @click="closeBackdrop">
<div class="modal fade show" tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable" @click.stop="">
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable" @click.stop="">
<div class="modal-content" ref="body">
<div class="modal-header">
<h5 class="modal-title">{{ title }}</h5>

View File

@ -63,16 +63,14 @@ const title = computed(() => {
return "" // otherwise interfaces.GetSelected will die...
}
if (selectedInterface.value.Mode === "server") {
return t("interfaces.peer.view") + ": " + selectedPeer.value.DisplayName
return t("modals.peer-view.headline-peer") + " " + selectedPeer.value.DisplayName
} else {
return t("interfaces.endpoint.view") + ": " + selectedPeer.value.DisplayName
return t("modals.peer-view.headline-endpoint") + " " + selectedPeer.value.DisplayName
}
})
watch(() => props.visible, async (newValue, oldValue) => {
if (oldValue === false && newValue === true) { // if modal is shown
console.log(selectedInterface.value)
console.log(selectedPeer.value)
await peers.LoadPeerConfig(selectedPeer.value.Identifier)
configString.value = peers.configuration
}
@ -121,21 +119,20 @@ function email() {
<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">
Peer Information
{{ $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 class="accordion-body">
<div class="row">
<div class="col-md-8">
<h4>Details</h4>
<ul>
<li>Identifier: {{ selectedPeer.PublicKey }}</li>
<li>IP Addresses: <span v-for="ip in selectedPeer.Addresses" :key="ip" class="badge rounded-pill bg-light">{{ ip }}</span></li>
<li>Linked User: {{ selectedPeer.UserIdentifier }}</li>
<li v-if="selectedPeer.Notes">Notes: {{ selectedPeer.Notes }}</li>
<li v-if="selectedPeer.ExpiresAt">Expires At: {{ selectedPeer.ExpiresAt }}</li>
<li v-if="selectedPeer.Disabled">Disabled Reason: {{ selectedPeer.DisabledReason }}</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.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>
</ul>
</div>
<div class="col-md-4">
@ -148,21 +145,21 @@ function email() {
<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">
Current Status
{{ $t('modals.peer-view.section-status') }}
</button>
</h2>
<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>Traffic</h4>
<p><i class="fas fa-long-arrow-alt-down"></i> {{ selectedStats.BytesReceived }} Bytes / <i class="fas fa-long-arrow-alt-up"></i> {{ selectedStats.BytesTransmitted }} Bytes</p>
<h4>Connection Stats</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>
<h4>{{ $t('modals.peer-view.connection-status') }}</h4>
<ul>
<li>Pingable: {{ selectedStats.IsPingable }}</li>
<li>Last Handshake: {{ selectedStats.LastHandshake }}</li>
<li>Connected Since: {{ selectedStats.LastSessionStart }}</li>
<li>Endpoint: {{ selectedStats.EndpointAddress }}</li>
<li>{{ $t('modals.peer-view.pingable') }}: {{ selectedStats.IsPingable }}</li>
<li>{{ $t('modals.peer-view.handshake') }}: {{ selectedStats.LastHandshake }}</li>
<li>{{ $t('modals.peer-view.connected-since') }}: {{ selectedStats.LastSessionStart }}</li>
<li>{{ $t('modals.peer-view.endpoint') }}: {{ selectedStats.EndpointAddress }}</li>
</ul>
</div>
</div>
@ -172,7 +169,7 @@ function email() {
<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">
Peer Configuration
{{ $t('modals.peer-view.section-config') }}
</button>
</h2>
<div id="collapseConfig" class="accordion-collapse collapse" aria-labelledby="headingConfig" data-bs-parent="#peerInformation" style="">
@ -185,10 +182,10 @@ function email() {
</template>
<template #footer>
<div class="flex-fill text-start">
<button @click.prevent="download" type="button" class="btn btn-primary me-1">Download</button>
<button @click.prevent="email" type="button" class="btn btn-primary me-1">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" 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">Close</button>
<button @click.prevent="close" type="button" class="btn btn-secondary">{{ $t('general.close') }}</button>
</template>

View File

@ -26,9 +26,9 @@ const title = computed(() => {
return "" // otherwise interfaces.GetSelected will die...
}
if (selectedUser.value) {
return t("users.edit") + ": " + selectedUser.value.Identifier
return t("modals.user-edit.headline-edit") + " " + selectedUser.value.Identifier
}
return t("users.new")
return t("modals.user-edit.headline-new")
})
const formData = ref(freshUser())
@ -97,78 +97,78 @@ async function del() {
<Modal :title="title" :visible="visible" @close="close">
<template #default>
<fieldset v-if="formData.Source==='db'">
<legend class="mt-4">General</legend>
<legend class="mt-4">{{ $t('modals.user-edit.header-general') }}</legend>
<div v-if="props.userId==='#NEW#'" class="form-group">
<label class="form-label mt-4">{{ $t('modals.useredit.identifier') }}</label>
<input v-model="formData.Identifier" class="form-control" placeholder="The user id" type="text">
<label class="form-label mt-4">{{ $t('modals.user-edit.identifier.label') }}</label>
<input v-model="formData.Identifier" class="form-control" :placeholder="$t('modals.user-edit.identifier.placeholder')" type="text">
</div>
<div class="form-group">
<label class="form-label mt-4">{{ $t('modals.useredit.source') }}</label>
<input v-model="formData.Source" class="form-control" disabled="disabled" placeholder="The user source" type="text">
<label class="form-label mt-4">{{ $t('modals.user-edit.source.label') }}</label>
<input v-model="formData.Source" class="form-control" disabled="disabled" :placeholder="$t('modals.user-edit.source.placeholder')" type="text">
</div>
<div v-if="formData.Source==='db'" class="form-group">
<label class="form-label mt-4">{{ $t('modals.useredit.password') }}</label>
<input v-model="formData.Password" aria-describedby="passwordHelp" class="form-control" placeholder="Password" type="text">
<small v-if="props.userId!=='#NEW#'" id="passwordHelp" class="form-text text-muted">Leave this field blank to keep current password.</small>
<label class="form-label mt-4">{{ $t('modals.user-edit.password.label') }}</label>
<input v-model="formData.Password" aria-describedby="passwordHelp" class="form-control" :placeholder="$t('modals.user-edit.password.placeholder')" type="text">
<small v-if="props.userId!=='#NEW#'" id="passwordHelp" class="form-text text-muted">{{ $t('modals.user-edit.password.description') }}</small>
</div>
</fieldset>
<fieldset v-if="formData.Source==='db'">
<legend class="mt-4">User Information</legend>
<legend class="mt-4">{{ $t('modals.user-edit.header-personal') }}</legend>
<div class="form-group">
<label class="form-label mt-4">{{ $t('modals.useredit.email') }}</label>
<input v-model="formData.Email" class="form-control" placeholder="Email" type="email">
<label class="form-label mt-4">{{ $t('modals.user-edit.email.label') }}</label>
<input v-model="formData.Email" class="form-control" :placeholder="$t('modals.user-edit.email.placeholder')" type="email">
</div>
<div class="row">
<div class="form-group col-md-6">
<label class="form-label mt-4">{{ $t('modals.useredit.firstname') }}</label>
<input v-model="formData.Firstname" class="form-control" placeholder="Firstname" type="text">
<label class="form-label mt-4">{{ $t('modals.user-edit.firstname.label') }}</label>
<input v-model="formData.Firstname" class="form-control" :placeholder="$t('modals.user-edit.firstname.placeholder')" type="text">
</div>
<div class="form-group col-md-6">
<label class="form-label mt-4">{{ $t('modals.useredit.lastname') }}</label>
<input v-model="formData.Lastname" class="form-control" placeholder="Lastname" type="text">
<label class="form-label mt-4">{{ $t('modals.user-edit.lastname.label') }}</label>
<input v-model="formData.Lastname" class="form-control" :placeholder="$t('modals.user-edit.lastname.placeholder')" type="text">
</div>
</div>
<div class="row">
<div class="form-group col-md-6">
<label class="form-label mt-4">{{ $t('modals.useredit.phone') }}</label>
<input v-model="formData.Phone" class="form-control" placeholder="Phone" type="text">
<label class="form-label mt-4">{{ $t('modals.user-edit.phone.label') }}</label>
<input v-model="formData.Phone" class="form-control" :placeholder="$t('modals.user-edit.phone.placeholder')" type="text">
</div>
<div class="form-group col-md-6">
<label class="form-label mt-4">{{ $t('modals.useredit.department') }}</label>
<input v-model="formData.Department" class="form-control" placeholder="Department" type="text">
<label class="form-label mt-4">{{ $t('modals.user-edit.department.label') }}</label>
<input v-model="formData.Department" class="form-control" :placeholder="$t('modals.user-edit.department.placeholder')" type="text">
</div>
</div>
</fieldset>
<fieldset>
<legend class="mt-4">Notes</legend>
<legend class="mt-4">{{ $t('modals.user-edit.header-notes') }}</legend>
<div class="form-group">
<label class="form-label mt-4">{{ $t('modals.useredit.notes') }}</label>
<label class="form-label mt-4">{{ $t('modals.user-edit.notes.label') }}</label>
<textarea v-model="formData.Notes" class="form-control" rows="2"></textarea>
</div>
</fieldset>
<fieldset>
<legend class="mt-4">State</legend>
<legend class="mt-4">{{ $t('modals.user-edit.header-state') }}</legend>
<div class="form-check form-switch">
<input v-model="formData.Disabled" class="form-check-input" type="checkbox">
<label class="form-check-label" >Disabled (no WireGuard connection and no login possible)</label>
<label class="form-check-label" >{{ $t('modals.user-edit.disabled.label') }}</label>
</div>
<div class="form-check form-switch">
<input v-model="formData.Locked" class="form-check-input" type="checkbox">
<label class="form-check-label" >Locked (no login possible, WireGuard connections still work)</label>
<label class="form-check-label" >{{ $t('modals.user-edit.locked.label') }}</label>
</div>
<div class="form-check form-switch" v-if="formData.Source==='db'">
<input v-model="formData.IsAdmin" checked="" class="form-check-input" type="checkbox">
<label class="form-check-label">Is Admin</label>
<label class="form-check-label">{{ $t('modals.user-edit.admin.label') }}</label>
</div>
</fieldset>
</template>
<template #footer>
<div class="flex-fill text-start">
<button v-if="props.userId!=='#NEW#'&&formData.Source==='db'" class="btn btn-danger me-1" type="button" @click.prevent="del">Delete</button>
<button v-if="props.userId!=='#NEW#'&&formData.Source==='db'" 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">Save</button>
<button class="btn btn-secondary" type="button" @click.prevent="close">Discard</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>
</template>
</Modal>
</template>

View File

@ -3,7 +3,6 @@ import Modal from "./Modal.vue";
import {userStore} from "../stores/users";
import {computed, ref, watch} from "vue";
import { useI18n } from 'vue-i18n';
import { notify } from "@kyvg/vue3-notification";
const { t } = useI18n()
@ -29,7 +28,7 @@ const title = computed(() => {
if (!props.visible) {
return "" // otherwise interfaces.GetSelected will die...
}
return t("users.view") + ": " + selectedUser.value.Identifier
return t("modals.user-view.headline") + " " + selectedUser.value.Identifier
})
const userPeers = computed(() => {
@ -56,52 +55,52 @@ function close() {
<template #default>
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#user">User</a>
<a class="nav-link active" data-bs-toggle="tab" href="#user">{{ $t('modals.user-view.tab-user') }}</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#peers">Peers</a>
<a class="nav-link" data-bs-toggle="tab" href="#peers">{{ $t('modals.user-view.tab-peers') }}</a>
</li>
</ul>
<div id="interfaceTabs" class="tab-content">
<div id="user" class="tab-pane fade active show">
<ul class="list-group list-group-flush">
<li class="list-group-item">
<h4>User Information:</h4>
<h4>{{ $t('modals.user-view.headline-info') }}</h4>
<table class="table table-sm table-borderless device-status-table">
<tbody>
<tr>
<td>{{ $t('users.label.email') }}:</td>
<td>{{ $t('modals.user-view.email') }}:</td>
<td>{{selectedUser.Email}}</td>
</tr>
<tr>
<td>{{ $t('users.label.firstname') }}:</td>
<td>{{ $t('modals.user-view.firstname') }}:</td>
<td>{{selectedUser.Firstname}}</td>
</tr>
<tr>
<td>{{ $t('users.label.lastname') }}:</td>
<td>{{ $t('modals.user-view.lastname') }}:</td>
<td>{{selectedUser.Lastname}}</td>
</tr>
<tr>
<td>{{ $t('users.label.phone') }}:</td>
<td>{{ $t('modals.user-view.phone') }}:</td>
<td>{{selectedUser.Phone}}</td>
</tr>
<tr>
<td>{{ $t('users.label.department') }}:</td>
<td>{{ $t('modals.user-view.department') }}:</td>
<td>{{selectedUser.Department}}</td>
</tr>
<tr v-if="selectedUser.Disabled">
<td>{{ $t('users.label.disabled') }}:</td>
<td>{{ $t('modals.user-view.disabled') }}:</td>
<td>{{selectedUser.DisabledReason}}</td>
</tr>
<tr v-if="selectedUser.Locked">
<td>{{ $t('users.label.locked') }}:</td>
<td>{{ $t('modals.user-view.locked') }}:</td>
<td>{{selectedUser.LockedReason}}</td>
</tr>
</tbody>
</table>
</li>
<li class="list-group-item" v-if="selectedUser.Notes">
<h4>Notes:</h4>
<h4>{{ $t('modals.user-view.headline-notes') }}</h4>
<table class="table table-sm table-borderless device-status-table">
<tbody>
<tr><td>{{selectedUser.Notes}}</td></tr>
@ -112,15 +111,15 @@ function close() {
</div>
<div id="peers" class="tab-pane fade">
<ul v-if="userPeers.length===0" class="list-group list-group-flush">
<li class="list-group-item">{{ $t('users.nopeers.message') }}</li>
<li class="list-group-item">{{ $t('modals.user-view.no-peers') }}</li>
</ul>
<table v-if="userPeers.length!==0" id="peerTable" class="table table-sm">
<thead>
<tr>
<th scope="col">{{ $t('user.peers.name') }}</th>
<th scope="col">{{ $t('user.peers.interface') }}</th>
<th scope="col">{{ $t('user.peers.ips') }}</th>
<th scope="col">{{ $t('modals.user-view.peers.name') }}</th>
<th scope="col">{{ $t('modals.user-view.peers.interface') }}</th>
<th scope="col">{{ $t('modals.user-view.peers.ip') }}</th>
<th scope="col"></th><!-- Actions -->
</tr>
</thead>
@ -129,7 +128,7 @@ function close() {
<td>{{peer.DisplayName}}</td>
<td>{{peer.InterfaceIdentifier}}</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 pill bg-light">{{ ip }}</span>
</td>
</tr>
</tbody>
@ -138,7 +137,7 @@ function close() {
</div>
</template>
<template #footer>
<button class="btn btn-primary" type="button" @click.prevent="close">Close</button>
<button class="btn btn-primary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
</template>
</Modal>
</template>

View File

@ -39,7 +39,10 @@ export function freshInterface() {
PeerDefPreUp: "",
PeerDefPostUp: "",
PeerDefPreDown: "",
PeerDefPostDown: ""
PeerDefPostDown: "",
TotalPeers: 0,
EnabledPeers: 0
}
}
@ -141,7 +144,9 @@ export function freshUser() {
Disabled: false,
DisabledReason: "",
Locked: false,
LockedReason: ""
LockedReason: "",
PeerCount: 0
}
}

View File

@ -1,7 +1,6 @@
// src/lang/index.js
import de from './translations/de.json';
import en from './translations/en.json';
import es from './translations/es.json';
import {createI18n} from "vue-i18n";
function getStoredLanguage() {
@ -21,8 +20,7 @@ const i18n = createI18n({
fallbackLocale: "en", // set fallback locale
messages: {
"de": de,
"en": en,
"es": es
"en": en
}
});

View File

@ -1,137 +1,278 @@
{
"hello": "Hello World!",
"general": {
"pagination": {
"size": "Number of Elements",
"all": "All (slow)"
},
"search": {
"placeholder": "Search...",
"button": "Search"
},
"select-all": "Select all",
"yes": "Yes",
"no": "No",
"cancel": "Cancel",
"close": "Close",
"save": "Save",
"delete": "Delete"
},
"login": {
"headline": "Please sign in",
"username": {
"label": "Username",
"placeholder": "Please enter your username"
},
"password": {
"label": "Password",
"placeholder": "Please enter your password"
},
"button": "Sign in"
},
"menu": {
"home": "Home",
"interfaces": "Interfaces",
"users": "Users",
"firstname": "Firstname",
"lastname": "Lastname",
"login": "Login",
"lang": "Toggle Language",
"profile": "My Profile",
"login": "Login",
"logout": "Logout"
},
"home": {
"h1": "WireGuard® VPN Portal",
"info": "More Information",
"headline": "WireGuard® VPN Portal",
"info-headline": "More Information",
"abstract": "WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN.",
"installation": {
"instruct": "Installation instructions for client software can be found on the official WireGuard website.",
"h1": "WireGuard Installation",
"h2": "Installation",
"box-header": "WireGuard Installation",
"headline": "Installation",
"content": "Installation instructions for client software can be found on the official WireGuard website.",
"btn": "Open Instructions"
},
"about-wg": {
"instruct": "WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography.",
"h1": "About WireGuard",
"h2": "About",
"btn": "More"
"box-header": "About WireGuard",
"headline": "About",
"content": "WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography.",
"button": "More"
},
"about-portal": {
"instruct": "WireGuard Portal is a simple, web based configuration portal for WireGuard.",
"h1": "About WireGuard Portal",
"h2": "WireGuard Portal",
"btn": "More"
"box-header": "About WireGuard Portal",
"headline": "WireGuard Portal",
"content": "WireGuard Portal is a simple, web based configuration portal for WireGuard.",
"button": "More"
},
"profiles": {
"h1": "VPN Profiles",
"headline": "VPN Profiles",
"abstract": "You can access and download your personal VPN configurations via your Userprofile.",
"instruct": "To find all your configured profiles click on the button below.",
"btn": "Open my profile"
"content": "To find all your configured profiles click on the button below.",
"button": "Open my profile"
},
"admin": {
"h1": "Administration Area",
"headline": "Administration Area",
"abstract": "In the administration area you can manage WireGuard peers and the server interface as well as users that are allowed to log in to the WireGuard Portal.",
"instruct": "To find all your configured profiles click on the button below.",
"btn-1": "Open Server Administration",
"btn-2": "Open User Administration"
"content": "To find all your configured profiles click on the button below.",
"button-admin": "Open Server Administration",
"button-user": "Open User Administration"
}
},
"interfaces": {
"h1": "Interface Administration",
"h2": "Current VPN Peers",
"h2-client": "Current Endpoints",
"tableHeadings": {
"headline": "Interface Administration",
"headline-peers": "Current VPN Peers",
"headline-endpoints": "Current Endpoints",
"no-interface": {
"default-selection": "No Interface available",
"headline": "No interfaces found...",
"abstract": "Click the plus button above to create a new WireGuard interface."
},
"no-peer": {
"headline": "No peers available",
"abstract": "Currently, there are no peers available for the selected WireGuard interface."
},
"table-heading": {
"name": "Name",
"user": "User",
"ip": "IP's",
"endpoint": "Endpoint",
"stats": "Status"
"status": "Status"
},
"noInterface": {
"h1": "No interfaces found...",
"message": "Click the plus button above to create a new WireGuard interface."
},
"notAvailable": "No Interface available",
"statusBox": {
"h1": "Interface status for",
"interface": {
"headline": "Interface status for",
"mode": "mode",
"key": "Public Key",
"endpoint": "Public Endpoint",
"port": "Listening Port",
"peers": "Enabled Peers",
"totalPeers": "Total Peers",
"total-peers": "Total Peers",
"endpoints": "Enabled Endpoints",
"total-endpoints": "Total Endpoints",
"ip": "IP Address",
"allowedIP": "Default allowed IPs",
"dnsServers": "DNS Servers",
"mtu": "Default MTU",
"interval": "Default Keepalive Interval"
"default-allowed-ip": "Default allowed IPs",
"dns": "DNS Servers",
"mtu": "MTU",
"default-keep-alive": "Default Keepalive Interval",
"button-show-config": "Show configuration",
"button-download-config": "Download configuration",
"button-store-config": "Store configuration for wg-quick",
"button-edit": "Edit interface"
},
"noPeerSelect": {
"h4": "No peers for the selected interface...",
"message": "Click the plus button above to create a new WireGuard interface."
},
"peer": {
"new": "Create new peer",
"edit": "Edit peer"
},
"interface": {
"new": "Create new interface",
"edit": "Edit interface"
},
"endpoint": {
"new": "Create new endpoint",
"edit": "Edit endpoint"
}
"button-add-interface": "Add Interface",
"button-add-peer": "Add Peer",
"button-add-peers": "Add Multiple Peers",
"button-show-peer": "Show Peer",
"button-edit-peer": "Edit Peer",
"peer-disabled": "Peer is disabled, reason:",
"peer-expiring": "Peer is expiring at",
"peer-connected": "Connected",
"peer-not-connected": "Not Connected",
"peer-handshake": "Last handshake:"
},
"login": {
"please": "Please sign in",
"username": "Username",
"userMessage": "Please enter your username",
"pass": "Password",
"passMessage": "Please enter your password",
"btn": "Sign in"
},
"user": {
"h1": "User Administration",
"id": "ID",
"email": "eMail",
"firstname": "Firstname",
"lastname": "Lastname",
"source": "Source",
"peers": "Peers",
"admin": "Admin",
"addUser": "Add User",
"addMulti": "Add Multiple Users"
"users": {
"headline": "User Administration",
"table-heading": {
"id": "ID",
"email": "E-Mail",
"firstname": "Firstname",
"lastname": "Lastname",
"source": "Source",
"peers": "Peers",
"admin": "Admin"
},
"no-user": {
"headline": "No users available",
"abstract": "Currently, there are no users registered with WireGuard Portal."
},
"button-add-user": "Add User",
"button-show-user": "Show User",
"button-edit-user": "Edit User",
"user-disabled": "User is disabled, reason:",
"user-locked": "Account is locked, reason:",
"admin": "User has administrator privileges",
"no-admin": "User has no administrator privileges"
},
"profile": {
"h2-clients": "My VPN Peers",
"tableHeadings": {
"headline": "My VPN Peers",
"table-heading": {
"name": "Name",
"ip": "IP's",
"stats": "Status",
"interface": "Server Interface"
}
},
"no-peer": {
"headline": "No peers available",
"abstract": "Currently, there are no peers associated with your user profile."
},
"peer-connected": "Connected",
"button-add-peer": "Add Peer",
"button-show-peer": "Show Peer",
"button-edit-peer": "Edit Peer"
},
"modals": {
"peeredit": {
"user-view": {
"headline": "User Account:",
"tab-user": "Information",
"tab-peers": "Peers",
"headline-info": "User Information:",
"headline-notes": "Notes:",
"email": "E-Mail",
"firstname": "Firstname",
"lastname": "Lastname",
"phone": "Phone number",
"department": "Department",
"disabled": "Account Disabled",
"locked": "Account Locked",
"no-peers": "User has no associated peers.",
"peers": {
"name": "Name",
"interface": "Interface",
"ip": "IP's"
}
},
"user-edit": {
"headline-edit": "Edit user:",
"headline-new": "New user",
"header-general": "General",
"header-personal": "User Information",
"header-notes": "Notes",
"header-state": "State",
"identifier": {
"label": "Identifier",
"placeholder": "The unique user identifier"
},
"source": {
"label": "Source",
"placeholder": "The user source"
},
"password": {
"label": "Password",
"placeholder": "A super secret password",
"description": "Leave this field blank to keep current password."
},
"email": {
"label": "Email",
"placeholder": "The email address"
},
"phone": {
"label": "Phone",
"placeholder": "The phone number"
},
"department": {
"label": "Department",
"placeholder": "The department"
},
"firstname": {
"label": "Firstname",
"placeholder": "Firstname"
},
"lastname": {
"label": "Lastname",
"placeholder": "Lastname"
},
"notes": {
"label": "Notes",
"placeholder": ""
},
"disabled": {
"label": "Disabled (no WireGuard connection and no login possible)"
},
"locked": {
"label": "Locked (no login possible, WireGuard connections still work)"
},
"admin": {
"label": "Is Admin"
}
},
"interface-view": {
"headline": "Config for Interface:"
},
"interface-edit": {
"privatekey": "Private Key"
},
"peer-view": {
"headline-peer": "Peer:",
"headline-endpoint": "Endpoint:",
"section-info": "Peer Information",
"section-status": "Current Status",
"section-config": "Configuration",
"identifier": "Identifier",
"ip": "IP Addresses",
"user": "Associated User",
"notes": "Notes",
"expiry-status": "Expires At",
"disabled-status": "Disabled At",
"traffic": "Traffic",
"connection-status": "Connection Stats",
"upload": "Uploaded Bytes (from Server to Peer)",
"download": "Downloaded Bytes (from Peer to Server)",
"pingable": "Is Pingable",
"handshake": "Last Handshake",
"connected-since": "Connected since",
"endpoint": "Endpoint",
"button-download": "Download configuration",
"button-email": "Send configuration via E-Mail"
},
"peer-edit": {
"privatekey": "Private Key"
},
"peer-multi-create": {
"privatekey": "Private Key"
}
},
"general": {
"pagination": {
"size": "Number of Elements",
"all": "All (slow)"
}
}
}

View File

@ -1,3 +0,0 @@
{
"hello": "Hola mundo!"
}

View File

@ -1,15 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>

View File

@ -7,65 +7,65 @@
<template>
<div class="page-header">
<h1>{{ $t('home.h1') }}</h1>
<h1>{{ $t('home.headline') }}</h1>
</div>
<p class="lead">{{ $t('home.abstract') }}</p>
<div class="bg-light p-5" v-if="auth.IsAuthenticated">
<h2 class="display-5">{{ $t('home.profiles.h1') }}</h2>
<h2 class="display-5">{{ $t('home.profiles.headline') }}</h2>
<p class="lead">{{ $t('home.profiles.abstract') }}</p>
<hr class="my-4">
<p>{{ $t('home.profiles.instruct') }}</p>
<p>{{ $t('home.profiles.content') }}</p>
<p class="lead">
<RouterLink :to="{ name: 'profile' }" class="btn btn-primary btn-lg">{{ $t('home.profiles.btn') }}</RouterLink>
<RouterLink :to="{ name: 'profile' }" class="btn btn-primary btn-lg">{{ $t('home.profiles.button') }}</RouterLink>
</p>
</div>
<div class="bg-light p-5 mt-4" v-if="auth.IsAuthenticated && auth.IsAdmin">
<h2 class="display-5">{{ $t('home.admin.h1') }}</h2>
<h2 class="display-5">{{ $t('home.admin.headline') }}</h2>
<p class="lead">{{ $t('home.admin.abstract') }}</p>
<hr class="my-4">
<p>{{ $t('home.admin.instruct') }}</p>
<p>{{ $t('home.admin.content') }}</p>
<p class="lead">
<RouterLink :to="{ name: 'interfaces' }" class="btn btn-primary btn-lg me-2">{{ $t('home.admin.btn-1') }}</RouterLink>
<RouterLink :to="{ name: 'users' }" class="btn btn-primary btn-lg">{{ $t('home.admin.btn-2') }}</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>
<h3 class="mt-5">{{ $t('home.info') }}</h3>
<h3 class="mt-5">{{ $t('home.info-headline') }}</h3>
<div class="row">
<div class="col-lg-4">
<div class="card border-secondary mb-4" style="min-height: 15rem;">
<div class="card-header">{{ $t('home.installation.h1') }}</div>
<div class="card-header">{{ $t('home.installation.box-header') }}</div>
<div class="card-body d-flex flex-column">
<h4 class="card-title">{{ $t('home.installation.h2') }}</h4>
<p class="card-text">{{ $t('home.installation.instruct') }}</p>
<h4 class="card-title">{{ $t('home.installation.headline') }}</h4>
<p class="card-text">{{ $t('home.installation.content') }}</p>
<a href="https://www.wireguard.com/install/" title="WireGuard Installation" target="_blank"
rel="noopener noreferrer" class="mt-auto btn btn-primary btn-sm">{{ $t('home.installation.btn') }}</a>
rel="noopener noreferrer" class="mt-auto btn btn-primary btn-sm">{{ $t('home.installation.button') }}</a>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card border-secondary mb-4" style="min-height: 15rem;">
<div class="card-header">{{ $t('home.about-wg.h1') }}</div>
<div class="card-header">{{ $t('home.about-wg.box-header') }}</div>
<div class="card-body d-flex flex-column">
<h4 class="card-title">{{ $t('home.about-wg.h2') }}</h4>
<p class="card-text">{{ $t('home.about-wg.instruct') }}</p>
<h4 class="card-title">{{ $t('home.about-wg.headline') }}</h4>
<p class="card-text">{{ $t('home.about-wg.content') }}</p>
<a href="https://www.wireguard.com/" title="WireGuard" target="_blank" rel="noopener noreferrer"
class="mt-auto btn btn-primary btn-sm">{{ $t('home.about-wg.btn') }}</a>
class="mt-auto btn btn-primary btn-sm">{{ $t('home.about-wg.button') }}</a>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card border-secondary mb-4" style="min-height: 15rem;">
<div class="card-header">{{ $t('home.about-portal.h1') }}</div>
<div class="card-header">{{ $t('home.about-portal.box-header') }}</div>
<div class="card-body d-flex flex-column">
<h4 class="card-title">{{ $t('home.about-portal.h2') }}</h4>
<p class="card-text">{{ $t('home.about-portal.instruct') }}</p>
<h4 class="card-title">{{ $t('home.about-portal.headline') }}</h4>
<p class="card-text">{{ $t('home.about-portal.content') }}</p>
<a href="https://github.com/h44z/wg-portal/" title="WireGuard Portal" target="_blank"
rel="noopener noreferrer" class="mt-auto btn btn-primary btn-sm">{{ $t('home.about-portal.btn') }}</a>
rel="noopener noreferrer" class="mt-auto btn btn-primary btn-sm">{{ $t('home.about-portal.button') }}</a>
</div>
</div>
</div>

View File

@ -83,7 +83,7 @@ onMounted(async () => {
<!-- Headline and interface selector -->
<div class="page-header row">
<div class="col-12 col-lg-8">
<h1>{{ $t('interfaces.h1') }}</h1>
<h1>{{ $t('interfaces.headline') }}</h1>
</div>
<div class="col-12 col-lg-4 text-end">
<div class="form-group">
@ -91,11 +91,11 @@ onMounted(async () => {
</div>
<div class="form-group">
<div class="input-group mb-3">
<button class="input-group-text btn btn-primary" title="Add new interface" @click.prevent="editInterfaceId='#NEW#'">
<button class="input-group-text btn btn-primary" :title="$t('interfaces.button-add-interface')" @click.prevent="editInterfaceId='#NEW#'">
<i class="fa-solid fa-plus-circle"></i>
</button>
<select v-model="interfaces.selected" :disabled="interfaces.Count===0" class="form-select" @change="peers.LoadPeers()">
<option v-if="interfaces.Count===0" value="nothing">{{ $t('interfaces.notAvailable') }}</option>
<option v-if="interfaces.Count===0" value="nothing">{{ $t('interfaces.no-interface.default-selection') }}</option>
<option v-for="iface in interfaces.All" :key="iface.Identifier" :value="iface.Identifier">{{ calculateInterfaceName(iface.Identifier,iface.DisplayName) }}</option>
</select>
</div>
@ -107,8 +107,8 @@ onMounted(async () => {
<div v-if="interfaces.Count===0" class="row">
<div class="col-lg-12">
<div class="mt-5">
<h4>{{ $t('interfaces.noInterface.h1') }}</h4>
<p>{{ $t('interfaces.noInterface.message') }}</p>
<h4>{{ $t('interfaces.no-interface.headline') }}</h4>
<p>{{ $t('interfaces.no-interface.abstract') }}</p>
</div>
</div>
</div>
@ -120,14 +120,14 @@ onMounted(async () => {
<div class="card-header">
<div class="row">
<div class="col-12 col-lg-8">
{{ $t('interfaces.statusBox.h1') }} <strong>{{interfaces.GetSelected.Identifier}}</strong> ({{interfaces.GetSelected.Mode}} {{ $t('interfaces.statusBox.mode') }})
{{ $t('interfaces.interface.headline') }} <strong>{{interfaces.GetSelected.Identifier}}</strong> ({{interfaces.GetSelected.Mode}} {{ $t('interfaces.interface.mode') }})
<span v-if="interfaces.GetSelected.Disabled" class="text-danger"><i class="fa fa-circle-xmark" :title="interfaces.GetSelected.DisabledReason"></i></span>
</div>
<div class="col-12 col-lg-4 text-lg-end">
<a class="btn-link" href="#" title="Show interface configuration" @click.prevent="viewedInterfaceId=interfaces.GetSelected.Identifier"><i class="fas fa-eye"></i></a>
<a class="ms-5 btn-link" href="#" title="Download interface configuration" @click.prevent="download"><i class="fas fa-download"></i></a>
<a v-if="settings.Setting('PersistentConfigSupported')" class="ms-5 btn-link" href="#" title="Write interface configuration file" @click.prevent="saveConfig"><i class="fas fa-save"></i></a>
<a class="ms-5 btn-link" href="#" title="Edit interface settings" @click.prevent="editInterfaceId=interfaces.GetSelected.Identifier"><i class="fas fa-cog"></i></a>
<a class="btn-link" href="#" :title="$t('interfaces.interface.button-show-config')" @click.prevent="viewedInterfaceId=interfaces.GetSelected.Identifier"><i class="fas fa-eye"></i></a>
<a class="ms-5 btn-link" href="#" :title="$t('interfaces.interface.button-download-config')" @click.prevent="download"><i class="fas fa-download"></i></a>
<a v-if="settings.Setting('PersistentConfigSupported')" class="ms-5 btn-link" href="#" :title="$t('interfaces.interface.button-store-config')" @click.prevent="saveConfig"><i class="fas fa-save"></i></a>
<a class="ms-5 btn-link" href="#" :title="$t('interfaces.interface.button-edit')" @click.prevent="editInterfaceId=interfaces.GetSelected.Identifier"><i class="fas fa-cog"></i></a>
</div>
</div>
</div>
@ -137,23 +137,23 @@ onMounted(async () => {
<table class="table table-sm table-borderless device-status-table">
<tbody>
<tr>
<td>{{ $t('interfaces.statusBox.key') }}:</td>
<td>{{ $t('interfaces.interface.key') }}:</td>
<td>{{interfaces.GetSelected.PublicKey}}</td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.endpoint') }}:</td>
<td>{{ $t('interfaces.interface.endpoint') }}:</td>
<td>{{interfaces.GetSelected.PeerDefEndpoint}}</td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.port') }}:</td>
<td>{{ $t('interfaces.interface.port') }}:</td>
<td>{{interfaces.GetSelected.ListenPort}}</td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.peers') }}:</td>
<td>{{ $t('interfaces.interface.peers') }}:</td>
<td>{{interfaces.GetSelected.EnabledPeers}}</td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.totalPeers') }}:</td>
<td>{{ $t('interfaces.interface.total-peers') }}:</td>
<td>{{interfaces.GetSelected.TotalPeers}}</td>
</tr>
</tbody>
@ -163,23 +163,23 @@ onMounted(async () => {
<table class="table table-sm table-borderless device-status-table">
<tbody>
<tr>
<td>{{ $t('interfaces.statusBox.ip') }}:</td>
<td>{{ $t('interfaces.interface.ip') }}:</td>
<td><span class="badge bg-light me-1" v-for="addr in interfaces.GetSelected.Addresses" :key="addr">{{addr}}</span></td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.dnsServers') }}:</td>
<td>{{ $t('interfaces.interface.dns') }}:</td>
<td><span class="badge bg-light me-1" v-for="addr in interfaces.GetSelected.Dns" :key="addr">{{addr}}</span></td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.mtu') }}:</td>
<td>{{ $t('interfaces.interface.mtu') }}:</td>
<td>{{interfaces.GetSelected.Mtu}}</td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.interval') }}:</td>
<td>{{ $t('interfaces.interface.default-keep-alive') }}:</td>
<td>{{interfaces.GetSelected.PeerDefPersistentKeepalive}}</td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.allowedIP') }}:</td>
<td>{{ $t('interfaces.interface.default-allowed-ip') }}:</td>
<td><span class="badge bg-light me-1" v-for="addr in interfaces.GetSelected.PeerDefAllowedIPs" :key="addr">{{addr}}</span></td>
</tr>
</tbody>
@ -191,15 +191,15 @@ onMounted(async () => {
<table class="table table-sm table-borderless device-status-table">
<tbody>
<tr>
<td>{{ $t('interfaces.statusBox.key') }}:</td>
<td>{{ $t('interfaces.interface.key') }}:</td>
<td>{{interfaces.GetSelected.PublicKey}}</td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.endpoint') }}:</td>
<td>{{interfaces.GetSelected.InterfacePeers}}</td>
<td>{{ $t('interfaces.interface.endpoints') }}:</td>
<td>{{interfaces.GetSelected.EnabledPeers}}</td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.totalPeers') }}:</td>
<td>{{ $t('interfaces.interface.total-endpoints') }}:</td>
<td>{{interfaces.GetSelected.TotalPeers}}</td>
</tr>
</tbody>
@ -209,15 +209,15 @@ onMounted(async () => {
<table class="table table-sm table-borderless device-status-table">
<tbody>
<tr>
<td>{{ $t('interfaces.statusBox.ip') }}:</td>
<td>{{ $t('interfaces.interface.ip') }}:</td>
<td><span class="badge bg-light me-1" v-for="addr in interfaces.GetSelected.Addresses" :key="addr">{{addr}}</span></td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.dnsServers') }}:</td>
<td>{{ $t('interfaces.interface.dns') }}:</td>
<td><span class="badge bg-light me-1" v-for="addr in interfaces.GetSelected.Dns" :key="addr">{{addr}}</span></td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.mtu') }}:</td>
<td>{{ $t('interfaces.interface.mtu') }}:</td>
<td>{{interfaces.GetSelected.Mtu}}</td>
</tr>
</tbody>
@ -229,23 +229,23 @@ onMounted(async () => {
<table class="table table-sm table-borderless device-status-table">
<tbody>
<tr>
<td>{{ $t('interfaces.statusBox.key') }}:</td>
<td>{{ $t('interfaces.interface.key') }}:</td>
<td>{{interfaces.GetSelected.PublicKey}}</td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.endpoint') }}:</td>
<td>{{ $t('interfaces.interface.endpoint') }}:</td>
<td>{{interfaces.GetSelected.PeerDefEndpoint}}</td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.port') }}:</td>
<td>{{ $t('interfaces.interface.port') }}:</td>
<td>{{interfaces.GetSelected.ListenPort}}</td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.peers') }}:</td>
<td>{{ $t('interfaces.interface.peers') }}:</td>
<td>{{interfaces.GetSelected.EnabledPeers}}</td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.totalPeers') }}:</td>
<td>{{ $t('interfaces.interface.total-peers') }}:</td>
<td>{{interfaces.GetSelected.TotalPeers}}</td>
</tr>
</tbody>
@ -255,23 +255,23 @@ onMounted(async () => {
<table class="table table-sm table-borderless device-status-table">
<tbody>
<tr>
<td>{{ $t('interfaces.statusBox.ip') }}:</td>
<td>{{ $t('interfaces.interface.ip') }}:</td>
<td><span class="badge bg-light me-1" v-for="addr in interfaces.GetSelected.Addresses" :key="addr">{{addr}}</span></td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.allowedIP') }}:</td>
<td>{{ $t('interfaces.interface.default-allowed-ip') }}:</td>
<td><span class="badge bg-light me-1" v-for="addr in interfaces.GetSelected.PeerDefAllowedIPs" :key="addr">{{addr}}</span></td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.dnsServers') }}:</td>
<td>{{ $t('interfaces.interface.dns') }}:</td>
<td><span class="badge bg-light me-1" v-for="addr in interfaces.GetSelected.PeerDefDns" :key="addr">{{addr}}</span></td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.mtu') }}:</td>
<td>{{ $t('interfaces.interface.mtu') }}:</td>
<td>{{interfaces.GetSelected.Mtu}}</td>
</tr>
<tr>
<td>{{ $t('interfaces.statusBox.interval') }}:</td>
<td>{{ $t('interfaces.interface.default-keep-alive') }}:</td>
<td>{{interfaces.GetSelected.PeerDefPersistentKeepalive}}</td>
</tr>
</tbody>
@ -286,40 +286,39 @@ onMounted(async () => {
<!-- Peer list -->
<div v-if="interfaces.Count!==0" class="mt-4 row">
<div class="col-12 col-lg-5">
<h2 v-if="interfaces.GetSelected.Mode==='server'" class="mt-2">{{ $t('interfaces.h2') }}</h2>
<h2 v-else class="mt-2">{{ $t('interfaces.h2-client') }}</h2>
<h2 v-if="interfaces.GetSelected.Mode==='server'" class="mt-2">{{ $t('interfaces.headline-peers') }}</h2>
<h2 v-else class="mt-2">{{ $t('interfaces.headline-endpoints') }}</h2>
</div>
<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="peers.filter" class="form-control" placeholder="Search..." type="text" @keyup="peers.afterPageSizeChange">
<button class="input-group-text btn btn-primary" title="Search"><i class="fa-solid fa-search"></i></button>
<input v-model="peers.filter" class="form-control" :placeholder="$t('general.search.placeholder')" type="text" @keyup="peers.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="interfaces.GetSelected.Mode==='server' && peers.Count!==0" class="btn btn-primary" href="#" title="Send mail to all peers"><i class="fa fa-paper-plane"></i></a-->
<a class="btn btn-primary ms-2" href="#" title="Add multiple peers" @click.prevent="multiCreatePeerId='#NEW#'"><i class="fa fa-plus me-1"></i><i class="fa fa-users"></i></a>
<a class="btn btn-primary ms-2" href="#" title="Add a peer" @click.prevent="editPeerId='#NEW#'"><i class="fa fa-plus me-1"></i><i class="fa fa-user"></i></a>
<a class="btn btn-primary ms-2" href="#" :title="$t('interfaces.button-add-peers')" @click.prevent="multiCreatePeerId='#NEW#'"><i class="fa fa-plus me-1"></i><i class="fa fa-users"></i></a>
<a class="btn btn-primary ms-2" href="#" :title="$t('interfaces.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 v-if="interfaces.Count!==0" class="mt-2 table-responsive">
<div v-if="peers.Count===0">
<h4>{{ $t('interfaces.noPeerSelect.h4') }}</h4>
<p>{{ $t('interfaces.noPeerSelect.message') }}</p>
<h4>{{ $t('interfaces.no-peer.headline') }}</h4>
<p>{{ $t('interfaces.no-peer.abstract') }}</p>
</div>
<table v-if="peers.Count!==0" id="peerTable" class="table table-sm">
<thead>
<tr>
<th scope="col">
<input id="flexCheckDefault" class="form-check-input" title="Select all" type="checkbox" value="">
<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('interfaces.tableHeadings.name') }}</th>
<th scope="col">{{ $t('interfaces.tableHeadings.user') }}</th>
<th scope="col">{{ $t('interfaces.tableHeadings.ip') }}</th>
<th v-if="interfaces.GetSelected.Mode==='client'" scope="col">{{ $t('interfaces.tableHeadings.endpoint') }}</th>
<th v-if="peers.hasStatistics" scope="col">{{ $t('interfaces.tableHeadings.stats') }}</th>
<th scope="col">{{ $t('interfaces.table-heading.name') }}</th>
<th scope="col">{{ $t('interfaces.table-heading.user') }}</th>
<th scope="col">{{ $t('interfaces.table-heading.ip') }}</th>
<th v-if="interfaces.GetSelected.Mode==='client'" scope="col">{{ $t('interfaces.table-heading.endpoint') }}</th>
<th v-if="peers.hasStatistics" scope="col">{{ $t('interfaces.table-heading.status') }}</th>
<th scope="col"></th><!-- Actions -->
</tr>
</thead>
@ -329,8 +328,8 @@ 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 expiring-peer" :title="peer.ExpiresAt"></i></span>
<span v-if="peer.Disabled" class="text-danger" :title="$t('interfaces.peer-disabled') + ' ' + peer.DisabledReason"><i class="fa fa-circle-xmark"></i></span>
<span v-if="!peer.Disabled && peer.ExpiresAt" class="text-warning" :title="$t('interfaces.peer-expiring') + ' ' + peer.ExpiresAt"><i class="fas fa-hourglass-end expiring-peer"></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>{{peer.UserIdentifier}}</td>
@ -340,15 +339,15 @@ onMounted(async () => {
<td v-if="interfaces.GetSelected.Mode==='client'">{{peer.Endpoint.Value}}</td>
<td v-if="peers.hasStatistics">
<div v-if="peers.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">Connected</span>
<span class="badge rounded-pill bg-success" :title="$t('interfaces.peer-connected')"><i class="fa-solid fa-link"></i></span> <span :title="$t('interfaces.peer-handshake') + ' ' + peers.Statistics(peer.Identifier).LastHandshake">{{ $t('interfaces.peer-connected') }}</span>
</div>
<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" :title="$t('interfaces.peer-not-connected')"><i class="fa-solid fa-link-slash"></i></span>
</div>
</td>
<td class="text-center">
<a href="#" title="Show peer" @click.prevent="viewedPeerId=peer.Identifier"><i class="fas fa-eye me-2"></i></a>
<a href="#" title="Edit peer" @click.prevent="editPeerId=peer.Identifier"><i class="fas fa-cog"></i></a>
<a href="#" :title="$t('interfaces.button-show-peer')" @click.prevent="viewedPeerId=peer.Identifier"><i class="fas fa-eye me-2"></i></a>
<a href="#" :title="$t('interfaces.button-edit-peer')" @click.prevent="editPeerId=peer.Identifier"><i class="fas fa-cog"></i></a>
</td>
</tr>
</tbody>

View File

@ -61,26 +61,26 @@ const externalLogin = function (provider) {
<div class="col-lg-3"></div><!-- left spacer -->
<div class="col-lg-6">
<div class="card mt-5">
<div class="card-header">Please sign in <div class="float-end">
<RouterLink :to="{ name: 'home' }" class="nav-link" title="Home"><i class="fas fa-times-circle"></i></RouterLink>
<div class="card-header">{{ $t('login.headline') }}<div class="float-end">
<RouterLink :to="{ name: 'home' }" class="nav-link" :title="$t('menu.home')"><i class="fas fa-times-circle"></i></RouterLink>
</div></div>
<div class="card-body">
<form method="post">
<fieldset>
<div class="form-group">
<label class="form-label" for="inputUsername">{{ $t('login.username') }}</label>
<label class="form-label" for="inputUsername">{{ $t('login.username.label') }}</label>
<div class="input-group mb-3">
<span class="input-group-text"><span class="far fa-user p-2"></span></span>
<input id="inputUsername" v-model="username" :class="{'is-invalid':usernameInvalid, 'is-valid':!usernameInvalid}" :placeholder="$t('login.userMessage')" aria-describedby="usernameHelp"
<input id="inputUsername" v-model="username" :class="{'is-invalid':usernameInvalid, 'is-valid':!usernameInvalid}" :placeholder="$t('login.username.placeholder')" aria-describedby="usernameHelp"
class="form-control"
name="username" type="text">
</div>
</div>
<div class="form-group">
<label class="form-label" for="inputPassword">{{ $t('login.pass') }}</label>
<label class="form-label" for="inputPassword">{{ $t('login.password.label') }}</label>
<div class="input-group mb-3">
<span class="input-group-text"><span class="fas fa-lock p-2"></span></span>
<input id="inputPassword" v-model="password" :class="{'is-invalid':passwordInvalid, 'is-valid':!passwordInvalid}" :placeholder="$t('login.passMessage')" class="form-control"
<input id="inputPassword" v-model="password" :class="{'is-invalid':passwordInvalid, 'is-valid':!passwordInvalid}" :placeholder="$t('login.password.placeholder')" class="form-control"
name="password" type="password">
</div>
</div>
@ -88,7 +88,7 @@ const externalLogin = function (provider) {
<div class="row mt-5 d-flex">
<div :class="{'col-lg-4':auth.LoginProviders.length < 3, 'col-lg-12':auth.LoginProviders.length >= 3}" class="d-flex mb-2">
<button :disabled="disableLoginBtn" class="btn btn-primary flex-fill" type="submit" @click.prevent="login">
{{ $t('login.btn') }} <div v-if="loggingIn" class="d-inline"><i class="ms-2 fa-solid fa-circle-notch fa-spin"></i></div>
{{ $t('login.button') }} <div v-if="loggingIn" class="d-inline"><i class="ms-2 fa-solid fa-circle-notch fa-spin"></i></div>
</button>
</div>
<div :class="{'col-lg-8':auth.LoginProviders.length < 3, 'col-lg-12':auth.LoginProviders.length >= 3}" class="d-flex mb-2">

View File

@ -26,36 +26,36 @@ onMounted(async () => {
<!-- Peer list -->
<div class="mt-4 row">
<div class="col-12 col-lg-5">
<h2 class="mt-2">{{ $t('profile.h2-clients') }}</h2>
<h2 class="mt-2">{{ $t('profile.headline') }}</h2>
</div>
<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="Search..." type="text" @keyup="profile.afterPageSizeChange">
<button class="input-group-text btn btn-primary" title="Search"><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="Add a 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.noPeerSelect.h4') }}</h4>
<p>{{ $t('profile.noPeerSelect.message') }}</p>
<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">
<thead>
<tr>
<th scope="col">
<input id="flexCheckDefault" class="form-check-input" title="Select all" type="checkbox" value="">
<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.tableHeadings.name') }}</th>
<th scope="col">{{ $t('profile.tableHeadings.ip') }}</th>
<th v-if="profile.hasStatistics" scope="col">{{ $t('profile.tableHeadings.stats') }}</th>
<th scope="col">{{ $t('profile.tableHeadings.interface') }}</th>
<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>
@ -74,7 +74,7 @@ onMounted(async () => {
</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">Connected</span>
<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>
</div>
<div v-else>
<span class="badge rounded-pill bg-light"><i class="fa-solid fa-link-slash"></i></span>
@ -82,8 +82,8 @@ onMounted(async () => {
</td>
<td>{{peer.InterfaceIdentifier}}</td>
<td class="text-center">
<a href="#" title="Show peer" @click.prevent="viewedPeerId=peer.Identifier"><i class="fas fa-eye me-2"></i></a>
<a href="#" title="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>

View File

@ -24,40 +24,41 @@ onMounted(() => {
<!-- User list -->
<div class="mt-4 row">
<div class="col-12 col-lg-5">
<h1>{{ $t('user.h1') }}</h1>
<h1>{{ $t('users.headline') }}</h1>
</div>
<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="users.filter" class="form-control" placeholder="Search..." type="text" @keyup="users.afterPageSizeChange">
<button class="input-group-text btn btn-primary" title="Search"><i class="fa-solid fa-search"></i></button>
<input v-model="users.filter" class="form-control" :placeholder="$t('general.search.placeholder')" type="text" @keyup="users.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 class="btn btn-primary ms-2" href="#" title="Add a user" @click.prevent="editUserId='#NEW#'"><i class="fa fa-plus me-1"></i><i
class="fa fa-user"></i></a>
<a class="btn btn-primary ms-2" href="#" :title="$t('users.button-add-user')" @click.prevent="editUserId='#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="users.Count===0">
<h4>{{ $t('users.noUsers.h4') }}</h4>
<p>{{ $t('users.noUsers.message') }}</p>
<h4>{{ $t('users.no-user.headline') }}</h4>
<p>{{ $t('users.no-user.abstract') }}</p>
</div>
<table v-if="users.Count!==0" id="userTable" class="table table-sm">
<thead>
<tr>
<th scope="col">
<input id="flexCheckDefault" class="form-check-input" title="Select all" type="checkbox" value="">
<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('user.id') }}</th>
<th scope="col">{{ $t('user.email') }}</th>
<th scope="col">{{ $t('user.firstname') }}</th>
<th scope="col">{{ $t('user.lastname') }}</th>
<th class="text-center" scope="col">{{ $t('user.source') }}</th>
<th class="text-center" scope="col">{{ $t('user.peers') }}</th>
<th class="text-center" scope="col">{{ $t('user.admin') }}</th>
<th scope="col">{{ $t('users.table-heading.id') }}</th>
<th scope="col">{{ $t('users.table-heading.email') }}</th>
<th scope="col">{{ $t('users.table-heading.firstname') }}</th>
<th scope="col">{{ $t('users.table-heading.lastname') }}</th>
<th class="text-center" scope="col">{{ $t('users.table-heading.source') }}</th>
<th class="text-center" scope="col">{{ $t('users.table-heading.peers') }}</th>
<th class="text-center" scope="col">{{ $t('users.table-heading.admin') }}</th>
<th scope="col"></th><!-- Actions -->
</tr>
</thead>
@ -67,8 +68,8 @@ onMounted(() => {
<input id="flexCheckDefault" class="form-check-input" type="checkbox" value="">
</th>
<td class="text-center">
<span v-if="user.Disabled" class="text-danger"><i class="fa fa-circle-xmark" :title="user.DisabledReason"></i></span>
<span v-if="user.Locked" class="text-danger"><i class="fas fa-lock" :title="user.LockedReason"></i></span>
<span v-if="user.Disabled" class="text-danger" :title="$t('users.user-disabled') + ' ' + user.DisabledReason"><i class="fa fa-circle-xmark"></i></span>
<span v-if="user.Locked" class="text-danger" :title="$t('users.user-locked') + ' ' + user.LockedReason"><i class="fas fa-lock"></i></span>
</td>
<td>{{user.Identifier}}</td>
<td>{{user.Email}}</td>
@ -77,12 +78,12 @@ onMounted(() => {
<td class="text-center"><span class="badge rounded-pill bg-light">{{user.Source}}</span></td>
<td class="text-center">{{user.PeerCount}}</td>
<td class="text-center">
<span v-if="user.IsAdmin" class="text-danger"><i class="fa fa-check-circle"></i></span>
<span v-else><i class="fa fa-circle-xmark"></i></span>
<span v-if="user.IsAdmin" class="text-danger" :title="$t('users.admin')"><i class="fa fa-check-circle"></i></span>
<span v-else><i class="fa fa-circle-xmark" :title="$t('users.no-admin')"></i></span>
</td>
<td class="text-center">
<a href="#" title="Show user" @click.prevent="viewedUserId=user.Identifier"><i class="fas fa-eye me-2"></i></a>
<a href="#" title="Edit user" @click.prevent="editUserId=user.Identifier"><i class="fas fa-cog me-2"></i></a>
<a href="#" :title="$t('users.button-show-user')" @click.prevent="viewedUserId=user.Identifier"><i class="fas fa-eye me-2"></i></a>
<a href="#" :title="$t('users.button-edit-user')" @click.prevent="editUserId=user.Identifier"><i class="fas fa-cog me-2"></i></a>
</td>
</tr>
</tbody>