Browse Source

fix even more stuff

pull/1666/head
Bernd Storath 6 months ago
parent
commit
5a115e33e3
  1. 3
      src/app/app.vue
  2. 3
      src/app/components/Base/Dialog.vue
  3. 44
      src/app/components/ClientCard/OneTimeLink.vue
  4. 40
      src/app/components/ClientCard/OneTimeLinkBtn.vue
  5. 4
      src/app/components/ClientCard/QRCode.vue
  6. 41
      src/app/components/ClientCard/Switch.vue
  7. 4
      src/app/components/Form/DateField.vue
  8. 9
      src/app/components/Form/Label.vue
  9. 4
      src/app/components/Form/NullTextField.vue
  10. 4
      src/app/components/Form/NumberField.vue
  11. 4
      src/app/components/Form/PasswordField.vue
  12. 4
      src/app/components/Form/SwitchField.vue
  13. 4
      src/app/components/Form/TextField.vue
  14. 15
      src/app/components/Icons/Link.vue
  15. 3
      src/app/components/Ui/Footer.vue
  16. 2
      src/app/pages/setup/4.vue
  17. 21
      src/app/stores/global.ts
  18. 1
      src/server/utils/Database.ts
  19. 4
      src/server/utils/WireGuard.ts
  20. 2
      src/server/utils/types.ts

3
src/app/app.vue

