Merge branch 'feat-h3' into master
This commit is contained in:
commit
1a3cff855e
|
@ -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
|
||||
|
|
|
@ -9,5 +9,6 @@
|
|||
"8": "Updated to Node.js v18.",
|
||||
"9": "Fixed issue running on devices with older kernels.",
|
||||
"10": "Added sessionless HTTP API auth & automatic dark mode.",
|
||||
"11": "Multilanguage Support & various bugfixes"
|
||||
"11": "Multilanguage Support & various bugfixes",
|
||||
"12": "UI_TRAFFIC_STATS, Import json configurations with no PreShared-Key, allow clients with no privateKey & more."
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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",
|
||||
"h3": "^1.11.1",
|
||||
"qrcode": "^1.5.3",
|
||||
|
@ -382,14 +382,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.4.tgz",
|
||||
"integrity": "sha512-Oud2QPM5dHviZNn4y/WhhYKSXksv+1xLEIsNrAbGcFzUN3ubqWRFT5gwPchNc5NuzILOU4tPBDTZ4VwhL8Y7cw==",
|
||||
"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"
|
||||
|
@ -405,9 +405,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"
|
||||
|
@ -420,9 +420,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.23",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.23.tgz",
|
||||
"integrity": "sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==",
|
||||
"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",
|
||||
|
@ -992,12 +992,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",
|
||||
|
@ -1005,7 +1005,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"
|
||||
},
|
||||
|
@ -1462,18 +1462,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",
|
||||
|
@ -1481,15 +1481,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",
|
||||
|
@ -1502,10 +1502,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"
|
||||
},
|
||||
|
@ -2144,13 +2144,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",
|
||||
|
@ -2745,9 +2745,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"
|
||||
},
|
||||
|
@ -4068,9 +4068,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",
|
||||
|
@ -4232,13 +4232,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"
|
||||
},
|
||||
|
@ -4366,16 +4366,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"
|
||||
|
@ -4423,11 +4423,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"
|
||||
|
@ -4817,9 +4817,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"
|
||||
|
@ -5125,16 +5125,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"
|
||||
|
@ -5193,9 +5193,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.0.tgz",
|
||||
"integrity": "sha512-j9iR8g+/t0lArF4V6NE/QCfT+CO7iLqrXAHZbJdo+LfjqP1vR8Fg5bSiaq6Q2lOD1AUEVrEVIgABvBFYojJVYQ==",
|
||||
"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"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"release": "11",
|
||||
"release": "12",
|
||||
"name": "wg-easy",
|
||||
"version": "1.0.1",
|
||||
"description": "The easiest way to run WireGuard VPN + Web-based Admin UI.",
|
||||
|
@ -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",
|
||||
"h3": "^1.11.1",
|
||||
"qrcode": "^1.5.3",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
@ -832,6 +852,10 @@ video {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.min-w-20 {
|
||||
min-width: 5rem;
|
||||
}
|
||||
|
||||
.max-w-3xl {
|
||||
max-width: 48rem;
|
||||
}
|
||||
|
@ -844,6 +868,10 @@ video {
|
|||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
@ -917,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;
|
||||
}
|
||||
|
@ -931,6 +971,10 @@ video {
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.whitespace-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
@ -1097,6 +1141,11 @@ video {
|
|||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.py-5 {
|
||||
padding-top: 1.25rem;
|
||||
padding-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.pb-1 {
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
@ -1105,10 +1154,6 @@ video {
|
|||
padding-bottom: 3rem;
|
||||
}
|
||||
|
||||
.pb-2 {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pb-20 {
|
||||
padding-bottom: 5rem;
|
||||
}
|
||||
|
@ -1342,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;
|
||||
}
|
||||
|
@ -1413,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;
|
||||
|
@ -1480,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;
|
||||
}
|
||||
|
@ -1524,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 {
|
||||
|
@ -1536,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) {
|
||||
|
|
|
@ -18,11 +18,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"
|
||||
|
@ -90,11 +87,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>
|
||||
|
@ -109,52 +109,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"
|
||||
|
@ -163,39 +137,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">
|
||||
|
@ -215,9 +255,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"
|
||||
|
@ -226,9 +271,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"
|
||||
|
@ -510,4 +562,4 @@
|
|||
<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: {
|
||||
|
@ -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: 'บริจาค',
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue