Browse Source

delete feature toggle

pull/1397/head
Bernd Storath 9 months ago
parent
commit
dd2434bfa9
  1. 1
      src/app/app.vue
  2. 1
      src/app/components/ClientCard/ExpireDate.vue
  3. 6
      src/app/components/ClientCard/OneTimeLink.vue
  4. 1
      src/app/components/ClientCard/OneTimeLinkBtn.vue
  5. 6
      src/app/components/Clients/CreateDialog.vue
  6. 1
      src/app/components/Clients/Sort.vue
  7. 4
      src/app/pages/admin.vue
  8. 24
      src/app/pages/admin/defaults.vue
  9. 83
      src/app/pages/admin/features.vue
  10. 9
      src/app/pages/admin/index.vue
  11. 18
      src/app/pages/admin/interface.vue
  12. 5
      src/app/pages/clients/[id].vue
  13. 5
      src/app/stores/clients.ts
  14. 23
      src/app/stores/global.ts
  15. 8
      src/server/api/admin/features.post.ts
  16. 7
      src/server/api/client/[clientId]/generateOneTimeLink.post.ts
  17. 7
      src/server/api/cnf/[oneTimeLink].ts
  18. 4
      src/server/api/features.get.ts
  19. 3
      src/server/middleware/session.ts
  20. 7
      src/server/utils/WireGuard.ts
  21. 18
      src/server/utils/types.ts
  22. 15
      src/services/database/lowdb.ts
  23. 15
      src/services/database/migrations/1.ts
  24. 19
      src/services/database/repositories/system.ts

1
src/app/app.vue

@ -11,7 +11,6 @@
<script setup lang="ts"> <script setup lang="ts">
const globalStore = useGlobalStore(); const globalStore = useGlobalStore();
globalStore.fetchFeatures();
globalStore.fetchRelease(); globalStore.fetchRelease();
globalStore.setLanguage(); globalStore.setLanguage();
globalStore.fetchStatistics(); globalStore.fetchStatistics();

1
src/app/components/ClientCard/ExpireDate.vue

@ -1,6 +1,5 @@
<template> <template>
<div <div
v-show="globalStore.features.clientExpiration.enabled"
class="block pb-1 text-xs text-gray-500 md:inline-block md:pb-0 dark:text-neutral-400" class="block pb-1 text-xs text-gray-500 md:inline-block md:pb-0 dark:text-neutral-400"
> >
<span class="inline-block">{{ expiredDateFormat(client.expiresAt) }}</span> <span class="inline-block">{{ expiredDateFormat(client.expiresAt) }}</span>

6
src/app/components/ClientCard/OneTimeLink.vue

