Browse Source

group even more

pull/1397/head
Bernd Storath 7 months ago
parent
commit
4b3f5b838e
  1. 1
      src/app/app.vue
  2. 16
      src/app/components/Client/Charts.vue
  3. 4
      src/app/components/Client/Client.vue
  4. 2
      src/app/components/Client/LastSeen.vue
  5. 2
      src/app/layouts/Header.vue
  6. 33
      src/app/stores/global.ts
  7. 18
      src/app/utils/api.ts
  8. 2
      src/server/api/cnf/[oneTimeLink].ts
  9. 7
      src/server/api/features.get.ts
  10. 4
      src/server/api/statistics.get.ts
  11. 2
      src/server/api/wireguard/client/[clientId]/generateOneTimeLink.post.ts
  12. 4
      src/server/utils/WireGuard.ts
  13. 8
      src/services/database/lowdb.ts
  14. 30
      src/services/database/migrations/1.ts
  15. 37
      src/services/database/repositories/system.ts

1
src/app/app.vue

@ -16,6 +16,7 @@ const globalStore = useGlobalStore();
globalStore.fetchFeatures(); globalStore.fetchFeatures();
globalStore.fetchRelease(); globalStore.fetchRelease();
globalStore.setLanguage(); globalStore.setLanguage();
globalStore.fetchStatistics();
useHead({ useHead({
bodyAttrs: { bodyAttrs: {
class: 'bg-gray-50 dark:bg-neutral-800', class: 'bg-gray-50 dark:bg-neutral-800',

16
src/app/components/Client/Charts.vue

@ -1,13 +1,13 @@
<template> <template>
<div <div
v-if="globalStore.features.trafficStats.type" v-if="globalStore.statistics.chartType"
:class="`absolute z-0 bottom-0 left-0 right-0 h-6 ${globalStore.features.trafficStats.type === 1 && 'line-chart'}`" :class="`absolute z-0 bottom-0 left-0 right-0 h-6 ${globalStore.statistics.chartType === 1 && 'line-chart'}`"
> >
<UiChart :options="chartOptionsTX" :series="client.transferTxSeries" /> <UiChart :options="chartOptionsTX" :series="client.transferTxSeries" />
</div> </div>
<div <div
v-if="globalStore.features.trafficStats.type" v-if="globalStore.statistics.chartType"
:class="`absolute z-0 top-0 left-0 right-0 h-6 ${globalStore.features.trafficStats.type === 1 && 'line-chart'}`" :class="`absolute z-0 top-0 left-0 right-0 h-6 ${globalStore.statistics.chartType === 1 && 'line-chart'}`"
> >
<UiChart <UiChart
:options="chartOptionsRX" :options="chartOptionsRX"
@ -33,9 +33,9 @@ const chartOptionsTX = computed(() => {
colors: [CHART_COLORS.tx[theme.value]], colors: [CHART_COLORS.tx[theme.value]],
}; };
opts.chart.type = opts.chart.type =
UI_CHART_TYPES[globalStore.features.trafficStats.type]?.type || undefined; UI_CHART_TYPES[globalStore.statistics.chartType]?.type || undefined;
opts.stroke.width = opts.stroke.width =
UI_CHART_TYPES[globalStore.features.trafficStats.type]?.strokeWidth ?? 0; UI_CHART_TYPES[globalStore.statistics.chartType]?.strokeWidth ?? 0;
return opts; return opts;
}); });
@ -45,9 +45,9 @@ const chartOptionsRX = computed(() => {
colors: [CHART_COLORS.rx[theme.value]], colors: [CHART_COLORS.rx[theme.value]],
}; };
opts.chart.type = opts.chart.type =
UI_CHART_TYPES[globalStore.features.trafficStats.type]?.type || undefined; UI_CHART_TYPES[globalStore.statistics.chartType]?.type || undefined;
opts.stroke.width = opts.stroke.width =
UI_CHART_TYPES[globalStore.features.trafficStats.type]?.strokeWidth ?? 0; UI_CHART_TYPES[globalStore.statistics.chartType]?.strokeWidth ?? 0;
return opts; return opts;
}); });

