Browse Source

improve useSubmit, i18n

pull/1666/head
Bernd Storath 6 months ago
parent
commit
92d3842958
  1. 14
      src/app/components/Clients/CreateDialog.vue
  2. 16
      src/app/components/Clients/DeleteDialog.vue
  3. 2
      src/app/components/Clients/QRCodeDialog.vue
  4. 6
      src/app/components/admin/CidrDialog.vue
  5. 9
      src/app/composables/useSubmit.ts
  6. 15
      src/app/pages/admin/config.vue
  7. 34
      src/app/pages/admin/hooks.vue
  8. 7
      src/app/pages/admin/index.vue
  9. 31
      src/app/pages/admin/interface.vue
  10. 101
      src/app/pages/clients/[id].vue
  11. 109
      src/app/pages/me.vue
  12. 53
      src/i18n/locales/en.json

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

@ -4,20 +4,24 @@
<slot />
</template>
<template #title>
{{ $t('newClient') }}
{{ $t('client.new') }}
</template>
<template #description>
<div class="flex flex-col">
<FormTextField id="name" v-model="name" label="Name" />
<FormDateField id="expiresAt" v-model="expiresAt" label="Expire Date" />
<FormTextField id="name" v-model="name" :label="$t('client.name')" />
<FormDateField
id="expiresAt"
v-model="expiresAt"
:label="$t('client.expireDate')"
/>
</div>
</template>
<template #actions>
<DialogClose as-child>
<BaseButton>{{ $t('cancel') }}</BaseButton>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
</DialogClose>
<DialogClose as-child>
<BaseButton @click="createClient">{{ $t('create') }}</BaseButton>
<BaseButton @click="createClient">{{ $t('client.create') }}</BaseButton>
</DialogClose>
</template>
</BaseDialog>

16
src/app/components/Clients/DeleteDialog.vue

@ -1,19 +1,19 @@
<template>
<BaseDialog>
<BaseDialog :trigger-class="triggerClass">
<template #trigger><slot /></template>
<template #title>{{ $t('deleteClient') }}</template>
<template #title>{{ $t('client.deleteClient') }}</template>
<template #description>
{{ $t('deleteDialog1') }}
<strong>{{ 'test' }}</strong
>? {{ $t('deleteDialog2') }}
{{ $t('client.deleteDialog1') }}
<strong>{{ clientName }}</strong
>? {{ $t('client.deleteDialog2') }}
</template>
<template #actions>
<DialogClose as-child>
<BaseButton>{{ $t('cancel') }}</BaseButton>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
</DialogClose>
<DialogClose as-child>
<BaseButton @click="$emit('delete')">{{
$t('deleteClient')
$t('client.deleteClient')
}}</BaseButton>
</DialogClose>
</template>
@ -22,5 +22,5 @@
<script lang="ts" setup>
defineEmits(['delete']);
defineProps<{ triggerClass?: string }>();
defineProps<{ triggerClass?: string; clientName: string }>();
</script>

2
src/app/components/Clients/QRCodeDialog.vue

@ -8,7 +8,7 @@
</template>
<template #actions>
<DialogClose>
<BaseButton>{{ $t('cancel') }}</BaseButton>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
</DialogClose>
</template>
</BaseDialog>

6
src/app/components/admin/CidrDialog.vue

@ -1,7 +1,7 @@
<template>
<BaseDialog :trigger-class="triggerClass">
<template #trigger><slot /></template>
<template #title>Change CIDR</template>
<template #title>{{ $t('admin.interface.changeCidr') }}</template>
<template #description>
<FormGroup>
<FormTextField id="ipv4Cidr" v-model="ipv4Cidr" label="IPv4" />
@ -10,11 +10,11 @@
</template>
<template #actions>
<DialogClose as-child>
<BaseButton>{{ $t('cancel') }}</BaseButton>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
</DialogClose>
<DialogClose as-child>
<BaseButton @click="$emit('change', ipv4Cidr, ipv6Cidr)">
Change
{{ $t('dialog.change') }}
</BaseButton>
</DialogClose>
</template>

9
src/app/composables/useSubmit.ts

