forked from mirrors/amnezia-wg-easy
		
	Merge branch 'feat-no-privateKey' into feat/clients-without-privatekey
This commit is contained in:
		
						commit
						ce1af6d691
					
				
					 19 changed files with 620 additions and 211 deletions
				
			
		
							
								
								
									
										17
									
								
								Dockerfile
									
										
									
									
									
								
							
							
						
						
									
										17
									
								
								Dockerfile
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -6,7 +6,8 @@ FROM docker.io/library/node:18-alpine AS build_node_modules
 | 
			
		|||
# Copy Web UI
 | 
			
		||||
COPY src/ /app/
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
RUN npm ci --omit=dev
 | 
			
		||||
RUN npm ci --omit=dev &&\
 | 
			
		||||
    mv node_modules /node_modules
 | 
			
		||||
 | 
			
		||||
# Copy build result to a new image.
 | 
			
		||||
# This saves a lot of disk space.
 | 
			
		||||
| 
						 | 
				
			
			@ -20,13 +21,15 @@ COPY --from=build_node_modules /app /app
 | 
			
		|||
# Also, some node_modules might be native, and
 | 
			
		||||
# the architecture & OS of your development machine might differ
 | 
			
		||||
# than what runs inside of docker.
 | 
			
		||||
RUN mv /app/node_modules /node_modules
 | 
			
		||||
COPY --from=build_node_modules /node_modules /node_modules
 | 
			
		||||
 | 
			
		||||
# Enable this to run `npm run serve`
 | 
			
		||||
RUN npm i -g nodemon
 | 
			
		||||
 | 
			
		||||
# Workaround CVE-2023-42282
 | 
			
		||||
RUN npm uninstall -g ip
 | 
			
		||||
RUN \
 | 
			
		||||
    # Enable this to run `npm run serve`
 | 
			
		||||
    npm i -g nodemon &&\
 | 
			
		||||
    # Workaround CVE-2023-42282
 | 
			
		||||
    npm uninstall -g ip &&\
 | 
			
		||||
    # Delete unnecessary files 
 | 
			
		||||
    npm cache clean --force && rm -rf ~/.npm
 | 
			
		||||
 | 
			
		||||
# Install Linux packages
 | 
			
		||||
RUN apk add --no-cache \
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -97,7 +97,8 @@ These options can be configured by setting environment variables using `-e KEY="
 | 
			
		|||
| `WG_POST_UP` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L20) for the default value. |
 | 
			
		||||
| `WG_PRE_DOWN` | `...` | - | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L27) for the default value. |
 | 
			
		||||
| `WG_POST_DOWN` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L28) for the default value. |
 | 
			
		||||
| `LANG` | `en` | `de` | Web UI language (Supports: en, ru, tr, no, pl, fr, de, ca, es, vi). |
 | 
			
		||||
| `LANG` | `en` | `de` | Web UI language (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th). |
 | 
			
		||||
| `UI_TRAFFIC_STATS` | `false` | `true` | Enable detailed RX / TX client stats in Web UI |
 | 
			
		||||
 | 
			
		||||
> If you change `WG_PORT`, make sure to also change the exposed port.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ services:
 | 
			
		|||
  wg-easy:
 | 
			
		||||
    environment:
 | 
			
		||||
      # Change Language:
 | 
			
		||||
      # (Supports: en, ru, tr, no, pl, fr, de, ca, es)
 | 
			
		||||
      # (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th)
 | 
			
		||||
      - LANG=de
 | 
			
		||||
      # ⚠️ Required:
 | 
			
		||||
      # Change this to your host's public address
 | 
			
		||||
| 
						 | 
				
			
			@ -24,6 +24,7 @@ services:
 | 
			
		|||
      # - WG_POST_UP=echo "Post Up" > /etc/wireguard/post-up.txt
 | 
			
		||||
      # - WG_PRE_DOWN=echo "Pre Down" > /etc/wireguard/pre-down.txt
 | 
			
		||||
      # - WG_POST_DOWN=echo "Post Down" > /etc/wireguard/post-down.txt
 | 
			
		||||
      # - UI_TRAFFIC_STATS=true 
 | 
			
		||||
 | 
			
		||||
    image: ghcr.io/wg-easy/wg-easy
 | 
			
		||||
    container_name: wg-easy
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,5 +27,11 @@ iptables -A FORWARD -o wg0 -j ACCEPT;
 | 
			
		|||
`.split('\n').join(' ');
 | 
			
		||||
 | 
			
		||||
module.exports.WG_PRE_DOWN = process.env.WG_PRE_DOWN || '';
 | 
			
		||||
module.exports.WG_POST_DOWN = process.env.WG_POST_DOWN || '';
 | 
			
		||||
module.exports.WG_POST_DOWN = process.env.WG_POST_DOWN || `
 | 
			
		||||
iptables -t nat -D POSTROUTING -s ${module.exports.WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ${module.exports.WG_DEVICE} -j MASQUERADE;
 | 
			
		||||
iptables -D INPUT -p udp -m udp --dport 51820 -j ACCEPT;
 | 
			
		||||
iptables -D FORWARD -i wg0 -j ACCEPT;
 | 
			
		||||
iptables -D FORWARD -o wg0 -j ACCEPT;
 | 
			
		||||
`.split('\n').join(' ');
 | 
			
		||||
module.exports.LANG = process.env.LANG || 'en';
 | 
			
		||||
module.exports.UI_TRAFFIC_STATS = process.env.UI_TRAFFIC_STATS || 'false';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,7 @@ const {
 | 
			
		|||
  RELEASE,
 | 
			
		||||
  PASSWORD,
 | 
			
		||||
  LANG,
 | 
			
		||||
  UI_TRAFFIC_STATS,
 | 
			
		||||
} = require('../config');
 | 
			
		||||
 | 
			
		||||
