Reconstruct Client App UI

This commit is contained in:
Donald Zou 2025-06-26 17:56:55 +08:00
parent e69e7ff3c1
commit 79ad3c0a84
8 changed files with 88 additions and 38 deletions

View File

@ -1,3 +1,5 @@
from tzlocal import get_localzone
from functools import wraps from functools import wraps
from flask import Blueprint, render_template, abort, request, Flask, current_app, session from flask import Blueprint, render_template, abort, request, Flask, current_app, session
@ -91,6 +93,12 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration],
def ClientIndex(): def ClientIndex():
return render_template('client.html') return render_template('client.html')
@client.get(f'{prefix}/api/serverInformation')
def ClientAPI_ServerInformation():
return ResponseObject(data={
"ServerTimezone": str(get_localzone())
})
@client.get(f'{prefix}/api/validateAuthentication') @client.get(f'{prefix}/api/validateAuthentication')
@login_required @login_required
def ClientAPI_ValidateAuthentication(): def ClientAPI_ValidateAuthentication():

View File

@ -3,7 +3,6 @@ import uuid
from .ConnectionString import ConnectionString from .ConnectionString import ConnectionString
from .DashboardLogger import DashboardLogger from .DashboardLogger import DashboardLogger
import sqlalchemy as db import sqlalchemy as db
from .WireguardConfiguration import WireguardConfiguration from .WireguardConfiguration import WireguardConfiguration

View File

@ -12,3 +12,4 @@ sqlalchemy
sqlalchemy_utils sqlalchemy_utils
psycopg2 psycopg2
mysqlclient mysqlclient
tzlocal

View File

