Browse Source

Merge commit '75df17476fac7c8eca6da0a69496f4a4c63ae567' into prometheus-metrics

pull/1299/head
Vadim Babadzhanyan 9 months ago
parent
commit
4edaec839d
  1. 2
      .github/ISSUE_TEMPLATE/01-bug-report.yml
  2. 2
      .github/ISSUE_TEMPLATE/02-feature-request.yml
  3. 4
      README.md
  4. 2
      docker-compose.yml
  5. 2
      src/config.js
  6. 35
      src/lib/Server.js
  7. 40
      src/lib/WireGuard.js
  8. 20
      src/www/index.html
  9. 11
      src/www/js/api.js
  10. 13
      src/www/js/app.js
  11. 2
      src/www/js/i18n.js

2
.github/ISSUE_TEMPLATE/01-bug-report.yml

@ -3,7 +3,7 @@ name: 🐛 Bug Report
description: Create a report to help us improve description: Create a report to help us improve
title: "[Bug]: " title: "[Bug]: "
labels: labels:
- "bug" - "type: bug"
body: body:
- type: markdown - type: markdown

2
.github/ISSUE_TEMPLATE/02-feature-request.yml

@ -3,7 +3,7 @@ name: 🛠️ Feature Request
description: Suggest an idea to help us improve description: Suggest an idea to help us improve
title: "[Feat]: " title: "[Feat]: "
labels: labels:
- "enhancement" - "type: feature request"
body: body:
- type: markdown - type: markdown

4
README.md

@ -24,7 +24,7 @@ You have found the easiest way to install & manage WireGuard on any Linux host!
* Automatic Light / Dark Mode * Automatic Light / Dark Mode
* Multilanguage Support * Multilanguage Support
* UI_TRAFFIC_STATS (default off) * UI_TRAFFIC_STATS (default off)
* UI_SHOW_LINKS (default off) * WG_ENABLE_ONE_TIME_LINKS (default off)
* WG_ENABLE_EXPIRES_TIME (default off) * WG_ENABLE_EXPIRES_TIME (default off)
* Prometheus metrics support * Prometheus metrics support
@ -126,7 +126,7 @@ These options can be configured by setting environment variables using `-e KEY="
| `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, hi). | | `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, hi). |
| `UI_TRAFFIC_STATS` | `false` | `true` | Enable detailed RX / TX client stats in Web UI | | `UI_TRAFFIC_STATS` | `false` | `true` | Enable detailed RX / TX client stats in Web UI |
| `UI_CHART_TYPE` | `0` | `1` | UI_CHART_TYPE=0 # Charts disabled, UI_CHART_TYPE=1 # Line chart, UI_CHART_TYPE=2 # Area chart, UI_CHART_TYPE=3 # Bar chart | | `UI_CHART_TYPE` | `0` | `1` | UI_CHART_TYPE=0 # Charts disabled, UI_CHART_TYPE=1 # Line chart, UI_CHART_TYPE=2 # Area chart, UI_CHART_TYPE=3 # Bar chart |
| `UI_SHOW_LINKS` | `false` | `true` | Enable display of a short download link in Web UI | | `WG_ENABLE_ONE_TIME_LINKS` | `false` | `true` | Enable display and generation of short one time download links (expire after 5 minutes) |
| `MAX_AGE` | `0` | `1440` | The maximum age of Web UI sessions in minutes. `0` means that the session will exist until the browser is closed. | | `MAX_AGE` | `0` | `1440` | The maximum age of Web UI sessions in minutes. `0` means that the session will exist until the browser is closed. |
| `UI_ENABLE_SORT_CLIENTS` | `false` | `true` | Enable UI sort clients by name | | `UI_ENABLE_SORT_CLIENTS` | `false` | `true` | Enable UI sort clients by name |
| `ENABLE_PROMETHEUS_METRICS` | `true` | `true` | Enable Prometheus metrics `http://0.0.0.0:51821/metrics` and `http://0.0.0.0:51821/metrics/json`| | `ENABLE_PROMETHEUS_METRICS` | `true` | `true` | Enable Prometheus metrics `http://0.0.0.0:51821/metrics` and `http://0.0.0.0:51821/metrics/json`|

2
docker-compose.yml

@ -27,7 +27,7 @@ services:
# - WG_POST_DOWN=echo "Post Down" > /etc/wireguard/post-down.txt # - WG_POST_DOWN=echo "Post Down" > /etc/wireguard/post-down.txt
# - UI_TRAFFIC_STATS=true # - UI_TRAFFIC_STATS=true
# - UI_CHART_TYPE=0 # (0 Charts disabled, 1 # Line chart, 2 # Area chart, 3 # Bar chart) # - UI_CHART_TYPE=0 # (0 Charts disabled, 1 # Line chart, 2 # Area chart, 3 # Bar chart)
# - UI_SHOW_LINKS=true # - WG_ENABLE_ONE_TIME_LINKS=true
# - UI_ENABLE_SORT_CLIENTS=true # - UI_ENABLE_SORT_CLIENTS=true
# - WG_ENABLE_EXPIRES_TIME=true # - WG_ENABLE_EXPIRES_TIME=true
# - ENABLE_PROMETHEUS_METRICS=false # - ENABLE_PROMETHEUS_METRICS=false

2
src/config.js

@ -38,7 +38,7 @@ iptables -D FORWARD -o wg0 -j ACCEPT;
module.exports.LANG = process.env.LANG || 'en'; module.exports.LANG = process.env.LANG || 'en';
module.exports.UI_TRAFFIC_STATS = process.env.UI_TRAFFIC_STATS || 'false'; module.exports.UI_TRAFFIC_STATS = process.env.UI_TRAFFIC_STATS || 'false';
module.exports.UI_CHART_TYPE = process.env.UI_CHART_TYPE || 0; module.exports.UI_CHART_TYPE = process.env.UI_CHART_TYPE || 0;
module.exports.UI_SHOW_LINKS = process.env.UI_SHOW_LINKS || 'false'; module.exports.WG_ENABLE_ONE_TIME_LINKS = process.env.WG_ENABLE_ONE_TIME_LINKS || 'false';
module.exports.UI_ENABLE_SORT_CLIENTS = process.env.UI_ENABLE_SORT_CLIENTS || 'false'; module.exports.UI_ENABLE_SORT_CLIENTS = process.env.UI_ENABLE_SORT_CLIENTS || 'false';
module.exports.WG_ENABLE_EXPIRES_TIME = process.env.WG_ENABLE_EXPIRES_TIME || 'false'; module.exports.WG_ENABLE_EXPIRES_TIME = process.env.WG_ENABLE_EXPIRES_TIME || 'false';
module.exports.ENABLE_PROMETHEUS_METRICS = process.env.ENABLE_PROMETHEUS_METRICS || 'true'; module.exports.ENABLE_PROMETHEUS_METRICS = process.env.ENABLE_PROMETHEUS_METRICS || 'true';

35
src/lib/Server.js

