diff --git a/.github/workflows/npm-update-bot.yml b/.github/workflows/npm-update-bot.yml index 8c377680..03af94b4 100644 --- a/.github/workflows/npm-update-bot.yml +++ b/.github/workflows/npm-update-bot.yml @@ -34,6 +34,7 @@ jobs: cd src ncu -u npm update + npm run buildcss git config --global user.name 'NPM Update Bot' git config --global user.email 'npmupbot@users.noreply.github.com' git add . diff --git a/.gitignore b/.gitignore index 85dbdfa3..567e6c6f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /wg0.conf /wg0.json .DS_Store +*.swp diff --git a/README.md b/README.md index d237d191..b6c7997b 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ To automatically install & run wg-easy, simply run:
 $ docker run -d \
   --name=wg-easy \
+  -e LANG=de
   -e WG_HOST=🚨YOUR_SERVER_IP \
   -e PASSWORD=🚨YOUR_ADMIN_PASSWORD \
   -v ~/.wg-easy:/etc/wireguard \
diff --git a/docker-compose.yml b/docker-compose.yml
index d9ddcf7c..d59aa9cf 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,6 +5,9 @@ volumes:
 services:
   wg-easy:
     environment:
+      # Change Language:
+      # (Supports: en, ru, tr, no, pl, fr, de)
+      - LANG=de
       # ⚠️ Required:
       # Change this to your host's public address
       - WG_HOST=raspberrypi.local
diff --git a/src/config.js b/src/config.js
index 3281523f..09fbcf3e 100644
--- a/src/config.js
+++ b/src/config.js
@@ -28,3 +28,4 @@ iptables -A FORWARD -o wg0 -j ACCEPT;
 
 module.exports.WG_PRE_DOWN = process.env.WG_PRE_DOWN || '';
 module.exports.WG_POST_DOWN = process.env.WG_POST_DOWN || '';
+module.exports.LANG = process.env.LANG || 'en';
diff --git a/src/lib/Server.js b/src/lib/Server.js
index 8f46de8f..2d88b642 100644
--- a/src/lib/Server.js
+++ b/src/lib/Server.js
@@ -16,7 +16,11 @@ const ServerError = require('./ServerError');
 const WireGuard = require('../services/WireGuard');
 
 const {
-  PORT, WEBUI_HOST, RELEASE, PASSWORD,
+  PORT,
+  WEBUI_HOST,
+  RELEASE,
+  PASSWORD,
+  LANG,
 } = require('../config');
 
 module.exports = class Server {
@@ -48,6 +52,10 @@ module.exports = class Server {
         ctx.body = RELEASE;
       })
 
