From dd0cb370d7987daff7064893b9f1fd0ddda71b65 Mon Sep 17 00:00:00 2001 From: Bernd Storath <999999bst@gmail.com> Date: Fri, 14 Feb 2025 12:37:42 +0100 Subject: [PATCH] improve --- src/app/components/ClientCard/Config.vue | 2 +- src/app/components/ClientCard/ExpireDate.vue | 2 +- src/app/components/ClientCard/LastSeen.vue | 2 +- src/app/components/ClientCard/Name.vue | 2 +- .../components/ClientCard/OneTimeLinkBtn.vue | 2 +- src/app/components/ClientCard/QRCode.vue | 2 +- src/app/components/ClientCard/Switch.vue | 5 +- src/app/components/Clients/CreateDialog.vue | 40 +++---- src/app/components/Clients/Empty.vue | 6 +- src/app/components/Clients/New.vue | 2 +- src/app/components/Clients/Sort.vue | 49 ++------- src/app/components/base/Container.vue | 0 src/app/components/base/Toast.vue | 40 +++---- src/app/components/form/NullTextField.vue | 17 ++- src/app/components/form/TextField.vue | 15 ++- src/app/components/header/ChartToggle.vue | 2 +- src/app/components/header/Update.vue | 4 +- src/app/components/panel/head/Title.vue | 12 +-- src/app/components/ui/ChooseLang.vue | 45 -------- src/app/components/ui/Footer.vue | 2 +- src/app/components/ui/UserMenu.vue | 27 +++-- src/app/composables/useSubmit.ts | 30 ++++-- src/app/pages/admin.vue | 1 + src/app/pages/admin/config.vue | 6 +- src/app/pages/admin/hooks.vue | 2 +- src/app/pages/admin/index.vue | 2 +- src/app/pages/admin/interface.vue | 12 ++- src/app/pages/clients/[id].vue | 14 +-- src/app/pages/index.vue | 1 + src/app/pages/login.vue | 54 +++++----- src/app/pages/me.vue | 16 +-- src/app/pages/setup/1.vue | 7 +- src/app/pages/setup/2.vue | 102 +++++++----------- src/app/pages/setup/3.vue | 11 +- src/app/pages/setup/4.vue | 86 +++++++-------- src/app/pages/setup/migrate.vue | 52 ++++----- src/app/pages/setup/success.vue | 7 +- src/app/stores/auth.ts | 23 +--- src/app/stores/global.ts | 10 +- src/app/stores/setup.ts | 36 ------- src/i18n/locales/en.json | 93 +++++++--------- .../database/repositories/user/types.ts | 7 -- 42 files changed, 348 insertions(+), 502 deletions(-) delete mode 100644 src/app/components/base/Container.vue delete mode 100644 src/app/components/ui/ChooseLang.vue diff --git a/src/app/components/ClientCard/Config.vue b/src/app/components/ClientCard/Config.vue index 2ce4ec61..eddbe1d9 100644 --- a/src/app/components/ClientCard/Config.vue +++ b/src/app/components/ClientCard/Config.vue @@ -3,7 +3,7 @@ :href="'/api/client/' + client.id + '/configuration'" download 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('downloadConfig')" + :title="$t('client.downloadConfig')" > diff --git a/src/app/components/ClientCard/ExpireDate.vue b/src/app/components/ClientCard/ExpireDate.vue index 98ed042d..3d8e1e04 100644 --- a/src/app/components/ClientCard/ExpireDate.vue +++ b/src/app/components/ClientCard/ExpireDate.vue @@ -12,7 +12,7 @@ defineProps<{ client: LocalClient }>(); const { t, locale } = useI18n(); function expiredDateFormat(value: string | null) { - if (value === null) return t('Permanent'); + if (value === null) return t('client.permanent'); const dateTime = new Date(value); return dateTime.toLocaleDateString(locale.value, { year: 'numeric', diff --git a/src/app/components/ClientCard/LastSeen.vue b/src/app/components/ClientCard/LastSeen.vue index 7836047b..fda45fb3 100644 --- a/src/app/components/ClientCard/LastSeen.vue +++ b/src/app/components/ClientCard/LastSeen.vue @@ -2,7 +2,7 @@ · {{ timeago(new Date(client.latestHandshakeAt)) }} diff --git a/src/app/components/ClientCard/Name.vue b/src/app/components/ClientCard/Name.vue index 7e7057a6..435db786 100644 --- a/src/app/components/ClientCard/Name.vue +++ b/src/app/components/ClientCard/Name.vue @@ -1,7 +1,7 @@ diff --git a/src/app/components/header/ChartToggle.vue b/src/app/components/header/ChartToggle.vue index 24c5378b..ad69d8d0 100644 --- a/src/app/components/header/ChartToggle.vue +++ b/src/app/components/header/ChartToggle.vue @@ -2,7 +2,7 @@
-

