Merge commit '75df17476fac7c8eca6da0a69496f4a4c63ae567' into prometheus-metrics
This commit is contained in:
@@ -3,7 +3,7 @@ name: 🐛 Bug Report
|
||||
description: Create a report to help us improve
|
||||
title: "[Bug]: "
|
||||
labels:
|
||||
- "bug"
|
||||
- "type: bug"
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`|
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
+28
-7
@@ -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') {
|
||||
|
||||
+35
-3
@@ -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();
|
||||
if (WG_ENABLE_EXPIRES_TIME === 'true') {
|
||||
let needSaveConfig = false;
|
||||
// Expires Feature
|
||||
if (WG_ENABLE_EXPIRES_TIME === 'true') {
|
||||
for (const client of Object.values(config.clients)) {
|
||||
if (client.enabled !== true) continue;
|
||||
if (client.expiredAt !== null && new Date() > new Date(client.expiredAt)) {
|
||||
@@ -394,11 +414,23 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
|
||||
client.updatedAt = new Date();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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() {
|
||||
const clients = await this.getClients();
|
||||
|
||||
+18
-2
@@ -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
|
||||
|
||||
+9
-2
@@ -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',
|
||||
|
||||
+9
-4
@@ -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()
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user