+      .get('/api/lang', (Util.promisify(async () => {
+        return LANG;
+      })))
+
     // Authentication
       .get('/api/session', async (ctx) => {
         const requiresPassword = !!process.env.PASSWORD;
diff --git a/src/package-lock.json b/src/package-lock.json
index 4dcb0e4f..1c119335 100644
--- a/src/package-lock.json
+++ b/src/package-lock.json
@@ -422,9 +422,9 @@
       "dev": true
     },
     "node_modules/@jridgewell/trace-mapping": {
-      "version": "0.3.20",
-      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
-      "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
+      "version": "0.3.22",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
+      "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
       "dev": true,
       "dependencies": {
         "@jridgewell/resolve-uri": "^3.1.0",
@@ -515,16 +515,16 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "6.18.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.1.tgz",
-      "integrity": "sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==",
+      "version": "6.19.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.0.tgz",
+      "integrity": "sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.5.1",
-        "@typescript-eslint/scope-manager": "6.18.1",
-        "@typescript-eslint/type-utils": "6.18.1",
-        "@typescript-eslint/utils": "6.18.1",
-        "@typescript-eslint/visitor-keys": "6.18.1",
+        "@typescript-eslint/scope-manager": "6.19.0",
+        "@typescript-eslint/type-utils": "6.19.0",
+        "@typescript-eslint/utils": "6.19.0",
+        "@typescript-eslint/visitor-keys": "6.19.0",
         "debug": "^4.3.4",
         "graphemer": "^1.4.0",
         "ignore": "^5.2.4",
@@ -550,15 +550,15 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "6.18.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.1.tgz",
-      "integrity": "sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==",
+      "version": "6.19.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.19.0.tgz",
+      "integrity": "sha512-1DyBLG5SH7PYCd00QlroiW60YJ4rWMuUGa/JBV0iZuqi4l4IK3twKPq5ZkEebmGqRjXWVgsUzfd3+nZveewgow==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "6.18.1",
-        "@typescript-eslint/types": "6.18.1",
-        "@typescript-eslint/typescript-estree": "6.18.1",
-        "@typescript-eslint/visitor-keys": "6.18.1",
+        "@typescript-eslint/scope-manager": "6.19.0",
+        "@typescript-eslint/types": "6.19.0",
+        "@typescript-eslint/typescript-estree": "6.19.0",
+        "@typescript-eslint/visitor-keys": "6.19.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -578,13 +578,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "6.18.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz",
-      "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==",
+      "version": "6.19.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz",
+      "integrity": "sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "6.18.1",
-        "@typescript-eslint/visitor-keys": "6.18.1"
+        "@typescript-eslint/types": "6.19.0",
+        "@typescript-eslint/visitor-keys": "6.19.0"
       },
       "engines": {
         "node": "^16.0.0 || >=18.0.0"
@@ -595,13 +595,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "6.18.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.1.tgz",
-      "integrity": "sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==",
+      "version": "6.19.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.0.tgz",
+      "integrity": "sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "6.18.1",
-        "@typescript-eslint/utils": "6.18.1",
+        "@typescript-eslint/typescript-estree": "6.19.0",
+        "@typescript-eslint/utils": "6.19.0",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.0.1"
       },
@@ -622,9 +622,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "6.18.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz",
-      "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==",
+      "version": "6.19.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz",
+      "integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==",
       "dev": true,
       "engines": {
         "node": "^16.0.0 || >=18.0.0"
@@ -635,13 +635,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "6.18.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz",
-      "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==",
+      "version": "6.19.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz",
+      "integrity": "sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "6.18.1",
-        "@typescript-eslint/visitor-keys": "6.18.1",
+        "@typescript-eslint/types": "6.19.0",
+        "@typescript-eslint/visitor-keys": "6.19.0",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -663,17 +663,17 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "6.18.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz",
-      "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==",
+      "version": "6.19.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.0.tgz",
+      "integrity": "sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
         "@types/json-schema": "^7.0.12",
         "@types/semver": "^7.5.0",
-        "@typescript-eslint/scope-manager": "6.18.1",
-        "@typescript-eslint/types": "6.18.1",
-        "@typescript-eslint/typescript-estree": "6.18.1",
+        "@typescript-eslint/scope-manager": "6.19.0",
+        "@typescript-eslint/types": "6.19.0",
+        "@typescript-eslint/typescript-estree": "6.19.0",
         "semver": "^7.5.4"
       },
       "engines": {
@@ -688,12 +688,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "6.18.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz",
-      "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==",
+      "version": "6.19.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz",
+      "integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "6.18.1",
+        "@typescript-eslint/types": "6.19.0",
         "eslint-visitor-keys": "^3.4.1"
       },
       "engines": {
@@ -4291,13 +4291,13 @@
       }
     },
     "node_modules/safe-array-concat": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz",
-      "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==",
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz",
+      "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==",
       "dev": true,
       "dependencies": {
-        "call-bind": "^1.0.2",
-        "get-intrinsic": "^1.2.1",
+        "call-bind": "^1.0.5",
+        "get-intrinsic": "^1.2.2",
         "has-symbols": "^1.0.3",
         "isarray": "^2.0.5"
       },
@@ -4370,14 +4370,15 @@
       "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
     },
     "node_modules/set-function-length": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
-      "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==",
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz",
+      "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==",
       "dependencies": {
         "define-data-property": "^1.1.1",
-        "get-intrinsic": "^1.2.1",
+        "function-bind": "^1.1.2",
+        "get-intrinsic": "^1.2.2",
         "gopd": "^1.0.1",
-        "has-property-descriptors": "^1.0.0"
+        "has-property-descriptors": "^1.0.1"
       },
       "engines": {
         "node": ">= 0.4"
diff --git a/src/package.json b/src/package.json
index bd9c00cd..306137d9 100644
--- a/src/package.json
+++ b/src/package.json
@@ -8,7 +8,7 @@
     "serve": "DEBUG=Server,WireGuard nodemon server.js",
     "serve-with-password": "PASSWORD=wg npm run serve",
     "lint": "eslint .",
-    "buildcss": "npx tailwindcss -i ./www/src/css/app.css -o ./www/css/app.css --watch"
+    "buildcss": "npx tailwindcss -i ./www/src/css/app.css -o ./www/css/app.css"
   },
   "author": "Emile Nijssen",
   "license": "GPL",
