2023-08-04 13:34:18 +02:00
|
|
|
<script setup>
|
|
|
|
import { RouterLink, RouterView } from 'vue-router';
|
2024-02-29 07:17:17 +03:00
|
|
|
import { computed, getCurrentInstance, onMounted, ref } from "vue";
|
|
|
|
import { authStore } from "./stores/auth";
|
|
|
|
import { securityStore } from "./stores/security";
|
|
|
|
import { settingsStore } from "@/stores/settings";
|
2025-02-28 16:11:55 +01:00
|
|
|
import { Notifications } from "@kyvg/vue3-notification";
|
2023-08-04 13:34:18 +02:00
|
|
|
|
|
|
|
const appGlobal = getCurrentInstance().appContext.config.globalProperties
|
|
|
|
const auth = authStore()
|
|
|
|
const sec = securityStore()
|
|
|
|
const settings = settingsStore()
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
console.log("Starting WireGuard Portal frontend...");
|
|
|
|
|
2025-07-27 00:15:27 +02:00
|
|
|
// restore theme from localStorage
|
|
|
|
const theme = localStorage.getItem('wgTheme') || 'light';
|
|
|
|
document.documentElement.setAttribute('data-bs-theme', theme);
|
|
|
|
|
2023-08-04 13:34:18 +02:00
|
|
|
await sec.LoadSecurityProperties();
|
|
|
|
await auth.LoadProviders();
|
|
|
|
|
|
|
|
let wasLoggedIn = auth.IsAuthenticated;
|
|
|
|
try {
|
|
|
|
await auth.LoadSession();
|
|
|
|
await settings.LoadSettings(); // only logs errors, does not throw
|
|
|
|
|
|
|
|
console.log("WireGuard Portal session is valid");
|
|
|
|
} catch (e) {
|
|
|
|
if (wasLoggedIn) {
|
|
|
|
console.log("WireGuard Portal invalid - logging out");
|
|
|
|
await auth.Logout();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log("WireGuard Portal ready!");
|
|
|
|
})
|
|
|
|
|
|
|
|
const switchLanguage = function (lang) {
|
|
|
|
if (appGlobal.$i18n.locale !== lang) {
|
|
|
|
localStorage.setItem('wgLang', lang);
|
|
|
|
appGlobal.$i18n.locale = lang;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-27 00:15:27 +02:00
|
|
|
const switchTheme = function (theme) {
|
|
|
|
if (document.documentElement.getAttribute('data-bs-theme') !== theme) {
|
|
|
|
localStorage.setItem('wgTheme', theme);
|
|
|
|
document.documentElement.setAttribute('data-bs-theme', theme);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-04 13:34:18 +02:00
|
|
|
const languageFlag = computed(() => {
|
|
|
|
// `this` points to the component instance
|
|
|
|
let lang = appGlobal.$i18n.locale.toLowerCase();
|
2024-07-04 23:40:16 +03:00
|
|
|
if (!appGlobal.$i18n.availableLocales.includes(lang)) {
|
|
|
|
lang = appGlobal.$i18n.fallbackLocale;
|
|
|
|
}
|
2025-02-07 22:04:26 +01:00
|
|
|
const langMap = {
|
|
|
|
en: "us",
|
2025-04-22 22:44:05 +02:00
|
|
|
pt: "pt",
|
2025-02-07 22:04:26 +01:00
|
|
|
uk: "ua",
|
|
|
|
zh: "cn",
|
2025-04-24 21:54:45 +09:00
|
|
|
ko: "kr",
|
2025-09-21 05:44:59 -05:00
|
|
|
es: "es",
|
2025-04-24 21:54:45 +09:00
|
|
|
|
2025-02-07 22:04:26 +01:00
|
|
|
};
|
|
|
|
return "fi-" + (langMap[lang] || lang);
|
2023-08-04 13:34:18 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
const companyName = ref(WGPORTAL_SITE_COMPANY_NAME);
|
|
|
|
const wgVersion = ref(WGPORTAL_VERSION);
|
|
|
|
const currentYear = ref(new Date().getFullYear())
|
|
|
|
|
2025-05-16 09:55:35 +02:00
|
|
|
const userDisplayName = computed(() => {
|
|
|
|
let displayName = "Unknown";
|
|
|
|
if (auth.IsAuthenticated) {
|
|
|
|
if (auth.User.Firstname === "" && auth.User.Lastname === "") {
|
|
|
|
displayName = auth.User.Identifier;
|
|
|
|
} else if (auth.User.Firstname === "" && auth.User.Lastname !== "") {
|
|
|
|
displayName = auth.User.Lastname;
|
|
|
|
} else if (auth.User.Firstname !== "" && auth.User.Lastname === "") {
|
|
|
|
displayName = auth.User.Firstname;
|
|
|
|
} else if (auth.User.Firstname !== "" && auth.User.Lastname !== "") {
|
|
|
|
displayName = auth.User.Firstname + " " + auth.User.Lastname;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// pad string to 20 characters so that the menu is always the same size on desktop
|
|
|
|
if (displayName.length < 20 && window.innerWidth > 992) {
|
|
|
|
displayName = displayName.padStart(20, "\u00A0");
|
|
|
|
}
|
|
|
|
return displayName;
|
|
|
|
})
|
2023-08-04 13:34:18 +02:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<notifications :duration="3000" :ignore-duplicates="true" position="top right" />
|
|
|
|
|
|
|
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
|
|
|
<div class="container-fluid">
|
2024-07-04 23:40:16 +03:00
|
|
|
<a class="navbar-brand" href="/"><img :alt="companyName" src="/img/header-logo.png" /></a>
|
2023-08-04 13:34:18 +02:00
|
|
|
<button aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler"
|
|
|
|
data-bs-target="#navbarTop" data-bs-toggle="collapse" type="button">
|
|
|
|
<span class="navbar-toggler-icon"></span>
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<div id="navbarTop" class="collapse navbar-collapse">
|
|
|
|
<ul class="navbar-nav me-auto">
|
|
|
|
<li class="nav-item">
|
|
|
|
<RouterLink :to="{ name: 'home' }" class="nav-link">{{ $t('menu.home') }}</RouterLink>
|
|
|
|
</li>
|
|
|
|
<li v-if="auth.IsAuthenticated && auth.IsAdmin" class="nav-item">
|
|
|
|
<RouterLink :to="{ name: 'interfaces' }" class="nav-link">{{ $t('menu.interfaces') }}</RouterLink>
|
|
|
|
</li>
|
|
|
|
<li v-if="auth.IsAuthenticated && auth.IsAdmin" class="nav-item">
|
|
|
|
<RouterLink :to="{ name: 'users' }" class="nav-link">{{ $t('menu.users') }}</RouterLink>
|
|
|
|
</li>
|
2025-05-02 18:48:35 +02:00
|
|
|
<li class="nav-item">
|
|
|
|
<RouterLink :to="{ name: 'key-generator' }" class="nav-link">{{ $t('menu.keygen') }}</RouterLink>
|
|
|
|
</li>
|
2023-08-04 13:34:18 +02:00
|
|
|
</ul>
|
|
|
|
|
|
|
|
<div class="navbar-nav d-flex justify-content-end">
|
|
|
|
<div v-if="auth.IsAuthenticated" class="nav-item dropdown">
|
2024-02-29 07:17:17 +03:00
|
|
|
<a aria-expanded="false" aria-haspopup="true" class="nav-link dropdown-toggle" data-bs-toggle="dropdown"
|
2025-05-16 09:55:35 +02:00
|
|
|
href="#" role="button">{{ userDisplayName }}</a>
|
2023-08-04 13:34:18 +02:00
|
|
|
<div class="dropdown-menu">
|
2024-02-29 08:18:40 +03:00
|
|
|
<RouterLink :to="{ name: 'profile' }" class="dropdown-item"><i class="fas fa-user"></i> {{ $t('menu.profile') }}</RouterLink>
|
2025-05-16 09:55:35 +02:00
|
|
|
<RouterLink :to="{ name: 'settings' }" class="dropdown-item" v-if="auth.IsAdmin || !settings.Setting('ApiAdminOnly') || settings.Setting('WebAuthnEnabled')"><i class="fas fa-gears"></i> {{ $t('menu.settings') }}</RouterLink>
|
2025-03-29 16:42:31 +01:00
|
|
|
<RouterLink :to="{ name: 'audit' }" class="dropdown-item" v-if="auth.IsAdmin"><i class="fas fa-file-shield"></i> {{ $t('menu.audit') }}</RouterLink>
|
2023-08-04 13:34:18 +02:00
|
|
|
<div class="dropdown-divider"></div>
|
2024-02-29 08:18:40 +03:00
|
|
|
<a class="dropdown-item" href="#" @click.prevent="auth.Logout"><i class="fas fa-sign-out-alt"></i> {{ $t('menu.logout') }}</a>
|
2023-08-04 13:34:18 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div v-if="!auth.IsAuthenticated" class="nav-item">
|
2024-02-29 08:18:40 +03:00
|
|
|
<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>
|
2023-08-04 13:34:18 +02:00
|
|
|
</div>
|
2025-07-27 00:15:27 +02:00
|
|
|
<div class="nav-item dropdown" data-bs-theme="light">
|
|
|
|
<a class="nav-link dropdown-toggle d-flex align-items-center" href="#" id="theme-menu" aria-expanded="false" data-bs-toggle="dropdown" data-bs-display="static" aria-label="Toggle theme">
|
|
|
|
<i class="fa-solid fa-circle-half-stroke"></i>
|
|
|
|
<span class="d-lg-none ms-2">Toggle theme</span>
|
|
|
|
</a>
|
|
|
|
<ul class="dropdown-menu dropdown-menu-end">
|
|
|
|
<li>
|
|
|
|
<button type="button" class="dropdown-item d-flex align-items-center" @click.prevent="switchTheme('light')" aria-pressed="false">
|
|
|
|
<i class="fa-solid fa-sun"></i><span class="ms-2">Light</span>
|
|
|
|
</button>
|
|
|
|
</li>
|
|
|
|
<li>
|
|
|
|
<button type="button" class="dropdown-item d-flex align-items-center" @click.prevent="switchTheme('dark')" aria-pressed="true">
|
|
|
|
<i class="fa-solid fa-moon"></i><span class="ms-2">Dark</span>
|
|
|
|
</button>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</div>
|
2023-08-04 13:34:18 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
<div class="container mt-5 flex-shrink-0">
|
|
|
|
<RouterView />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<footer class="page-footer mt-auto">
|
|
|
|
<div class="container mt-5">
|
|
|
|
<div class="row align-items-center">
|
2024-02-29 08:18:40 +03:00
|
|
|
<div class="col-6">Copyright © {{ companyName }} {{ currentYear }} <span v-if="auth.IsAuthenticated"> - version {{ wgVersion }}</span></div>
|
2023-08-04 13:34:18 +02:00
|
|
|
<div class="col-6 text-end">
|
|
|
|
<div :aria-label="$t('menu.lang')" class="btn-group" role="group">
|
|
|
|
<div class="btn-group" role="group">
|
2025-07-27 00:15:27 +02:00
|
|
|
<button aria-expanded="false" aria-haspopup="true" class="btn flag-button pe-0"
|
2024-02-29 07:17:17 +03:00
|
|
|
data-bs-toggle="dropdown" type="button"><span :class="languageFlag" class="fi"></span></button>
|
2023-08-04 13:34:18 +02:00
|
|
|
<div aria-labelledby="btnGroupDrop3" class="dropdown-menu" style="">
|
2024-07-04 23:40:16 +03:00
|
|
|
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('de')"><span class="fi fi-de"></span> Deutsch</a>
|
2025-02-07 22:04:26 +01:00
|
|
|
<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('fr')"><span class="fi fi-fr"></span> Français</a>
|
2025-04-24 21:54:45 +09:00
|
|
|
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('ko')"><span class="fi fi-kr"></span> 한국어</a>
|
|
|
|
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('pt')"><span class="fi fi-pt"></span> Português</a>
|
2024-07-04 23:40:16 +03:00
|
|
|
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('ru')"><span class="fi fi-ru"></span> Русский</a>
|
2025-02-07 22:04:26 +01:00
|
|
|
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('uk')"><span class="fi fi-ua"></span> Українська</a>
|
2024-09-22 16:54:41 +07:00
|
|
|
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('vi')"><span class="fi fi-vi"></span> Tiếng Việt</a>
|
2024-11-02 01:05:38 +08:00
|
|
|
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('zh')"><span class="fi fi-cn"></span> 中文</a>
|
2025-09-21 05:44:59 -05:00
|
|
|
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('es')"><span class="fi fi-es"></span> Español</a>
|
2025-04-24 21:54:45 +09:00
|
|
|
|
2023-08-04 13:34:18 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2025-05-12 22:53:43 +02:00
|
|
|
</footer>
|
|
|
|
</template>
|
2023-08-04 13:34:18 +02:00
|
|
|
|
2025-07-27 00:15:27 +02:00
|
|
|
<style>
|
|
|
|
.flag-button:active,.flag-button:hover,.flag-button:focus,.flag-button:checked,.flag-button:disabled,.flag-button:not(:disabled) {
|
|
|
|
border: 1px solid transparent!important;
|
|
|
|
}
|
|
|
|
[data-bs-theme=dark] .form-select {
|
|
|
|
color: #0c0c0c!important;
|
|
|
|
background-color: #c1c1c1!important;
|
|
|
|
--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")!important;
|
|
|
|
}
|
|
|
|
[data-bs-theme=dark] .form-control {
|
|
|
|
color: #0c0c0c!important;
|
|
|
|
background-color: #c1c1c1!important;
|
|
|
|
}
|
|
|
|
[data-bs-theme=dark] .form-control:focus {
|
|
|
|
color: #0c0c0c!important;
|
|
|
|
background-color: #c1c1c1!important;
|
|
|
|
}
|
|
|
|
[data-bs-theme=dark] .badge.bg-light {
|
|
|
|
--bs-bg-opacity: 1;
|
|
|
|
background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important;
|
|
|
|
color: var(--bs-badge-color)!important;
|
|
|
|
}
|
|
|
|
[data-bs-theme=dark] span.input-group-text {
|
|
|
|
--bs-bg-opacity: 1;
|
|
|
|
background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important;
|
|
|
|
color: var(--bs-badge-color)!important;
|
|
|
|
}
|
|
|
|
</style>
|