4
src/app/components/Client/Client.vue

@ -14,7 +14,7 @@
> >
<ClientAddress4 :client="client" /> <ClientAddress4 :client="client" />
<ClientInlineTransfer <ClientInlineTransfer
v-if="!globalStore.features.trafficStats.enabled" v-if="!globalStore.statistics.enabled"
:client="client" :client="client"
/> />
<ClientLastSeen :client="client" /> <ClientLastSeen :client="client" />
@ -25,7 +25,7 @@
<!-- Info --> <!-- Info -->
<div <div
v-if="globalStore.features.trafficStats.enabled" v-if="globalStore.statistics.enabled"
class="flex gap-2 items-center shrink-0 text-gray-400 dark:text-neutral-400 text-xs mt-px justify-end" class="flex gap-2 items-center shrink-0 text-gray-400 dark:text-neutral-400 text-xs mt-px justify-end"
> >
<ClientTransfer :client="client" /> <ClientTransfer :client="client" />

2
src/app/components/Client/LastSeen.vue

@ -4,7 +4,7 @@
class="text-gray-400 dark:text-neutral-500 whitespace-nowrap" class="text-gray-400 dark:text-neutral-500 whitespace-nowrap"
:title="$t('lastSeen') + dateTime(new Date(client.latestHandshakeAt))" :title="$t('lastSeen') + dateTime(new Date(client.latestHandshakeAt))"
> >
{{ !globalStore.features.trafficStats.enabled ? ' · ' : '' {{ !globalStore.statistics.enabled ? ' · ' : ''
}}{{ timeago(new Date(client.latestHandshakeAt)) }} }}{{ timeago(new Date(client.latestHandshakeAt)) }}
</span> </span>
</template> </template>

2
src/app/layouts/Header.vue

@ -38,7 +38,7 @@
</button> </button>
<!-- Show / hide charts --> <!-- Show / hide charts -->
<label <label
v-if="globalStore.features.trafficStats.type > 0" v-if="globalStore.statistics.chartType > 0"
class="inline-flex items-center justify-center cursor-pointer w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 whitespace-nowrap transition group" class="inline-flex items-center justify-center cursor-pointer w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 whitespace-nowrap transition group"
:title="$t('toggleCharts')" :title="$t('toggleCharts')"
> >

33
src/app/stores/global.ts

