Merge branch 'feat-no-privateKey' into feat/clients-without-privatekey
This commit is contained in:
commit
ce1af6d691
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(() => { });
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -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: 'บริจาค',
|
||||
},
|
||||
};
|
||||
|
|
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