smaller UI improvements; add system theme
Some checks failed
Docker / Build and Push (push) Has been cancelled
Docker / release (push) Has been cancelled
github-pages / deploy (push) Has been cancelled

This commit is contained in:
Christoph
2025-10-12 00:48:35 +02:00
parent 4d19f1d8bb
commit c7724b620a
4 changed files with 53 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
<script setup> <script setup>
import { RouterLink, RouterView } from 'vue-router'; import { RouterLink, RouterView } from 'vue-router';
import { computed, getCurrentInstance, onMounted, ref } from "vue"; import {computed, getCurrentInstance, nextTick, onMounted, ref} from "vue";
import { authStore } from "./stores/auth"; import { authStore } from "./stores/auth";
import { securityStore } from "./stores/security"; import { securityStore } from "./stores/security";
import { settingsStore } from "@/stores/settings"; import { settingsStore } from "@/stores/settings";
@@ -11,12 +11,13 @@ const auth = authStore()
const sec = securityStore() const sec = securityStore()
const settings = settingsStore() const settings = settingsStore()
const currentTheme = ref("auto")
onMounted(async () => { onMounted(async () => {
console.log("Starting WireGuard Portal frontend..."); console.log("Starting WireGuard Portal frontend...");
// restore theme from localStorage // restore theme from localStorage
const theme = localStorage.getItem('wgTheme') || 'light'; switchTheme(getTheme());
document.documentElement.setAttribute('data-bs-theme', theme);
await sec.LoadSecurityProperties(); await sec.LoadSecurityProperties();
await auth.LoadProviders(); await auth.LoadProviders();
@@ -44,10 +45,22 @@ const switchLanguage = function (lang) {
} }
} }
const getTheme = function () {
return localStorage.getItem('wgTheme') || 'auto';
}
const switchTheme = function (theme) { const switchTheme = function (theme) {
if (document.documentElement.getAttribute('data-bs-theme') !== theme) { let bsTheme = theme;
if (theme === 'auto') {
bsTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
currentTheme.value = theme;
if (document.documentElement.getAttribute('data-bs-theme') !== bsTheme) {
console.log("Switching theme to " + theme + " (" + bsTheme + ")");
localStorage.setItem('wgTheme', theme); localStorage.setItem('wgTheme', theme);
document.documentElement.setAttribute('data-bs-theme', theme); document.documentElement.setAttribute('data-bs-theme', bsTheme);
} }
} }
@@ -137,20 +150,25 @@ const userDisplayName = computed(() => {
<div v-if="!auth.IsAuthenticated" class="nav-item"> <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 class="nav-item dropdown" data-bs-theme="light"> <div class="nav-item dropdown" :key="currentTheme">
<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"> <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> <i class="fa-solid fa-circle-half-stroke"></i>
<span class="d-lg-none ms-2">Toggle theme</span> <span class="d-lg-none ms-2">Toggle theme</span>
</a> </a>
<ul class="dropdown-menu dropdown-menu-end"> <ul class="dropdown-menu dropdown-menu-end">
<li>
<button type="button" class="dropdown-item d-flex align-items-center" @click.prevent="switchTheme('auto')" aria-pressed="false">
<i class="fa-solid fa-circle-half-stroke"></i><span class="ms-2">System</span><i class="fa-solid fa-check ms-5" :class="{invisible:currentTheme!=='auto'}"></i>
</button>
</li>
<li> <li>
<button type="button" class="dropdown-item d-flex align-items-center" @click.prevent="switchTheme('light')" aria-pressed="false"> <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> <i class="fa-solid fa-sun"></i><span class="ms-2">Light</span><i class="fa-solid fa-check ms-5" :class="{invisible:currentTheme!=='light'}"></i>
</button> </button>
</li> </li>
<li> <li>
<button type="button" class="dropdown-item d-flex align-items-center" @click.prevent="switchTheme('dark')" aria-pressed="true"> <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> <i class="fa-solid fa-moon"></i><span class="ms-2">Dark</span><i class="fa-solid fa-check ms-5" :class="{invisible:currentTheme!=='dark'}"></i>
</button> </button>
</li> </li>
</ul> </ul>
@@ -221,4 +239,8 @@ const userDisplayName = computed(() => {
background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important; background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important;
color: var(--bs-badge-color)!important; color: var(--bs-badge-color)!important;
} }
[data-bs-theme=dark] .navbar-dark, .navbar {
background-color: #000 !important;
}
</style> </style>

View File

@@ -316,6 +316,16 @@ async function del() {
isDeleting.value = true isDeleting.value = true
try { try {
await interfaces.DeleteInterface(selectedInterface.value.Identifier) await interfaces.DeleteInterface(selectedInterface.value.Identifier)
// reload all interfaces and peers
await interfaces.LoadInterfaces()
if (interfaces.Count > 0 && interfaces.GetSelected !== undefined) {
const selectedInterface = interfaces.GetSelected
await peers.LoadPeers(selectedInterface.Identifier)
await peers.LoadStats(selectedInterface.Identifier)
} else {
await peers.Reset() // reset peers if no interfaces are available
}
close() close()
} catch (e) { } catch (e) {
console.log(e) console.log(e)

View File

@@ -115,6 +115,7 @@ export const interfaceStore = defineStore('interfaces', {
return apiWrapper.post(`${baseUrl}/new`, formData) return apiWrapper.post(`${baseUrl}/new`, formData)
.then(iface => { .then(iface => {
this.interfaces.push(iface) this.interfaces.push(iface)
this.selected = iface.Identifier
this.fetching = false this.fetching = false
}) })
.catch(error => { .catch(error => {

View File

@@ -126,9 +126,14 @@ export const peerStore = defineStore('peers', {
if (!statsResponse) { if (!statsResponse) {
this.stats = {} this.stats = {}
this.statsEnabled = false this.statsEnabled = false
} else {
this.stats = statsResponse.Stats
this.statsEnabled = statsResponse.Enabled
} }
this.stats = statsResponse.Stats },
this.statsEnabled = statsResponse.Enabled async Reset() {
this.setPeers([])
this.setStats(undefined)
}, },
async PreparePeer(interfaceId) { async PreparePeer(interfaceId) {
return apiWrapper.get(`${baseUrl}/iface/${base64_url_encode(interfaceId)}/prepare`) return apiWrapper.get(`${baseUrl}/iface/${base64_url_encode(interfaceId)}/prepare`)
@@ -186,10 +191,10 @@ export const peerStore = defineStore('peers', {
async LoadStats(interfaceId) { async LoadStats(interfaceId) {
// if no interfaceId is given, use the currently selected interface // if no interfaceId is given, use the currently selected interface
if (!interfaceId) { if (!interfaceId) {
interfaceId = interfaceStore().GetSelected.Identifier if (!interfaceStore().GetSelected || !interfaceStore().GetSelected.Identifier) {
if (!interfaceId) { return // no interface, nothing to load
return // no interface, nothing to load
} }
interfaceId = interfaceStore().GetSelected.Identifier
} }
this.fetching = true this.fetching = true
@@ -260,10 +265,10 @@ export const peerStore = defineStore('peers', {
async LoadPeers(interfaceId) { async LoadPeers(interfaceId) {
// if no interfaceId is given, use the currently selected interface // if no interfaceId is given, use the currently selected interface
if (!interfaceId) { if (!interfaceId) {
interfaceId = interfaceStore().GetSelected.Identifier if (!interfaceStore().GetSelected || !interfaceStore().GetSelected.Identifier) {
if (!interfaceId) {
return // no interface, nothing to load return // no interface, nothing to load
} }
interfaceId = interfaceStore().GetSelected.Identifier
} }
this.fetching = true this.fetching = true