{{ $t('updateAvailable') }}

+

{{ $t('update.updateAvailable') }}

{{ globalStore.latestRelease.changelog }}

@@ -15,7 +15,7 @@ target="_blank" class="font-sm float-right flex-shrink-0 rounded-md border-2 border-red-800 bg-white p-3 font-semibold text-red-800 transition-all hover:border-white hover:bg-red-800 hover:text-white dark:border-red-600 dark:bg-red-100 dark:text-red-600 dark:hover:border-red-600 dark:hover:bg-red-600 dark:hover:text-red-100" > - {{ $t('update') }} → + {{ $t('update.update') }} →
diff --git a/src/app/components/panel/head/Title.vue b/src/app/components/panel/head/Title.vue index d9bdf504..93a2db31 100644 --- a/src/app/components/panel/head/Title.vue +++ b/src/app/components/panel/head/Title.vue @@ -1,11 +1,11 @@ - - + + diff --git a/src/app/components/ui/ChooseLang.vue b/src/app/components/ui/ChooseLang.vue deleted file mode 100644 index a5df8e67..00000000 --- a/src/app/components/ui/ChooseLang.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - diff --git a/src/app/components/ui/Footer.vue b/src/app/components/ui/Footer.vue index cfc5c356..3395848b 100644 --- a/src/app/components/ui/Footer.vue +++ b/src/app/components/ui/Footer.vue @@ -26,7 +26,7 @@ class="hover:underline" href="https://github.com/sponsors/WeeJeWel" target="_blank" - >{{ $t('donate') }}{{ $t('layout.donate') }}