@ -33,7 +33,7 @@ const {
LANG, LANG,
UI_TRAFFIC_STATS, UI_TRAFFIC_STATS,
UI_CHART_TYPE, UI_CHART_TYPE,
UI_SHOW_LINKS, WG_ENABLE_ONE_TIME_LINKS,
UI_ENABLE_SORT_CLIENTS, UI_ENABLE_SORT_CLIENTS,
WG_ENABLE_EXPIRES_TIME, WG_ENABLE_EXPIRES_TIME,
ENABLE_PROMETHEUS_METRICS, ENABLE_PROMETHEUS_METRICS,
@ -107,9 +107,9 @@ module.exports = class Server {
return `"${UI_CHART_TYPE}"`; return `"${UI_CHART_TYPE}"`;
})) }))
.get('/api/ui-show-links', defineEventHandler((event) => { .get('/api/wg-enable-one-time-links', defineEventHandler((event) => {
setHeader(event, 'Content-Type', 'application/json'); setHeader(event, 'Content-Type', 'application/json');
return `${UI_SHOW_LINKS}`; return `${WG_ENABLE_ONE_TIME_LINKS}`;
})) }))
.get('/api/ui-sort-clients', defineEventHandler((event) => { .get('/api/ui-sort-clients', defineEventHandler((event) => {
@ -133,14 +133,21 @@ module.exports = class Server {
authenticated, authenticated,
}; };
})) }))
.get('/:clientHash', defineEventHandler(async (event) => { .get('/cnf/:clientOneTimeLink', defineEventHandler(async (event) => {
const clientHash = getRouterParam(event, 'clientHash'); if (WG_ENABLE_ONE_TIME_LINKS === 'false') {
throw createError({
status: 404,
message: 'Invalid state',
});
}
const clientOneTimeLink = getRouterParam(event, 'clientOneTimeLink');
const clients = await WireGuard.getClients(); const clients = await WireGuard.getClients();
const client = clients.find((client) => client.hash === clientHash); const client = clients.find((client) => client.oneTimeLink === clientOneTimeLink);
if (!client) return; if (!client) return;
const clientId = client.id; const clientId = client.id;
const config = await WireGuard.getClientConfiguration({ clientId }); const config = await WireGuard.getClientConfiguration({ clientId });
setHeader(event, 'Content-Disposition', `attachment; filename="${clientHash}.conf"`); await WireGuard.eraseOneTimeLink({ clientId });
setHeader(event, 'Content-Disposition', `attachment; filename="${clientOneTimeLink}.conf"`);
setHeader(event, 'Content-Type', 'text/plain'); setHeader(event, 'Content-Type', 'text/plain');
return config; return config;
})) }))
@ -253,6 +260,20 @@ module.exports = class Server {
await WireGuard.enableClient({ clientId }); await WireGuard.enableClient({ clientId });
return { success: true }; return { success: true };
})) }))
.post('/api/wireguard/client/:clientId/generateOneTimeLink', defineEventHandler(async (event) => {
if (WG_ENABLE_ONE_TIME_LINKS === 'false') {
throw createError({
status: 404,
message: 'Invalid state',
});
}
const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {
throw createError({ status: 403 });
}
await WireGuard.generateOneTimeLink({ clientId });
return { success: true };
}))
.post('/api/wireguard/client/:clientId/disable', defineEventHandler(async (event) => { .post('/api/wireguard/client/:clientId/disable', defineEventHandler(async (event) => {
const clientId = getRouterParam(event, 'clientId'); const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') { if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {

40
src/lib/WireGuard.js

@ -25,6 +25,7 @@ const {
WG_PRE_DOWN, WG_PRE_DOWN,
WG_POST_DOWN, WG_POST_DOWN,
WG_ENABLE_EXPIRES_TIME, WG_ENABLE_EXPIRES_TIME,
WG_ENABLE_ONE_TIME_LINKS,
} = require('../config'); } = require('../config');
module.exports = class WireGuard { module.exports = class WireGuard {
@ -152,7 +153,8 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
? new Date(client.expiredAt) ? new Date(client.expiredAt)
: null, : null,
allowedIPs: client.allowedIPs, allowedIPs: client.allowedIPs,
hash: Math.abs(CRC32.str(clientId)).toString(16), oneTimeLink: client.oneTimeLink ?? null,
oneTimeLinkExpiresAt: client.oneTimeLinkExpiresAt ?? null,
downloadableConfig: 'privateKey' in client, downloadableConfig: 'privateKey' in client,
persistentKeepalive: null, persistentKeepalive: null,
latestHandshakeAt: null, latestHandshakeAt: null,
@ -308,6 +310,23 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
await this.saveConfig(); await this.saveConfig();
} }
async generateOneTimeLink({ clientId }) {
const client = await this.getClient({ clientId });
const key = `${clientId}-${Math.floor(Math.random() * 1000)}`;
client.oneTimeLink = Math.abs(CRC32.str(key)).toString(16);
client.oneTimeLinkExpiresAt = new Date(Date.now() + 5 * 60 * 1000);
client.updatedAt = new Date();
await this.saveConfig();
}
async eraseOneTimeLink({ clientId }) {
const client = await this.getClient({ clientId });
client.oneTimeLink = null;
client.oneTimeLinkExpiresAt = null;
client.updatedAt = new Date();
await this.saveConfig();
}
async disableClient({ clientId }) { async disableClient({ clientId }) {
const client = await this.getClient({ clientId }); const client = await this.getClient({ clientId });
@ -383,8 +402,9 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
async cronJobEveryMinute() { async cronJobEveryMinute() {
const config = await this.getConfig(); const config = await this.getConfig();
let needSaveConfig = false;
// Expires Feature
if (WG_ENABLE_EXPIRES_TIME === 'true') { if (WG_ENABLE_EXPIRES_TIME === 'true') {
let needSaveConfig = false;
for (const client of Object.values(config.clients)) { for (const client of Object.values(config.clients)) {
if (client.enabled !== true) continue; if (client.enabled !== true) continue;
if (client.expiredAt !== null && new Date() > new Date(client.expiredAt)) { if (client.expiredAt !== null && new Date() > new Date(client.expiredAt)) {
@ -394,10 +414,22 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
client.updatedAt = new Date(); client.updatedAt = new Date();
} }
} }
if (needSaveConfig) { }
await this.saveConfig(); // One Time Link Feature
if (WG_ENABLE_ONE_TIME_LINKS === 'true') {
for (const client of Object.values(config.clients)) {
if (client.oneTimeLink !== null && new Date() > new Date(client.oneTimeLinkExpiresAt)) {
debug(`Client ${client.id} One Time Link expired.`);
needSaveConfig = true;
client.oneTimeLink = null;
client.oneTimeLinkExpiresAt = null;
client.updatedAt = new Date();
}
} }
} }
if (needSaveConfig) {
await this.saveConfig();
}
} }
async getMetrics() { async getMetrics() {

20
src/www/index.html

@ -256,8 +256,8 @@
{{!uiTrafficStats ? " · " : ""}}{{new Date(client.latestHandshakeAt) | timeago}} {{!uiTrafficStats ? " · " : ""}}{{new Date(client.latestHandshakeAt) | timeago}}
</span> </span>
</div> </div>
<div v-if="uiShowLinks" :ref="'client-' + client.id + '-hash'" class="text-gray-400 text-xs"> <div v-if="enableOneTimeLinks && client.oneTimeLink !== null && client.oneTimeLink !== ''" :ref="'client-' + client.id + '-link'" class="text-gray-400 text-xs">
<a :href="'./' + client.hash + ''">{{document.location.protocol}}//{{document.location.host}}/{{client.hash}}</a> <a :href="'./cnf/' + client.oneTimeLink + ''">{{document.location.protocol}}//{{document.location.host}}/cnf/{{client.oneTimeLink}}</a>
</div> </div>
<!-- Expire Date --> <!-- Expire Date -->
<div v-show="enableExpireTime" class=" block md:inline-block pb-1 md:pb-0 text-gray-500 dark:text-neutral-400 text-xs"> <div v-show="enableExpireTime" class=" block md:inline-block pb-1 md:pb-0 text-gray-500 dark:text-neutral-400 text-xs">
@ -384,6 +384,22 @@
</svg> </svg>
</a> </a>
<!-- Short OneTime Link -->
<button v-if="enableOneTimeLinks" :disabled="!client.downloadableConfig"
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('OneTimeLink')"
@click="if(client.downloadableConfig) { showOneTimeLink(client); }">
<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"
d="M13.213 9.787a3.391 3.391 0 0 0-4.795 0l-3.425 3.426a3.39 3.39 0 0 0 4.795 4.794l.321-.304m-.321-4.49a3.39 3.39 0 0 0 4.795 0l3.424-3.426a3.39 3.39 0 0 0-4.794-4.795l-1.028.961"/>
</svg>
</button>
<!-- Delete --> <!-- Delete -->
<button <button

11
src/www/js/api.js

@ -64,10 +64,10 @@ class API {
}); });
} }
async getUIShowLinks() { async getWGEnableOneTimeLinks() {
return this.call({ return this.call({
method: 'get', method: 'get',
path: '/ui-show-links', path: '/wg-enable-one-time-links',
}); });
} }
@ -132,6 +132,13 @@ class API {
}); });
} }
async showOneTimeLink({ clientId }) {
return this.call({
method: 'post',
path: `/wireguard/client/${clientId}/generateOneTimeLink`,
});
}
async enableClient({ clientId }) { async enableClient({ clientId }) {
return this.call({ return this.call({
method: 'post', method: 'post',

13
src/www/js/app.js

@ -92,7 +92,7 @@ new Vue({
uiTrafficStats: false, uiTrafficStats: false,
uiChartType: 0, uiChartType: 0,
uiShowLinks: false, enableOneTimeLinks: false,
enableSortClient: false, enableSortClient: false,
sortClient: true, // Sort clients by name, true = asc, false = desc sortClient: true, // Sort clients by name, true = asc, false = desc
enableExpireTime: false, enableExpireTime: false,
@ -312,6 +312,11 @@ new Vue({
.catch((err) => alert(err.message || err.toString())) .catch((err) => alert(err.message || err.toString()))
.finally(() => this.refresh().catch(console.error)); .finally(() => this.refresh().catch(console.error));
}, },
showOneTimeLink(client) {
this.api.showOneTimeLink({ clientId: client.id })
.catch((err) => alert(err.message || err.toString()))
.finally(() => this.refresh().catch(console.error));
},
enableClient(client) { enableClient(client) {
this.api.enableClient({ clientId: client.id }) this.api.enableClient({ clientId: client.id })
.catch((err) => alert(err.message || err.toString())) .catch((err) => alert(err.message || err.toString()))
@ -436,12 +441,12 @@ new Vue({
this.uiChartType = 0; this.uiChartType = 0;
}); });
this.api.getUIShowLinks() this.api.getWGEnableOneTimeLinks()
.then((res) => { .then((res) => {
this.uiShowLinks = res; this.enableOneTimeLinks = res;
}) })
.catch(() => { .catch(() => {
this.uiShowLinks = false; this.enableOneTimeLinks = false;
}); });
this.api.getUiSortClients() this.api.getUiSortClients()

2
src/www/js/i18n.js

@ -39,6 +39,7 @@ const messages = { // eslint-disable-line no-unused-vars
sort: 'Sort', sort: 'Sort',
ExpireDate: 'Expire Date', ExpireDate: 'Expire Date',
Permanent: 'Permanent', Permanent: 'Permanent',
OneTimeLink: 'Generate short one time link',
}, },
ua: { ua: {
name: 'Ім`я', name: 'Ім`я',
@ -112,6 +113,7 @@ const messages = { // eslint-disable-line no-unused-vars
sort: 'Сортировка', sort: 'Сортировка',
ExpireDate: 'Дата истечения срока', ExpireDate: 'Дата истечения срока',
Permanent: 'Бессрочно', Permanent: 'Бессрочно',
OneTimeLink: 'Создать короткую одноразовую ссылку',
}, },
tr: { // Müslüm Barış Korkmazer @babico tr: { // Müslüm Barış Korkmazer @babico
name: 'İsim', name: 'İsim',

Loading…
Cancel
Save