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
title: "[Bug]: "
labels:
- "bug"
- "type: bug"
body:
- 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
title: "[Feat]: "
labels:
- "enhancement"
- "type: feature request"
body:
- 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
* Multilanguage Support
* UI_TRAFFIC_STATS (default off)
* UI_SHOW_LINKS (default off)
* WG_ENABLE_ONE_TIME_LINKS (default off)
* WG_ENABLE_EXPIRES_TIME (default off)
* 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). |
| `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_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. |
| `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`|

2
docker-compose.yml

@ -27,7 +27,7 @@ services:
# - WG_POST_DOWN=echo "Post Down" > /etc/wireguard/post-down.txt
# - UI_TRAFFIC_STATS=true
# - 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
# - WG_ENABLE_EXPIRES_TIME=true
# - 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.UI_TRAFFIC_STATS = process.env.UI_TRAFFIC_STATS || 'false';
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.WG_ENABLE_EXPIRES_TIME = process.env.WG_ENABLE_EXPIRES_TIME || 'false';
module.exports.ENABLE_PROMETHEUS_METRICS = process.env.ENABLE_PROMETHEUS_METRICS || 'true';

35
src/lib/Server.js

@ -33,7 +33,7 @@ const {
LANG,
UI_TRAFFIC_STATS,
UI_CHART_TYPE,
UI_SHOW_LINKS,
WG_ENABLE_ONE_TIME_LINKS,
UI_ENABLE_SORT_CLIENTS,
WG_ENABLE_EXPIRES_TIME,
ENABLE_PROMETHEUS_METRICS,
@ -107,9 +107,9 @@ module.exports = class Server {
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');
return `${UI_SHOW_LINKS}`;
return `${WG_ENABLE_ONE_TIME_LINKS}`;
}))
.get('/api/ui-sort-clients', defineEventHandler((event) => {
@ -133,14 +133,21 @@ module.exports = class Server {
authenticated,
};
}))
.get('/:clientHash', defineEventHandler(async (event) => {
const clientHash = getRouterParam(event, 'clientHash');
.get('/cnf/:clientOneTimeLink', defineEventHandler(async (event) => {
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 client = clients.find((client) => client.hash === clientHash);
const client = clients.find((client) => client.oneTimeLink === clientOneTimeLink);
if (!client) return;
const clientId = client.id;
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');
return config;
}))
@ -253,6 +260,20 @@ module.exports = class Server {
await WireGuard.enableClient({ clientId });
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) => {
const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {

40
src/lib/WireGuard.js

@ -25,6 +25,7 @@ const {
WG_PRE_DOWN,
WG_POST_DOWN,
WG_ENABLE_EXPIRES_TIME,
WG_ENABLE_ONE_TIME_LINKS,
} = require('../config');
module.exports = class WireGuard {
@ -152,7 +153,8 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
? new Date(client.expiredAt)
: null,
allowedIPs: client.allowedIPs,
hash: Math.abs(CRC32.str(clientId)).toString(16),
oneTimeLink: client.oneTimeLink ?? null,
oneTimeLinkExpiresAt: client.oneTimeLinkExpiresAt ?? null,
downloadableConfig: 'privateKey' in client,
persistentKeepalive: null,
latestHandshakeAt: null,
@ -308,6 +310,23 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
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 }) {
const client = await this.getClient({ clientId });
@ -383,8 +402,9 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
async cronJobEveryMinute() {
const config = await this.getConfig();
let needSaveConfig = false;
// Expires Feature
if (WG_ENABLE_EXPIRES_TIME === 'true') {
let needSaveConfig = false;
for (const client of Object.values(config.clients)) {
if (client.enabled !== true) continue;
if (client.expiredAt !== null && new Date() > new Date(client.expiredAt)) {
@ -394,10 +414,22 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
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() {

20
src/www/index.html

@ -256,8 +256,8 @@
{{!uiTrafficStats ? " · " : ""}}{{new Date(client.latestHandshakeAt) | timeago}}
</span>
</div>
<div v-if="uiShowLinks" :ref="'client-' + client.id + '-hash'" class="text-gray-400 text-xs">
<a :href="'./' + client.hash + ''">{{document.location.protocol}}//{{document.location.host}}/{{client.hash}}</a>
<div v-if="enableOneTimeLinks && client.oneTimeLink !== null && client.oneTimeLink !== ''" :ref="'client-' + client.id + '-link'" class="text-gray-400 text-xs">
<a :href="'./cnf/' + client.oneTimeLink + ''">{{document.location.protocol}}//{{document.location.host}}/cnf/{{client.oneTimeLink}}</a>
</div>
<!-- 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">
@ -384,6 +384,22 @@
</svg>
</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 -->
<button

11
src/www/js/api.js

@ -64,10 +64,10 @@ class API {
});
}
async getUIShowLinks() {
async getWGEnableOneTimeLinks() {
return this.call({
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 }) {
return this.call({
method: 'post',

13
src/www/js/app.js

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

2
src/www/js/i18n.js

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

Loading…
Cancel
Save