diff --git a/src/app/components/ui/UserMenu.vue b/src/app/components/ui/UserMenu.vue index 8d48b3fe..ca4d19ec 100644 --- a/src/app/components/ui/UserMenu.vue +++ b/src/app/components/ui/UserMenu.vue @@ -52,10 +52,10 @@ @@ -67,16 +67,21 @@ const authStore = useAuthStore(); const toggleState = ref(false); -async function logout() { - try { - await authStore.logout(); - navigateTo('/login'); - } catch (err) { - if (err instanceof Error) { - // TODO: better ui - alert(err.message || err.toString()); - } +const _submit = useSubmit( + '/api/session', + { + method: 'delete', + }, + { + revert: async () => { + await navigateTo('/login'); + }, + noSuccessToast: true, } +); + +function submit() { + return _submit(undefined); } const fallbackName = computed(() => { diff --git a/src/app/composables/useSubmit.ts b/src/app/composables/useSubmit.ts index a5fd76c9..27d734c1 100644 --- a/src/app/composables/useSubmit.ts +++ b/src/app/composables/useSubmit.ts @@ -1,12 +1,19 @@ import type { NitroFetchRequest, NitroFetchOptions } from 'nitropack/types'; import { FetchError } from 'ofetch'; -type RevertFn = () => Promise; +type RevertFn = (success: boolean) => Promise; + +type SubmitOpts = { + revert: RevertFn; + successMsg?: string; + errorMsg?: string; + noSuccessToast?: boolean; +}; export function useSubmit< R extends NitroFetchRequest, O extends NitroFetchOptions & { body?: never }, ->(url: R, options: O, revert: RevertFn, success?: string, error?: string) { +>(url: R, options: O, opts: SubmitOpts) { const toast = useToast(); const { t: $t } = useI18n(); @@ -16,15 +23,20 @@ export function useSubmit< ...options, body: data, }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any if (!(res as any).success) { - throw new Error(error || $t('toast.errored')); + throw new Error(opts.errorMsg || $t('toast.errored')); } - toast.showToast({ - type: 'success', - message: success, - }); - await revert(); + + if (!opts.noSuccessToast) { + toast.showToast({ + type: 'success', + message: opts.successMsg, + }); + } + + await opts.revert(true); } catch (e) { if (e instanceof FetchError) { toast.showToast({ @@ -39,7 +51,7 @@ export function useSubmit< } else { console.error(e); } - await revert(); + await opts.revert(false); } }; } diff --git a/src/app/pages/admin.vue b/src/app/pages/admin.vue index 3e937134..71c0f6d6 100644 --- a/src/app/pages/admin.vue +++ b/src/app/pages/admin.vue @@ -37,6 +37,7 @@ diff --git a/src/app/pages/me.vue b/src/app/pages/me.vue index e7009917..333422c3 100644 --- a/src/app/pages/me.vue +++ b/src/app/pages/me.vue @@ -65,8 +65,10 @@ const _submit = useSubmit( { method: 'post', }, - async () => { - authStore.update(); + { + revert: () => { + return authStore.update(); + }, } ); @@ -83,10 +85,12 @@ const _updatePassword = useSubmit( { method: 'post', }, - async () => { - currentPassword.value = ''; - newPassword.value = ''; - confirmPassword.value = ''; + { + revert: async () => { + currentPassword.value = ''; + newPassword.value = ''; + confirmPassword.value = ''; + }, } ); diff --git a/src/app/pages/setup/1.vue b/src/app/pages/setup/1.vue index ac67462e..a059e0a5 100644 --- a/src/app/pages/setup/1.vue +++ b/src/app/pages/setup/1.vue @@ -1,9 +1,11 @@ @@ -11,6 +13,7 @@ definePageMeta({ layout: 'setup', }); + const setupStore = useSetupStore(); setupStore.setStep(1); diff --git a/src/app/pages/setup/2.vue b/src/app/pages/setup/2.vue index 71e36e14..72cbffc1 100644 --- a/src/app/pages/setup/2.vue +++ b/src/app/pages/setup/2.vue @@ -1,83 +1,59 @@ diff --git a/src/app/pages/setup/3.vue b/src/app/pages/setup/3.vue index 620844be..54e3cae0 100644 --- a/src/app/pages/setup/3.vue +++ b/src/app/pages/setup/3.vue @@ -1,11 +1,15 @@ @@ -14,6 +18,7 @@ definePageMeta({ layout: 'setup', }); + const setupStore = useSetupStore(); setupStore.setStep(3); diff --git a/src/app/pages/setup/4.vue b/src/app/pages/setup/4.vue index 9cd88bd2..5c1b95a5 100644 --- a/src/app/pages/setup/4.vue +++ b/src/app/pages/setup/4.vue @@ -1,71 +1,59 @@ diff --git a/src/app/pages/setup/migrate.vue b/src/app/pages/setup/migrate.vue index 77622c81..7eb5ddda 100644 --- a/src/app/pages/setup/migrate.vue +++ b/src/app/pages/setup/migrate.vue @@ -1,68 +1,56 @@ diff --git a/src/app/stores/auth.ts b/src/app/stores/auth.ts index b574945d..fab80ea0 100644 --- a/src/app/stores/auth.ts +++ b/src/app/stores/auth.ts @@ -14,26 +14,5 @@ export const useAuthStore = defineStore('Auth', () => { } } - /** - * @throws if unsuccessful - */ - async function login(username: string, password: string, remember: boolean) { - await $fetch('/api/session', { - method: 'post', - body: { username, password, remember }, - }); - return true as const; - } - - /** - * @throws if unsuccessful - */ - async function logout() { - const response = await $fetch('/api/session', { - method: 'delete', - }); - return response.success; - } - - return { userData, login, logout, update, getSession }; + return { userData, update, getSession }; }); diff --git a/src/app/stores/global.ts b/src/app/stores/global.ts index 7f515872..923202a7 100644 --- a/src/app/stores/global.ts +++ b/src/app/stores/global.ts @@ -1,6 +1,8 @@ -import { defineStore } from 'pinia'; - export const useGlobalStore = defineStore('Global', () => { + const { data: release } = useFetch('/api/release', { + method: 'get', + }); + const sortClient = ref(true); // Sort clients by name, true = asc, false = desc const currentRelease = ref(null); @@ -10,10 +12,6 @@ export const useGlobalStore = defineStore('Global', () => { const updateAvailable = ref(false); async function fetchRelease() { - const { data: release } = await useFetch('/api/release', { - method: 'get', - }); - if (!release.value) { return; } diff --git a/src/app/stores/setup.ts b/src/app/stores/setup.ts index 478c9e0a..e7bcc64e 100644 --- a/src/app/stores/setup.ts +++ b/src/app/stores/setup.ts @@ -1,39 +1,6 @@ import { defineStore } from 'pinia'; export const useSetupStore = defineStore('Setup', () => { - /** - * @throws if unsuccessful - */ - async function step2(username: string, password: string, accept: boolean) { - const response = await $fetch('/api/setup/2', { - method: 'post', - body: { username, password, accept }, - }); - return response.success; - } - - /** - * @throws if unsuccessful - */ - async function step4(host: string, port: number) { - const response = await $fetch('/api/setup/4', { - method: 'post', - body: { host, port }, - }); - return response.success; - } - - /** - * @throws if unsuccessful - */ - async function runMigration(file: string) { - const response = await $fetch('/api/setup/migrate', { - method: 'post', - body: { file }, - }); - return response.success; - } - const step = ref(1); const totalSteps = ref(5); function setStep(i: number) { @@ -41,9 +8,6 @@ export const useSetupStore = defineStore('Setup', () => { } return { - step2, - step4, - runMigration, step, totalSteps, setStep, diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 07e72bb7..1743c153 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -25,60 +25,39 @@ "updatePassword": "Update Password", "mtu": "MTU", "allowedIps": "Allowed IPs", - "persistentKeepalive": "Persistent Keepalive" + "persistentKeepalive": "Persistent Keepalive", + "logout": "Logout", + "continue": "Continue", + "host": "Host", + "port": "Port", + "yes": "Yes", + "no": "No" }, "setup": { "welcome": "Welcome to your first setup of wg-easy !", - "messageWelcome": { - "whatIs": "You have found the easiest way to install and manage WireGuard on any Linux host!", - "warning": "First of all, make sure you have a backup of your data if you want to migrate your users to your new wg-easy.", - "next": "Click on the arrow button to proceed to the next step." - }, - "messageSetupLanguage": "Please choose a language for the setup.", - "messageSetupCreateAdminUser": "Please first enter an admin username and a strong secure password. This information will be used to log in to your administration panel.", - "messageSetupHostPort": "Please enter the host and port information. This will be used for the client configuration when setting up WireGuard on their devices.", - "messageSetupMigration": "Please provide the backup file if you want to migrate your data from your previous wg-easy version to your new setup.", - "messageSetupValidation": "Welcome to wg-easy ! The easiest way to run WireGuard VPN and Web-based Admin UI.", - "emptyFields": "The fields are required", - "chooseLang": "Select a language...", - "accept": "I accept the condition", - "submitBtn": "Create admin account", - "usernamePlaceholder": "Administrator", - "passwordPlaceholder": "Strong password", - "requirements": "Setup requirements", - "host": "Host", - "hostPlaceholder": "wg-easy.example.com", - "port": "Port", - "portPlaceholder": "443", - "migration": "Restore the backup" + "welcomeDesc": "You have found the easiest way to install and manage WireGuard on any Linux host!", + "existingSetup": "Do you have an existing setup?", + "createAdminDesc": "Please first enter an admin username and a strong secure password. This information will be used to log in to your administration panel.", + "setupConfigDesc": "Please enter the host and port information. This will be used for the client configuration when setting up WireGuard on their devices.", + "setupMigrationDesc": "Please provide the backup file if you want to migrate your data from your previous wg-easy version to your new setup.", + "upload": "Upload", + "migration": "Restore the backup", + "createAccount": "Create Account", + "successful": "Setup successful" + }, + "update": { + "updateAvailable": "There is an update available!", + "update": "Update" }, - "logout": "Logout", - "updateAvailable": "There is an update available!", - "update": "Update", - "new": "New", - "createdOn": "Created on ", - "lastSeen": "Last seen on ", - "totalDownload": "Total Download: ", - "totalUpload": "Total Upload: ", - "newClient": "New Client", - "disableClient": "Disable Client", - "enableClient": "Enable Client", - "noClients": "There are no clients yet.", - "noPrivKey": "This client has no known private key. Cannot create Configuration.", - "showQR": "Show QR Code", - "downloadConfig": "Download Configuration", - "madeBy": "Made by", - "donate": "Donate", - "toggleCharts": "Show/hide Charts", "theme": { "dark": "Dark theme", "light": "Light theme", "system": "System theme" }, - "sort": "Sort", - "Permanent": "Permanent", - "OneTimeLink": "Generate short one time link", - "errorInit": "Initialization failed.", + "layout": { + "toggleCharts": "Show/hide Charts", + "donate": "Donate" + }, "login": { "signIn": "Sign In", "rememberMe": "Remember me", @@ -89,7 +68,11 @@ "login": "Log in error" }, "client": { + "empty": "There are no clients yet.", + "newShort": "New", + "sort": "Sort", "create": "Create Client", + "created": "Client created", "new": "New Client", "name": "Name", "expireDate": "Expire Date", @@ -98,7 +81,19 @@ "deleteDialog2": "This action cannot be undone.", "enabled": "Enabled", "address": "Address", - "serverAllowedIps": "Server Allowed IPs" + "serverAllowedIps": "Server Allowed IPs", + "otlDesc": "Generate short one time link", + "permanent": "Permanent", + "createdOn": "Created on ", + "lastSeen": "Last seen on ", + "totalDownload": "Total Download: ", + "totalUpload": "Total Upload: ", + "newClient": "New Client", + "disableClient": "Disable Client", + "enableClient": "Enable Client", + "noPrivKey": "This client has no known private key. Cannot create Configuration.", + "showQR": "Show QR Code", + "downloadConfig": "Download Configuration" }, "dialog": { "change": "Change", @@ -132,7 +127,6 @@ }, "config": { "connection": "Connection", - "host": "Host", "hostDesc": "Public hostname clients will connect to (invalidates config)", "portDesc": "Public UDP port clients will connect to (invalidates config)", "allowedIpsDesc": "Allowed IPs clients will use (invalidates config)", @@ -149,9 +143,6 @@ "mtuDesc": "MTU WireGuard will use", "portDesc": "UDP Port WireGuard will listen on (could invalidate config)", "changeCidr": "Change CIDR" - }, - "generic": { - "port": "Port" } }, "zod": { @@ -180,8 +171,6 @@ "passwordNumber": "Password must have at least 1 number", "passwordSpecial": "Password must have at least 1 special character", "remember": "Remember", - "accept": "Accept", - "acceptTrue": "Accept Conditions to continue", "name": "Name", "email": "Email", "emailInvalid": "Email must be a valid email", diff --git a/src/server/database/repositories/user/types.ts b/src/server/database/repositories/user/types.ts index 1c071926..51eac4e5 100644 --- a/src/server/database/repositories/user/types.ts +++ b/src/server/database/repositories/user/types.ts @@ -29,17 +29,10 @@ export const UserLoginSchema = z.object( { message: objectMessage } ); -const accept = z - .boolean({ message: t('zod.user.accept') }) - .refine((val) => val === true, { - message: t('zod.user.acceptTrue'), - }); - export const UserSetupSchema = z.object( { username: username, password: password, - accept: accept, }, { message: objectMessage } );