diff --git a/src/www/css/app.css b/src/www/css/app.css
index e9fe0c7c..4f4e21dd 100644
--- a/src/www/css/app.css
+++ b/src/www/css/app.css
@@ -1,5 +1,5 @@
 /*
-! tailwindcss v3.3.2 | MIT License | https://tailwindcss.com
+! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com
 */
 
 /*
@@ -32,9 +32,11 @@
 4. Use the user's configured `sans` font-family by default.
 5. Use the user's configured `sans` font-feature-settings by default.
 6. Use the user's configured `sans` font-variation-settings by default.
+7. Disable tap highlights on iOS
 */
 
-html {
+html,
+:host {
   line-height: 1.5;
   /* 1 */
   -webkit-text-size-adjust: 100%;
@@ -44,12 +46,14 @@ html {
   -o-tab-size: 4;
      tab-size: 4;
   /* 3 */
-  font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
   /* 4 */
   font-feature-settings: normal;
   /* 5 */
   font-variation-settings: normal;
   /* 6 */
+  -webkit-tap-highlight-color: transparent;
+  /* 7 */
 }
 
 /*
@@ -121,8 +125,10 @@ strong {
 }
 
 /*
-1. Use the user's configured `mono` font family by default.
-2. Correct the odd `em` font sizing in all browsers.
+1. Use the user's configured `mono` font-family by default.
+2. Use the user's configured `mono` font-feature-settings by default.
+3. Use the user's configured `mono` font-variation-settings by default.
+4. Correct the odd `em` font sizing in all browsers.
 */
 
 code,
@@ -131,8 +137,12 @@ samp,
 pre {
   font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
   /* 1 */
-  font-size: 1em;
+  font-feature-settings: normal;
   /* 2 */
+  font-variation-settings: normal;
+  /* 3 */
+  font-size: 1em;
+  /* 4 */
 }
 
 /*
@@ -191,6 +201,10 @@ select,
 textarea {
   font-family: inherit;
   /* 1 */
+  font-feature-settings: inherit;
+  /* 1 */
+  font-variation-settings: inherit;
+  /* 1 */
   font-size: 100%;
   /* 1 */
   font-weight: inherit;
@@ -341,6 +355,14 @@ menu {
   padding: 0;
 }
 
+/*
+Reset default styling for dialogs.
+*/
+
+dialog {
+  padding: 0;
+}
+
 /*
 Prevent resizing textareas horizontally by default.
 */
@@ -746,6 +768,10 @@ video {
   height: 3rem;
 }
 
+.h-14 {
+  height: 3.5rem;
+}
+
 .h-2 {
   height: 0.5rem;
 }
@@ -758,12 +784,12 @@ video {
   height: 0.75rem;
 }
 
-.h-4 {
-  height: 1rem;
+.h-32 {
+  height: 8rem;
 }
 
-.h-5 {
-  height: 1.25rem;
+.h-4 {
+  height: 1rem;
 }
 
 .h-6 {
@@ -907,6 +933,12 @@ video {
   overflow-y: auto;
 }
 
+.truncate {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
 .rounded {
   border-radius: 0.25rem;
 }
@@ -1027,10 +1059,6 @@ video {
   padding: 0.5rem;
 }
 
-.p-2\.5 {
-  padding: 0.625rem;
-}
-
 .p-3 {
   padding: 0.75rem;
 }
@@ -1067,11 +1095,6 @@ video {
   padding-right: 1.25rem;
 }
 
-.py-1 {
-  padding-top: 0.25rem;
-  padding-bottom: 0.25rem;
-}
-
 .py-2 {
   padding-top: 0.5rem;
   padding-bottom: 0.5rem;
@@ -1398,200 +1421,6 @@ video {
   opacity: 1;
 }
 
-:is(.dark .dark\:border-green-600) {
-  --tw-border-opacity: 1;
-  border-color: rgb(22 163 74 / var(--tw-border-opacity));
-}
-
-:is(.dark .dark\:border-neutral-500) {
-  --tw-border-opacity: 1;
-  border-color: rgb(115 115 115 / var(--tw-border-opacity));
-}
-
-:is(.dark .dark\:border-neutral-600) {
-  --tw-border-opacity: 1;
-  border-color: rgb(82 82 82 / var(--tw-border-opacity));
-}
-
-:is(.dark .dark\:bg-black) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(0 0 0 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:bg-green-100) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(220 252 231 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:bg-green-600) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(22 163 74 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:bg-neutral-200) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(229 229 229 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:bg-neutral-400) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(163 163 163 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:bg-neutral-500) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(115 115 115 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:bg-neutral-600) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(82 82 82 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:bg-neutral-700) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(64 64 64 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:bg-neutral-800) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(38 38 38 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:bg-red-100) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(254 226 226 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:text-green-600) {
-  --tw-text-opacity: 1;
-  color: rgb(22 163 74 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:text-neutral-200) {
-  --tw-text-opacity: 1;
-  color: rgb(229 229 229 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:text-neutral-300) {
-  --tw-text-opacity: 1;
-  color: rgb(212 212 212 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:text-neutral-400) {
-  --tw-text-opacity: 1;
-  color: rgb(163 163 163 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:text-neutral-50) {
-  --tw-text-opacity: 1;
-  color: rgb(250 250 250 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:text-neutral-500) {
-  --tw-text-opacity: 1;
-  color: rgb(115 115 115 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:text-neutral-600) {
-  --tw-text-opacity: 1;
-  color: rgb(82 82 82 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:text-red-300) {
-  --tw-text-opacity: 1;
-  color: rgb(252 165 165 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:text-red-600) {
-  --tw-text-opacity: 1;
-  color: rgb(220 38 38 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:opacity-50) {
-  opacity: 0.5;
-}
-
-:is(.dark .dark\:placeholder\:text-neutral-400)::-moz-placeholder {
-  --tw-text-opacity: 1;
-  color: rgb(163 163 163 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:placeholder\:text-neutral-400)::placeholder {
-  --tw-text-opacity: 1;
-  color: rgb(163 163 163 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:placeholder\:text-neutral-500)::-moz-placeholder {
-  --tw-text-opacity: 1;
-  color: rgb(115 115 115 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:placeholder\:text-neutral-500)::placeholder {
-  --tw-text-opacity: 1;
-  color: rgb(115 115 115 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:hover\:border-green-600:hover) {
-  --tw-border-opacity: 1;
-  border-color: rgb(22 163 74 / var(--tw-border-opacity));
-}
-
-:is(.dark .dark\:hover\:border-neutral-600:hover) {
-  --tw-border-opacity: 1;
-  border-color: rgb(82 82 82 / var(--tw-border-opacity));
-}
-
-:is(.dark .dark\:hover\:bg-green-600:hover) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(22 163 74 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:hover\:bg-neutral-500:hover) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(115 115 115 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:hover\:bg-neutral-600:hover) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(82 82 82 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:hover\:bg-red-200:hover) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(254 202 202 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:hover\:bg-red-800:hover) {
-  --tw-bg-opacity: 1;
-  background-color: rgb(153 27 27 / var(--tw-bg-opacity));
-}
-
-:is(.dark .dark\:hover\:text-green-100:hover) {
-  --tw-text-opacity: 1;
-  color: rgb(220 252 231 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:hover\:text-neutral-700:hover) {
-  --tw-text-opacity: 1;
-  color: rgb(64 64 64 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:hover\:text-white:hover) {
-  --tw-text-opacity: 1;
-  color: rgb(255 255 255 / var(--tw-text-opacity));
-}
-
-:is(.dark .dark\:focus\:border-neutral-500:focus) {
-  --tw-border-opacity: 1;
-  border-color: rgb(115 115 115 / var(--tw-border-opacity));
-}
-
-:is(.dark .focus\:dark\:border-neutral-500):focus {
-  --tw-border-opacity: 1;
-  border-color: rgb(115 115 115 / var(--tw-border-opacity));
-}
-
 @media (min-width: 640px) {
   .sm\:mx-0 {
     margin-left: 0px;
@@ -1716,3 +1545,209 @@ video {
     padding-bottom: 0px;
   }
 }
+
+@media (prefers-color-scheme: dark) {
+  .dark\:border-neutral-500 {
+    --tw-border-opacity: 1;
+    border-color: rgb(115 115 115 / var(--tw-border-opacity));
+  }
+
+  .dark\:border-neutral-600 {
+    --tw-border-opacity: 1;
+    border-color: rgb(82 82 82 / var(--tw-border-opacity));
+  }
+
+  .dark\:border-neutral-800 {
+    --tw-border-opacity: 1;
+    border-color: rgb(38 38 38 / var(--tw-border-opacity));
+  }
+
+  .dark\:border-red-600 {
+    --tw-border-opacity: 1;
+    border-color: rgb(220 38 38 / var(--tw-border-opacity));
+  }
+
+  .dark\:bg-black {
+    --tw-bg-opacity: 1;
+    background-color: rgb(0 0 0 / var(--tw-bg-opacity));
+  }
+
+  .dark\:bg-neutral-400 {
+    --tw-bg-opacity: 1;
+    background-color: rgb(163 163 163 / var(--tw-bg-opacity));
+  }
+
+  .dark\:bg-neutral-500 {
+    --tw-bg-opacity: 1;
+    background-color: rgb(115 115 115 / var(--tw-bg-opacity));
+  }
+
+  .dark\:bg-neutral-600 {
+    --tw-bg-opacity: 1;
+    background-color: rgb(82 82 82 / var(--tw-bg-opacity));
+  }
+
+  .dark\:bg-neutral-700 {
+    --tw-bg-opacity: 1;
+    background-color: rgb(64 64 64 / var(--tw-bg-opacity));
+  }
+
+  .dark\:bg-neutral-800 {
+    --tw-bg-opacity: 1;
+    background-color: rgb(38 38 38 / var(--tw-bg-opacity));
+  }
+
+  .dark\:bg-red-100 {
+    --tw-bg-opacity: 1;
+    background-color: rgb(254 226 226 / var(--tw-bg-opacity));
+  }
+
+  .dark\:bg-red-600 {
+    --tw-bg-opacity: 1;
+    background-color: rgb(220 38 38 / var(--tw-bg-opacity));
+  }
+
+  .dark\:bg-red-800 {
+    --tw-bg-opacity: 1;
+    background-color: rgb(153 27 27 / var(--tw-bg-opacity));
+  }
+
+  .dark\:text-gray-500 {
+    --tw-text-opacity: 1;
+    color: rgb(107 114 128 / var(--tw-text-opacity));
+  }
+
+  .dark\:text-neutral-200 {
+    --tw-text-opacity: 1;
+    color: rgb(229 229 229 / var(--tw-text-opacity));
+  }
+
+  .dark\:text-neutral-300 {
+    --tw-text-opacity: 1;
+    color: rgb(212 212 212 / var(--tw-text-opacity));
+  }
+
+  .dark\:text-neutral-400 {
+    --tw-text-opacity: 1;
+    color: rgb(163 163 163 / var(--tw-text-opacity));
+  }
+
+  .dark\:text-neutral-50 {
+    --tw-text-opacity: 1;
+    color: rgb(250 250 250 / var(--tw-text-opacity));
+  }
+
+  .dark\:text-neutral-500 {
+    --tw-text-opacity: 1;
+    color: rgb(115 115 115 / var(--tw-text-opacity));
+  }
+
+  .dark\:text-neutral-600 {
+    --tw-text-opacity: 1;
+    color: rgb(82 82 82 / var(--tw-text-opacity));
+  }
+
+  .dark\:text-red-300 {
+    --tw-text-opacity: 1;
+    color: rgb(252 165 165 / var(--tw-text-opacity));
+  }
+
+  .dark\:text-red-600 {
+    --tw-text-opacity: 1;
+    color: rgb(220 38 38 / var(--tw-text-opacity));
+  }
+
+  .dark\:text-white {
+    --tw-text-opacity: 1;
+    color: rgb(255 255 255 / var(--tw-text-opacity));
+  }
+
+  .dark\:opacity-50 {
+    opacity: 0.5;
+  }
+
+  .dark\:placeholder\:text-neutral-400::-moz-placeholder {
+    --tw-text-opacity: 1;
+    color: rgb(163 163 163 / var(--tw-text-opacity));
+  }
+
+  .dark\:placeholder\:text-neutral-400::placeholder {
+    --tw-text-opacity: 1;
+    color: rgb(163 163 163 / var(--tw-text-opacity));
+  }
+
+  .dark\:placeholder\:text-neutral-500::-moz-placeholder {
+    --tw-text-opacity: 1;
+    color: rgb(115 115 115 / var(--tw-text-opacity));
+  }
+
+  .dark\:placeholder\:text-neutral-500::placeholder {
+    --tw-text-opacity: 1;
+    color: rgb(115 115 115 / var(--tw-text-opacity));
+  }
+
+  .dark\:hover\:border-neutral-600:hover {
+    --tw-border-opacity: 1;
+    border-color: rgb(82 82 82 / var(--tw-border-opacity));
+  }
+
+  .dark\:hover\:border-red-600:hover {
+    --tw-border-opacity: 1;
+    border-color: rgb(220 38 38 / var(--tw-border-opacity));
+  }
+
+  .dark\:hover\:bg-neutral-500:hover {
+    --tw-bg-opacity: 1;
+    background-color: rgb(115 115 115 / var(--tw-bg-opacity));
+  }
+
+  .dark\:hover\:bg-neutral-600:hover {
+    --tw-bg-opacity: 1;
+    background-color: rgb(82 82 82 / var(--tw-bg-opacity));
+  }
+
+  .dark\:hover\:bg-red-600:hover {
+    --tw-bg-opacity: 1;
+    background-color: rgb(220 38 38 / var(--tw-bg-opacity));
+  }
+
+  .dark\:hover\:bg-red-700:hover {
+    --tw-bg-opacity: 1;
+    background-color: rgb(185 28 28 / var(--tw-bg-opacity));
+  }
+
+  .dark\:hover\:bg-red-800:hover {
+    --tw-bg-opacity: 1;
+    background-color: rgb(153 27 27 / var(--tw-bg-opacity));
+  }
+
+  .dark\:hover\:text-neutral-700:hover {
+    --tw-text-opacity: 1;
+    color: rgb(64 64 64 / var(--tw-text-opacity));
+  }
+
+  .dark\:hover\:text-red-100:hover {
+    --tw-text-opacity: 1;
+    color: rgb(254 226 226 / var(--tw-text-opacity));
+  }
+
+  .dark\:hover\:text-white:hover {
+    --tw-text-opacity: 1;
+    color: rgb(255 255 255 / var(--tw-text-opacity));
+  }
+
+  .dark\:focus\:border-neutral-500:focus {
+    --tw-border-opacity: 1;
+    border-color: rgb(115 115 115 / var(--tw-border-opacity));
+  }
+
+  .dark\:focus\:border-red-800:focus {
+    --tw-border-opacity: 1;
+    border-color: rgb(153 27 27 / var(--tw-border-opacity));
+  }
+
+  .focus\:dark\:border-neutral-500:focus {
+    --tw-border-opacity: 1;
+    border-color: rgb(115 115 115 / var(--tw-border-opacity));
+  }
+}
diff --git a/src/www/index.html b/src/www/index.html
index 0d0b52d9..149aa69d 100644
--- a/src/www/index.html
+++ b/src/www/index.html
@@ -3,13 +3,7 @@
 
 
   WireGuard
-  
-  
-  
+  
   
   
   
@@ -32,7 +26,8 @@
         
-          Logout
+          {{$t("logout")}}
+
           
             
           
-

There is an update available!

+

{{$t("updateAvailable")}}

{{latestRelease.changelog}}

- Update → + {{$t("update")}} →
@@ -64,7 +59,7 @@
-

Clients

+

{{$t("clients")}}

@@ -94,7 +89,6 @@ style="transform: scaleY(-1);">
-
@@ -118,7 +112,7 @@
+ :title="$t('createdOn') + dateTime(new Date(client.createdAt))"> - + · @@ -183,7 +177,7 @@ - + · @@ -196,7 +190,7 @@ + :title="$t('lastSeen') + dateTime(new Date(client.latestHandshakeAt))"> · {{new Date(client.latestHandshakeAt) | timeago}}
@@ -207,19 +201,22 @@
-
-
+
+

@@ -340,13 +338,13 @@

+ type="text" v-model.trim="clientCreateName" :placeholder="$t('name')" />

@@ -355,15 +353,15 @@
@@ -415,12 +413,11 @@

- Are you sure you want to delete {{clientDelete.name}}? - This action cannot be undone. + {{$t("deleteDialog1")}} {{clientDelete.name}}? {{$t("deleteDialog2")}}

@@ -429,11 +426,11 @@
@@ -454,7 +451,7 @@ -