@ -1,8 +1,6 @@
<template> <template>
<div <div
v-if=" v-if="client.oneTimeLink !== null"
globalStore.features.oneTimeLinks.enabled && client.oneTimeLink !== null
"
:ref="'client-' + client.id + '-link'" :ref="'client-' + client.id + '-link'"
class="text-xs text-gray-400" class="text-xs text-gray-400"
> >
@ -13,8 +11,6 @@
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps<{ client: LocalClient }>(); const props = defineProps<{ client: LocalClient }>();
const globalStore = useGlobalStore();
const path = computed(() => { const path = computed(() => {
if (import.meta.client) { if (import.meta.client) {
return `${document.location.protocol}//${document.location.host}/cnf/${props.client.oneTimeLink}`; return `${document.location.protocol}//${document.location.host}/cnf/${props.client.oneTimeLink}`;

1
src/app/components/ClientCard/OneTimeLinkBtn.vue

@ -1,6 +1,5 @@
<template> <template>
<button <button
v-if="globalStore.features.oneTimeLinks.enabled"
:disabled="!client.downloadableConfig" :disabled="!client.downloadableConfig"
class="inline-block rounded bg-gray-100 p-2 align-middle transition dark:bg-neutral-600 dark:text-neutral-300" class="inline-block rounded bg-gray-100 p-2 align-middle transition dark:bg-neutral-600 dark:text-neutral-300"
:class="{ :class="{

6
src/app/components/Clients/CreateDialog.vue

@ -71,10 +71,7 @@
/> />
</p> </p>
</div> </div>
<div <div class="mt-2">
v-show="globalStore.features.clientExpiration.enabled"
class="mt-2"
>
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
<label <label
class="mb-2 block text-sm font-bold text-gray-900 dark:text-neutral-200" class="mb-2 block text-sm font-bold text-gray-900 dark:text-neutral-200"
@ -130,5 +127,4 @@
<script setup lang="ts"> <script setup lang="ts">
const modalStore = useModalStore(); const modalStore = useModalStore();
const globalStore = useGlobalStore();
</script> </script>

1
src/app/components/Clients/Sort.vue

@ -1,6 +1,5 @@
<template> <template>
<button <button
v-if="globalStore.features.sortClients.enabled"
class="inline-flex items-center border-2 border-gray-100 px-4 py-2 text-gray-700 transition hover:border-red-800 hover:bg-red-800 hover:text-white max-md:border-x-0 md:rounded dark:border-neutral-600 dark:text-neutral-200" class="inline-flex items-center border-2 border-gray-100 px-4 py-2 text-gray-700 transition hover:border-red-800 hover:bg-red-800 hover:text-white max-md:border-x-0 md:rounded dark:border-neutral-600 dark:text-neutral-200"
@click="globalStore.sortClient = !globalStore.sortClient" @click="globalStore.sortClient = !globalStore.sortClient"
> >

4
src/app/pages/admin.vue

@ -41,7 +41,9 @@ authStore.update();
const route = useRoute(); const route = useRoute();
const menuItems = [ const menuItems = [
{ id: 'features', name: 'Features' }, { id: '', name: 'General' },
{ id: 'defaults', name: 'Defaults' },
{ id: 'interface', name: 'Interface' },
{ id: 'statistics', name: 'Statistics' }, { id: 'statistics', name: 'Statistics' },
{ id: 'metrics', name: 'Metrics' }, { id: 'metrics', name: 'Metrics' },
]; ];

24
src/app/pages/admin/defaults.vue

@ -0,0 +1,24 @@
<template>
<div>
<FormGroup>
<FormHeading>Connection</FormHeading>
<FormTextField id="host" label="Host" />
<FormTextField id="port" label="Port" />
</FormGroup>
<FormGroup>
<FormHeading>Allowed IPs</FormHeading>
<FormArrayField />
</FormGroup>
<FormGroup>
<FormHeading>DNS</FormHeading>
<FormArrayField />
</FormGroup>
<FormGroup>
<FormHeading>Advanced</FormHeading>
<FormNumberField id="mtu" label="MTU" />
<FormNumberField id="keepalive" label="Persistent Keepalive" />
</FormGroup>
</div>
</template>
<script setup lang="ts"></script>

83
src/app/pages/admin/features.vue

@ -1,83 +0,0 @@
<template>
<div class="flex flex-col">
<div v-for="(feature, key) in featuresData" :key="key" class="space-y-2">
<div class="flex items-center justify-between">
<div>
<h3 class="text-lg font-medium text-gray-900 dark:text-neutral-200">
{{ feature.name }}
</h3>
<p class="text-sm text-gray-500 dark:text-neutral-300">
{{ feature.description }}
</p>
</div>
<SwitchRoot
:checked="feature.enabled"
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-red-800 focus:ring-offset-2"
:class="feature.enabled ? 'bg-red-800' : 'bg-gray-200'"
@update:checked="toggleFeature(key)"
>
<SwitchThumb
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform"
:class="feature.enabled ? 'translate-x-6' : 'translate-x-1'"
/>
</SwitchRoot>
</div>
</div>
<BaseButton class="self-end" @click="submit">Save</BaseButton>
<UiToast
v-model:open="open"
title="Saved successfully"
content="Features saved successfully"
/>
</div>
</template>
<script setup lang="ts">
const globalStore = useGlobalStore();
const open = ref(false);
type ExtendedFeatures = {
[K in keyof (typeof globalStore)['features']]: {
name: string;
description: string;
enabled: boolean;
};
};
const featuresData = ref({
sortClients: {
name: 'Sort Clients',
description: 'Be able to sort Clients by Name',
enabled: globalStore.features.sortClients.enabled,
},
oneTimeLinks: {
name: 'One Time Links',
description: 'Be able to generate One Time Link to download Config',
enabled: globalStore.features.oneTimeLinks.enabled,
},
clientExpiration: {
name: 'Client Expiration',
description: 'Be able to set Date when Client will be disabled',
enabled: globalStore.features.clientExpiration.enabled,
},
} satisfies ExtendedFeatures);
function toggleFeature(key: keyof ExtendedFeatures) {
const feat = featuresData.value[key];
if (!feat) {
return;
}
feat.enabled = !feat.enabled;
}
async function submit() {
const response = await $fetch('/api/admin/features', {
method: 'post',
body: { features: featuresData.value },
});
if (response.success) {
open.value = true;
}
globalStore.fetchFeatures();
}
</script>

9
src/app/pages/admin/index.vue

@ -1,10 +1,9 @@
<template> <template>
<div> <div>
This is the Admin Panel. Your are running wg-easy <FormGroup>
{{ globalStore.currentRelease }} <FormNumberField id="session" label="Session Timeout" />
</FormGroup>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts"></script>
const globalStore = useGlobalStore();
</script>

18
src/app/pages/admin/interface.vue

@ -1,3 +1,19 @@
<template><div></div></template> <template>
<div>
<FormGroup>
<FormHeading>Interface Settings</FormHeading>
<FormNumberField id="mtu" label="MTU" />
<FormNumberField id="port" label="Port" />
<FormTextField id="device" label="Device" />
</FormGroup>
<FormGroup>
<FormHeading>Scripts</FormHeading>
<FormTextField id="mtu" label="PreUp" />
<FormTextField id="port" label="PostUp" />
<FormTextField id="device" label="PreDown" />
<FormTextField id="device" label="PostDown" />
</FormGroup>
</div>
</template>
<script setup lang="ts"></script> <script setup lang="ts"></script>

5
src/app/pages/clients/[id].vue

@ -25,6 +25,11 @@
<FormHeading>Allowed IPs</FormHeading> <FormHeading>Allowed IPs</FormHeading>
<FormArrayField v-model="data.allowedIPs" /> <FormArrayField v-model="data.allowedIPs" />
</FormGroup> </FormGroup>
<FormGroup>
<FormHeading>Server Allowed IPs</FormHeading>
<FormArrayField v-model="data.serverAllowedIPs" />
</FormGroup>
<FormGroup></FormGroup>
<FormGroup> <FormGroup>
<FormHeading>Advanced</FormHeading> <FormHeading>Advanced</FormHeading>
<FormNumberField id="mtu" v-model="data.mtu" label="MTU" /> <FormNumberField id="mtu" v-model="data.mtu" label="MTU" />

5
src/app/stores/clients.ts

@ -108,10 +108,7 @@ export const useClientsStore = defineStore('Clients', () => {
}; };
}); });
if ( if (transformedClients !== undefined) {
globalStore.features.sortClients.enabled &&
transformedClients !== undefined
) {
transformedClients = sortByProperty( transformedClients = sortByProperty(
transformedClients, transformedClients,
'name', 'name',

23
src/app/stores/global.ts

@ -38,27 +38,6 @@ export const useGlobalStore = defineStore('Global', () => {
updateAvailable.value = release.value.updateAvailable; updateAvailable.value = release.value.updateAvailable;
} }
const features = ref({
sortClients: {
enabled: false,
},
clientExpiration: {
enabled: false,
},
oneTimeLinks: {
enabled: false,
},
});
async function fetchFeatures() {
const { data: apiFeatures } = await useFetch('/api/features', {
method: 'get',
});
if (apiFeatures.value) {
features.value = apiFeatures.value;
}
}
const statistics = ref({ const statistics = ref({
enabled: false, enabled: false,
chartType: 0, chartType: 0,
@ -97,8 +76,6 @@ export const useGlobalStore = defineStore('Global', () => {
latestRelease, latestRelease,
updateAvailable, updateAvailable,
fetchRelease, fetchRelease,
features,
fetchFeatures,
statistics, statistics,
fetchStatistics, fetchStatistics,
uiShowCharts, uiShowCharts,

8
src/server/api/admin/features.post.ts

@ -1,8 +0,0 @@
export default defineEventHandler(async (event) => {
const { features } = await readValidatedBody(
event,
validateZod(featuresType)
);
await Database.system.updateFeatures(features);
return { success: true };
});

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

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

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

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

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

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

3
src/server/middleware/session.ts

@ -9,8 +9,7 @@ export default defineEventHandler(async (event) => {
url.pathname.startsWith('/api/setup/') || url.pathname.startsWith('/api/setup/') ||
url.pathname === '/api/session' || url.pathname === '/api/session' ||
url.pathname === '/api/lang' || url.pathname === '/api/lang' ||
url.pathname === '/api/release' || url.pathname === '/api/release'
url.pathname === '/api/features'
) { ) {
return; return;
} }

7
src/server/utils/WireGuard.ts

@ -325,9 +325,7 @@ class WireGuard {
async cronJob() { async cronJob() {
const clients = await Database.client.findAll(); const clients = await Database.client.findAll();
const system = await Database.system.get();
// Expires Feature // Expires Feature
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 (
@ -338,9 +336,8 @@ class WireGuard {
await Database.client.toggle(client.id, false); await Database.client.toggle(client.id, false);
} }
} }
}
// One Time Link Feature // One Time Link Feature
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 &&
@ -350,7 +347,7 @@ class WireGuard {
await Database.client.deleteOneTimeLink(client.id); await Database.client.deleteOneTimeLink(client.id);
} }
} }
}
await this.saveConfig(); await this.saveConfig();
} }

18
src/server/utils/types.ts

@ -55,17 +55,6 @@ const oneTimeLink = z
.min(1, 'zod.otlMin') // i18n key .min(1, 'zod.otlMin') // i18n key
.pipe(safeStringRefine); .pipe(safeStringRefine);
const features = z.record(
z.string({ message: 'zod.features' }), // i18n key
z.object(
{
enabled: z.boolean({ message: 'zod.ftBool' }), // i18n key
},
{ message: 'zod.ftObj' } // i18n key
),
{ message: 'zod.ftObj2' } // i18n key
);
const statistics = z.object( const statistics = z.object(
{ {
enabled: z.boolean({ message: 'zod.statBool' }), // i18n key enabled: z.boolean({ message: 'zod.statBool' }), // i18n key
@ -180,13 +169,6 @@ export const passwordSetupType = z.object(
{ message: objectMessage } { message: objectMessage }
); );
export const featuresType = z.object(
{
features: features,
},
{ message: objectMessage }
);
export const statisticsType = z.object( export const statisticsType = z.object(
{ {
statistics: statistics, statistics: statistics,

15
src/services/database/lowdb.ts

@ -19,11 +19,8 @@ import {
type OneTimeLink, type OneTimeLink,
} from './repositories/client'; } from './repositories/client';
import { import {
AvailableFeatures,
ChartType, ChartType,
SystemRepository, SystemRepository,
type Feature,
type Features,
type Lang, type Lang,
type Statistics, type Statistics,
} from './repositories/system'; } from './repositories/system';
@ -81,18 +78,6 @@ class LowDBSystem extends SystemRepository {
return makeReadonly(system); return makeReadonly(system);
} }
async updateFeatures(features: Record<string, Feature>) {
DEBUG('Update Features');
this.#db.update((v) => {
for (const key in features) {
if (AvailableFeatures.includes(key as keyof Features)) {
v.system.features[key as keyof Features].enabled =
features[key]!.enabled;
}
}
});
}
async updateStatistics(statistics: Statistics) { async updateStatistics(statistics: Statistics) {
DEBUG('Update Statistics'); DEBUG('Update Statistics');
this.#db.update((v) => { this.#db.update((v) => {

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

@ -49,21 +49,6 @@ export async function run1(db: Low<Database>) {
PreDown: '', PreDown: '',
PostDown: '', PostDown: '',
}, },
features: {
clientExpiration: {
enabled: false,
},
oneTimeLinks: {
enabled: false,
},
sortClients: {
enabled: false,
},
},
statistics: {
enabled: false,
chartType: ChartType.None,
},
metrics: { metrics: {
prometheus: { prometheus: {
enabled: false, enabled: false,

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

@ -49,10 +49,6 @@ export type Prometheus = {
password: string | null; password: string | null;
}; };
export type Feature = {
enabled: boolean;
};
export type Metrics = { export type Metrics = {
prometheus: Prometheus; prometheus: Prometheus;
}; };
@ -62,18 +58,6 @@ 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;
export type System = { export type System = {
general: General; general: General;
@ -83,8 +67,6 @@ export type System = {
iptables: IpTables; iptables: IpTables;
features: Features;
statistics: Statistics; statistics: Statistics;
metrics: Metrics; metrics: Metrics;
@ -100,7 +82,6 @@ export type System = {
export abstract class SystemRepository { export abstract class SystemRepository {
abstract get(): Promise<DeepReadonly<System>>; abstract get(): Promise<DeepReadonly<System>>;
abstract updateFeatures(features: Record<string, Feature>): Promise<void>;
abstract updateStatistics(statistics: Statistics): Promise<void>; abstract updateStatistics(statistics: Statistics): Promise<void>;
abstract updateLang(lang: Lang): Promise<void>; abstract updateLang(lang: Lang): Promise<void>;
abstract updateClientsHostPort(host: string, port: number): Promise<void>; abstract updateClientsHostPort(host: string, port: number): Promise<void>;

Loading…
Cancel
Save