Version 12: Big feature update (#891)
* UI_TRAFFIC_STATS * Import json configurations with no PreShared-Key * allow clients with no privateKey * Reduce Docker image size * Added Thai language * Added Italian language. Updated supported languages list List see: https://github.com/wg-easy/wg-easy/milestone/1?closed=1
This commit is contained in:
commit
303f8847fe
|
@ -2,6 +2,7 @@ name: Build & Publish Development
|
|||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
|
|
@ -4,7 +4,7 @@ on:
|
|||
push:
|
||||
branches: [ "master" ]
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
- cron: "0 0 * * 1"
|
||||
|
||||
jobs:
|
||||
npmupbot:
|
||||
|
|
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, nl, is, chs, cht,). |
|
||||
| `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, ua, ru, tr, no, pl, fr, de, ca, es, pt, chs, cht)
|
||||
# (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
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
const { release } = require('./package.json');
|
||||
|
||||
module.exports.RELEASE = release;
|
||||
module.exports.PORT = process.env.PORT || 51821;
|
||||
module.exports.PORT = process.env.PORT || '51821';
|
||||
module.exports.WEBUI_HOST = process.env.WEBUI_HOST || '0.0.0.0';
|
||||
module.exports.PASSWORD = process.env.PASSWORD;
|
||||
module.exports.WG_PATH = process.env.WG_PATH || '/etc/wireguard/';
|
||||
module.exports.WG_DEVICE = process.env.WG_DEVICE || 'eth0';
|
||||
module.exports.WG_HOST = process.env.WG_HOST;
|
||||
module.exports.WG_PORT = process.env.WG_PORT || 51820;
|
||||
module.exports.WG_PORT = process.env.WG_PORT || '51820';
|
||||
module.exports.WG_MTU = process.env.WG_MTU || null;
|
||||
module.exports.WG_PERSISTENT_KEEPALIVE = process.env.WG_PERSISTENT_KEEPALIVE || 0;
|
||||
module.exports.WG_PERSISTENT_KEEPALIVE = process.env.WG_PERSISTENT_KEEPALIVE || '0';
|
||||
module.exports.WG_DEFAULT_ADDRESS = process.env.WG_DEFAULT_ADDRESS || '10.8.0.x';
|
||||
module.exports.WG_DEFAULT_DNS = typeof process.env.WG_DEFAULT_DNS === 'string'
|
||||
? process.env.WG_DEFAULT_DNS
|
||||
|
@ -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) => {
|
||||
|
|
|
@ -110,8 +110,8 @@ PostDown = ${WG_POST_DOWN}
|
|||
# Client: ${client.name} (${clientId})
|
||||
[Peer]
|
||||
PublicKey = ${client.publicKey}
|
||||
PresharedKey = ${client.preSharedKey}
|
||||
AllowedIPs = ${client.address}/32`;
|
||||
${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
|
||||
}AllowedIPs = ${client.address}/32`;
|
||||
}
|
||||
|
||||
debug('Config saving...');
|
||||
|
@ -141,7 +141,7 @@ AllowedIPs = ${client.address}/32`;
|
|||
createdAt: new Date(client.createdAt),
|
||||
updatedAt: new Date(client.updatedAt),
|
||||
allowedIPs: client.allowedIPs,
|
||||
|
||||
downloadableConfig: 'privateKey' in client,
|
||||
persistentKeepalive: null,
|
||||
latestHandshakeAt: null,
|
||||
transferRx: null,
|
||||
|
@ -196,16 +196,17 @@ AllowedIPs = ${client.address}/32`;
|
|||
const config = await this.getConfig();
|
||||
const client = await this.getClient({ clientId });
|
||||
|
||||
return `[Interface]
|
||||
PrivateKey = ${client.privateKey}
|
||||
return `
|
||||
[Interface]
|
||||
PrivateKey = ${client.privateKey ? `${client.privateKey}` : 'REPLACE_ME'}
|
||||
Address = ${client.address}/24
|
||||
${WG_DEFAULT_DNS ? `DNS = ${WG_DEFAULT_DNS}\n` : ''}\
|
||||
${WG_MTU ? `MTU = ${WG_MTU}\n` : ''}\
|
||||
|
||||
[Peer]
|
||||
PublicKey = ${config.server.publicKey}
|
||||
PresharedKey = ${client.preSharedKey}
|
||||
AllowedIPs = ${WG_ALLOWED_IPS}
|
||||
${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
|
||||
}AllowedIPs = ${WG_ALLOWED_IPS}
|
||||
PersistentKeepalive = ${WG_PERSISTENT_KEEPALIVE}
|
||||
Endpoint = ${WG_HOST}:${WG_PORT}`;
|
||||
}
|
||||
|
@ -318,4 +319,9 @@ Endpoint = ${WG_HOST}:${WG_PORT}`;
|
|||
await this.saveConfig();
|
||||
}
|
||||
|
||||
// Shutdown wireguard
|
||||
async Shutdown() {
|
||||
await Util.exec('wg-quick down wg0').catch(() => { });
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -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.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"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": {
|
||||
|
@ -991,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",
|
||||
|
@ -1004,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"
|
||||
},
|
||||
|
@ -1425,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",
|
||||
|
@ -1444,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",
|
||||
|
@ -1465,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"
|
||||
},
|
||||
|
@ -1681,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"
|
||||
|
@ -2107,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",
|
||||
|
@ -2691,9 +2691,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
|
||||
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
|
@ -3986,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",
|
||||
|
@ -4150,13 +4150,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/safe-array-concat": {
|
||||
"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==",
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz",
|
||||
"integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.5",
|
||||
"get-intrinsic": "^1.2.2",
|
||||
"call-bind": "^1.0.7",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"has-symbols": "^1.0.3",
|
||||
"isarray": "^2.0.5"
|
||||
},
|
||||
|
@ -4284,16 +4284,16 @@
|
|||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
|
||||
"integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.2",
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.3",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.1"
|
||||
"has-property-descriptors": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
@ -4341,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"
|
||||
|
@ -4735,9 +4735,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/ts-api-utils": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz",
|
||||
"integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
|
||||
"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
@ -5010,16 +5010,16 @@
|
|||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
|
||||
},
|
||||
"node_modules/which-typed-array": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz",
|
||||
"integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==",
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz",
|
||||
"integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"available-typed-arrays": "^1.0.6",
|
||||
"call-bind": "^1.0.5",
|
||||
"available-typed-arrays": "^1.0.7",
|
||||
"call-bind": "^1.0.7",
|
||||
"for-each": "^0.3.3",
|
||||
"gopd": "^1.0.1",
|
||||
"has-tostringtag": "^1.0.1"
|
||||
"has-tostringtag": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
@ -5078,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.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz",
|
||||
"integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==",
|
||||
"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,4 +5,26 @@
|
|||
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 = {
|
||||
'.is-disabled': {
|
||||
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;
|
||||
}
|
||||
|
@ -1350,6 +1387,11 @@ video {
|
|||
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.is-disabled {
|
||||
opacity: 0.25;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.last\:border-b-0:last-child {
|
||||
border-bottom-width: 0px;
|
||||
}
|
||||
|
@ -1421,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;
|
||||
|
@ -1488,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;
|
||||
}
|
||||
|
@ -1532,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 {
|
||||
|
@ -1544,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">
|
||||
|
@ -214,9 +254,14 @@
|
|||
|
||||
<!-- Show QR-->
|
||||
|
||||
<button
|
||||
class="align-middle bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white p-2 rounded transition"
|
||||
:title="$t('showQR')" @click="qrcode = `./api/wireguard/client/${client.id}/qrcode.svg`">
|
||||
<button :disabled="!client.downloadableConfig"
|
||||
class="align-middle bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 p-2 rounded transition"
|
||||
:class="{
|
||||
'hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white': client.downloadableConfig,
|
||||
'is-disabled': !client.downloadableConfig
|
||||
}"
|
||||
:title="!client.downloadableConfig ? $t('noPrivKey') : $t('showQR')"
|
||||
@click="qrcode = `./api/wireguard/client/${client.id}/qrcode.svg`">
|
||||
<svg class="w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
|
@ -225,9 +270,16 @@
|
|||
</button>
|
||||
|
||||
<!-- Download Config -->
|
||||
<a :href="'./api/wireguard/client/' + client.id + '/configuration'" download
|
||||
class="align-middle inline-block bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white p-2 rounded transition"
|
||||
:title="$t('downloadConfig')">
|
||||
<a :disabled="!client.downloadableConfig"
|
||||
:href="'./api/wireguard/client/' + client.id + '/configuration'"
|
||||
:download="client.downloadableConfig ? 'configuration' : null"
|
||||
class="align-middle inline-block bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 p-2 rounded transition"
|
||||
:class="{
|
||||
'hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white': client.downloadableConfig,
|
||||
'is-disabled': !client.downloadableConfig
|
||||
}"
|
||||
:title="!client.downloadableConfig ? $t('noPrivKey') : $t('downloadConfig')"
|
||||
@click="if(!client.downloadableConfig) { $event.preventDefault(); }">
|
||||
<svg class="w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
|
@ -502,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));
|
||||
},
|
||||
});
|
||||
|
|
|
@ -23,6 +23,7 @@ const messages = { // eslint-disable-line no-unused-vars
|
|||
disableClient: 'Disable Client',
|
||||
enableClient: 'Enable Client',
|
||||
noClients: 'There are no clients yet.',
|
||||
noPrivKey: 'This client has no known private key. Cannot create Configuration.',
|
||||
showQR: 'Show QR Code',
|
||||
downloadConfig: 'Download Configuration',
|
||||
madeBy: 'Made by',
|
||||
|
@ -213,6 +214,7 @@ const messages = { // eslint-disable-line no-unused-vars
|
|||
disableClient: 'Client deaktivieren',
|
||||
enableClient: 'Client aktivieren',
|
||||
noClients: 'Es wurden noch keine Clients konfiguriert.',
|
||||
noPrivKey: 'Es ist kein Private Key für diesen Client bekannt. Eine Konfiguration kann nicht erstellt werden.',
|
||||
showQR: 'Zeige den QR Code',
|
||||
downloadConfig: 'Konfiguration herunterladen',
|
||||
madeBy: 'Erstellt von',
|
||||
|
@ -461,4 +463,58 @@ const messages = { // eslint-disable-line no-unused-vars
|
|||
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: 'บริจาค',
|
||||
},
|
||||
};
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
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…
Reference in New Issue