@ -8,10 +8,6 @@ export const useGlobalStore = defineStore('Global', () => {
); );
const updateAvailable = ref(false); const updateAvailable = ref(false);
const features = ref({ const features = ref({
trafficStats: {
enabled: false,
type: 0,
},
sortClients: { sortClients: {
enabled: false, enabled: false,
}, },
@ -22,12 +18,18 @@ export const useGlobalStore = defineStore('Global', () => {
enabled: false, enabled: false,
}, },
}); });
const statistics = ref({
enabled: false,
chartType: 0,
});
const sortClient = ref(true); // Sort clients by name, true = asc, false = desc const sortClient = ref(true); // Sort clients by name, true = asc, false = desc
const { availableLocales, locale } = useI18n(); const { availableLocales, locale } = useI18n();
async function setLanguage() { async function setLanguage() {
const { data: lang } = await api.getLang(); const { data: lang } = await useFetch('/api/lang', {
method: 'get',
});
if ( if (
lang.value !== getItem('lang') && lang.value !== getItem('lang') &&
availableLocales.includes(lang.value!) availableLocales.includes(lang.value!)
@ -38,7 +40,9 @@ export const useGlobalStore = defineStore('Global', () => {
} }
async function fetchRelease() { async function fetchRelease() {
const { data: release } = await api.getRelease(); const { data: release } = await useFetch('/api/release', {
method: 'get',
});
if (!release.value) { if (!release.value) {
return; return;
@ -50,14 +54,25 @@ export const useGlobalStore = defineStore('Global', () => {
} }
async function fetchFeatures() { async function fetchFeatures() {
const { data: apiFeatures } = await api.getFeatures(); const { data: apiFeatures } = await useFetch('/api/features', {
method: 'get',
});
if (apiFeatures.value) { if (apiFeatures.value) {
features.value = apiFeatures.value; features.value = apiFeatures.value;
} }
} }
async function fetchStatistics() {
const { data: apiStatistics } = await useFetch('/api/statistics', {
method: 'get',
});
if (apiStatistics.value) {
statistics.value = apiStatistics.value;
}
}
const updateCharts = computed(() => { const updateCharts = computed(() => {
return features.value.trafficStats.type > 0 && uiShowCharts.value; return statistics.value.chartType > 0 && uiShowCharts.value;
}); });
return { return {
@ -68,8 +83,10 @@ export const useGlobalStore = defineStore('Global', () => {
currentRelease, currentRelease,
latestRelease, latestRelease,
updateAvailable, updateAvailable,
statistics,
fetchRelease, fetchRelease,
fetchFeatures, fetchFeatures,
setLanguage, setLanguage,
fetchStatistics,
}; };
}); });

18
src/app/utils/api.ts

@ -1,16 +1,4 @@
class API { class API {
async getRelease() {
return useFetch('/api/release', {
method: 'get',
});
}
async getLang() {
return useFetch('/api/lang', {
method: 'get',
});
}
async getSession() { async getSession() {
return useFetch('/api/session', { return useFetch('/api/session', {
method: 'get', method: 'get',
@ -140,12 +128,6 @@ class API {
}); });
} }
async getFeatures() {
return useFetch('/api/features', {
method: 'get',
});
}
async updateFeatures(features: Record<string, { enabled: boolean }>) { async updateFeatures(features: Record<string, { enabled: boolean }>) {
return $fetch('/api/admin/features', { return $fetch('/api/admin/features', {
method: 'post', method: 'post',

2
src/server/api/cnf/[oneTimeLink].ts

@ -1,6 +1,6 @@
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const system = await Database.system.get(); const system = await Database.system.get();
if (!system.oneTimeLinks.enabled) { if (!system.features.oneTimeLinks.enabled) {
throw createError({ throw createError({
statusCode: 404, statusCode: 404,
statusMessage: 'Invalid state', statusMessage: 'Invalid state',

7
src/server/api/features.get.ts

@ -1,9 +1,4 @@
export default defineEventHandler(async () => { export default defineEventHandler(async () => {
const system = await Database.system.get(); const system = await Database.system.get();
return { return system.features;
trafficStats: system.trafficStats,
sortClients: system.sortClients,
clientExpiration: system.clientExpiration,
oneTimeLinks: system.oneTimeLinks,
};
}); });

4
src/server/api/statistics.get.ts

@ -0,0 +1,4 @@
export default defineEventHandler(async () => {
const system = await Database.system.get();
return system.statistics;
});

2
src/server/api/wireguard/client/[clientId]/generateOneTimeLink.post.ts

@ -1,6 +1,6 @@
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const system = await Database.system.get(); const system = await Database.system.get();
if (!system.oneTimeLinks.enabled) { if (!system.features.oneTimeLinks.enabled) {
throw createError({ throw createError({
status: 404, status: 404,
message: 'Invalid state', message: 'Invalid state',

4
src/server/utils/WireGuard.ts

@ -318,7 +318,7 @@ class WireGuard {
const clients = await Database.client.findAll(); const clients = await Database.client.findAll();
const system = await Database.system.get(); const system = await Database.system.get();
// Expires Feature // Expires Feature
if (system.clientExpiration.enabled) { if (system.features.clientExpiration.enabled) {
for (const client of Object.values(clients)) { for (const client of Object.values(clients)) {
if (client.enabled !== true) continue; if (client.enabled !== true) continue;
if ( if (
@ -331,7 +331,7 @@ class WireGuard {
} }
} }
// One Time Link Feature // One Time Link Feature
if (system.oneTimeLinks.enabled) { if (system.features.oneTimeLinks.enabled) {
for (const client of Object.values(clients)) { for (const client of Object.values(clients)) {
if ( if (
client.oneTimeLink !== null && client.oneTimeLink !== null &&

8
src/services/database/lowdb.ts

@ -19,9 +19,10 @@ import {
type OneTimeLink, type OneTimeLink,
} from './repositories/client'; } from './repositories/client';
import { import {
Features, AvailableFeatures,
SystemRepository, SystemRepository,
type Feature, type Feature,
type Features,
} from './repositories/system'; } from './repositories/system';
const DEBUG = debug('LowDB'); const DEBUG = debug('LowDB');
@ -46,8 +47,9 @@ export class LowDBSystem extends SystemRepository {
DEBUG('Update Features'); DEBUG('Update Features');
this.#db.update((v) => { this.#db.update((v) => {
for (const key in features) { for (const key in features) {
if (Features.includes(key as Features)) { if (AvailableFeatures.includes(key as keyof Features)) {
v.system[key as Features].enabled = features[key]!.enabled; v.system.features[key as keyof Features].enabled =
features[key]!.enabled;
} }
} }
}); });

30
src/services/database/migrations/1.ts

@ -16,6 +16,10 @@ export async function run1(db: Low<Database>) {
const database: Database = { const database: Database = {
migrations: [], migrations: [],
system: { system: {
general: {
sessionTimeout: 3600, // 1 hour
lang: 'en',
},
// Config to configure Server // Config to configure Server
interface: { interface: {
privateKey: privateKey, privateKey: privateKey,
@ -26,10 +30,6 @@ export async function run1(db: Low<Database>) {
port: 51820, port: 51820,
device: 'eth0', device: 'eth0',
}, },
general: {
sessionTimeout: 3600, // 1 hour
lang: 'en',
},
// Config to configure Peer & Client Config // Config to configure Peer & Client Config
userConfig: { userConfig: {
mtu: 1420, mtu: 1420,
@ -49,18 +49,20 @@ export async function run1(db: Low<Database>) {
PreDown: '', PreDown: '',
PostDown: '', PostDown: '',
}, },
trafficStats: { features: {
enabled: false, clientExpiration: {
type: ChartType.None, enabled: false,
}, },
clientExpiration: { oneTimeLinks: {
enabled: false, enabled: false,
}, },
oneTimeLinks: { sortClients: {
enabled: false, enabled: false,
},
}, },
sortClients: { statistics: {
enabled: false, enabled: false,
chartType: ChartType.None,
}, },
metrics: { metrics: {
prometheus: { prometheus: {

37
src/services/database/repositories/system.ts

@ -37,9 +37,9 @@ export enum ChartType {
Bar = 3, Bar = 3,
} }
export type TrafficStats = { export type Statistics = {
enabled: boolean; enabled: boolean;
type: ChartType; chartType: ChartType;
}; };
export type Prometheus = { export type Prometheus = {
@ -60,34 +60,39 @@ export type General = {
lang: Lang; lang: Lang;
}; };
export type Features = {
clientExpiration: Feature;
oneTimeLinks: Feature;
sortClients: Feature;
};
export const AvailableFeatures: (keyof Features)[] = [
'clientExpiration',
'oneTimeLinks',
'sortClients',
] as const;
/** /**
* Representing the WireGuard network configuration data structure of a computer interface system. * Representing the WireGuard network configuration data structure of a computer interface system.
*/ */
export type System = { export type System = {
interface: WGInterface;
general: General; general: General;
interface: WGInterface;
userConfig: WGConfig; userConfig: WGConfig;
iptables: IpTables; iptables: IpTables;
trafficStats: TrafficStats; features: Features;
metrics: Metrics;
clientExpiration: Feature; statistics: Statistics;
oneTimeLinks: Feature;
sortClients: Feature; metrics: Metrics;
sessionConfig: SessionConfig; sessionConfig: SessionConfig;
}; };
export const Features = [
'clientExpiration',
'oneTimeLinks',
'sortClients',
] as const;
export type Features = (typeof Features)[number];
/** /**
* Interface for system-related database operations. * Interface for system-related database operations.
* This interface provides methods for retrieving system configuration data * This interface provides methods for retrieving system configuration data

Loading…
Cancel
Save