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 flask import Blueprint, render_template, abort, request, Flask, current_app, session
@ -91,6 +93,12 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration],
def ClientIndex():
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')
@login_required
def ClientAPI_ValidateAuthentication():

View File

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

View File

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

View File

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

View File

@ -1,12 +1,50 @@
<script setup>
import {ref} from "vue";
import {computed, ref} from "vue";
import ConfigurationQRCode from "@/components/Configuration/configurationQRCode.vue";
import dayjs from "dayjs";
import Duration from 'dayjs/plugin/Duration'
dayjs.extend(Duration);
const props = defineProps([
'config'
])
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>
<template>
@ -21,25 +59,32 @@ const showQRCode = ref(false)
{{ props.config.protocol === 'wg' ? 'WireGuard': 'AmneziaWG' }}
</span>
</div>
<div class="card-body p-3">
<div class="row gy-2 mb-2">
<div class="col-sm text-center">
<small class="text-muted mb-2">
<div class="card-body p-3 d-flex gap-3 flex-column">
<div>
<div class="mb-1 d-flex align-items-center">
<small class="text-muted ">
<i class="bi bi-bar-chart-fill me-1"></i> Data Usage
</small>
<h6 class="fw-bold ">
3.42 / 4.00 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 class="fw-bold ms-sm-auto">
{{ props.config.data.toFixed(4) }} / {{ totalDataLimit ? parseFloat(totalDataLimit).toFixed(4) : 'Unlimited'}} GB
</small>
<h6 class="fw-bold ">
3.42 / 4.00 GB
</h6>
</div>
<div class="progress" role="progressbar" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100" style="height: 6px">
<div class="progress-bar bg-primary"
:style="{'width': '' + totalDataPercentage + '%'}"></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">
<i class="bi bi-link-45deg me-2"></i><small>Connect</small>
</button>

View File

@ -6,8 +6,14 @@ import router from "@/router/router.js";
import {createPinia} from "pinia";
import 'bootstrap/dist/js/bootstrap.bundle.js'
import {clientStore} from "@/stores/clientStore.js";
createApp(App)
.use(createPinia())
.use(router)
.mount('#app')
const app = createApp(App)
app.use(createPinia())
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', {
state: () => ({
serverInformation: {},
notifications: [],
configurations: [],
clientProfile: {
@ -36,21 +37,6 @@ export const clientStore = defineStore('clientStore', {
const data = await axiosGet("/api/configurations")
if (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{
this.newNotification("Failed to fetch configurations", "danger")
}

View File

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