@ -6,6 +6,7 @@ import PeerSettingsDropdown from "@/components/configurationComponents/peerSetti
import LocaleText from "@/components/text/localeText.vue"; import LocaleText from "@/components/text/localeText.vue";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {GetLocale} from "../../utilities/locale.js"; import {GetLocale} from "../../utilities/locale.js";
export default { export default {
name: "peer", name: "peer",
methods: {GetLocale}, methods: {GetLocale},

View File

@ -1,12 +1,50 @@
<script setup> <script setup>
import {ref} from "vue"; import {computed, ref} from "vue";
import ConfigurationQRCode from "@/components/Configuration/configurationQRCode.vue"; import ConfigurationQRCode from "@/components/Configuration/configurationQRCode.vue";
import dayjs from "dayjs";
import Duration from 'dayjs/plugin/Duration'
dayjs.extend(Duration);
const props = defineProps([ const props = defineProps([
'config' 'config'
]) ])
const showQRCode = ref(false) const showQRCode = ref(false)
const dateJobs = computed(() => {
return props.config.jobs.filter(x => x.Field === 'date').sort((x, y) => {
if (dayjs(x.Value).isBefore(y.Value)){
return -1
}else if (dayjs(x.Value).isAfter(y.Value)){
return 1
}else{
return 0
}
})
});
const totalDataJobs = computed(() => {
return props.config.jobs.filter(x => x.Field === "total_data").sort((x, y) => {
return parseFloat(y.Value) - parseFloat(x.Value)
})
});
const dateLimit = computed(() => {
if (dateJobs.value.length > 0){
return dateJobs.value[0].Value
}
return undefined
})
const totalDataLimit = computed(() => {
if (totalDataJobs.value.length > 0){
return totalDataJobs.value[0].Value
}
return undefined
})
const totalDataPercentage = computed(() => {
if (!totalDataLimit.value) return 100
return ( props.config.data / totalDataLimit.value ) * 100
})
window.dayjs = dayjs
</script> </script>
<template> <template>
@ -21,25 +59,32 @@ const showQRCode = ref(false)
{{ props.config.protocol === 'wg' ? 'WireGuard': 'AmneziaWG' }} {{ props.config.protocol === 'wg' ? 'WireGuard': 'AmneziaWG' }}
</span> </span>
</div> </div>
<div class="card-body p-3"> <div class="card-body p-3 d-flex gap-3 flex-column">
<div class="row gy-2 mb-2"> <div>
<div class="col-sm text-center"> <div class="mb-1 d-flex align-items-center">
<small class="text-muted mb-2"> <small class="text-muted ">
<i class="bi bi-bar-chart-fill me-1"></i> Data Usage <i class="bi bi-bar-chart-fill me-1"></i> Data Usage
</small> </small>
<h6 class="fw-bold "> <small class="fw-bold ms-sm-auto">
3.42 / 4.00 GB {{ props.config.data.toFixed(4) }} / {{ totalDataLimit ? parseFloat(totalDataLimit).toFixed(4) : 'Unlimited'}} GB
</h6>
</div>
<div class="col-sm text-center">
<small class="text-muted mb-2">
<i class="bi bi-calendar me-1"></i> Valid Until
</small> </small>
<h6 class="fw-bold "> </div>
3.42 / 4.00 GB <div class="progress" role="progressbar" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100" style="height: 6px">
</h6> <div class="progress-bar bg-primary"
:style="{'width': '' + totalDataPercentage + '%'}"></div>
</div> </div>
</div> </div>
<div>
<div class="mb-1 d-flex align-items-center">
<small class="text-muted">
<i class="bi bi-calendar me-1"></i> Valid Until
</small>
<small class="fw-bold ms-auto">
{{ dateLimit ? dateLimit : 'Unlimited Time' }}
</small>
</div>
</div>
<button class="btn btn-outline-body rounded-3 flex-grow-1 fw-bold w-100" @click="showQRCode = true"> <button class="btn btn-outline-body rounded-3 flex-grow-1 fw-bold w-100" @click="showQRCode = true">
<i class="bi bi-link-45deg me-2"></i><small>Connect</small> <i class="bi bi-link-45deg me-2"></i><small>Connect</small>
</button> </button>

View File

@ -6,8 +6,14 @@ import router from "@/router/router.js";
import {createPinia} from "pinia"; import {createPinia} from "pinia";
import 'bootstrap/dist/js/bootstrap.bundle.js' import 'bootstrap/dist/js/bootstrap.bundle.js'
import {clientStore} from "@/stores/clientStore.js";
createApp(App) const app = createApp(App)
.use(createPinia())
.use(router) app.use(createPinia())
.mount('#app') const store = clientStore()
await fetch("/client/api/serverInformation").then(res => res.json()).then(res => store.serverInformation = res.data)
app.use(router)
app.mount("#app")

View File

@ -7,6 +7,7 @@ import {axiosGet} from "@/utilities/request.js";
export const clientStore = defineStore('clientStore', { export const clientStore = defineStore('clientStore', {
state: () => ({ state: () => ({
serverInformation: {},
notifications: [], notifications: [],
configurations: [], configurations: [],
clientProfile: { clientProfile: {
@ -36,21 +37,6 @@ export const clientStore = defineStore('clientStore', {
const data = await axiosGet("/api/configurations") const data = await axiosGet("/api/configurations")
if (data){ if (data){
this.configurations = data.data this.configurations = data.data
this.configurations.forEach(c => {
console.log(
c.jobs.sort((x, y) => {
if (dayjs(x.CreationDate).isBefore(y.CreationDate)){
return 1
}else if (dayjs(x.CreationDate).isAfter(y.CreationDate)){
return -1
}else{
return 0
}
})
)
console.log(c.jobs.find(x => x.Field === 'date'))
})
}else{ }else{
this.newNotification("Failed to fetch configurations", "danger") this.newNotification("Failed to fetch configurations", "danger")
} }

View File

@ -13,6 +13,10 @@ const configurations = computed(() => {
onMounted(async () => { onMounted(async () => {
await store.getConfigurations() await store.getConfigurations()
loading.value = false; loading.value = false;
setInterval(async () => {
await store.getConfigurations()
}, 5000)
}) })
</script> </script>