@ -5,14 +5,17 @@ type RevertFn = () => Promise<void>;
export function useSubmit<
R extends NitroFetchRequest,
O extends NitroFetchOptions<R>,
O extends NitroFetchOptions<R> & { body?: never },
>(url: R, options: O, revert: RevertFn, success?: string, error?: string) {
const toast = useToast();
const { t: $t } = useI18n();
return async () => {
return async (data: unknown) => {
try {
const res = await $fetch(url, options);
const res = await $fetch(url, {
...options,
body: data,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (!(res as any).success) {
throw new Error(error || $t('toast.errored'));

15
src/app/pages/admin/config.vue

@ -18,7 +18,7 @@
</FormGroup>
<FormGroup>
<FormHeading :description="$t('admin.config.allowedIpsDesc')">{{
$t('admin.config.allowedIps')
$t('general.allowedIps')
}}</FormHeading>
<FormArrayField
v-model="data.defaultAllowedIps"
@ -32,17 +32,17 @@
<FormArrayField v-model="data.defaultDns" name="defaultDns" />
</FormGroup>
<FormGroup>
<FormHeading>{{ $t('admin.config.advanced') }}</FormHeading>
<FormHeading>{{ $t('form.sectionAdvanced') }}</FormHeading>
<FormNumberField
id="defaultMtu"
v-model="data.defaultMtu"
:label="$t('admin.generic.mtu')"
:label="$t('general.mtu')"
:description="$t('admin.config.mtuDesc')"
/>
<FormNumberField
id="defaultPersistentKeepalive"
v-model="data.defaultPersistentKeepalive"
:label="$t('admin.config.persistentKeepalive')"
:label="$t('general.persistentKeepalive')"
:description="$t('admin.config.persistentKeepaliveDesc')"
/>
</FormGroup>
@ -62,15 +62,18 @@ const { data: _data, refresh } = await useFetch(`/api/admin/userconfig`, {
const data = toRef(_data.value);
const submit = useSubmit(
const _submit = useSubmit(
`/api/admin/userconfig`,
{
method: 'post',
body: data.value,
},
revert
);
function submit() {
return _submit(data.value);
}
async function revert() {
await refresh();
data.value = toRef(_data.value).value;

34
src/app/pages/admin/hooks.vue

@ -17,38 +17,22 @@
</template>
<script setup lang="ts">
const toast = useToast();
const { data: _data, refresh } = await useFetch(`/api/admin/hooks`, {
method: 'get',
});
const data = toRef(_data.value);
const _submit = useSubmit(
`/api/admin/hooks`,
{
method: 'post',
},
revert
);
async function submit() {
try {
const res = await $fetch(`/api/admin/hooks`, {
method: 'post',
body: data.value,
});
toast.showToast({
type: 'success',
title: 'Success',
message: 'Saved',
});
if (!res.success) {
throw new Error('Failed to save');
}
await refreshNuxtData();
} catch (e) {
if (e instanceof Error) {
toast.showToast({
type: 'error',
title: 'Error',
message: e.message,
});
}
}
return _submit(data.value);
}
async function revert() {

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

@ -45,15 +45,18 @@ const { data: _data, refresh } = await useFetch(`/api/admin/general`, {
});
const data = toRef(_data.value);
const submit = useSubmit(
const _submit = useSubmit(
`/api/admin/general`,
{
method: 'post',
body: data.value,
},
revert
);
function submit() {
return _submit(data.value);
}
async function revert() {
await refresh();
data.value = toRef(_data.value).value;

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

@ -5,7 +5,7 @@
<FormNumberField
id="mtu"
v-model="data.mtu"
:label="$t('admin.generic.mtu')"
:label="$t('general.mtu')"
:description="$t('admin.interface.mtuDesc')"
/>
<FormNumberField
@ -50,31 +50,34 @@ const { data: _data, refresh } = await useFetch(`/api/admin/interface`, {
const data = toRef(_data.value);
const submit = useSubmit(
const _submit = useSubmit(
`/api/admin/interface`,
{
method: 'post',
body: data.value,
},
revert
);
function submit() {
return _submit(data.value);
}
async function revert() {
await refresh();
data.value = toRef(_data.value).value;
}
const _changeCidr = useSubmit(
`/api/admin/interface/cidr`,
{
method: 'post',
},
revert,
t('admin.interface.cidrSuccess'),
t('admin.interface.cidrError')
);
async function changeCidr(ipv4Cidr: string, ipv6Cidr: string) {
const _changeCidr = useSubmit(
`/api/admin/interface/cidr`,
{
method: 'post',
body: { ipv4Cidr, ipv6Cidr },
},
revert,
t('admin.interface.cidrSuccess'),
t('admin.interface.cidrError')
);
await _changeCidr();
await _changeCidr({ ipv4Cidr, ipv6Cidr });
}
</script>

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

@ -8,22 +8,26 @@
<FormElement @submit.prevent="submit">
<FormGroup>
<FormHeading>
{{ $t('me.sectionGeneral') }}
{{ $t('form.sectionGeneral') }}
</FormHeading>
<FormTextField id="name" v-model="data.name" label="Name" />
<FormTextField
id="name"
v-model="data.name"
:label="$t('general.name')"
/>
<FormSwitchField
id="enabled"
v-model="data.enabled"
label="Enabled"
:label="$t('client.enabled')"
/>
<FormDateField
id="expiresAt"
v-model="data.expiresAt"
label="Expire Date"
:label="$t('client.expireDate')"
/>
</FormGroup>
<FormGroup>
<FormHeading>Address</FormHeading>
<FormHeading>{{ $t('client.address') }}</FormHeading>
<FormTextField
id="ipv4Address"
v-model="data.ipv4Address"
@ -36,11 +40,11 @@
/>
</FormGroup>
<FormGroup>
<FormHeading>Allowed IPs</FormHeading>
<FormHeading>{{ $t('general.allowedIps') }}</FormHeading>
<FormArrayField v-model="data.allowedIps" name="allowedIps" />
</FormGroup>
<FormGroup>
<FormHeading>Server Allowed IPs</FormHeading>
<FormHeading>{{ $t('client.serverAllowedIps') }}</FormHeading>
<FormArrayField
v-model="data.serverAllowedIps"
name="serverAllowedIps"
@ -48,20 +52,25 @@
</FormGroup>
<FormGroup></FormGroup>
<FormGroup>
<FormHeading>Advanced</FormHeading>
<FormNumberField id="mtu" v-model="data.mtu" label="MTU" />
<FormHeading>{{ $t('form.sectionAdvanced') }}</FormHeading>
<FormNumberField
id="mtu"
v-model="data.mtu"
:label="$t('general.mtu')"
/>
<FormNumberField
id="persistentKeepalive"
v-model="data.persistentKeepalive"
label="Persistent Keepalive"
:label="$t('general.persistentKeepalive')"
/>
</FormGroup>
<FormGroup>
<FormHeading>Actions</FormHeading>
<FormActionField type="submit" label="Save" />
<FormActionField label="Revert" @click="revert" />
<FormHeading>{{ $t('form.actions') }}</FormHeading>
<FormActionField type="submit" :label="$t('form.save')" />
<FormActionField :label="$t('form.revert')" @click="revert" />
<ClientsDeleteDialog
trigger-class="col-span-2"
:client-name="data.name"
@delete="deleteClient"
>
<FormActionField label="Delete" class="w-full" />
@ -76,9 +85,10 @@
<script lang="ts" setup>
const authStore = useAuthStore();
authStore.update();
const router = useRouter();
const route = useRoute();
const toast = useToast();
const id = route.params.id as string;
const { data: _data, refresh } = await useFetch(`/api/client/${id}`, {
@ -86,30 +96,18 @@ const { data: _data, refresh } = await useFetch(`/api/client/${id}`, {
});
const data = toRef(_data.value);
async function submit() {
try {
const res = await $fetch(`/api/client/${id}`, {
method: 'post',
body: data.value,
});
toast.showToast({
type: 'success',
title: 'Success',
message: 'Saved',
});
if (!res.success) {
throw new Error('Failed to save');
}
const _submit = useSubmit(
`/api/client/${id}`,
{
method: 'post',
},
async () => {
router.push('/');
} catch (e) {
if (e instanceof Error) {
toast.showToast({
type: 'error',
title: 'Error',
message: e.message,
});
}
}
);
function submit() {
return _submit(data.value);
}
async function revert() {
@ -117,28 +115,17 @@ async function revert() {
data.value = toRef(_data.value).value;
}
async function deleteClient() {
try {
const res = await $fetch(`/api/client/${id}`, {
method: 'delete',
});
toast.showToast({
type: 'success',
title: 'Success',
message: 'Deleted',
});
if (!res.success) {
throw new Error('Failed to delete');
}
const _deleteClient = useSubmit(
`/api/client/${id}`,
{
method: 'delete',
},
async () => {
router.push('/');
} catch (e) {
if (e instanceof Error) {
toast.showToast({
type: 'error',
title: 'Error',
message: e.message,
});
}
}
);
function deleteClient() {
return _deleteClient(undefined);
}
</script>

109
src/app/pages/me.vue

@ -7,38 +7,45 @@
<PanelBody class="dark:text-neutral-200">
<FormElement @submit.prevent="submit">
<FormGroup>
<FormHeading>{{ $t('me.sectionGeneral') }}</FormHeading>
<FormTextField id="name" v-model="name" :label="$t('name')" />
<FormHeading>{{ $t('form.sectionGeneral') }}</FormHeading>
<FormTextField
id="name"
v-model="name"
:label="$t('general.name')"
/>
<FormNullTextField
id="email"
v-model="email"
:label="$t('email')"
:label="$t('user.email')"
/>
<FormActionField type="submit" :label="$t('save')" />
<FormActionField type="submit" :label="$t('form.save')" />
</FormGroup>
</FormElement>
<FormElement @submit.prevent="updatePassword">
<FormGroup>
<FormHeading>{{ $t('me.sectionPassword') }}</FormHeading>
<FormHeading>{{ $t('general.password') }}</FormHeading>
<FormPasswordField
id="current-password"
v-model="currentPassword"
autocomplete="current-password"
:label="$t('currentPassword')"
:label="$t('me.currentPassword')"
/>
<FormPasswordField
id="new-password"
v-model="newPassword"
autocomplete="new-password"
:label="$t('setup.newPassword')"
:label="$t('general.newPassword')"
/>
<FormPasswordField
id="confirm-password"
v-model="confirmPassword"
autocomplete="new-password"
:label="$t('confirmPassword')"
:label="$t('me.confirmPassword')"
/>
<FormActionField
type="submit"
:label="$t('general.updatePassword')"
/>
<FormActionField type="submit" :label="$t('updatePassword')" />
</FormGroup>
</FormElement>
</PanelBody>
@ -47,75 +54,47 @@
</template>
<script setup lang="ts">
import { FetchError } from 'ofetch';
const authStore = useAuthStore();
authStore.update();
const toast = useToast();
const name = ref(authStore.userData?.name);
const email = ref(authStore.userData?.email);
async function submit() {
try {
const res = await $fetch(`/api/me`, {
method: 'post',
body: {
name: name.value,
email: email.value,
},
});
toast.showToast({
type: 'success',
title: 'Success',
message: 'Saved',
});
if (!res.success) {
throw new Error('Failed to update general');
}
await refreshNuxtData();
} catch (e) {
if (e instanceof FetchError) {
toast.showToast({
type: 'error',
title: 'Error',
message: e.data.message,
});
}
const _submit = useSubmit(
`/api/me`,
{
method: 'post',
},
async () => {
authStore.update();
}
);
function submit() {
return _submit({ name: name.value, email: email.value });
}
// TODO: handle update password
const currentPassword = ref('');
const newPassword = ref('');
const confirmPassword = ref('');
async function updatePassword() {
try {
const res = await $fetch(`/api/me/password`, {
method: 'post',
body: {
currentPassword: currentPassword.value,
newPassword: newPassword.value,
confirmPassword: confirmPassword.value,
},
});
toast.showToast({
type: 'success',
title: 'Success',
message: 'Saved',
});
if (!res.success) {
throw new Error('Failed to update password');
}
await refreshNuxtData();
} catch (e) {
if (e instanceof FetchError) {
toast.showToast({
type: 'error',
title: 'Error',
message: e.data.message,
});
}
const _updatePassword = useSubmit(
`/api/me/password`,
{
method: 'post',
},
async () => {
currentPassword.value = '';
newPassword.value = '';
confirmPassword.value = '';
}
);
function updatePassword() {
return _updatePassword({
currentPassword: currentPassword.value,
newPassword: newPassword.value,
confirmPassword: confirmPassword.value,
});
}
</script>

53
src/i18n/locales/en.json

@ -10,15 +10,22 @@
"hooks": "Hooks"
}
},
"user": {
"email": "E-Mail"
},
"me": {
"sectionGeneral": "General",
"sectionPassword": "Password"
"currentPassword": "Current Password",
"confirmPassword": "Confirm Password"
},
"general": {
"name": "Name",
"password": "Password",
"newPassword": "New Password",
"updatePassword": "Update Password",
"mtu": "MTU",
"allowedIps": "Allowed IPs",
"persistentKeepalive": "Persistent Keepalive"
},
"email": "E-Mail",
"save": "Save",
"updatePassword": "Update Password",
"currentPassword": "Current Password",
"confirmPassword": "Confirm Password",
"setup": {
"welcome": "Welcome to your first setup of wg-easy !",
"messageWelcome": {
@ -33,7 +40,6 @@
"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...",
"newPassword": "New Password",
"accept": "I accept the condition",
"submitBtn": "Create admin account",
"usernamePlaceholder": "Administrator",
@ -52,11 +58,6 @@
"updateAvailable": "There is an update available!",
"update": "Update",
"new": "New",
"deleteClient": "Delete Client",
"deleteDialog1": "Are you sure you want to delete",
"deleteDialog2": "This action cannot be undone.",
"cancel": "Cancel",
"create": "Create",
"createdOn": "Created on ",
"lastSeen": "Last seen on ",
"totalDownload": "Total Download: ",
@ -79,7 +80,6 @@
"rememberMe": "Remember me",
"titleRememberMe": "Stay logged after closing the browser",
"sort": "Sort",
"ExpireDate": "Expire Date",
"Permanent": "Permanent",
"OneTimeLink": "Generate short one time link",
"errorInit": "Initialization failed.",
@ -87,6 +87,23 @@
"clear": "Clear",
"login": "Log in error"
},
"client": {
"create": "Create Client",
"new": "New Client",
"name": "Name",
"expireDate": "Expire Date",
"deleteClient": "Delete Client",
"deleteDialog1": "Are you sure you want to delete",
"deleteDialog2": "This action cannot be undone.",
"enabled": "Enabled",
"address": "Address",
"serverAllowedIps": "Server Allowed IPs"
},
"dialog": {
"change": "Change",
"cancel": "Cancel",
"create": "Create"
},
"toast": {
"success": "Success",
"saved": "Saved",
@ -96,7 +113,9 @@
"form": {
"actions": "Actions",
"save": "Save",
"revert": "Revert"
"revert": "Revert",
"sectionGeneral": "General",
"sectionAdvanced": "Advanced"
},
"password": "Password",
"admin": {
@ -116,13 +135,10 @@
"host": "Host",
"hostDesc": "Public hostname clients will connect to (invalidates config)",
"portDesc": "Public UDP port clients will connect to (invalidates config)",
"allowedIps": "Allowed IPs",
"allowedIpsDesc": "Allowed IPs clients will use (invalidates config)",
"dns": "DNS",
"dnsDesc": "DNS server clients will use (invalidates config)",
"advanced": "Advanced",
"mtuDesc": "MTU clients will use (invalidates config)",
"persistentKeepalive": "Persistent Keepalive",
"persistentKeepaliveDesc": "Interval in seconds to send keepalives to the server. 0 = disabled (invalidates config)"
},
"interface": {
@ -135,7 +151,6 @@
"changeCidr": "Change CIDR"
},
"generic": {
"mtu": "MTU",
"port": "Port"
}
},

Loading…
Cancel
Save