module.exports = class Server {
 | 
			
		||||
| 
						 | 
				
			
			@ -44,6 +45,9 @@ module.exports = class Server {
 | 
			
		|||
      .get('/api/lang', (Util.promisify(async () => {
 | 
			
		||||
        return LANG;
 | 
			
		||||
      })))
 | 
			
		||||
      .get('/api/ui-traffic-stats', (Util.promisify(async () => {
 | 
			
		||||
        return UI_TRAFFIC_STATS === 'true';
 | 
			
		||||
      })))
 | 
			
		||||
 | 
			
		||||
    // Authentication
 | 
			
		||||
      .get('/api/session', Util.promisify(async (req) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -319,4 +319,9 @@ Endpoint = ${WG_HOST}:${WG_PORT}`;
 | 
			
		|||
    await this.saveConfig();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Shutdown wireguard
 | 
			
		||||
  async Shutdown() {
 | 
			
		||||
    await Util.exec('wg-quick down wg0').catch(() => { });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										218
									
								
								src/package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										218
									
								
								src/package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -11,7 +11,7 @@
 | 
			
		|||
      "dependencies": {
 | 
			
		||||
        "bcryptjs": "^2.4.3",
 | 
			
		||||
        "debug": "^4.3.4",
 | 
			
		||||
        "express": "^4.18.2",
 | 
			
		||||
        "express": "^4.18.3",
 | 
			
		||||
        "express-session": "^1.18.0",
 | 
			
		||||
        "qrcode": "^1.5.3",
 | 
			
		||||
        "uuid": "^9.0.1"
 | 
			
		||||
| 
						 | 
				
			
			@ -381,14 +381,14 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@jridgewell/gen-mapping": {
 | 
			
		||||
      "version": "0.3.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
 | 
			
		||||
      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
 | 
			
		||||
      "version": "0.3.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
 | 
			
		||||
      "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@jridgewell/set-array": "^1.0.1",
 | 
			
		||||
        "@jridgewell/set-array": "^1.2.1",
 | 
			
		||||
        "@jridgewell/sourcemap-codec": "^1.4.10",
 | 
			
		||||
        "@jridgewell/trace-mapping": "^0.3.9"
 | 
			
		||||
        "@jridgewell/trace-mapping": "^0.3.24"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=6.0.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -404,9 +404,9 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@jridgewell/set-array": {
 | 
			
		||||
      "version": "1.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
 | 
			
		||||
      "version": "1.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=6.0.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -419,9 +419,9 @@
 | 
			
		|||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@jridgewell/trace-mapping": {
 | 
			
		||||
      "version": "0.3.22",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
 | 
			
		||||
      "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
 | 
			
		||||
      "version": "0.3.24",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.24.tgz",
 | 
			
		||||
      "integrity": "sha512-+VaWXDa6+l6MhflBvVXjIEAzb59nQ2JUK3bwRp2zRpPtU+8TFRy9Gg/5oIcNlkEL5PGlBFGfemUVvIgLnTzq7Q==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@jridgewell/resolve-uri": "^3.1.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -486,9 +486,9 @@
 | 
			
		|||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/semver": {
 | 
			
		||||
      "version": "7.5.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz",
 | 
			
		||||
      "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==",
 | 
			
		||||
      "version": "7.5.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
 | 
			
		||||
      "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@typescript-eslint/eslint-plugin": {
 | 
			
		||||
| 
						 | 
				
			
			@ -956,10 +956,13 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/available-typed-arrays": {
 | 
			
		||||
      "version": "1.0.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz",
 | 
			
		||||
      "integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==",
 | 
			
		||||
      "version": "1.0.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
 | 
			
		||||
      "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "possible-typed-array-names": "^1.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -988,12 +991,12 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/body-parser": {
 | 
			
		||||
      "version": "1.20.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
 | 
			
		||||
      "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
 | 
			
		||||
      "version": "1.20.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
 | 
			
		||||
      "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "bytes": "3.1.2",
 | 
			
		||||
        "content-type": "~1.0.4",
 | 
			
		||||
        "content-type": "~1.0.5",
 | 
			
		||||
        "debug": "2.6.9",
 | 
			
		||||
        "depd": "2.0.0",
 | 
			
		||||
        "destroy": "1.2.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -1001,7 +1004,7 @@
 | 
			
		|||
        "iconv-lite": "0.4.24",
 | 
			
		||||
        "on-finished": "2.4.1",
 | 
			
		||||
        "qs": "6.11.0",
 | 
			
		||||
        "raw-body": "2.5.1",
 | 
			
		||||
        "raw-body": "2.5.2",
 | 
			
		||||
        "type-is": "~1.6.18",
 | 
			
		||||
        "unpipe": "1.0.0"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -1422,18 +1425,18 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/es-abstract": {
 | 
			
		||||
      "version": "1.22.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.4.tgz",
 | 
			
		||||
      "integrity": "sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==",
 | 
			
		||||
      "version": "1.22.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz",
 | 
			
		||||
      "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "array-buffer-byte-length": "^1.0.1",
 | 
			
		||||
        "arraybuffer.prototype.slice": "^1.0.3",
 | 
			
		||||
        "available-typed-arrays": "^1.0.6",
 | 
			
		||||
        "available-typed-arrays": "^1.0.7",
 | 
			
		||||
        "call-bind": "^1.0.7",
 | 
			
		||||
        "es-define-property": "^1.0.0",
 | 
			
		||||
        "es-errors": "^1.3.0",
 | 
			
		||||
        "es-set-tostringtag": "^2.0.2",
 | 
			
		||||
        "es-set-tostringtag": "^2.0.3",
 | 
			
		||||
        "es-to-primitive": "^1.2.1",
 | 
			
		||||
        "function.prototype.name": "^1.1.6",
 | 
			
		||||
        "get-intrinsic": "^1.2.4",
 | 
			
		||||
| 
						 | 
				
			
			@ -1441,15 +1444,15 @@
 | 
			
		|||
        "globalthis": "^1.0.3",
 | 
			
		||||
        "gopd": "^1.0.1",
 | 
			
		||||
        "has-property-descriptors": "^1.0.2",
 | 
			
		||||
        "has-proto": "^1.0.1",
 | 
			
		||||
        "has-proto": "^1.0.3",
 | 
			
		||||
        "has-symbols": "^1.0.3",
 | 
			
		||||
        "hasown": "^2.0.1",
 | 
			
		||||
        "internal-slot": "^1.0.7",
 | 
			
		||||
        "is-array-buffer": "^3.0.4",
 | 
			
		||||
        "is-callable": "^1.2.7",
 | 
			
		||||
        "is-negative-zero": "^2.0.2",
 | 
			
		||||
        "is-negative-zero": "^2.0.3",
 | 
			
		||||
        "is-regex": "^1.1.4",
 | 
			
		||||
        "is-shared-array-buffer": "^1.0.2",
 | 
			
		||||
        "is-shared-array-buffer": "^1.0.3",
 | 
			
		||||
        "is-string": "^1.0.7",
 | 
			
		||||
        "is-typed-array": "^1.1.13",
 | 
			
		||||
        "is-weakref": "^1.0.2",
 | 
			
		||||
| 
						 | 
				
			
			@ -1462,10 +1465,10 @@
 | 
			
		|||
        "string.prototype.trim": "^1.2.8",
 | 
			
		||||
        "string.prototype.trimend": "^1.0.7",
 | 
			
		||||
        "string.prototype.trimstart": "^1.0.7",
 | 
			
		||||
        "typed-array-buffer": "^1.0.1",
 | 
			
		||||
        "typed-array-byte-length": "^1.0.0",
 | 
			
		||||
        "typed-array-byte-offset": "^1.0.0",
 | 
			
		||||
        "typed-array-length": "^1.0.4",
 | 
			
		||||
        "typed-array-buffer": "^1.0.2",
 | 
			
		||||
        "typed-array-byte-length": "^1.0.1",
 | 
			
		||||
        "typed-array-byte-offset": "^1.0.2",
 | 
			
		||||
        "typed-array-length": "^1.0.5",
 | 
			
		||||
        "unbox-primitive": "^1.0.2",
 | 
			
		||||
        "which-typed-array": "^1.1.14"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -1502,14 +1505,14 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/es-set-tostringtag": {
 | 
			
		||||
      "version": "2.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==",
 | 
			
		||||
      "version": "2.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "get-intrinsic": "^1.2.2",
 | 
			
		||||
        "has-tostringtag": "^1.0.0",
 | 
			
		||||
        "hasown": "^2.0.0"
 | 
			
		||||
        "get-intrinsic": "^1.2.4",
 | 
			
		||||
        "has-tostringtag": "^1.0.2",
 | 
			
		||||
        "hasown": "^2.0.1"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
| 
						 | 
				
			
			@ -1678,9 +1681,9 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/eslint-module-utils": {
 | 
			
		||||
      "version": "2.8.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz",
 | 
			
		||||
      "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==",
 | 
			
		||||
      "version": "2.8.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz",
 | 
			
		||||
      "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "debug": "^3.2.7"
 | 
			
		||||
| 
						 | 
				
			
			@ -2104,13 +2107,13 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/express": {
 | 
			
		||||
      "version": "4.18.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
 | 
			
		||||
      "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
 | 
			
		||||
      "version": "4.18.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz",
 | 
			
		||||
      "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "accepts": "~1.3.8",
 | 
			
		||||
        "array-flatten": "1.1.1",
 | 
			
		||||
        "body-parser": "1.20.1",
 | 
			
		||||
        "body-parser": "1.20.2",
 | 
			
		||||
        "content-disposition": "0.5.4",
 | 
			
		||||
        "content-type": "~1.0.4",
 | 
			
		||||
        "cookie": "0.5.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -2342,9 +2345,9 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/flatted": {
 | 
			
		||||
      "version": "3.2.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz",
 | 
			
		||||
      "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
 | 
			
		||||
      "version": "3.3.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
 | 
			
		||||
      "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "peer": true
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -2651,9 +2654,9 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/has-proto": {
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -2923,9 +2926,9 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/is-negative-zero": {
 | 
			
		||||
      "version": "2.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
 | 
			
		||||
      "version": "2.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
| 
						 | 
				
			
			@ -2975,12 +2978,15 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/is-shared-array-buffer": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "call-bind": "^1.0.2"
 | 
			
		||||
        "call-bind": "^1.0.7"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
| 
						 | 
				
			
			@ -3706,6 +3712,15 @@
 | 
			
		|||
        "node": ">=10.13.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/possible-typed-array-names": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/postcss": {
 | 
			
		||||
      "version": "8.4.35",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -3971,9 +3986,9 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/raw-body": {
 | 
			
		||||
      "version": "2.5.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
 | 
			
		||||
      "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
 | 
			
		||||
      "version": "2.5.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
 | 
			
		||||
      "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "bytes": "3.1.2",
 | 
			
		||||
        "http-errors": "2.0.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -4285,14 +4300,15 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/set-function-name": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==",
 | 
			
		||||
      "version": "2.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "define-data-property": "^1.0.1",
 | 
			
		||||
        "define-data-property": "^1.1.4",
 | 
			
		||||
        "es-errors": "^1.3.0",
 | 
			
		||||
        "functions-have-names": "^1.2.3",
 | 
			
		||||
        "has-property-descriptors": "^1.0.0"
 | 
			
		||||
        "has-property-descriptors": "^1.0.2"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
| 
						 | 
				
			
			@ -4325,11 +4341,11 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/side-channel": {
 | 
			
		||||
      "version": "1.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
 | 
			
		||||
      "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
 | 
			
		||||
      "version": "1.0.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
 | 
			
		||||
      "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "call-bind": "^1.0.6",
 | 
			
		||||
        "call-bind": "^1.0.7",
 | 
			
		||||
        "es-errors": "^1.3.0",
 | 
			
		||||
        "get-intrinsic": "^1.2.4",
 | 
			
		||||
        "object-inspect": "^1.13.1"
 | 
			
		||||
| 
						 | 
				
			
			@ -4787,12 +4803,12 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/typed-array-buffer": {
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==",
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "call-bind": "^1.0.6",
 | 
			
		||||
        "call-bind": "^1.0.7",
 | 
			
		||||
        "es-errors": "^1.3.0",
 | 
			
		||||
        "is-typed-array": "^1.1.13"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -4801,15 +4817,16 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/typed-array-byte-length": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==",
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "call-bind": "^1.0.2",
 | 
			
		||||
        "call-bind": "^1.0.7",
 | 
			
		||||
        "for-each": "^0.3.3",
 | 
			
		||||
        "has-proto": "^1.0.1",
 | 
			
		||||
        "is-typed-array": "^1.1.10"
 | 
			
		||||
        "gopd": "^1.0.1",
 | 
			
		||||
        "has-proto": "^1.0.3",
 | 
			
		||||
        "is-typed-array": "^1.1.13"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
| 
						 | 
				
			
			@ -4819,16 +4836,16 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/typed-array-byte-offset": {
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-tcqKMrTRXjqvHN9S3553NPCaGL0VPgFI92lXszmrE8DMhiDPLBYLlvo8Uu4WZAAX/aGqp/T1sbA4ph8EWjDF9Q==",
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "available-typed-arrays": "^1.0.6",
 | 
			
		||||
        "available-typed-arrays": "^1.0.7",
 | 
			
		||||
        "call-bind": "^1.0.7",
 | 
			
		||||
        "for-each": "^0.3.3",
 | 
			
		||||
        "gopd": "^1.0.1",
 | 
			
		||||
        "has-proto": "^1.0.1",
 | 
			
		||||
        "has-proto": "^1.0.3",
 | 
			
		||||
        "is-typed-array": "^1.1.13"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
| 
						 | 
				
			
			@ -4839,14 +4856,20 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/typed-array-length": {
 | 
			
		||||
      "version": "1.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
 | 
			
		||||
      "version": "1.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz",
 | 
			
		||||
      "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "call-bind": "^1.0.2",
 | 
			
		||||
        "call-bind": "^1.0.7",
 | 
			
		||||
        "for-each": "^0.3.3",
 | 
			
		||||
        "is-typed-array": "^1.1.9"
 | 
			
		||||
        "gopd": "^1.0.1",
 | 
			
		||||
        "has-proto": "^1.0.3",
 | 
			
		||||
        "is-typed-array": "^1.1.13",
 | 
			
		||||
        "possible-typed-array-names": "^1.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.4"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
| 
						 | 
				
			
			@ -5055,10 +5078,13 @@
 | 
			
		|||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/yaml": {
 | 
			
		||||
      "version": "2.3.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
 | 
			
		||||
      "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
 | 
			
		||||
      "version": "2.4.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.0.tgz",
 | 
			
		||||
      "integrity": "sha512-j9iR8g+/t0lArF4V6NE/QCfT+CO7iLqrXAHZbJdo+LfjqP1vR8Fg5bSiaq6Q2lOD1AUEVrEVIgABvBFYojJVYQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "yaml": "bin.mjs"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 14"
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,7 @@
 | 
			
		|||
  "dependencies": {
 | 
			
		||||
    "bcryptjs": "^2.4.3",
 | 
			
		||||
    "debug": "^4.3.4",
 | 
			
		||||
    "express": "^4.18.2",
 | 
			
		||||
    "express": "^4.18.3",
 | 
			
		||||
    "express-session": "^1.18.0",
 | 
			
		||||
    "qrcode": "^1.5.3",
 | 
			
		||||
    "uuid": "^9.0.1"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,3 +12,18 @@ WireGuard.getConfig()
 | 
			
		|||
    // eslint-disable-next-line no-process-exit
 | 
			
		||||
    process.exit(1);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
// Handle terminate signal
 | 
			
		||||
process.on('SIGTERM', async () => {
 | 
			
		||||
  // eslint-disable-next-line no-console
 | 
			
		||||
  console.log('SIGTERM signal received.');
 | 
			
		||||
  await WireGuard.Shutdown();
 | 
			
		||||
  // eslint-disable-next-line no-process-exit
 | 
			
		||||
  process.exit(0);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Handle interupt signal
 | 
			
		||||
process.on('SIGINT', () => {
 | 
			
		||||
  // eslint-disable-next-line no-console
 | 
			
		||||
  console.log('SIGINT signal received.');
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,17 @@
 | 
			
		|||
module.exports = {
 | 
			
		||||
  darkMode: 'media',
 | 
			
		||||
  content: ['./www/**/*.{html,js}'],
 | 
			
		||||
  theme: {
 | 
			
		||||
    screens: {
 | 
			
		||||
      xxs: '450px',
 | 
			
		||||
      xs: '576px',
 | 
			
		||||
      sm: '640px',
 | 
			
		||||
      md: '768px',
 | 
			
		||||
      lg: '1024px',
 | 
			
		||||
      xl: '1280px',
 | 
			
		||||
      '2xl': '1536px',
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  plugins: [
 | 
			
		||||
    function addDisabledClass({ addUtilities }) {
 | 
			
		||||
      const newUtilities = {
 | 
			
		||||
| 
						 | 
				
			
			@ -12,8 +23,8 @@ module.exports = {
 | 
			
		|||
          opacity: '0.25',
 | 
			
		||||
          cursor: 'default',
 | 
			
		||||
        },
 | 
			
		||||
      };
 | 
			
		||||
      },
 | 
			
		||||
      addUtilities(newUtilities);
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
};
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -548,6 +548,18 @@ video {
 | 
			
		|||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 450px) {
 | 
			
		||||
  .container {
 | 
			
		||||
    max-width: 450px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 576px) {
 | 
			
		||||
  .container {
 | 
			
		||||
    max-width: 576px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 640px) {
 | 
			
		||||
  .container {
 | 
			
		||||
    max-width: 640px;
 | 
			
		||||
| 
						 | 
				
			
			@ -700,8 +712,12 @@ video {
 | 
			
		|||
  margin-right: 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mr-5 {
 | 
			
		||||
  margin-right: 1.25rem;
 | 
			
		||||
.mt-0 {
 | 
			
		||||
  margin-top: 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mt-0\.5 {
 | 
			
		||||
  margin-top: 0.125rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mt-10 {
 | 
			
		||||
| 
						 | 
				
			
			@ -720,6 +736,10 @@ video {
 | 
			
		|||
  margin-top: 1.25rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mt-px {
 | 
			
		||||
  margin-top: 1px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.block {
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -768,10 +788,6 @@ video {
 | 
			
		|||
  height: 3rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.h-14 {
 | 
			
		||||
  height: 3.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.h-2 {
 | 
			
		||||
  height: 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -784,10 +800,6 @@ video {
 | 
			
		|||
  height: 0.75rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.h-32 {
 | 
			
		||||
  height: 8rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.h-4 {
 | 
			
		||||
  height: 1rem;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -840,6 +852,10 @@ video {
 | 
			
		|||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.min-w-20 {
 | 
			
		||||
  min-width: 5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.max-w-3xl {
 | 
			
		||||
  max-width: 48rem;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -852,6 +868,10 @@ video {
 | 
			
		|||
  flex-shrink: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.shrink-0 {
 | 
			
		||||
  flex-shrink: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.flex-grow {
 | 
			
		||||
  flex-grow: 1;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -925,6 +945,18 @@ video {
 | 
			
		|||
  gap: 0.25rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.gap-2 {
 | 
			
		||||
  gap: 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.gap-3 {
 | 
			
		||||
  gap: 0.75rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.self-start {
 | 
			
		||||
  align-self: flex-start;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.overflow-hidden {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -939,6 +971,10 @@ video {
 | 
			
		|||
  white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.whitespace-nowrap {
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.rounded {
 | 
			
		||||
  border-radius: 0.25rem;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1105,6 +1141,11 @@ video {
 | 
			
		|||
  padding-bottom: 0.75rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.py-5 {
 | 
			
		||||
  padding-top: 1.25rem;
 | 
			
		||||
  padding-bottom: 1.25rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pb-1 {
 | 
			
		||||
  padding-bottom: 0.25rem;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1113,10 +1154,6 @@ video {
 | 
			
		|||
  padding-bottom: 3rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pb-2 {
 | 
			
		||||
  padding-bottom: 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pb-20 {
 | 
			
		||||
  padding-bottom: 5rem;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1426,6 +1463,12 @@ video {
 | 
			
		|||
  opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 450px) {
 | 
			
		||||
  .xxs\:flex-row {
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (min-width: 640px) {
 | 
			
		||||
  .sm\:mx-0 {
 | 
			
		||||
    margin-left: 0px;
 | 
			
		||||
| 
						 | 
				
			
			@ -1493,6 +1536,10 @@ video {
 | 
			
		|||
    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .sm\:flex-row {
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .sm\:flex-row-reverse {
 | 
			
		||||
    flex-direction: row-reverse;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -1537,8 +1584,12 @@ video {
 | 
			
		|||
    display: inline-block;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .md\:flex-row {
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
  .md\:min-w-24 {
 | 
			
		||||
    min-width: 6rem;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .md\:gap-4 {
 | 
			
		||||
    gap: 1rem;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .md\:px-0 {
 | 
			
		||||
| 
						 | 
				
			
			@ -1549,6 +1600,11 @@ video {
 | 
			
		|||
  .md\:pb-0 {
 | 
			
		||||
    padding-bottom: 0px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .md\:text-base {
 | 
			
		||||
    font-size: 1rem;
 | 
			
		||||
    line-height: 1.5rem;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (prefers-color-scheme: dark) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,11 +17,8 @@
 | 
			
		|||
</style>
 | 
			
		||||
 | 
			
		||||
<body class="bg-gray-50 dark:bg-neutral-800">
 | 
			
		||||
 | 
			
		||||
  <div id="app">
 | 
			
		||||
 | 
			
		||||
    <div v-cloak class="container mx-auto max-w-3xl px-5 md:px-0">
 | 
			
		||||
 | 
			
		||||
    <div v-cloak class="container mx-auto max-w-3xl px-3 md:px-0">
 | 
			
		||||
      <div v-if="authenticated === true">
 | 
			
		||||
        <span v-if="requiresPassword"
 | 
			
		||||
          class="text-sm text-gray-400 dark:text-neutral-400 mb-10 mr-2 mt-3 cursor-pointer hover:underline float-right"
 | 
			
		||||
| 
						 | 
				
			
			@ -89,11 +86,14 @@
 | 
			
		|||
                  style="transform: scaleY(-1);">
 | 
			
		||||
                </apexchart>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="relative p-5 z-10 flex flex-col md:flex-row justify-between">
 | 
			
		||||
                <div class="flex items-center pb-2 md:pb-0">
 | 
			
		||||
                  <div class="h-10 w-10 mr-5 rounded-full bg-gray-50 relative">
 | 
			
		||||
                    <svg class="w-6 m-2 text-gray-300" xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
                      viewBox="0 0 20 20" fill="currentColor">
 | 
			
		||||
 | 
			
		||||
              <div class="relative py-5 px-3 z-10 flex flex-col sm:flex-row justify-between gap-3">
 | 
			
		||||
                <div class="flex gap-3 md:gap-4 w-full items-center ">
 | 
			
		||||
 | 
			
		||||
                  <!-- Avatar -->
 | 
			
		||||
                  <div class="h-10 w-10 mt-2 self-start rounded-full bg-gray-50 relative">
 | 
			
		||||
                    <svg class="w-6 m-2 text-gray-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
 | 
			
		||||
                      fill="currentColor">
 | 
			
		||||
                      <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
 | 
			
		||||
                        clip-rule="evenodd" />
 | 
			
		||||
                    </svg>
 | 
			
		||||
| 
						 | 
				
			
			@ -108,52 +108,26 @@
 | 
			
		|||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
 | 
			
		||||
                  <div class="flex-grow">
 | 
			
		||||
                  <!-- Name & Info -->
 | 
			
		||||
                  <div class="flex flex-col xxs:flex-row w-full gap-2">
 | 
			
		||||
 | 
			
		||||
                    <!-- Name -->
 | 
			
		||||
                    <div class="text-gray-700 dark:text-neutral-200 group"
 | 
			
		||||
                      :title="$t('createdOn') + dateTime(new Date(client.createdAt))">
 | 
			
		||||
 | 
			
		||||
                      <!-- Show -->
 | 
			
		||||
                      <input v-show="clientEditNameId === client.id" v-model="clientEditName"
 | 
			
		||||
                        v-on:keyup.enter="updateClientName(client, clientEditName); clientEditName = null; clientEditNameId = null;"
 | 
			
		||||
                        v-on:keyup.escape="clientEditName = null; clientEditNameId = null;"
 | 
			
		||||
                        :ref="'client-' + client.id + '-name'"
 | 
			
		||||
                        class="rounded px-1 border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 dark:placeholder:text-neutral-500 outline-none w-30" />
 | 
			
		||||
                      <span v-show="clientEditNameId !== client.id"
 | 
			
		||||
                        class="inline-block border-t-2 border-b-2 border-transparent">{{client.name}}</span>
 | 
			
		||||
 | 
			
		||||
                      <!-- Edit -->
 | 
			
		||||
                      <span v-show="clientEditNameId !== client.id"
 | 
			
		||||
                        @click="clientEditName = client.name; clientEditNameId = client.id; setTimeout(() => $refs['client-' + client.id + '-name'][0].select(), 1);"
 | 
			
		||||
                        class="cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity">
 | 
			
		||||
                        <svg xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
                          class="h-4 w-4 inline align-middle opacity-25 hover:opacity-100" fill="none"
 | 
			
		||||
                          viewBox="0 0 24 24" stroke="currentColor">
 | 
			
		||||
                          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
 | 
			
		||||
                            d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
 | 
			
		||||
                        </svg>
 | 
			
		||||
                      </span>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <!-- Info -->
 | 
			
		||||
                    <div class="text-gray-400 dark:text-neutral-400 text-xs">
 | 
			
		||||
 | 
			
		||||
                      <!-- Address -->
 | 
			
		||||
                      <span class="group block md:inline-block pb-1 md:pb-0">
 | 
			
		||||
                    <div class="flex flex-col flex-grow gap-1">
 | 
			
		||||
                      <div class="text-gray-700 dark:text-neutral-200 group text-sm md:text-base"
 | 
			
		||||
                        :title="$t('createdOn') + dateTime(new Date(client.createdAt))">
 | 
			
		||||
 | 
			
		||||
                        <!-- Show -->
 | 
			
		||||
                        <input v-show="clientEditAddressId === client.id" v-model="clientEditAddress"
 | 
			
		||||
                          v-on:keyup.enter="updateClientAddress(client, clientEditAddress); clientEditAddress = null; clientEditAddressId = null;"
 | 
			
		||||
                          v-on:keyup.escape="clientEditAddress = null; clientEditAddressId = null;"
 | 
			
		||||
                          :ref="'client-' + client.id + '-address'"
 | 
			
		||||
                          class="rounded border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 outline-none w-20 text-black dark:text-neutral-300 dark:placeholder:text-neutral-500" />
 | 
			
		||||
                        <span v-show="clientEditAddressId !== client.id"
 | 
			
		||||
                          class="inline-block border-t-2 border-b-2 border-transparent">{{client.address}}</span>
 | 
			
		||||
                        <input v-show="clientEditNameId === client.id" v-model="clientEditName"
 | 
			
		||||
                          v-on:keyup.enter="updateClientName(client, clientEditName); clientEditName = null; clientEditNameId = null;"
 | 
			
		||||
                          v-on:keyup.escape="clientEditName = null; clientEditNameId = null;"
 | 
			
		||||
                          :ref="'client-' + client.id + '-name'"
 | 
			
		||||
                          class="rounded px-1 border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 dark:placeholder:text-neutral-500 outline-none w-30" />
 | 
			
		||||
                        <span v-show="clientEditNameId !== client.id"
 | 
			
		||||
                          class="border-t-2 border-b-2 border-transparent">{{client.name}}</span>
 | 
			
		||||
 | 
			
		||||
                        <!-- Edit -->
 | 
			
		||||
                        <span v-show="clientEditAddressId !== client.id"
 | 
			
		||||
                          @click="clientEditAddress = client.address; clientEditAddressId = client.id; setTimeout(() => $refs['client-' + client.id + '-address'][0].select(), 1);"
 | 
			
		||||
                        <span v-show="clientEditNameId !== client.id"
 | 
			
		||||
                          @click="clientEditName = client.name; clientEditNameId = client.id; setTimeout(() => $refs['client-' + client.id + '-name'][0].select(), 1);"
 | 
			
		||||
                          class="cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity">
 | 
			
		||||
                          <svg xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
                            class="h-4 w-4 inline align-middle opacity-25 hover:opacity-100" fill="none"
 | 
			
		||||
| 
						 | 
				
			
			@ -162,39 +136,105 @@
 | 
			
		|||
                              d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
 | 
			
		||||
                          </svg>
 | 
			
		||||
                        </span>
 | 
			
		||||
                      </span>
 | 
			
		||||
                      </div>
 | 
			
		||||
                      <!-- Address -->
 | 
			
		||||
                      <div class=" block md:inline-block pb-1 md:pb-0 text-gray-500 dark:text-neutral-400 text-xs">
 | 
			
		||||
                        <span class="group">
 | 
			
		||||
                          <!-- Show -->
 | 
			
		||||
                          <input v-show="clientEditAddressId === client.id" v-model="clientEditAddress"
 | 
			
		||||
                            v-on:keyup.enter="updateClientAddress(client, clientEditAddress); clientEditAddress = null; clientEditAddressId = null;"
 | 
			
		||||
                            v-on:keyup.escape="clientEditAddress = null; clientEditAddressId = null;"
 | 
			
		||||
                            :ref="'client-' + client.id + '-address'"
 | 
			
		||||
                            class="rounded border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 outline-none w-20 text-black dark:text-neutral-300 dark:placeholder:text-neutral-500" />
 | 
			
		||||
                          <span v-show="clientEditAddressId !== client.id"
 | 
			
		||||
                            class="inline-block ">{{client.address}}</span>
 | 
			
		||||
 | 
			
		||||
                          <!-- Edit -->
 | 
			
		||||
                          <span v-show="clientEditAddressId !== client.id"
 | 
			
		||||
                            @click="clientEditAddress = client.address; clientEditAddressId = client.id; setTimeout(() => $refs['client-' + client.id + '-address'][0].select(), 1);"
 | 
			
		||||
                            class="cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity">
 | 
			
		||||
                            <svg xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
                              class="h-4 w-4 inline align-middle opacity-25 hover:opacity-100" fill="none"
 | 
			
		||||
                              viewBox="0 0 24 24" stroke="currentColor">
 | 
			
		||||
                              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
 | 
			
		||||
                                d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
 | 
			
		||||
                            </svg>
 | 
			
		||||
                          </span>
 | 
			
		||||
                        </span>
 | 
			
		||||
                        <!-- Inline Transfer TX -->
 | 
			
		||||
                        <span v-if="!uiTrafficStats && client.transferTx" class="whitespace-nowrap" :title="$t('totalDownload') + bytes(client.transferTx)">
 | 
			
		||||
                          ·
 | 
			
		||||
                          <svg class="align-middle h-3 inline" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
 | 
			
		||||
                            <path fill-rule="evenodd"
 | 
			
		||||
                              d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l4.293-4.293a1 1 0 011.414 0z"
 | 
			
		||||
                              clip-rule="evenodd" />
 | 
			
		||||
                          </svg>
 | 
			
		||||
                          {{client.transferTxCurrent | bytes}}/s
 | 
			
		||||
                        </span>
 | 
			
		||||
                        
 | 
			
		||||
                        <!-- Inline Transfer RX -->
 | 
			
		||||
                        <span v-if="!uiTrafficStats && client.transferRx" class="whitespace-nowrap" :title="$t('totalUpload') + bytes(client.transferRx)">
 | 
			
		||||
                          ·
 | 
			
		||||
                          <svg class="align-middle h-3 inline" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
 | 
			
		||||
                            <path fill-rule="evenodd"
 | 
			
		||||
                              d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z"
 | 
			
		||||
                              clip-rule="evenodd" />
 | 
			
		||||
                          </svg>
 | 
			
		||||
                          {{client.transferRxCurrent | bytes}}/s
 | 
			
		||||
                        </span>
 | 
			
		||||
                        <!-- Last seen -->
 | 
			
		||||
                        <span class="text-gray-400 dark:text-neutral-500 whitespace-nowrap" v-if="client.latestHandshakeAt"
 | 
			
		||||
                          :title="$t('lastSeen') + dateTime(new Date(client.latestHandshakeAt))">
 | 
			
		||||
                          {{!uiTrafficStats ? " · " : ""}}{{new Date(client.latestHandshakeAt) | timeago}}
 | 
			
		||||
                        </span>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <!-- Info -->
 | 
			
		||||
                    <div v-if="uiTrafficStats"
 | 
			
		||||
                      class="flex gap-2 items-center shrink-0 text-gray-400 dark:text-neutral-400 text-xs mt-px justify-end">
 | 
			
		||||
 | 
			
		||||
                      <!-- Transfer TX -->
 | 
			
		||||
                      <span v-if="client.transferTx" :title="$t('totalDownload') + bytes(client.transferTx)">
 | 
			
		||||
                        ·
 | 
			
		||||
                        <svg class="align-middle h-3 inline" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
 | 
			
		||||
                          fill="currentColor">
 | 
			
		||||
                          <path fill-rule="evenodd"
 | 
			
		||||
                            d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l4.293-4.293a1 1 0 011.414 0z"
 | 
			
		||||
                            clip-rule="evenodd" />
 | 
			
		||||
                        </svg>
 | 
			
		||||
                        {{client.transferTxCurrent | bytes}}/s
 | 
			
		||||
                      </span>
 | 
			
		||||
                      <div class="min-w-20 md:min-w-24" v-if="client.transferTx">
 | 
			
		||||
                        <span class="flex gap-1" :title="$t('totalDownload') + bytes(client.transferTx)">
 | 
			
		||||
                          <svg class="align-middle h-3 inline mt-0.5" xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
                            viewBox="0 0 20 20" fill="currentColor">
 | 
			
		||||
                            <path fill-rule="evenodd"
 | 
			
		||||
                              d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l4.293-4.293a1 1 0 011.414 0z"
 | 
			
		||||
                              clip-rule="evenodd" />
 | 
			
		||||
                          </svg>
 | 
			
		||||
                          <div>
 | 
			
		||||
                            <span class="text-gray-700 dark:text-neutral-200">{{client.transferTxCurrent |
 | 
			
		||||
                              bytes}}/s</span>
 | 
			
		||||
                            <!-- Total TX -->
 | 
			
		||||
                            <br><span class="font-regular" style="font-size:0.85em">{{bytes(client.transferTx)}}</span>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </span>
 | 
			
		||||
 | 
			
		||||
                      </div>
 | 
			
		||||
 | 
			
		||||
                      <!-- Transfer RX -->
 | 
			
		||||
                      <span v-if="client.transferRx" :title="$t('totalUpload') + bytes(client.transferRx)">
 | 
			
		||||
                        ·
 | 
			
		||||
                        <svg class="align-middle h-3 inline" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
 | 
			
		||||
                          fill="currentColor">
 | 
			
		||||
                          <path fill-rule="evenodd"
 | 
			
		||||
                            d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z"
 | 
			
		||||
                            clip-rule="evenodd" />
 | 
			
		||||
                        </svg>
 | 
			
		||||
                        {{client.transferRxCurrent | bytes}}/s
 | 
			
		||||
                      </span>
 | 
			
		||||
                      <div class="min-w-20 md:min-w-24" v-if="client.transferRx">
 | 
			
		||||
                        <span class="flex gap-1" :title="$t('totalUpload') + bytes(client.transferRx)">
 | 
			
		||||
 | 
			
		||||
                          <svg class="align-middle h-3 inline mt-0.5" xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
                            viewBox="0 0 20 20" fill="currentColor">
 | 
			
		||||
                            <path fill-rule="evenodd"
 | 
			
		||||
                              d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z"
 | 
			
		||||
                              clip-rule="evenodd" />
 | 
			
		||||
                          </svg>
 | 
			
		||||
                          <div>
 | 
			
		||||
                            <span class="text-gray-700 dark:text-neutral-200">{{client.transferRxCurrent |
 | 
			
		||||
                              bytes}}/s</span>
 | 
			
		||||
                            <!-- Total RX -->
 | 
			
		||||
                            <br><span class="font-regular" style="font-size:0.85em">{{bytes(client.transferRx)}}</span>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </span>
 | 
			
		||||
                      </div>
 | 
			
		||||
 | 
			
		||||
                      <!-- Last seen -->
 | 
			
		||||
                      <span v-if="client.latestHandshakeAt"
 | 
			
		||||
                        :title="$t('lastSeen') + dateTime(new Date(client.latestHandshakeAt))">
 | 
			
		||||
                        · {{new Date(client.latestHandshakeAt) | timeago}}
 | 
			
		||||
                      </span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <!-- </div> --> <!-- <div class="flex flex-grow items-center"> -->
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="flex items-center justify-end">
 | 
			
		||||
| 
						 | 
				
			
			@ -514,11 +554,11 @@
 | 
			
		|||
  <script src="./js/vendor/vue-i18n.min.js"></script>
 | 
			
		||||
  <script src="./js/vendor/apexcharts.min.js"></script>
 | 
			
		||||
  <script src="./js/vendor/vue-apexcharts.min.js"></script>
 | 
			
		||||
  <script src="./js/vendor/sha512.min.js"></script>
 | 
			
		||||
  <script src="./js/vendor/sha256.min.js"></script>
 | 
			
		||||
  <script src="./js/vendor/timeago.full.min.js"></script>
 | 
			
		||||
  <script src="./js/api.js"></script>
 | 
			
		||||
  <script src="./js/i18n.js"></script>
 | 
			
		||||
  <script src="./js/app.js"></script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			@ -43,6 +43,13 @@ class API {
 | 
			
		|||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getuiTrafficStats() {
 | 
			
		||||
    return this.call({
 | 
			
		||||
      method: 'get',
 | 
			
		||||
      path: '/ui-traffic-stats',
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getSession() {
 | 
			
		||||
    return this.call({
 | 
			
		||||
      method: 'get',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -53,6 +53,7 @@ new Vue({
 | 
			
		|||
    latestRelease: null,
 | 
			
		||||
 | 
			
		||||
    isDark: null,
 | 
			
		||||
    uiTrafficStats: false,
 | 
			
		||||
 | 
			
		||||
    chartOptions: {
 | 
			
		||||
      chart: {
 | 
			
		||||
| 
						 | 
				
			
			@ -138,7 +139,7 @@ new Vue({
 | 
			
		|||
      const clients = await this.api.getClients();
 | 
			
		||||
      this.clients = clients.map((client) => {
 | 
			
		||||
        if (client.name.includes('@') && client.name.includes('.')) {
 | 
			
		||||
          client.avatar = `https://www.gravatar.com/avatar/${sha512(client.name)}?d=blank`;
 | 
			
		||||
          client.avatar = `https://gravatar.com/avatar/${sha256(client.name.toLowerCase().trim())}.jpg`;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.clientsPersist[client.id]) {
 | 
			
		||||
| 
						 | 
				
			
			@ -292,6 +293,15 @@ new Vue({
 | 
			
		|||
      }).catch(console.error);
 | 
			
		||||
    }, 1000);
 | 
			
		||||
 | 
			
		||||
    this.api.getuiTrafficStats()
 | 
			
		||||
      .then((res) => {
 | 
			
		||||
        this.uiTrafficStats = res;
 | 
			
		||||
      })
 | 
			
		||||
      .catch(() => {
 | 
			
		||||
        console.log('Failed to get ui-traffic-stats');
 | 
			
		||||
        this.uiTrafficStats = false;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    Promise.resolve().then(async () => {
 | 
			
		||||
      const lang = await this.api.getLang();
 | 
			
		||||
      if (lang !== localStorage.getItem('lang') && i18n.availableLocales.includes(lang)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -321,6 +331,6 @@ new Vue({
 | 
			
		|||
 | 
			
		||||
      this.currentRelease = currentRelease;
 | 
			
		||||
      this.latestRelease = latestRelease;
 | 
			
		||||
    }).catch(console.error);
 | 
			
		||||
    }).catch((err) => console.error(err));
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,6 +29,33 @@ const messages = { // eslint-disable-line no-unused-vars
 | 
			
		|||
    madeBy: 'Made by',
 | 
			
		||||
    donate: 'Donate',
 | 
			
		||||
  },
 | 
			
		||||
  ua: {
 | 
			
		||||
    name: 'Ім`я',
 | 
			
		||||
    password: 'Пароль',
 | 
			
		||||
    signIn: 'Увійти',
 | 
			
		||||
    logout: 'Вихід',
 | 
			
		||||
    updateAvailable: 'Доступне оновлення!',
 | 
			
		||||
    update: 'Оновити',
 | 
			
		||||
    clients: 'Клієнти',
 | 
			
		||||
    new: 'Новий',
 | 
			
		||||
    deleteClient: 'Видалити клієнта',
 | 
			
		||||
    deleteDialog1: 'Ви впевнені, що бажаєте видалити',
 | 
			
		||||
    deleteDialog2: 'Цю дію неможливо скасувати.',
 | 
			
		||||
    cancel: 'Скасувати',
 | 
			
		||||
    create: 'Створити',
 | 
			
		||||
    createdOn: 'Створено ',
 | 
			
		||||
    lastSeen: 'Останнє підключення в ',
 | 
			
		||||
    totalDownload: 'Всього завантажено: ',
 | 
			
		||||
    totalUpload: 'Всього відправлено: ',
 | 
			
		||||
    newClient: 'Новий клієнт',
 | 
			
		||||
    disableClient: 'Вимкнути клієнта',
 | 
			
		||||
    enableClient: 'Увімкнути клієнта',
 | 
			
		||||
    noClients: 'Ще немає клієнтів.',
 | 
			
		||||
    showQR: 'Показати QR-код',
 | 
			
		||||
    downloadConfig: 'Завантажити конфігурацію',
 | 
			
		||||
    madeBy: 'Зроблено',
 | 
			
		||||
    donate: 'Пожертвувати',
 | 
			
		||||
  },
 | 
			
		||||
  ru: {
 | 
			
		||||
    name: 'Имя',
 | 
			
		||||
    password: 'Пароль',
 | 
			
		||||
| 
						 | 
				
			
			@ -301,4 +328,193 @@ const messages = { // eslint-disable-line no-unused-vars
 | 
			
		|||
    madeBy: 'Được tạo bởi',
 | 
			
		||||
    donate: 'Ủng hộ',
 | 
			
		||||
  },
 | 
			
		||||
  nl: {
 | 
			
		||||
    name: 'Naam',
 | 
			
		||||
    password: 'Wachtwoord',
 | 
			
		||||
    signIn: 'Inloggen',
 | 
			
		||||
    logout: 'Uitloggen',
 | 
			
		||||
    updateAvailable: 'Nieuw update beschikbaar!',
 | 
			
		||||
    update: 'update',
 | 
			
		||||
    clients: 'clients',
 | 
			
		||||
    new: 'Nieuw',
 | 
			
		||||
    deleteClient: 'client verwijderen',
 | 
			
		||||
    deleteDialog1: 'Weet je zeker dat je wilt verwijderen',
 | 
			
		||||
    deleteDialog2: 'Deze actie kan niet ongedaan worden gemaakt.',
 | 
			
		||||
    cancel: 'Annuleren',
 | 
			
		||||
    create: 'Creëren',
 | 
			
		||||
    createdOn: 'Gemaakt op ',
 | 
			
		||||
    lastSeen: 'Laatst gezien op ',
 | 
			
		||||
    totalDownload: 'Totaal Gedownload: ',
 | 
			
		||||
    totalUpload: 'Totaal Geupload: ',
 | 
			
		||||
    newClient: 'Nieuwe client',
 | 
			
		||||
    disableClient: 'client uitschakelen',
 | 
			
		||||
    enableClient: 'client inschakelen',
 | 
			
		||||
    noClients: 'Er zijn nog geen clients.',
 | 
			
		||||
    showQR: 'QR-code weergeven',
 | 
			
		||||
    downloadConfig: 'Configuratie downloaden',
 | 
			
		||||
    madeBy: 'Gemaakt door',
 | 
			
		||||
    donate: 'Doneren',
 | 
			
		||||
  },
 | 
			
		||||
  is: {
 | 
			
		||||
    name: 'Nafn',
 | 
			
		||||
    password: 'Lykilorð',
 | 
			
		||||
    signIn: 'Skrá inn',
 | 
			
		||||
    logout: 'Útskráning',
 | 
			
		||||
    updateAvailable: 'Það er uppfærsla í boði!',
 | 
			
		||||
    update: 'Uppfæra',
 | 
			
		||||
    clients: 'Viðskiptavinir',
 | 
			
		||||
    new: 'Nýtt',
 | 
			
		||||
    deleteClient: 'Eyða viðskiptavin',
 | 
			
		||||
    deleteDialog1: 'Ertu viss um að þú viljir eyða',
 | 
			
		||||
    deleteDialog2: 'Þessi aðgerð getur ekki verið afturkallað.',
 | 
			
		||||
    cancel: 'Hætta við',
 | 
			
		||||
    create: 'Búa til',
 | 
			
		||||
    createdOn: 'Búið til á ',
 | 
			
		||||
    lastSeen: 'Síðast séð á ',
 | 
			
		||||
    totalDownload: 'Samtals Niðurhlaða: ',
 | 
			
		||||
    totalUpload: 'Samtals Upphlaða: ',
 | 
			
		||||
    newClient: 'Nýr Viðskiptavinur',
 | 
			
		||||
    disableClient: 'Gera viðskiptavin óvirkan',
 | 
			
		||||
    enableClient: 'Gera viðskiptavin virkan',
 | 
			
		||||
    noClients: 'Engir viðskiptavinir ennþá.',
 | 
			
		||||
    showQR: 'Sýna QR-kóða',
 | 
			
		||||
    downloadConfig: 'Niðurhal Stillingar',
 | 
			
		||||
    madeBy: 'Gert af',
 | 
			
		||||
    donate: 'Gefa',
 | 
			
		||||
  },
 | 
			
		||||
  pt: {
 | 
			
		||||
    name: 'Nome',
 | 
			
		||||
    password: 'Palavra Chave',
 | 
			
		||||
    signIn: 'Entrar',
 | 
			
		||||
    logout: 'Sair',
 | 
			
		||||
    updateAvailable: 'Existe uma atualização disponível!',
 | 
			
		||||
    update: 'Atualizar',
 | 
			
		||||
    clients: 'Clientes',
 | 
			
		||||
    new: 'Novo',
 | 
			
		||||
    deleteClient: 'Apagar Clientes',
 | 
			
		||||
    deleteDialog1: 'Tem certeza que pretende apagar',
 | 
			
		||||
    deleteDialog2: 'Esta ação não pode ser revertida.',
 | 
			
		||||
    cancel: 'Cancelar',
 | 
			
		||||
    create: 'Criar',
 | 
			
		||||
    createdOn: 'Criado em ',
 | 
			
		||||
    lastSeen: 'Último acesso em ',
 | 
			
		||||
    totalDownload: 'Total Download: ',
 | 
			
		||||
    totalUpload: 'Total Upload: ',
 | 
			
		||||
    newClient: 'Novo Cliente',
 | 
			
		||||
    disableClient: 'Desativar Cliente',
 | 
			
		||||
    enableClient: 'Ativar Cliente',
 | 
			
		||||
    noClients: 'Não existem ainda clientes.',
 | 
			
		||||
    showQR: 'Apresentar o código QR',
 | 
			
		||||
    downloadConfig: 'Descarregar Configuração',
 | 
			
		||||
    madeBy: 'Feito por',
 | 
			
		||||
    donate: 'Doar',
 | 
			
		||||
  },
 | 
			
		||||
  chs: {
 | 
			
		||||
    name: '名称',
 | 
			
		||||
    password: '密码',
 | 
			
		||||
    signIn: '登录',
 | 
			
		||||
    logout: '退出',
 | 
			
		||||
    updateAvailable: '有新版本可用!',
 | 
			
		||||
    update: '更新',
 | 
			
		||||
    clients: '客户端',
 | 
			
		||||
    new: '新建',
 | 
			
		||||
    deleteClient: '删除客户端',
 | 
			
		||||
    deleteDialog1: '您确定要删除',
 | 
			
		||||
    deleteDialog2: '此操作无法撤销。',
 | 
			
		||||
    cancel: '取消',
 | 
			
		||||
    create: '创建',
 | 
			
		||||
    createdOn: '创建于 ',
 | 
			
		||||
    lastSeen: '最后访问于 ',
 | 
			
		||||
    totalDownload: '总下载: ',
 | 
			
		||||
    totalUpload: '总上传: ',
 | 
			
		||||
    newClient: '新建客户端',
 | 
			
		||||
    disableClient: '禁用客户端',
 | 
			
		||||
    enableClient: '启用客户端',
 | 
			
		||||
    noClients: '目前没有客户端。',
 | 
			
		||||
    showQR: '显示二维码',
 | 
			
		||||
    downloadConfig: '下载配置',
 | 
			
		||||
    madeBy: '由',
 | 
			
		||||
    donate: '捐赠',
 | 
			
		||||
  },
 | 
			
		||||
  cht: {
 | 
			
		||||
    name: '名字',
 | 
			
		||||
    password: '密碼',
 | 
			
		||||
    signIn: '登入',
 | 
			
		||||
    logout: '登出',
 | 
			
		||||
    updateAvailable: '有新版本可用!',
 | 
			
		||||
    update: '更新',
 | 
			
		||||
    clients: '客戶',
 | 
			
		||||
    new: '新建',
 | 
			
		||||
    deleteClient: '刪除客戶',
 | 
			
		||||
    deleteDialog1: '您確定要刪除',
 | 
			
		||||
    deleteDialog2: '此操作無法撤銷。',
 | 
			
		||||
    cancel: '取消',
 | 
			
		||||
    create: '建立',
 | 
			
		||||
    createdOn: '建立於 ',
 | 
			
		||||
    lastSeen: '最後訪問於 ',
 | 
			
		||||
    totalDownload: '總下載: ',
 | 
			
		||||
    totalUpload: '總上傳: ',
 | 
			
		||||
    newClient: '新客戶',
 | 
			
		||||
    disableClient: '禁用客戶',
 | 
			
		||||
    enableClient: '啟用客戶',
 | 
			
		||||
    noClients: '目前沒有客戶。',
 | 
			
		||||
    showQR: '顯示二維碼',
 | 
			
		||||
    downloadConfig: '下載配置',
 | 
			
		||||
    madeBy: '由',
 | 
			
		||||
    donate: '捐贈',
 | 
			
		||||
  },
 | 
			
		||||
  it: {
 | 
			
		||||
    name: 'Nome',
 | 
			
		||||
    password: 'Password',
 | 
			
		||||
    signIn: 'Accedi',
 | 
			
		||||
    logout: 'Esci',
 | 
			
		||||
    updateAvailable: 'È disponibile un aggiornamento!',
 | 
			
		||||
    update: 'Aggiorna',
 | 
			
		||||
    clients: 'Client',
 | 
			
		||||
    new: 'Nuovo',
 | 
			
		||||
    deleteClient: 'Elimina Client',
 | 
			
		||||
    deleteDialog1: 'Sei sicuro di voler eliminare',
 | 
			
		||||
    deleteDialog2: 'Questa azione non può essere annullata.',
 | 
			
		||||
    cancel: 'Annulla',
 | 
			
		||||
    create: 'Crea',
 | 
			
		||||
    createdOn: 'Creato il ',
 | 
			
		||||
    lastSeen: 'Visto l\'ultima volta il ',
 | 
			
		||||
    totalDownload: 'Totale Download: ',
 | 
			
		||||
    totalUpload: 'Totale Upload: ',
 | 
			
		||||
    newClient: 'Nuovo Client',
 | 
			
		||||
    disableClient: 'Disabilita Client',
 | 
			
		||||
    enableClient: 'Abilita Client',
 | 
			
		||||
    noClients: 'Non ci sono ancora client.',
 | 
			
		||||
    showQR: 'Mostra codice QR',
 | 
			
		||||
    downloadConfig: 'Scarica configurazione',
 | 
			
		||||
    madeBy: 'Realizzato da',
 | 
			
		||||
    donate: 'Donazione',
 | 
			
		||||
  },
 | 
			
		||||
  th: {
 | 
			
		||||
    name: 'ชื่อ',
 | 
			
		||||
    password: 'รหัสผ่าน',
 | 
			
		||||
    signIn: 'ลงชื่อเข้าใช้',
 | 
			
		||||
    logout: 'ออกจากระบบ',
 | 
			
		||||
    updateAvailable: 'มีอัปเดตพร้อมใช้งาน!',
 | 
			
		||||
    update: 'อัปเดต',
 | 
			
		||||
    clients: 'Clients',
 | 
			
		||||
    new: 'ใหม่',
 | 
			
		||||
    deleteClient: 'ลบ Client',
 | 
			
		||||
    deleteDialog1: 'คุณแน่ใจหรือไม่ว่าต้องการลบ',
 | 
			
		||||
    deleteDialog2: 'การกระทำนี้;ไม่สามารถยกเลิกได้',
 | 
			
		||||
    cancel: 'ยกเลิก',
 | 
			
		||||
    create: 'สร้าง',
 | 
			
		||||
    createdOn: 'สร้างเมื่อ ',
 | 
			
		||||
    lastSeen: 'เห็นครั้งสุดท้ายเมื่อ ',
 | 
			
		||||
    totalDownload: 'ดาวน์โหลดทั้งหมด: ',
 | 
			
		||||
    totalUpload: 'อัพโหลดทั้งหมด: ',
 | 
			
		||||
    newClient: 'Client ใหม่',
 | 
			
		||||
    disableClient: 'ปิดการใช้งาน Client',
 | 
			
		||||
    enableClient: 'เปิดการใช้งาน Client',
 | 
			
		||||
    noClients: 'ยังไม่มี Clients เลย',
 | 
			
		||||
    showQR: 'แสดงรหัส QR',
 | 
			
		||||
    downloadConfig: 'ดาวน์โหลดการตั้งค่า',
 | 
			
		||||
    madeBy: 'สร้างโดย',
 | 
			
		||||
    donate: 'บริจาค',
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								src/www/js/vendor/apexcharts.min.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								src/www/js/vendor/apexcharts.min.js
									
										
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										9
									
								
								src/www/js/vendor/sha256.min.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/www/js/vendor/sha256.min.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								src/www/js/vendor/sha512.min.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								src/www/js/vendor/sha512.min.js
									
										
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
[Unit]
 | 
			
		||||
Description=Wireguard VPN + + Web-based Admin UI
 | 
			
		||||
Description=Wireguard VPN + Web-based Admin UI
 | 
			
		||||
After=network-online.target nss-lookup.target
 | 
			
		||||
 | 
			
		||||
[Service]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue