diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index a20fe5b0..6a0e929e 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,3 +1,3 @@
# These are supported funding model platforms
-github: weejewel
+github: [weejewel, kaaax0815]
diff --git a/.github/workflows/deploy-development.yml b/.github/workflows/deploy-development.yml
index 212ccff4..d1907ba1 100644
--- a/.github/workflows/deploy-development.yml
+++ b/.github/workflows/deploy-development.yml
@@ -32,7 +32,7 @@ jobs:
with:
context: .
push: true
- platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
+ platforms: linux/amd64,linux/arm64/v8
tags: ghcr.io/wg-easy/wg-easy:development
cache-from: type=gha
cache-to: type=gha,mode=min
diff --git a/.github/workflows/deploy-nightly.yml b/.github/workflows/deploy-nightly.yml
index 03a75bba..8fe8927c 100644
--- a/.github/workflows/deploy-nightly.yml
+++ b/.github/workflows/deploy-nightly.yml
@@ -36,7 +36,7 @@ jobs:
with:
context: .
push: true
- platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
+ platforms: linux/amd64,linux/arm64/v8
tags: ghcr.io/wg-easy/wg-easy:nightly
cache-from: type=gha
cache-to: type=gha,mode=min
diff --git a/.github/workflows/deploy-pr.yml b/.github/workflows/deploy-pr.yml
index d1c226c1..a0a0b442 100644
--- a/.github/workflows/deploy-pr.yml
+++ b/.github/workflows/deploy-pr.yml
@@ -37,7 +37,7 @@ jobs:
with:
context: .
push: false
- platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
+ platforms: linux/amd64,linux/arm64/v8
tags: ghcr.io/wg-easy/wg-easy:pr
cache-from: type=gha
cache-to: type=gha,mode=min
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 321c72e1..82d23344 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -48,7 +48,7 @@ jobs:
with:
context: .
push: true
- platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
+ platforms: linux/amd64,linux/arm64/v8
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4b50efa..2266d660 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,13 +20,10 @@ This update is an entire rewrite to make it even easier to set up your own VPN.
- CIDR Support
- IPv6 Support
- Changed API Structure
-- Changed Database Structure
+- SQLite Database
- Deprecated Dockerless Installations
-- Added Docker Volume Mount
-
-## Minor Changes
-
-- Renamed Chinese Locales (cht -> zh-cht, chs -> zh-chs)
+- Added Docker Volume Mount (`/lib/modules`)
+- Removed ARMv6 and ARMv7 support
## [14.0.0] - 2024-09-04
diff --git a/Dockerfile b/Dockerfile
index 32591d9f..50d60b93 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,4 @@
-# nodejs 20 hangs on build with armv6/armv7 (https://github.com/nodejs/docker-node/issues/2077)
-FROM docker.io/library/node:18-alpine AS build
+FROM docker.io/library/node:lts-alpine AS build
WORKDIR /app
# update corepack
@@ -7,20 +6,14 @@ RUN npm install --global corepack@latest
# Install pnpm
RUN corepack enable pnpm
-# add build tools for argon2
-RUN apk add --no-cache make gcc g++ python3
-
# Copy Web UI
-COPY src ./
+COPY src/package.json src/pnpm-lock.yaml ./
RUN pnpm install
# Build UI
+COPY src ./
RUN pnpm build
-# Remove unnecessary node modules
-RUN find ./node_modules/.pnpm -mindepth 1 -maxdepth 1 -type d ! -name '@libsql+linux*' -exec rm -r {} +
-RUN find ./node_modules/@libsql -mindepth 1 -maxdepth 1 -type l ! -name 'linux*' -exec rm -r {} +
-
# Copy build result to a new image.
# This saves a lot of disk space.
FROM docker.io/library/node:lts-alpine
@@ -33,8 +26,7 @@ COPY --from=build /app/.output /app
# Copy migrations
COPY --from=build /app/server/database/migrations /app/server/database/migrations
# libsql
-COPY --from=build /app/node_modules/.pnpm/ /app/node_modules/.pnpm/
-COPY --from=build /app/node_modules/@libsql /app/node_modules/@libsql
+RUN npm install --no-save libsql
# Install Linux packages
RUN apk add --no-cache \
diff --git a/src/app/app.vue b/src/app/app.vue
index 9ce499e4..9991e288 100644
--- a/src/app/app.vue
+++ b/src/app/app.vue
@@ -16,6 +16,9 @@ const toast = useToast();
const toastRef = useTemplateRef('toastRef');
toast.setToast(toastRef);
+// make sure to fetch release early
+useGlobalStore();
+
useHead({
bodyAttrs: {
class: 'bg-gray-50 dark:bg-neutral-800',
diff --git a/src/app/components/admin/CidrDialog.vue b/src/app/components/Admin/CidrDialog.vue
similarity index 78%
rename from src/app/components/admin/CidrDialog.vue
rename to src/app/components/Admin/CidrDialog.vue
index 4a6d7b15..d9635009 100644
--- a/src/app/components/admin/CidrDialog.vue
+++ b/src/app/components/Admin/CidrDialog.vue
@@ -1,7 +1,7 @@
-
+
- Change CIDR
+ {{ $t('admin.interface.changeCidr') }}
@@ -10,11 +10,11 @@
- {{ $t('cancel') }}
+ {{ $t('dialog.cancel') }}
- Change
+ {{ $t('dialog.change') }}
diff --git a/src/app/components/base/Avatar.vue b/src/app/components/Base/Avatar.vue
similarity index 100%
rename from src/app/components/base/Avatar.vue
rename to src/app/components/Base/Avatar.vue
diff --git a/src/app/components/base/Button.vue b/src/app/components/Base/Button.vue
similarity index 100%
rename from src/app/components/base/Button.vue
rename to src/app/components/Base/Button.vue
diff --git a/src/app/components/base/Chart.vue b/src/app/components/Base/Chart.vue
similarity index 100%
rename from src/app/components/base/Chart.vue
rename to src/app/components/Base/Chart.vue
diff --git a/src/app/components/base/Dialog.vue b/src/app/components/Base/Dialog.vue
similarity index 70%
rename from src/app/components/base/Dialog.vue
rename to src/app/components/Base/Dialog.vue
index 05e882fd..eea4fa56 100644
--- a/src/app/components/base/Dialog.vue
+++ b/src/app/components/Base/Dialog.vue
@@ -3,10 +3,10 @@
-
- {{ label }}
-
diff --git a/src/app/components/base/Switch.vue b/src/app/components/Base/Switch.vue
similarity index 100%
rename from src/app/components/base/Switch.vue
rename to src/app/components/Base/Switch.vue
diff --git a/src/app/components/Base/Toast.vue b/src/app/components/Base/Toast.vue
new file mode 100644
index 00000000..e1d84bb1
--- /dev/null
+++ b/src/app/components/Base/Toast.vue
@@ -0,0 +1,46 @@
+
+
+
+ {{ e.title }}
+
+ {{
+ e.message
+ }}
+
+
+
+
+ ×
+
+
+
+
+
diff --git a/src/app/components/Base/Tooltip.vue b/src/app/components/Base/Tooltip.vue
new file mode 100644
index 00000000..ed01b150
--- /dev/null
+++ b/src/app/components/Base/Tooltip.vue
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+ {{ text }}
+
+
+
+
+
+
+
+
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 @@
{{ client.name }}
diff --git a/src/app/components/ClientCard/OneTimeLink.vue b/src/app/components/ClientCard/OneTimeLink.vue
index 7e3d142c..4973b41e 100644
--- a/src/app/components/ClientCard/OneTimeLink.vue
+++ b/src/app/components/ClientCard/OneTimeLink.vue
@@ -7,11 +7,45 @@
diff --git a/src/app/components/ClientCard/OneTimeLinkBtn.vue b/src/app/components/ClientCard/OneTimeLinkBtn.vue
index 314a48a6..e3ff7e3f 100644
--- a/src/app/components/ClientCard/OneTimeLinkBtn.vue
+++ b/src/app/components/ClientCard/OneTimeLinkBtn.vue
@@ -1,38 +1,32 @@
-
-
-
+
diff --git a/src/app/components/ClientCard/QRCode.vue b/src/app/components/ClientCard/QRCode.vue
index a383d80d..3f3b1f2e 100644
--- a/src/app/components/ClientCard/QRCode.vue
+++ b/src/app/components/ClientCard/QRCode.vue
@@ -1,11 +1,11 @@
-
-
+
diff --git a/src/app/components/ClientCard/Switch.vue b/src/app/components/ClientCard/Switch.vue
index f71cebd7..60e7b68a 100644
--- a/src/app/components/ClientCard/Switch.vue
+++ b/src/app/components/ClientCard/Switch.vue
@@ -1,7 +1,9 @@
@@ -15,21 +17,37 @@ const enabled = ref(props.client.enabled);
const clientsStore = useClientsStore();
+const _disableClient = useSubmit(
+ `/api/client/${props.client.id}/disable`,
+ {
+ method: 'post',
+ },
+ {
+ revert: async () => {
+ await clientsStore.refresh();
+ },
+ noSuccessToast: true,
+ }
+);
+
+const _enableClient = useSubmit(
+ `/api/client/${props.client.id}/enable`,
+ {
+ method: 'post',
+ },
+ {
+ revert: async () => {
+ await clientsStore.refresh();
+ },
+ noSuccessToast: true,
+ }
+);
+
async function toggleClient() {
- try {
- if (props.client.enabled) {
- await $fetch(`/api/client/${props.client.id}/disable`, {
- method: 'post',
- });
- } else {
- await $fetch(`/api/client/${props.client.id}/enable`, {
- method: 'post',
- });
- }
- } catch (err) {
- alert(err);
- } finally {
- clientsStore.refresh().catch(console.error);
+ if (props.client.enabled) {
+ await _disableClient(undefined);
+ } else {
+ await _enableClient(undefined);
}
}
diff --git a/src/app/components/Clients/CreateDialog.vue b/src/app/components/Clients/CreateDialog.vue
index c5d2aa0e..3d34a7f3 100644
--- a/src/app/components/Clients/CreateDialog.vue
+++ b/src/app/components/Clients/CreateDialog.vue
@@ -4,56 +4,50 @@
- {{ $t('newClient') }}
+ {{ $t('client.new') }}
-
-
+
+
- {{ $t('cancel') }}
+ {{ $t('dialog.cancel') }}
- {{ $t('create') }}
+ {{ $t('client.create') }}
diff --git a/src/app/components/Clients/DeleteDialog.vue b/src/app/components/Clients/DeleteDialog.vue
index 21fec0b6..974eb7c4 100644
--- a/src/app/components/Clients/DeleteDialog.vue
+++ b/src/app/components/Clients/DeleteDialog.vue
@@ -1,19 +1,19 @@
-
+
- {{ $t('deleteClient') }}
+ {{ $t('client.deleteClient') }}
- {{ $t('deleteDialog1') }}
- {{ 'test' }} ? {{ $t('deleteDialog2') }}
+ {{ $t('client.deleteDialog1') }}
+ {{ clientName }} ? {{ $t('client.deleteDialog2') }}
- {{ $t('cancel') }}
+ {{ $t('dialog.cancel') }}
{{
- $t('deleteClient')
+ $t('client.deleteClient')
}}
@@ -22,5 +22,5 @@
diff --git a/src/app/components/Clients/Empty.vue b/src/app/components/Clients/Empty.vue
index 97a102dd..004fa6be 100644
--- a/src/app/components/Clients/Empty.vue
+++ b/src/app/components/Clients/Empty.vue
@@ -1,10 +1,10 @@
- {{ $t('noClients') }}
+ {{ $t('client.empty') }}
-
+
- {{ $t('new') }}
+ {{ $t('client.new') }}
diff --git a/src/app/components/Clients/New.vue b/src/app/components/Clients/New.vue
index 7f28eea8..87b6a071 100644
--- a/src/app/components/Clients/New.vue
+++ b/src/app/components/Clients/New.vue
@@ -1,8 +1,8 @@
-
+
- {{ $t('new') }}
+ {{ $t('client.newShort') }}
diff --git a/src/app/components/Clients/QRCodeDialog.vue b/src/app/components/Clients/QRCodeDialog.vue
index 8623abbe..7a3e7183 100644
--- a/src/app/components/Clients/QRCodeDialog.vue
+++ b/src/app/components/Clients/QRCodeDialog.vue
@@ -8,7 +8,7 @@
- {{ $t('cancel') }}
+ {{ $t('dialog.cancel') }}
diff --git a/src/app/components/Clients/Sort.vue b/src/app/components/Clients/Sort.vue
index 1b73546d..a3aaaf79 100644
--- a/src/app/components/Clients/Sort.vue
+++ b/src/app/components/Clients/Sort.vue
@@ -1,52 +1,15 @@
-
-
+
-
-
-
-
-
-
-
- {{ $t('sort') }}
-
+ />
+
+ {{ $t('client.sort') }}
+
diff --git a/src/app/components/form/Element.vue b/src/app/components/Form/Element.vue
similarity index 100%
rename from src/app/components/form/Element.vue
rename to src/app/components/Form/Element.vue
diff --git a/src/app/components/form/Group.vue b/src/app/components/Form/Group.vue
similarity index 100%
rename from src/app/components/form/Group.vue
rename to src/app/components/Form/Group.vue
diff --git a/src/app/components/Form/Heading.vue b/src/app/components/Form/Heading.vue
new file mode 100644
index 00000000..999639a7
--- /dev/null
+++ b/src/app/components/Form/Heading.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/Form/Label.vue b/src/app/components/Form/Label.vue
new file mode 100644
index 00000000..90f4804b
--- /dev/null
+++ b/src/app/components/Form/Label.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/src/app/components/Form/NullTextField.vue b/src/app/components/Form/NullTextField.vue
new file mode 100644
index 00000000..7a97e7b2
--- /dev/null
+++ b/src/app/components/Form/NullTextField.vue
@@ -0,0 +1,38 @@
+
+
+
+ {{ label }}
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/Form/NumberField.vue b/src/app/components/Form/NumberField.vue
new file mode 100644
index 00000000..07ad5872
--- /dev/null
+++ b/src/app/components/Form/NumberField.vue
@@ -0,0 +1,17 @@
+
+
+
+ {{ label }}
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/Form/PasswordField.vue b/src/app/components/Form/PasswordField.vue
new file mode 100644
index 00000000..c17691a9
--- /dev/null
+++ b/src/app/components/Form/PasswordField.vue
@@ -0,0 +1,18 @@
+
+
+ {{ label }}
+
+
+
+
+
diff --git a/src/app/components/Form/SwitchField.vue b/src/app/components/Form/SwitchField.vue
new file mode 100644
index 00000000..43eaf3f8
--- /dev/null
+++ b/src/app/components/Form/SwitchField.vue
@@ -0,0 +1,16 @@
+
+
+
+ {{ label }}
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/Form/TextField.vue b/src/app/components/Form/TextField.vue
new file mode 100644
index 00000000..6cffda28
--- /dev/null
+++ b/src/app/components/Form/TextField.vue
@@ -0,0 +1,28 @@
+
+
+
+ {{ label }}
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/header/ChartToggle.vue b/src/app/components/Header/ChartToggle.vue
similarity index 93%
rename from src/app/components/header/ChartToggle.vue
rename to 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 @@
diff --git a/src/app/components/icons/ArrowDown.vue b/src/app/components/Icons/ArrowDown.vue
similarity index 100%
rename from src/app/components/icons/ArrowDown.vue
rename to src/app/components/Icons/ArrowDown.vue
diff --git a/src/app/components/icons/ArrowInf.vue b/src/app/components/Icons/ArrowInf.vue
similarity index 100%
rename from src/app/components/icons/ArrowInf.vue
rename to src/app/components/Icons/ArrowInf.vue
diff --git a/src/app/components/icons/ArrowLeftCircle.vue b/src/app/components/Icons/ArrowLeftCircle.vue
similarity index 100%
rename from src/app/components/icons/ArrowLeftCircle.vue
rename to src/app/components/Icons/ArrowLeftCircle.vue
diff --git a/src/app/components/icons/ArrowRightCircle.vue b/src/app/components/Icons/ArrowRightCircle.vue
similarity index 100%
rename from src/app/components/icons/ArrowRightCircle.vue
rename to src/app/components/Icons/ArrowRightCircle.vue
diff --git a/src/app/components/icons/ArrowUp.vue b/src/app/components/Icons/ArrowUp.vue
similarity index 100%
rename from src/app/components/icons/ArrowUp.vue
rename to src/app/components/Icons/ArrowUp.vue
diff --git a/src/app/components/icons/Avatar.vue b/src/app/components/Icons/Avatar.vue
similarity index 100%
rename from src/app/components/icons/Avatar.vue
rename to src/app/components/Icons/Avatar.vue
diff --git a/src/app/components/icons/Chart.vue b/src/app/components/Icons/Chart.vue
similarity index 100%
rename from src/app/components/icons/Chart.vue
rename to src/app/components/Icons/Chart.vue
diff --git a/src/app/components/icons/CheckCircle.vue b/src/app/components/Icons/CheckCircle.vue
similarity index 100%
rename from src/app/components/icons/CheckCircle.vue
rename to src/app/components/Icons/CheckCircle.vue
diff --git a/src/app/components/icons/Close.vue b/src/app/components/Icons/Close.vue
similarity index 100%
rename from src/app/components/icons/Close.vue
rename to src/app/components/Icons/Close.vue
diff --git a/src/app/components/icons/Delete.vue b/src/app/components/Icons/Delete.vue
similarity index 100%
rename from src/app/components/icons/Delete.vue
rename to src/app/components/Icons/Delete.vue
diff --git a/src/app/components/icons/Download.vue b/src/app/components/Icons/Download.vue
similarity index 100%
rename from src/app/components/icons/Download.vue
rename to src/app/components/Icons/Download.vue
diff --git a/src/app/components/icons/Edit.vue b/src/app/components/Icons/Edit.vue
similarity index 100%
rename from src/app/components/icons/Edit.vue
rename to src/app/components/Icons/Edit.vue
diff --git a/src/app/components/icons/HalfMoon.vue b/src/app/components/Icons/HalfMoon.vue
similarity index 100%
rename from src/app/components/icons/HalfMoon.vue
rename to src/app/components/Icons/HalfMoon.vue
diff --git a/src/app/components/Icons/Info.vue b/src/app/components/Icons/Info.vue
new file mode 100644
index 00000000..cedf7d45
--- /dev/null
+++ b/src/app/components/Icons/Info.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/src/app/components/icons/Language.vue b/src/app/components/Icons/Language.vue
similarity index 100%
rename from src/app/components/icons/Language.vue
rename to src/app/components/Icons/Language.vue
diff --git a/src/app/components/Icons/Link.vue b/src/app/components/Icons/Link.vue
new file mode 100644
index 00000000..d8395c28
--- /dev/null
+++ b/src/app/components/Icons/Link.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/src/app/components/icons/Loading.vue b/src/app/components/Icons/Loading.vue
similarity index 100%
rename from src/app/components/icons/Loading.vue
rename to src/app/components/Icons/Loading.vue
diff --git a/src/app/components/icons/Logout.vue b/src/app/components/Icons/Logout.vue
similarity index 100%
rename from src/app/components/icons/Logout.vue
rename to src/app/components/Icons/Logout.vue
diff --git a/src/app/components/icons/Moon.vue b/src/app/components/Icons/Moon.vue
similarity index 100%
rename from src/app/components/icons/Moon.vue
rename to src/app/components/Icons/Moon.vue
diff --git a/src/app/components/icons/Plus.vue b/src/app/components/Icons/Plus.vue
similarity index 100%
rename from src/app/components/icons/Plus.vue
rename to src/app/components/Icons/Plus.vue
diff --git a/src/app/components/icons/QRCode.vue b/src/app/components/Icons/QRCode.vue
similarity index 100%
rename from src/app/components/icons/QRCode.vue
rename to src/app/components/Icons/QRCode.vue
diff --git a/src/app/components/icons/Stack.vue b/src/app/components/Icons/Stack.vue
similarity index 100%
rename from src/app/components/icons/Stack.vue
rename to src/app/components/Icons/Stack.vue
diff --git a/src/app/components/icons/Sun.vue b/src/app/components/Icons/Sun.vue
similarity index 100%
rename from src/app/components/icons/Sun.vue
rename to src/app/components/Icons/Sun.vue
diff --git a/src/app/components/icons/Warning.vue b/src/app/components/Icons/Warning.vue
similarity index 100%
rename from src/app/components/icons/Warning.vue
rename to src/app/components/Icons/Warning.vue
diff --git a/src/app/components/panel/Body.vue b/src/app/components/Panel/Body.vue
similarity index 100%
rename from src/app/components/panel/Body.vue
rename to src/app/components/Panel/Body.vue
diff --git a/src/app/components/panel/Panel.vue b/src/app/components/Panel/Panel.vue
similarity index 100%
rename from src/app/components/panel/Panel.vue
rename to src/app/components/Panel/Panel.vue
diff --git a/src/app/components/panel/head/Boat.vue b/src/app/components/Panel/head/Boat.vue
similarity index 100%
rename from src/app/components/panel/head/Boat.vue
rename to src/app/components/Panel/head/Boat.vue
diff --git a/src/app/components/panel/head/Head.vue b/src/app/components/Panel/head/Head.vue
similarity index 100%
rename from src/app/components/panel/head/Head.vue
rename to src/app/components/Panel/head/Head.vue
diff --git a/src/app/components/panel/head/Title.vue b/src/app/components/Panel/head/Title.vue
similarity index 100%
rename from src/app/components/panel/head/Title.vue
rename to 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 @@
-
-
{{ text }}
+
+
diff --git a/src/app/components/ui/Banner.vue b/src/app/components/Ui/Banner.vue
similarity index 100%
rename from src/app/components/ui/Banner.vue
rename to src/app/components/Ui/Banner.vue
diff --git a/src/app/components/ui/Footer.vue b/src/app/components/Ui/Footer.vue
similarity index 87%
rename from src/app/components/ui/Footer.vue
rename to src/app/components/Ui/Footer.vue
index cfc5c356..a7ace59c 100644
--- a/src/app/components/ui/Footer.vue
+++ b/src/app/components/Ui/Footer.vue
@@ -7,7 +7,7 @@
href="https://github.com/wg-easy/wg-easy"
>WireGuard Easy
- ({{ globalStore.currentRelease }}) © 2021-2025 by
+ ({{ globalStore.release?.currentRelease }}) © 2021-2025 by
{{ $t('donate') }} {{ $t('layout.donate') }}
@@ -34,5 +34,4 @@
diff --git a/src/app/components/ui/StepProgress.vue b/src/app/components/Ui/StepProgress.vue
similarity index 100%
rename from src/app/components/ui/StepProgress.vue
rename to src/app/components/Ui/StepProgress.vue
diff --git a/src/app/components/ui/UserMenu.vue b/src/app/components/Ui/UserMenu.vue
similarity index 84%
rename from src/app/components/ui/UserMenu.vue
rename to src/app/components/Ui/UserMenu.vue
index 77542d53..ca4d19ec 100644
--- a/src/app/components/ui/UserMenu.vue
+++ b/src/app/components/Ui/UserMenu.vue
@@ -1,15 +1,14 @@
-
{{ fallbackName }}
{{ authStore.userData?.name }}
-
+
@@ -26,7 +25,7 @@
to="/"
class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"
>
- Clients
+ {{ $t('pages.clients') }}
@@ -34,7 +33,7 @@
to="/me"
class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"
>
- Account
+ {{ $t('pages.me') }}
- Admin Panel
+ {{ $t('pages.admin.panel') }}
- {{ $t('logout') }}
+ {{ $t('general.logout') }}
@@ -68,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/components/base/Container.vue b/src/app/components/base/Container.vue
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/app/components/base/Toast.vue b/src/app/components/base/Toast.vue
deleted file mode 100644
index c3bae05c..00000000
--- a/src/app/components/base/Toast.vue
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
- {{ e.title }}
-
- {{ e.message }}
-
-
-
-
- ×
-
-
-
diff --git a/src/app/components/form/DateField.vue b/src/app/components/form/DateField.vue
deleted file mode 100644
index a675796a..00000000
--- a/src/app/components/form/DateField.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
- {{ label }}
-
-
-
-
-
diff --git a/src/app/components/form/Heading.vue b/src/app/components/form/Heading.vue
deleted file mode 100644
index cd58ded2..00000000
--- a/src/app/components/form/Heading.vue
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/src/app/components/form/NullTextField.vue b/src/app/components/form/NullTextField.vue
deleted file mode 100644
index 6d904531..00000000
--- a/src/app/components/form/NullTextField.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
- {{ label }}
-
-
-
-
-
diff --git a/src/app/components/form/NumberField.vue b/src/app/components/form/NumberField.vue
deleted file mode 100644
index 706e7257..00000000
--- a/src/app/components/form/NumberField.vue
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
- {{ label }}
-
-
-
-
-
diff --git a/src/app/components/form/PasswordField.vue b/src/app/components/form/PasswordField.vue
deleted file mode 100644
index e2bca565..00000000
--- a/src/app/components/form/PasswordField.vue
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
- {{ label }}
-
-
-
-
-
diff --git a/src/app/components/form/SwitchField.vue b/src/app/components/form/SwitchField.vue
deleted file mode 100644
index a8adf822..00000000
--- a/src/app/components/form/SwitchField.vue
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
- {{ label }}
-
-
-
-
-
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 @@
-
-
-
-
-
-
-
-
-
-
-
-
- {{ option.name }}
-
-
-
-
-
-
-
-
-
diff --git a/src/app/composables/useSubmit.ts b/src/app/composables/useSubmit.ts
new file mode 100644
index 00000000..27d734c1
--- /dev/null
+++ b/src/app/composables/useSubmit.ts
@@ -0,0 +1,57 @@
+import type { NitroFetchRequest, NitroFetchOptions } from 'nitropack/types';
+import { FetchError } from 'ofetch';
+
+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, opts: SubmitOpts) {
+ const toast = useToast();
+ const { t: $t } = useI18n();
+
+ return async (data: unknown) => {
+ try {
+ 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(opts.errorMsg || $t('toast.errored'));
+ }
+
+ if (!opts.noSuccessToast) {
+ toast.showToast({
+ type: 'success',
+ message: opts.successMsg,
+ });
+ }
+
+ await opts.revert(true);
+ } catch (e) {
+ if (e instanceof FetchError) {
+ toast.showToast({
+ type: 'error',
+ message: e.data.message,
+ });
+ } else if (e instanceof Error) {
+ toast.showToast({
+ type: 'error',
+ message: e.message,
+ });
+ } else {
+ console.error(e);
+ }
+ await opts.revert(false);
+ }
+ };
+}
diff --git a/src/app/pages/admin.vue b/src/app/pages/admin.vue
index aeb01b3d..71c0f6d6 100644
--- a/src/app/pages/admin.vue
+++ b/src/app/pages/admin.vue
@@ -5,7 +5,7 @@
- Admin Panel
+ {{ t('pages.admin.panel') }}
@@ -38,13 +38,15 @@
const authStore = useAuthStore();
authStore.update();
+const { t } = useI18n();
+
const route = useRoute();
const menuItems = [
- { id: '', name: 'General' },
- { id: 'config', name: 'Config' },
- { id: 'interface', name: 'Interface' },
- { id: 'hooks', name: 'Hooks' },
+ { id: '', name: t('pages.admin.general') },
+ { id: 'config', name: t('pages.admin.config') },
+ { id: 'interface', name: t('pages.admin.interface') },
+ { id: 'hooks', name: t('pages.admin.hooks') },
];
const activeMenuItem = computed(() => {
diff --git a/src/app/pages/admin/config.vue b/src/app/pages/admin/config.vue
index 42428367..e7217ba7 100644
--- a/src/app/pages/admin/config.vue
+++ b/src/app/pages/admin/config.vue
@@ -2,78 +2,76 @@
- Connection
-
-
+ {{ $t('admin.config.connection') }}
+
+
- Allowed IPs
+ {{
+ $t('general.allowedIps')
+ }}
- DNS
+ {{
+ $t('admin.config.dns')
+ }}
- Advanced
+ {{ $t('form.sectionAdvanced') }}
- Actions
-
-
+ {{ $t('form.actions') }}
+
+
diff --git a/src/app/pages/clients/[id].vue b/src/app/pages/clients/[id].vue
index fdb63a68..4b7a9575 100644
--- a/src/app/pages/clients/[id].vue
+++ b/src/app/pages/clients/[id].vue
@@ -8,22 +8,27 @@
- {{ $t('me.sectionGeneral') }}
+ {{ $t('form.sectionGeneral') }}
-
+
- Address
+ {{ $t('client.address') }}
- Allowed IPs
+ {{
+ $t('general.allowedIps')
+ }}
- Server Allowed IPs
+ {{
+ $t('client.serverAllowedIps')
+ }}
- Advanced
-
+ {{ $t('form.sectionAdvanced') }}
+
- Actions
-
-
+ {{ $t('form.actions') }}
+
+
-
+
@@ -76,9 +98,8 @@
diff --git a/src/app/pages/index.vue b/src/app/pages/index.vue
index 06a9befa..3579da17 100644
--- a/src/app/pages/index.vue
+++ b/src/app/pages/index.vue
@@ -30,6 +30,7 @@
diff --git a/src/app/pages/me.vue b/src/app/pages/me.vue
index b1de45a1..333422c3 100644
--- a/src/app/pages/me.vue
+++ b/src/app/pages/me.vue
@@ -7,38 +7,45 @@
- {{ $t('me.sectionGeneral') }}
-
+ {{ $t('form.sectionGeneral') }}
+
-
+
- {{ $t('me.sectionPassword') }}
+ {{ $t('general.password') }}
+
-
@@ -47,75 +54,51 @@
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 @@
- {{ $t('setup.messageWelcome.whatIs') }}
+ {{ $t('setup.welcomeDesc') }}
-
Continue
+
+ {{ $t('general.continue') }}
+
@@ -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 @@
- {{ $t('setup.messageSetupCreateAdminUser') }}
+ {{ $t('setup.createAdminDesc') }}
-
-
-
{{ $t('username') }}
-
+
+
+
+
+
+
+
+
+ {{ $t('setup.createAccount') }}
+
-
- {{ $t('setup.newPassword') }}
-
-
-
- {{ $t('setup.accept') }}
-
-
-
Create Account
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 @@
- {{ 'Do you have a existing Setup?' }}
+ {{ $t('setup.existingSetup') }}
- No
- Yes
+
+ {{ $t('general.no') }}
+
+
+ {{ $t('general.yes') }}
+
@@ -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..40e22eef 100644
--- a/src/app/pages/setup/4.vue
+++ b/src/app/pages/setup/4.vue
@@ -1,71 +1,60 @@
- {{ $t('setup.messageSetupHostPort') }}
+ {{ $t('setup.setupConfigDesc') }}
-
-
{{ $t('setup.host') }}
-
+
+
+
+
+
+
+
+
+ {{ $t('general.continue') }}
+
-
- {{ $t('setup.port') }}
-
-
-
Continue
diff --git a/src/app/pages/setup/migrate.vue b/src/app/pages/setup/migrate.vue
index 77622c81..a1185bbd 100644
--- a/src/app/pages/setup/migrate.vue
+++ b/src/app/pages/setup/migrate.vue
@@ -1,68 +1,57 @@
- {{ $t('setup.messageSetupMigration') }}
+ {{ $t('setup.setupMigrationDesc') }}
{{ $t('setup.migration') }}
-
Upload
+
{{ $t('setup.upload') }}
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..ddf83ffd 100644
--- a/src/app/stores/global.ts
+++ b/src/app/stores/global.ts
@@ -1,27 +1,9 @@
-import { defineStore } from 'pinia';
-
export const useGlobalStore = defineStore('Global', () => {
- const sortClient = ref(true); // Sort clients by name, true = asc, false = desc
-
- const currentRelease = ref
(null);
- const latestRelease = ref(
- null
- );
- const updateAvailable = ref(false);
-
- async function fetchRelease() {
- const { data: release } = await useFetch('/api/release', {
- method: 'get',
- });
+ const { data: release } = useFetch('/api/release', {
+ method: 'get',
+ });
- if (!release.value) {
- return;
- }
-
- currentRelease.value = release.value.currentRelease;
- latestRelease.value = release.value.latestRelease;
- updateAvailable.value = release.value.updateAvailable;
- }
+ const sortClient = ref(true); // Sort clients by name, true = asc, false = desc
const uiShowCharts = ref(getItem('uiShowCharts') === '1');
@@ -33,10 +15,7 @@ export const useGlobalStore = defineStore('Global', () => {
return {
sortClient,
- currentRelease,
- latestRelease,
- updateAvailable,
- fetchRelease,
+ release,
uiShowCharts,
toggleCharts,
uiChartType,
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/app/stores/toast.ts b/src/app/stores/toast.ts
index d56588db..9463154c 100644
--- a/src/app/stores/toast.ts
+++ b/src/app/stores/toast.ts
@@ -1,6 +1,8 @@
export const useToast = defineStore('Toast', () => {
+ const { t: $t } = useI18n();
+
type ToastInterface = {
- publish: (e: { title: string; message: string }) => void;
+ publish: (e: ToastParams) => void;
};
type ToastRef = Ref;
@@ -11,15 +13,36 @@ export const useToast = defineStore('Toast', () => {
toast.value = toastInstance;
}
- function showToast({
- title,
- message,
- }: {
- type: 'success' | 'error';
- title: string;
- message: string;
- }) {
- toast.value?.value?.publish({ title, message });
+ type ShowToast =
+ | {
+ type: 'success';
+ title?: string;
+ message?: string;
+ }
+ | {
+ type: 'error';
+ title?: string;
+ message: string;
+ };
+
+ function showToast({ type, title, message }: ShowToast) {
+ if (type === 'success') {
+ if (!title) {
+ title = $t('toast.success');
+ }
+ if (!message) {
+ message = $t('toast.saved');
+ }
+ } else if (type === 'error') {
+ if (!title) {
+ title = $t('toast.error');
+ }
+ }
+ toast.value?.value?.publish({
+ type,
+ title: title ?? '',
+ message: message ?? '',
+ });
}
return { setToast, showToast };
diff --git a/src/app/utils/types.ts b/src/app/utils/types.ts
new file mode 100644
index 00000000..0736ab97
--- /dev/null
+++ b/src/app/utils/types.ts
@@ -0,0 +1,6 @@
+export type ToastTypes = 'error' | 'success';
+export type ToastParams = {
+ type: ToastTypes;
+ title: string;
+ message: string;
+};
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index 2edb235e..d623508d 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -1,91 +1,155 @@
{
"pages": {
"me": "Account",
- "clients": "Clients"
+ "clients": "Clients",
+ "admin": {
+ "panel": "Admin Panel",
+ "general": "General",
+ "config": "Config",
+ "interface": "Interface",
+ "hooks": "Hooks"
+ }
+ },
+ "user": {
+ "email": "E-Mail"
},
"me": {
- "sectionGeneral": "General",
- "sectionPassword": "Password"
+ "currentPassword": "Current Password",
+ "confirmPassword": "Confirm Password"
},
- "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": {
- "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...",
+ "general": {
+ "name": "Name",
+ "username": "Username",
+ "password": "Password",
"newPassword": "New Password",
- "accept": "I accept the condition",
- "submitBtn": "Create admin account",
- "usernamePlaceholder": "Administrator",
- "passwordPlaceholder": "Strong password",
- "requirements": "Setup requirements",
+ "updatePassword": "Update Password",
+ "mtu": "MTU",
+ "allowedIps": "Allowed IPs",
+ "persistentKeepalive": "Persistent Keepalive",
+ "logout": "Logout",
+ "continue": "Continue",
"host": "Host",
- "hostPlaceholder": "wg-easy.example.com",
"port": "Port",
- "portPlaceholder": "443",
- "migration": "Restore the backup"
+ "yes": "Yes",
+ "no": "No"
+ },
+ "setup": {
+ "welcome": "Welcome to your first setup of wg-easy !",
+ "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"
},
- "name": "Name",
- "username": "Username",
- "signIn": "Sign In",
- "logout": "Logout",
- "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: ",
- "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"
},
- "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.",
+ "layout": {
+ "toggleCharts": "Show/hide Charts",
+ "donate": "Donate"
+ },
+ "login": {
+ "signIn": "Sign In",
+ "rememberMe": "Remember me",
+ "rememberMeDesc": "Stay logged after closing the browser"
+ },
"error": {
"clear": "Clear",
"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",
+ "expireDateDesc": "Date the client will be disabled. Blank for permanent",
+ "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",
+ "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",
+ "allowedIpsDesc": "Which IPs will be routed through the VPN",
+ "serverAllowedIpsDesc": "Which IPs the server will route to the client",
+ "mtuDesc": "Sets the maximum transmission unit (packet size) for the VPN tunnel",
+ "persistentKeepaliveDesc": "Sets the interval (in seconds) for keep-alive packets. 0 disables it"
+ },
+ "dialog": {
+ "change": "Change",
+ "cancel": "Cancel",
+ "create": "Create"
+ },
+ "toast": {
+ "success": "Success",
+ "saved": "Saved",
+ "error": "Error",
+ "errored": "Failed to save"
+ },
"form": {
"actions": "Actions",
"save": "Save",
- "revert": "Revert"
+ "revert": "Revert",
+ "sectionGeneral": "General",
+ "sectionAdvanced": "Advanced"
+ },
+ "admin": {
+ "general": {
+ "sessionTimeout": "Session Timeout",
+ "sessionTimeoutDesc": "Session duration for Remember Me (seconds)",
+ "metrics": "Metrics",
+ "metricsPassword": "Password",
+ "metricsPasswordDesc": "Bearer Password for the metrics endpoint (argon2 hash)",
+ "json": "JSON",
+ "jsonDesc": "Route for metrics in JSON format",
+ "prometheus": "Prometheus",
+ "prometheusDesc": "Route for Prometheus metrics"
+ },
+ "config": {
+ "connection": "Connection",
+ "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)",
+ "dns": "DNS",
+ "dnsDesc": "DNS server clients will use (invalidates config)",
+ "mtuDesc": "MTU clients will use (invalidates config)",
+ "persistentKeepaliveDesc": "Interval in seconds to send keepalives to the server. 0 = disabled (invalidates config)"
+ },
+ "interface": {
+ "cidrSuccess": "Changed CIDR",
+ "cidrError": "Failed to change CIDR",
+ "device": "Device",
+ "deviceDesc": "Ethernet device the wireguard traffic should be forwarded through",
+ "mtuDesc": "MTU WireGuard will use",
+ "portDesc": "UDP Port WireGuard will listen on (could invalidate config)",
+ "changeCidr": "Change CIDR"
+ }
},
- "password": "Password",
"zod": {
"generic": {
"required": "{0} is required",
@@ -112,8 +176,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 }
);
diff --git a/src/server/utils/Database.ts b/src/server/utils/Database.ts
index 297eba2c..76ae6797 100644
--- a/src/server/utils/Database.ts
+++ b/src/server/utils/Database.ts
@@ -21,5 +21,4 @@ connect().then((db) => {
WireGuard.Startup();
});
-// TODO: check if old config exists and tell user about migration path
export default provider;
diff --git a/src/server/utils/WireGuard.ts b/src/server/utils/WireGuard.ts
index 44928695..488ad7aa 100644
--- a/src/server/utils/WireGuard.ts
+++ b/src/server/utils/WireGuard.ts
@@ -180,12 +180,11 @@ class WireGuard {
// TODO: handle as worker_thread
async startCronJob() {
- await this.cronJob().catch((err) => {
- WG_DEBUG('Running Cron Job failed.');
- console.error(err);
- });
- setTimeout(() => {
- this.startCronJob();
+ setIntervalImmediately(() => {
+ this.cronJob().catch((err) => {
+ WG_DEBUG('Running Cron Job failed.');
+ console.error(err);
+ });
}, 60 * 1000);
}
@@ -208,7 +207,6 @@ class WireGuard {
await Database.clients.toggle(client.id, false);
}
}
-
// One Time Link Feature
for (const client of clients) {
if (
@@ -216,7 +214,7 @@ class WireGuard {
new Date() > new Date(client.oneTimeLink.expiresAt)
) {
WG_DEBUG(`Client ${client.id} One Time Link expired.`);
- await Database.oneTimeLinks.delete(client.id);
+ await Database.oneTimeLinks.delete(client.oneTimeLink.id);
}
}
diff --git a/src/server/utils/types.ts b/src/server/utils/types.ts
index 6fae6536..ca9db348 100644
--- a/src/server/utils/types.ts
+++ b/src/server/utils/types.ts
@@ -19,8 +19,6 @@ export const safeStringRefine = z
{ message: t('zod.stringMalformed') }
);
-// TODO: create custom getValidatedRouterParams and readValidatedBody wrapper
-
export const EnabledSchema = z.boolean({ message: t('zod.enabled') });
export const MtuSchema = z
diff --git a/src/shared/utils/time.ts b/src/shared/utils/time.ts
index 3d4085e4..a7ac27c1 100644
--- a/src/shared/utils/time.ts
+++ b/src/shared/utils/time.ts
@@ -8,3 +8,8 @@ export function isPeerConnected(client: { latestHandshakeAt: Date | null }) {
// connected if last handshake was less than 10 minutes ago
return lastHandshakeMs < 1000 * 60 * 10;
}
+
+export function setIntervalImmediately(func: () => void, interval: number) {
+ func();
+ return setInterval(func, interval);
+}