@ -16,6 +16,9 @@ const toast = useToast();
const toastRef = useTemplateRef('toastRef'); const toastRef = useTemplateRef('toastRef');
toast.setToast(toastRef); toast.setToast(toastRef);
// make sure to fetch release early
useGlobalStore();
useHead({ useHead({
bodyAttrs: { bodyAttrs: {
class: 'bg-gray-50 dark:bg-neutral-800', class: 'bg-gray-50 dark:bg-neutral-800',

3
src/app/components/Base/Dialog.vue

@ -3,7 +3,7 @@
<DialogTrigger :class="triggerClass"><slot name="trigger" /></DialogTrigger> <DialogTrigger :class="triggerClass"><slot name="trigger" /></DialogTrigger>
<DialogPortal> <DialogPortal>
<DialogOverlay <DialogOverlay
class="data-[state=open]:animate-overlayShow fixed inset-0 z-30 bg-gray-500 opacity-75 dark:bg-black dark:opacity-50" class="fixed inset-0 z-30 bg-gray-500 opacity-75 dark:bg-black dark:opacity-50"
/> />
<DialogContent <DialogContent
class="fixed left-1/2 top-1/2 z-[100] max-h-[85vh] w-[90vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-md p-6 shadow-2xl focus:outline-none dark:bg-neutral-700" class="fixed left-1/2 top-1/2 z-[100] max-h-[85vh] w-[90vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-md p-6 shadow-2xl focus:outline-none dark:bg-neutral-700"
@ -27,6 +27,5 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
// TODO: improve
defineProps<{ triggerClass?: string }>(); defineProps<{ triggerClass?: string }>();
</script> </script>

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

@ -7,11 +7,45 @@
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps<{ client: LocalClient }>(); const props = defineProps<{ client: LocalClient }>();
const path = computed(() => { const path = ref('Loading...');
if (import.meta.client) { const timer = ref<NodeJS.Timeout | null>(null);
// TODO: show how long its still valid
return `${document.location.protocol}//${document.location.host}/cnf/${props.client.oneTimeLink?.oneTimeLink}`; const { localeProperties } = useI18n();
onMounted(() => {
timer.value = setInterval(() => {
if (props.client.oneTimeLink === null) {
return;
}
const timeLeft =
new Date(props.client.oneTimeLink.expiresAt).getTime() - Date.now();
if (timeLeft <= 0) {
path.value = `${document.location.protocol}//${document.location.host}/cnf/${props.client.oneTimeLink.oneTimeLink} (00:00)`;
return;
}
const formatter = new Intl.DateTimeFormat(localeProperties.value.language, {
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23',
});
const minutes = Math.floor(timeLeft / 60000);
const seconds = Math.floor((timeLeft % 60000) / 1000);
const date = new Date(0);
date.setMinutes(minutes);
date.setSeconds(seconds);
path.value = `${document.location.protocol}//${document.location.host}/cnf/${props.client.oneTimeLink.oneTimeLink} (${formatter.format(date)})`;
}, 1000);
});
onUnmounted(() => {
if (timer.value) {
clearTimeout(timer.value);
} }
return 'Loading...';
}); });
</script> </script>

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

@ -2,37 +2,31 @@
<button <button
class="inline-block rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white" class="inline-block rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white"
:title="$t('client.otlDesc')" :title="$t('client.otlDesc')"
@click="showOneTimeLink(client)" @click="showOneTimeLink"
> >
<svg <IconsLink class="w-5" />
class="w-5"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13.213 9.787a3.391 3.391 0 0 0-4.795 0l-3.425 3.426a3.39 3.39 0 0 0 4.795 4.794l.321-.304m-.321-4.49a3.39 3.39 0 0 0 4.795 0l3.424-3.426a3.39 3.39 0 0 0-4.794-4.795l-1.028.961"
/>
</svg>
</button> </button>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// TODO: improve const props = defineProps<{ client: LocalClient }>();
defineProps<{ client: LocalClient }>();
const clientsStore = useClientsStore(); const clientsStore = useClientsStore();
function showOneTimeLink(client: LocalClient) { const _showOneTimeLink = useSubmit(
// TODO: improve `/api/client/${props.client.id}/generateOneTimeLink`,
$fetch(`/api/client/${client.id}/generateOneTimeLink`, { {
method: 'post', method: 'post',
}) },
.catch((err) => alert(err.message || err.toString())) {
.finally(() => clientsStore.refresh().catch(console.error)); revert: async () => {
await clientsStore.refresh();
},
noSuccessToast: true,
}
);
function showOneTimeLink() {
return _showOneTimeLink(undefined);
} }
</script> </script>

4
src/app/components/ClientCard/QRCode.vue

@ -1,11 +1,11 @@
<template> <template>
<ClientsQRCodeDialog :qr-code="`./api/client/${client.id}/qrcode.svg`"> <ClientsQRCodeDialog :qr-code="`./api/client/${client.id}/qrcode.svg`">
<button <div
class="rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white" class="rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white"
:title="$t('client.showQR')" :title="$t('client.showQR')"
> >
<IconsQRCode class="w-5" /> <IconsQRCode class="w-5" />
</button> </div>
</ClientsQRCodeDialog> </ClientsQRCodeDialog>
</template> </template>

41
src/app/components/ClientCard/Switch.vue

@ -17,22 +17,37 @@ const enabled = ref(props.client.enabled);
const clientsStore = useClientsStore(); const clientsStore = useClientsStore();
async function toggleClient() { const _disableClient = useSubmit(
// Improve `/api/client/${props.client.id}/disable`,
try { {
if (props.client.enabled) {
await $fetch(`/api/client/${props.client.id}/disable`, {
method: 'post', method: 'post',
}); },
} else { {
await $fetch(`/api/client/${props.client.id}/enable`, { revert: async () => {
await clientsStore.refresh();
},
noSuccessToast: true,
}
);
const _enableClient = useSubmit(
`/api/client/${props.client.id}/enable`,
{
method: 'post', method: 'post',
}); },
{
revert: async () => {
await clientsStore.refresh();
},
noSuccessToast: true,
} }
} catch (err) { );
alert(err);
} finally { async function toggleClient() {
clientsStore.refresh().catch(console.error); if (props.client.enabled) {
await _disableClient(undefined);
} else {
await _enableClient(undefined);
} }
} }
</script> </script>

4
src/app/components/Form/DateField.vue

@ -1,7 +1,7 @@
<template> <template>
<Label :for="id" class="font-semibold md:align-middle md:leading-10"> <FormLabel :for="id">
{{ label }} {{ label }}
</Label> </FormLabel>
<BaseInput :id="id" v-model="data" :name="id" type="date" /> <BaseInput :id="id" v-model="data" :name="id" type="date" />
</template> </template>

9
src/app/components/Form/Label.vue

@ -0,0 +1,9 @@
<template>
<RLabel :for="id" class="md:align-middle md:leading-10"><slot /></RLabel>
</template>
<script lang="ts" setup>
import { Label as RLabel } from 'radix-vue';
defineProps<{ id: string }>();
</script>

4
src/app/components/Form/NullTextField.vue

@ -1,8 +1,8 @@
<template> <template>
<div class="flex items-center"> <div class="flex items-center">
<Label :for="id" class="font-semibold md:align-middle md:leading-10"> <FormLabel :for="id">
{{ label }} {{ label }}
</Label> </FormLabel>
<BaseTooltip v-if="description" :text="description"> <BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" /> <IconsInfo class="size-4" />
</BaseTooltip> </BaseTooltip>

4
src/app/components/Form/NumberField.vue

@ -1,8 +1,8 @@
<template> <template>
<div class="flex items-center"> <div class="flex items-center">
<Label :for="id" class="font-semibold md:align-middle md:leading-10"> <FormLabel :for="id">
{{ label }} {{ label }}
</Label> </FormLabel>
<BaseTooltip v-if="description" :text="description"> <BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" /> <IconsInfo class="size-4" />
</BaseTooltip> </BaseTooltip>

4
src/app/components/Form/PasswordField.vue

@ -1,7 +1,7 @@
<template> <template>
<Label :for="id" class="font-semibold md:align-middle md:leading-10"> <FormLabel :for="id">
{{ label }} {{ label }}
</Label> </FormLabel>
<BaseInput <BaseInput
:id="id" :id="id"
v-model.trim="data" v-model.trim="data"

4
src/app/components/Form/SwitchField.vue

@ -1,8 +1,8 @@
<template> <template>
<div class="flex items-center"> <div class="flex items-center">
<Label :for="id" class="font-semibold md:align-middle md:leading-10"> <FormLabel :for="id">
{{ label }} {{ label }}
</Label> </FormLabel>
<BaseTooltip v-if="description" :text="description"> <BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" /> <IconsInfo class="size-4" />
</BaseTooltip> </BaseTooltip>

4
src/app/components/Form/TextField.vue

@ -1,8 +1,8 @@
<template> <template>
<div class="flex items-center"> <div class="flex items-center">
<Label :for="id" class="font-semibold md:align-middle md:leading-10"> <FormLabel :for="id">
{{ label }} {{ label }}
</Label> </FormLabel>
<BaseTooltip v-if="description" :text="description"> <BaseTooltip v-if="description" :text="description">
<IconsInfo class="size-4" /> <IconsInfo class="size-4" />
</BaseTooltip> </BaseTooltip>

15
src/app/components/Icons/Link.vue

@ -0,0 +1,15 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13.213 9.787a3.391 3.391 0 0 0-4.795 0l-3.425 3.426a3.39 3.39 0 0 0 4.795 4.794l.321-.304m-.321-4.49a3.39 3.39 0 0 0 4.795 0l3.424-3.426a3.39 3.39 0 0 0-4.794-4.795l-1.028.961"
/>
</svg>
</template>

3
src/app/components/Ui/Footer.vue

@ -7,7 +7,7 @@
href="https://github.com/wg-easy/wg-easy" href="https://github.com/wg-easy/wg-easy"
>WireGuard Easy</a >WireGuard Easy</a
> >
({{ globalStore.currentRelease }}) © 2021-2025 by ({{ globalStore.release?.currentRelease }}) © 2021-2025 by
<a <a
class="hover:underline" class="hover:underline"
target="_blank" target="_blank"
@ -34,5 +34,4 @@
<script lang="ts" setup> <script lang="ts" setup>
const globalStore = useGlobalStore(); const globalStore = useGlobalStore();
globalStore.fetchRelease();
</script> </script>

2
src/app/pages/setup/4.vue

@ -47,7 +47,7 @@ const _submit = useSubmit(
{ {
revert: async (success) => { revert: async (success) => {
if (success) { if (success) {
await navigateTo('/setup/5'); await navigateTo('/setup/success');
} }
}, },
} }

21
src/app/stores/global.ts

@ -5,22 +5,6 @@ export const useGlobalStore = defineStore('Global', () => {
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 currentRelease = ref<null | string>(null);
const latestRelease = ref<null | { version: string; changelog: string }>(
null
);
const updateAvailable = ref(false);
async function fetchRelease() {
if (!release.value) {
return;
}
currentRelease.value = release.value.currentRelease;
latestRelease.value = release.value.latestRelease;
updateAvailable.value = release.value.updateAvailable;
}
const uiShowCharts = ref(getItem('uiShowCharts') === '1'); const uiShowCharts = ref(getItem('uiShowCharts') === '1');
function toggleCharts() { function toggleCharts() {
@ -31,10 +15,7 @@ export const useGlobalStore = defineStore('Global', () => {
return { return {
sortClient, sortClient,
currentRelease, release,
latestRelease,
updateAvailable,
fetchRelease,
uiShowCharts, uiShowCharts,
toggleCharts, toggleCharts,
uiChartType, uiChartType,

1
src/server/utils/Database.ts

@ -21,5 +21,4 @@ connect().then((db) => {
WireGuard.Startup(); WireGuard.Startup();
}); });
// TODO: check if old config exists and tell user about migration path
export default provider; export default provider;

4
src/server/utils/WireGuard.ts

@ -208,15 +208,15 @@ class WireGuard {
await Database.clients.toggle(client.id, false); await Database.clients.toggle(client.id, false);
} }
} }
// One Time Link Feature // One Time Link Feature
for (const client of clients) { for (const client of clients) {
if ( if (
client.oneTimeLink !== null && client.oneTimeLink !== null &&
new Date() > new Date(client.oneTimeLink.expiresAt) new Date() > new Date(client.oneTimeLink.expiresAt)
) { ) {
console.log(client);
WG_DEBUG(`Client ${client.id} One Time Link expired.`); WG_DEBUG(`Client ${client.id} One Time Link expired.`);
await Database.oneTimeLinks.delete(client.id); await Database.oneTimeLinks.delete(client.oneTimeLink.id);
} }
} }

2
src/server/utils/types.ts

@ -19,8 +19,6 @@ export const safeStringRefine = z
{ message: t('zod.stringMalformed') } { message: t('zod.stringMalformed') }
); );
// TODO: create custom getValidatedRouterParams and readValidatedBody wrapper
export const EnabledSchema = z.boolean({ message: t('zod.enabled') }); export const EnabledSchema = z.boolean({ message: t('zod.enabled') });
export const MtuSchema = z export const MtuSchema = z

Loading…
Cancel
Save