From 3cb83dc53aa5a401f689319d10ceab69333af85d Mon Sep 17 00:00:00 2001 From: Philip H <47042125+pheiduck@users.noreply.github.com> Date: Tue, 12 Mar 2024 19:07:42 +0100 Subject: [PATCH 01/11] deploy.yml: rebuild v11 mistake --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 332b3a76..1d73dcfc 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: production + ref: 80310bfd95cd023db30ed11152bbef5fc7c4436a - name: Set up QEMU uses: docker/setup-qemu-action@v3 From 8ce2d44c0873c641c451f3c8172ae201cbe03fd4 Mon Sep 17 00:00:00 2001 From: Philip H <47042125+pheiduck@users.noreply.github.com> Date: Tue, 12 Mar 2024 19:21:25 +0100 Subject: [PATCH 02/11] Update deploy.yml --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1d73dcfc..332b3a76 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: 80310bfd95cd023db30ed11152bbef5fc7c4436a + ref: production - name: Set up QEMU uses: docker/setup-qemu-action@v3 From 3d117309263d93397a84f46afa14b7ef747aec5d Mon Sep 17 00:00:00 2001 From: Sergei Birukov Date: Wed, 13 Mar 2024 12:15:50 +0300 Subject: [PATCH 03/11] Fix traffic charts. Add chart vars Add UI_TRAFFIC_STATS, UI_CHART_TYPE --- src/.env | 3 ++ src/config.js | 1 + src/lib/Server.js | 4 ++ src/www/index.html | 8 ++-- src/www/js/api.js | 7 +++ src/www/js/app.js | 111 ++++++++++++++++++++++++++++++++++++++------- 6 files changed, 113 insertions(+), 21 deletions(-) create mode 100644 src/.env diff --git a/src/.env b/src/.env new file mode 100644 index 00000000..122fe628 --- /dev/null +++ b/src/.env @@ -0,0 +1,3 @@ +WG_HOST=127.0.0.1 +UI_TRAFFIC_STATS=true +UI_CHART_TYPE=2 \ No newline at end of file diff --git a/src/config.js b/src/config.js index a8fe2c38..1fff4c27 100644 --- a/src/config.js +++ b/src/config.js @@ -35,3 +35,4 @@ 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'; +module.exports.UI_CHART_TYPE = process.env.UI_CHART_TYPE || 0; diff --git a/src/lib/Server.js b/src/lib/Server.js index bf8c50e6..901f1a05 100644 --- a/src/lib/Server.js +++ b/src/lib/Server.js @@ -19,6 +19,7 @@ const { PASSWORD, LANG, UI_TRAFFIC_STATS, + UI_CHART_TYPE, } = require('../config'); module.exports = class Server { @@ -45,6 +46,9 @@ module.exports = class Server { .get('/api/ui-traffic-stats', (Util.promisify(async () => { return UI_TRAFFIC_STATS === 'true'; }))) + .get('/api/ui-chart-type', (Util.promisify(async () => { + return UI_CHART_TYPE || 0; + }))) // Authentication .get('/api/session', Util.promisify(async (req) => { diff --git a/src/www/index.html b/src/www/index.html index 826cf526..25173644 100644 --- a/src/www/index.html +++ b/src/www/index.html @@ -77,12 +77,12 @@ class="relative overflow-hidden border-b last:border-b-0 border-gray-100 dark:border-neutral-600 border-solid"> -
- +
+
-
- +
diff --git a/src/www/js/api.js b/src/www/js/api.js index e69e5a3a..356164c5 100644 --- a/src/www/js/api.js +++ b/src/www/js/api.js @@ -50,6 +50,13 @@ class API { }); } + async getChartType() { + return this.call({ + method: 'get', + path: '/ui-chart-type', + }); + } + async getSession() { return this.call({ method: 'get', diff --git a/src/www/js/app.js b/src/www/js/app.js index 22fe3bfc..23252663 100644 --- a/src/www/js/app.js +++ b/src/www/js/app.js @@ -23,14 +23,33 @@ function bytes(bytes, decimals, kib, maxunit) { return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; } +const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); +const theme = darkModeMediaQuery.matches ? 'dark' : 'light'; + const i18n = new VueI18n({ locale: localStorage.getItem('lang') || 'en', fallbackLocale: 'en', messages, }); +const UI_CHART_TYPES = [ + { type: false, strokeWidth: 0 }, + { type: 'line', strokeWidth: 3 }, + { type: 'area', strokeWidth: 0 }, + { type: 'bar', strokeWidth: 0 }, +] + +const CHART_COLORS = { + rx: { light: 'rgba(0,0,0,0.2)', dark: 'rgba(255,255,255,0.3)' }, + tx: { light: 'rgba(0,0,0,0.3)', dark: 'rgba(255,255,255,0.5)' }, + gradient: {light: ['rgba(0,0,0,0.1)', 'rgba(0,0,0,0)'], dark: ['rgba(255,255,255,0.1)', 'rgba(255,255,255,0)']}, +} + new Vue({ el: '#app', + components: { + apexchart: VueApexCharts, + }, i18n, data: { authenticated: null, @@ -55,10 +74,11 @@ new Vue({ isDark: null, uiTrafficStats: false, + uiChartType: 0, + chartOptions: { chart: { background: 'transparent', - type: 'bar', stacked: false, toolbar: { show: false, @@ -67,10 +87,23 @@ new Vue({ enabled: false, }, }, - colors: [ - '#DDDDDD', // rx - '#EEEEEE', // tx - ], + colors: [], + stroke: { + curve: 'smooth', + }, + fill: { + type: 'gradient', + gradient: { + shade: 'dark', + type: 'vertical', + shadeIntensity: 1, + gradientToColors: CHART_COLORS.gradient[theme], + inverseColors: true, + opacityFrom: 1, + opacityTo: 1, + stops: [0, 100], + }, + }, dataLabels: { enabled: false, }, @@ -135,7 +168,7 @@ new Vue({ updateCharts = false, } = {}) { if (!this.authenticated) return; - + const clients = await this.api.getClients(); this.clients = clients.map((client) => { if (client.name.includes('@') && client.name.includes('.')) { @@ -154,26 +187,40 @@ new Vue({ // client.transferRx = this.clientsPersist[client.id].transferRxPrevious + Math.random() * 1000; // client.transferTx = this.clientsPersist[client.id].transferTxPrevious + Math.random() * 1000; + this.clientsPersist[client.id].transferRxCurrent = client.transferRx - this.clientsPersist[client.id].transferRxPrevious; + this.clientsPersist[client.id].transferRxPrevious = client.transferRx; + this.clientsPersist[client.id].transferTxCurrent = client.transferTx - this.clientsPersist[client.id].transferTxPrevious; + this.clientsPersist[client.id].transferTxPrevious = client.transferTx; + if (updateCharts) { - this.clientsPersist[client.id].transferRxCurrent = client.transferRx - this.clientsPersist[client.id].transferRxPrevious; - this.clientsPersist[client.id].transferRxPrevious = client.transferRx; - this.clientsPersist[client.id].transferTxCurrent = client.transferTx - this.clientsPersist[client.id].transferTxPrevious; - this.clientsPersist[client.id].transferTxPrevious = client.transferTx; this.clientsPersist[client.id].transferRxHistory.push(this.clientsPersist[client.id].transferRxCurrent); this.clientsPersist[client.id].transferRxHistory.shift(); this.clientsPersist[client.id].transferTxHistory.push(this.clientsPersist[client.id].transferTxCurrent); this.clientsPersist[client.id].transferTxHistory.shift(); + + this.clientsPersist[client.id].transferTxSeries = [{ + name: 'Tx', + data: this.clientsPersist[client.id].transferTxHistory, + }]; + + this.clientsPersist[client.id].transferRxSeries = [{ + name: 'Rx', + data: this.clientsPersist[client.id].transferRxHistory, + }]; + + client.transferTxHistory = this.clientsPersist[client.id].transferTxHistory; + client.transferRxHistory = this.clientsPersist[client.id].transferRxHistory; + client.transferMax = Math.max(...client.transferTxHistory, ...client.transferRxHistory); + + client.transferTxSeries = this.clientsPersist[client.id].transferTxSeries; + client.transferRxSeries = this.clientsPersist[client.id].transferRxSeries; } client.transferTxCurrent = this.clientsPersist[client.id].transferTxCurrent; client.transferRxCurrent = this.clientsPersist[client.id].transferRxCurrent; - client.transferTxHistory = this.clientsPersist[client.id].transferTxHistory; - client.transferRxHistory = this.clientsPersist[client.id].transferRxHistory; - client.transferMax = Math.max(...client.transferTxHistory, ...client.transferRxHistory); - client.hoverTx = this.clientsPersist[client.id].hoverTx; client.hoverRx = this.clientsPersist[client.id].hoverRx; @@ -278,7 +325,7 @@ new Vue({ this.authenticated = session.authenticated; this.requiresPassword = session.requiresPassword; this.refresh({ - updateCharts: true, + updateCharts: this.updateCharts, }).catch((err) => { alert(err.message || err.toString()); }); @@ -289,7 +336,7 @@ new Vue({ setInterval(() => { this.refresh({ - updateCharts: true, + updateCharts: this.updateCharts, }).catch(console.error); }, 1000); @@ -298,9 +345,16 @@ new Vue({ this.uiTrafficStats = res; }) .catch(() => { - console.log('Failed to get ui-traffic-stats'); this.uiTrafficStats = false; }); + + this.api.getChartType() + .then((res) => { + this.uiChartType = parseInt(res); + }) + .catch(() => { + this.uiChartType = 0; + }); Promise.resolve().then(async () => { const lang = await this.api.getLang(); @@ -333,4 +387,27 @@ new Vue({ this.latestRelease = latestRelease; }).catch((err) => console.error(err)); }, + computed: { + chartOptionsTX() { + const opts = { + ...this.chartOptions, + colors: [CHART_COLORS.tx[theme]], + }; + opts.chart.type = UI_CHART_TYPES[this.uiChartType].type || false; + opts.stroke.width = UI_CHART_TYPES[this.uiChartType].strokeWidth; + return opts; + }, + chartOptionsRX() { + const opts = { + ...this.chartOptions, + colors: [CHART_COLORS.rx[theme]], + }; + opts.chart.type = UI_CHART_TYPES[this.uiChartType].type || false; + opts.stroke.width = UI_CHART_TYPES[this.uiChartType].strokeWidth; + return opts; + }, + updateCharts() { + return this.uiChartType > 0; + } + }, }); From 73242c61c382f85bd9bf675305ce7f7536ca4424 Mon Sep 17 00:00:00 2001 From: Sergei Birukov Date: Wed, 13 Mar 2024 12:28:21 +0300 Subject: [PATCH 04/11] Remove .env from repo --- src/.env | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 src/.env diff --git a/src/.env b/src/.env deleted file mode 100644 index 122fe628..00000000 --- a/src/.env +++ /dev/null @@ -1,3 +0,0 @@ -WG_HOST=127.0.0.1 -UI_TRAFFIC_STATS=true -UI_CHART_TYPE=2 \ No newline at end of file From 166a58a68513734ae5d1191c33bb9f760d84d5d6 Mon Sep 17 00:00:00 2001 From: Sergei Birukov Date: Wed, 13 Mar 2024 13:24:24 +0300 Subject: [PATCH 05/11] Fix charts on mobile --- src/www/css/app.css | 10 +++++----- src/www/index.html | 6 +++--- src/www/js/app.js | 13 +++++++++---- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/www/css/app.css b/src/www/css/app.css index 00837d4c..9c6c85fd 100644 --- a/src/www/css/app.css +++ b/src/www/css/app.css @@ -1141,11 +1141,6 @@ video { padding-bottom: 0.75rem; } -.py-5 { - padding-top: 1.25rem; - padding-bottom: 1.25rem; -} - .pb-1 { padding-bottom: 0.25rem; } @@ -1597,6 +1592,11 @@ video { padding-right: 0px; } + .md\:py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; + } + .md\:pb-0 { padding-bottom: 0px; } diff --git a/src/www/index.html b/src/www/index.html index 25173644..2b1b7ba8 100644 --- a/src/www/index.html +++ b/src/www/index.html @@ -77,17 +77,17 @@ class="relative overflow-hidden border-b last:border-b-0 border-gray-100 dark:border-neutral-600 border-solid"> -
+
-
+
-
+
diff --git a/src/www/js/app.js b/src/www/js/app.js index 23252663..b0166129 100644 --- a/src/www/js/app.js +++ b/src/www/js/app.js @@ -86,6 +86,10 @@ new Vue({ animations: { enabled: false, }, + parentHeightOffset: 0, + sparkline: { + enabled: true, + } }, colors: [], stroke: { @@ -117,10 +121,10 @@ new Vue({ show: false, }, axisTicks: { - show: true, + show: false, }, axisBorder: { - show: true, + show: false, }, }, yaxis: { @@ -184,8 +188,9 @@ new Vue({ } // Debug - // client.transferRx = this.clientsPersist[client.id].transferRxPrevious + Math.random() * 1000; - // client.transferTx = this.clientsPersist[client.id].transferTxPrevious + Math.random() * 1000; + client.transferRx = this.clientsPersist[client.id].transferRxPrevious + Math.random() * 1000; + client.transferTx = this.clientsPersist[client.id].transferTxPrevious + Math.random() * 1000; + client.latestHandshakeAt = new Date(); this.clientsPersist[client.id].transferRxCurrent = client.transferRx - this.clientsPersist[client.id].transferRxPrevious; this.clientsPersist[client.id].transferRxPrevious = client.transferRx; From ccde2fdfd369e6df964827331fe0b0adc8a8b10a Mon Sep 17 00:00:00 2001 From: Sergei Birukov Date: Wed, 13 Mar 2024 13:26:27 +0300 Subject: [PATCH 06/11] Disable debug --- src/www/js/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/www/js/app.js b/src/www/js/app.js index b0166129..61d1ceeb 100644 --- a/src/www/js/app.js +++ b/src/www/js/app.js @@ -188,9 +188,9 @@ new Vue({ } // Debug - client.transferRx = this.clientsPersist[client.id].transferRxPrevious + Math.random() * 1000; - client.transferTx = this.clientsPersist[client.id].transferTxPrevious + Math.random() * 1000; - client.latestHandshakeAt = new Date(); + // client.transferRx = this.clientsPersist[client.id].transferRxPrevious + Math.random() * 1000; + // client.transferTx = this.clientsPersist[client.id].transferTxPrevious + Math.random() * 1000; + // client.latestHandshakeAt = new Date(); this.clientsPersist[client.id].transferRxCurrent = client.transferRx - this.clientsPersist[client.id].transferRxPrevious; this.clientsPersist[client.id].transferRxPrevious = client.transferRx; From 12b72cf3894dc2ee667b1676ad3ac5915ea96ef8 Mon Sep 17 00:00:00 2001 From: Sergei Birukov Date: Wed, 13 Mar 2024 13:51:32 +0300 Subject: [PATCH 07/11] Fix lint errors --- src/www/js/app.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/www/js/app.js b/src/www/js/app.js index 61d1ceeb..a2536ed6 100644 --- a/src/www/js/app.js +++ b/src/www/js/app.js @@ -37,13 +37,13 @@ const UI_CHART_TYPES = [ { type: 'line', strokeWidth: 3 }, { type: 'area', strokeWidth: 0 }, { type: 'bar', strokeWidth: 0 }, -] +]; const CHART_COLORS = { rx: { light: 'rgba(0,0,0,0.2)', dark: 'rgba(255,255,255,0.3)' }, tx: { light: 'rgba(0,0,0,0.3)', dark: 'rgba(255,255,255,0.5)' }, - gradient: {light: ['rgba(0,0,0,0.1)', 'rgba(0,0,0,0)'], dark: ['rgba(255,255,255,0.1)', 'rgba(255,255,255,0)']}, -} + gradient: { light: ['rgba(0,0,0,0.1)', 'rgba(0,0,0,0)'], dark: ['rgba(255,255,255,0.1)', 'rgba(255,255,255,0)'] }, +}; new Vue({ el: '#app', @@ -89,7 +89,7 @@ new Vue({ parentHeightOffset: 0, sparkline: { enabled: true, - } + }, }, colors: [], stroke: { @@ -107,7 +107,7 @@ new Vue({ opacityTo: 1, stops: [0, 100], }, - }, + }, dataLabels: { enabled: false, }, @@ -172,7 +172,7 @@ new Vue({ updateCharts = false, } = {}) { if (!this.authenticated) return; - + const clients = await this.api.getClients(); this.clients = clients.map((client) => { if (client.name.includes('@') && client.name.includes('.')) { @@ -198,7 +198,6 @@ new Vue({ this.clientsPersist[client.id].transferTxPrevious = client.transferTx; if (updateCharts) { - this.clientsPersist[client.id].transferRxHistory.push(this.clientsPersist[client.id].transferRxCurrent); this.clientsPersist[client.id].transferRxHistory.shift(); @@ -352,10 +351,10 @@ new Vue({ .catch(() => { this.uiTrafficStats = false; }); - + this.api.getChartType() .then((res) => { - this.uiChartType = parseInt(res); + this.uiChartType = parseInt(res, 10); }) .catch(() => { this.uiChartType = 0; @@ -413,6 +412,6 @@ new Vue({ }, updateCharts() { return this.uiChartType > 0; - } + }, }, }); From 4d5a5c9e0d9e7c0f3fb416c6030f95e4f9422b9d Mon Sep 17 00:00:00 2001 From: Philip H <47042125+pheiduck@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:43:51 +0100 Subject: [PATCH 08/11] docker-compose.yml: add UI_CHART_TYPE --- docker-compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index a489356f..e118115f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,8 @@ 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 + # - UI_TRAFFIC_STATS=true + # - UI_CHART_TYPE=0 (0 # Charts disabled, 1 # Line chart, 2 # Area chart, 3 # Bar chart) image: ghcr.io/wg-easy/wg-easy container_name: wg-easy From f3a8ff649071c53e863403d1d5c09cc0137c81d4 Mon Sep 17 00:00:00 2001 From: Sergei Birukov Date: Sun, 17 Mar 2024 11:26:38 +0300 Subject: [PATCH 09/11] Add dark/light mode toggle. Add chart toggle Change dark/light mode detection Add saving uiShowCharts to local storage Add saving uiTheme to local storage Add ui buttons --- src/tailwind.config.js | 2 +- src/www/css/app.css | 391 ++++++++++++++++++++++++----------------- src/www/index.html | 65 +++++-- src/www/js/app.js | 65 ++++--- src/www/js/i18n.js | 2 + 5 files changed, 314 insertions(+), 211 deletions(-) diff --git a/src/tailwind.config.js b/src/tailwind.config.js index 33ba6746..bc7b40da 100644 --- a/src/tailwind.config.js +++ b/src/tailwind.config.js @@ -3,7 +3,7 @@ 'use strict'; module.exports = { - darkMode: 'media', + darkMode: 'selector', content: ['./www/**/*.{html,js}'], theme: { screens: { diff --git a/src/www/css/app.css b/src/www/css/app.css index 9c6c85fd..2564cdd8 100644 --- a/src/www/css/app.css +++ b/src/www/css/app.css @@ -590,6 +590,18 @@ video { } } +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + .visible { visibility: visible; } @@ -692,8 +704,8 @@ video { margin-bottom: 2.5rem; } -.mb-2 { - margin-bottom: 0.5rem; +.mb-4 { + margin-bottom: 1rem; } .mb-5 { @@ -736,6 +748,10 @@ video { margin-top: 1.25rem; } +.mt-6 { + margin-top: 1.5rem; +} + .mt-px { margin-top: 1px; } @@ -804,10 +820,18 @@ video { height: 1rem; } +.h-5 { + height: 1.25rem; +} + .h-6 { height: 1.5rem; } +.h-8 { + height: 2rem; +} + .min-h-screen { min-height: 100vh; } @@ -925,6 +949,10 @@ video { flex-wrap: wrap; } +.items-end { + align-items: flex-end; +} + .items-center { align-items: center; } @@ -1087,6 +1115,14 @@ video { --tw-bg-opacity: 0.5; } +.fill-gray-400 { + fill: #9ca3af; +} + +.fill-gray-600 { + fill: #4b5563; +} + .p-1 { padding: 0.25rem; } @@ -1271,6 +1307,11 @@ video { color: rgb(17 24 39 / var(--tw-text-opacity)); } +.text-neutral-400 { + --tw-text-opacity: 1; + color: rgb(163 163 163 / var(--tw-text-opacity)); +} + .text-red-600 { --tw-text-opacity: 1; color: rgb(220 38 38 / var(--tw-text-opacity)); @@ -1458,6 +1499,10 @@ video { opacity: 1; } +.peer:checked ~ .peer-checked\:fill-gray-600 { + fill: #4b5563; +} + @media (min-width: 450px) { .xxs\:flex-row { flex-direction: row; @@ -1607,208 +1652,222 @@ video { } } -@media (prefers-color-scheme: dark) { - .dark\:border-neutral-500 { - --tw-border-opacity: 1; - border-color: rgb(115 115 115 / var(--tw-border-opacity)); - } +.dark\:border-neutral-500:where(.dark, .dark *) { + --tw-border-opacity: 1; + border-color: rgb(115 115 115 / var(--tw-border-opacity)); +} - .dark\:border-neutral-600 { - --tw-border-opacity: 1; - border-color: rgb(82 82 82 / var(--tw-border-opacity)); - } +.dark\:border-neutral-600:where(.dark, .dark *) { + --tw-border-opacity: 1; + border-color: rgb(82 82 82 / var(--tw-border-opacity)); +} - .dark\:border-neutral-800 { - --tw-border-opacity: 1; - border-color: rgb(38 38 38 / var(--tw-border-opacity)); - } +.dark\:border-neutral-800:where(.dark, .dark *) { + --tw-border-opacity: 1; + border-color: rgb(38 38 38 / var(--tw-border-opacity)); +} - .dark\:border-red-600 { - --tw-border-opacity: 1; - border-color: rgb(220 38 38 / var(--tw-border-opacity)); - } +.dark\:border-red-600:where(.dark, .dark *) { + --tw-border-opacity: 1; + border-color: rgb(220 38 38 / var(--tw-border-opacity)); +} - .dark\:bg-black { - --tw-bg-opacity: 1; - background-color: rgb(0 0 0 / var(--tw-bg-opacity)); - } +.dark\:bg-black:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); +} - .dark\:bg-neutral-400 { - --tw-bg-opacity: 1; - background-color: rgb(163 163 163 / var(--tw-bg-opacity)); - } +.dark\:bg-neutral-400:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(163 163 163 / var(--tw-bg-opacity)); +} - .dark\:bg-neutral-500 { - --tw-bg-opacity: 1; - background-color: rgb(115 115 115 / var(--tw-bg-opacity)); - } +.dark\:bg-neutral-500:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(115 115 115 / var(--tw-bg-opacity)); +} - .dark\:bg-neutral-600 { - --tw-bg-opacity: 1; - background-color: rgb(82 82 82 / var(--tw-bg-opacity)); - } +.dark\:bg-neutral-600:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(82 82 82 / var(--tw-bg-opacity)); +} - .dark\:bg-neutral-700 { - --tw-bg-opacity: 1; - background-color: rgb(64 64 64 / var(--tw-bg-opacity)); - } +.dark\:bg-neutral-700:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(64 64 64 / var(--tw-bg-opacity)); +} - .dark\:bg-neutral-800 { - --tw-bg-opacity: 1; - background-color: rgb(38 38 38 / var(--tw-bg-opacity)); - } +.dark\:bg-neutral-800:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(38 38 38 / var(--tw-bg-opacity)); +} - .dark\:bg-red-100 { - --tw-bg-opacity: 1; - background-color: rgb(254 226 226 / var(--tw-bg-opacity)); - } +.dark\:bg-red-100:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity)); +} - .dark\:bg-red-600 { - --tw-bg-opacity: 1; - background-color: rgb(220 38 38 / var(--tw-bg-opacity)); - } +.dark\:bg-red-600:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(220 38 38 / var(--tw-bg-opacity)); +} - .dark\:bg-red-800 { - --tw-bg-opacity: 1; - background-color: rgb(153 27 27 / var(--tw-bg-opacity)); - } +.dark\:bg-red-800:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(153 27 27 / var(--tw-bg-opacity)); +} - .dark\:text-gray-500 { - --tw-text-opacity: 1; - color: rgb(107 114 128 / var(--tw-text-opacity)); - } +.dark\:fill-neutral-400:where(.dark, .dark *) { + fill: #a3a3a3; +} - .dark\:text-neutral-200 { - --tw-text-opacity: 1; - color: rgb(229 229 229 / var(--tw-text-opacity)); - } +.dark\:fill-neutral-600:where(.dark, .dark *) { + fill: #525252; +} - .dark\:text-neutral-300 { - --tw-text-opacity: 1; - color: rgb(212 212 212 / var(--tw-text-opacity)); - } +.dark\:text-gray-500:where(.dark, .dark *) { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} - .dark\:text-neutral-400 { - --tw-text-opacity: 1; - color: rgb(163 163 163 / var(--tw-text-opacity)); - } +.dark\:text-neutral-200:where(.dark, .dark *) { + --tw-text-opacity: 1; + color: rgb(229 229 229 / var(--tw-text-opacity)); +} - .dark\:text-neutral-50 { - --tw-text-opacity: 1; - color: rgb(250 250 250 / var(--tw-text-opacity)); - } +.dark\:text-neutral-300:where(.dark, .dark *) { + --tw-text-opacity: 1; + color: rgb(212 212 212 / var(--tw-text-opacity)); +} - .dark\:text-neutral-500 { - --tw-text-opacity: 1; - color: rgb(115 115 115 / var(--tw-text-opacity)); - } +.dark\:text-neutral-400:where(.dark, .dark *) { + --tw-text-opacity: 1; + color: rgb(163 163 163 / var(--tw-text-opacity)); +} - .dark\:text-neutral-600 { - --tw-text-opacity: 1; - color: rgb(82 82 82 / var(--tw-text-opacity)); - } +.dark\:text-neutral-50:where(.dark, .dark *) { + --tw-text-opacity: 1; + color: rgb(250 250 250 / var(--tw-text-opacity)); +} - .dark\:text-red-300 { - --tw-text-opacity: 1; - color: rgb(252 165 165 / var(--tw-text-opacity)); - } +.dark\:text-neutral-500:where(.dark, .dark *) { + --tw-text-opacity: 1; + color: rgb(115 115 115 / var(--tw-text-opacity)); +} - .dark\:text-red-600 { - --tw-text-opacity: 1; - color: rgb(220 38 38 / var(--tw-text-opacity)); - } +.dark\:text-neutral-600:where(.dark, .dark *) { + --tw-text-opacity: 1; + color: rgb(82 82 82 / var(--tw-text-opacity)); +} - .dark\:text-white { - --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); - } +.dark\:text-red-300:where(.dark, .dark *) { + --tw-text-opacity: 1; + color: rgb(252 165 165 / var(--tw-text-opacity)); +} - .dark\:opacity-50 { - opacity: 0.5; - } +.dark\:text-red-600:where(.dark, .dark *) { + --tw-text-opacity: 1; + color: rgb(220 38 38 / var(--tw-text-opacity)); +} - .dark\:placeholder\:text-neutral-400::-moz-placeholder { - --tw-text-opacity: 1; - color: rgb(163 163 163 / var(--tw-text-opacity)); - } +.dark\:text-white:where(.dark, .dark *) { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} - .dark\:placeholder\:text-neutral-400::placeholder { - --tw-text-opacity: 1; - color: rgb(163 163 163 / var(--tw-text-opacity)); - } +.dark\:opacity-50:where(.dark, .dark *) { + opacity: 0.5; +} - .dark\:placeholder\:text-neutral-500::-moz-placeholder { - --tw-text-opacity: 1; - color: rgb(115 115 115 / var(--tw-text-opacity)); - } +.dark\:placeholder\:text-neutral-400:where(.dark, .dark *)::-moz-placeholder { + --tw-text-opacity: 1; + color: rgb(163 163 163 / var(--tw-text-opacity)); +} - .dark\:placeholder\:text-neutral-500::placeholder { - --tw-text-opacity: 1; - color: rgb(115 115 115 / var(--tw-text-opacity)); - } +.dark\:placeholder\:text-neutral-400:where(.dark, .dark *)::placeholder { + --tw-text-opacity: 1; + color: rgb(163 163 163 / var(--tw-text-opacity)); +} - .dark\:hover\:border-neutral-600:hover { - --tw-border-opacity: 1; - border-color: rgb(82 82 82 / var(--tw-border-opacity)); - } +.dark\:placeholder\:text-neutral-500:where(.dark, .dark *)::-moz-placeholder { + --tw-text-opacity: 1; + color: rgb(115 115 115 / var(--tw-text-opacity)); +} - .dark\:hover\:border-red-600:hover { - --tw-border-opacity: 1; - border-color: rgb(220 38 38 / var(--tw-border-opacity)); - } +.dark\:placeholder\:text-neutral-500:where(.dark, .dark *)::placeholder { + --tw-text-opacity: 1; + color: rgb(115 115 115 / var(--tw-text-opacity)); +} - .dark\:hover\:bg-neutral-500:hover { - --tw-bg-opacity: 1; - background-color: rgb(115 115 115 / var(--tw-bg-opacity)); - } +.dark\:hover\:border-neutral-600:hover:where(.dark, .dark *) { + --tw-border-opacity: 1; + border-color: rgb(82 82 82 / var(--tw-border-opacity)); +} - .dark\:hover\:bg-neutral-600:hover { - --tw-bg-opacity: 1; - background-color: rgb(82 82 82 / var(--tw-bg-opacity)); - } +.dark\:hover\:border-red-600:hover:where(.dark, .dark *) { + --tw-border-opacity: 1; + border-color: rgb(220 38 38 / var(--tw-border-opacity)); +} - .dark\:hover\:bg-red-600:hover { - --tw-bg-opacity: 1; - background-color: rgb(220 38 38 / var(--tw-bg-opacity)); - } +.dark\:hover\:bg-neutral-500:hover:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(115 115 115 / var(--tw-bg-opacity)); +} - .dark\:hover\:bg-red-700:hover { - --tw-bg-opacity: 1; - background-color: rgb(185 28 28 / var(--tw-bg-opacity)); - } +.dark\:hover\:bg-neutral-600:hover:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(82 82 82 / var(--tw-bg-opacity)); +} - .dark\:hover\:bg-red-800:hover { - --tw-bg-opacity: 1; - background-color: rgb(153 27 27 / var(--tw-bg-opacity)); - } +.dark\:hover\:bg-red-600:hover:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(220 38 38 / var(--tw-bg-opacity)); +} - .dark\:hover\:text-neutral-700:hover { - --tw-text-opacity: 1; - color: rgb(64 64 64 / var(--tw-text-opacity)); - } +.dark\:hover\:bg-red-700:hover:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(185 28 28 / var(--tw-bg-opacity)); +} - .dark\:hover\:text-red-100:hover { - --tw-text-opacity: 1; - color: rgb(254 226 226 / var(--tw-text-opacity)); - } +.dark\:hover\:bg-red-800:hover:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(153 27 27 / var(--tw-bg-opacity)); +} - .dark\:hover\:text-white:hover { - --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); - } +.dark\:hover\:text-neutral-700:hover:where(.dark, .dark *) { + --tw-text-opacity: 1; + color: rgb(64 64 64 / var(--tw-text-opacity)); +} - .dark\:focus\:border-neutral-500:focus { - --tw-border-opacity: 1; - border-color: rgb(115 115 115 / var(--tw-border-opacity)); - } +.dark\:hover\:text-red-100:hover:where(.dark, .dark *) { + --tw-text-opacity: 1; + color: rgb(254 226 226 / var(--tw-text-opacity)); +} - .dark\:focus\:border-red-800:focus { - --tw-border-opacity: 1; - border-color: rgb(153 27 27 / var(--tw-border-opacity)); - } +.dark\:hover\:text-white:hover:where(.dark, .dark *) { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} - .focus\:dark\:border-neutral-500:focus { - --tw-border-opacity: 1; - border-color: rgb(115 115 115 / var(--tw-border-opacity)); - } +.dark\:focus\:border-neutral-500:focus:where(.dark, .dark *) { + --tw-border-opacity: 1; + border-color: rgb(115 115 115 / var(--tw-border-opacity)); +} + +.dark\:focus\:border-red-800:focus:where(.dark, .dark *) { + --tw-border-opacity: 1; + border-color: rgb(153 27 27 / var(--tw-border-opacity)); +} + +.focus\:dark\:border-neutral-500:where(.dark, .dark *):focus { + --tw-border-opacity: 1; + border-color: rgb(115 115 115 / var(--tw-border-opacity)); +} + +.group:hover .group-hover\:dark\:fill-neutral-500:where(.dark, .dark *) { + fill: #737373; +} + +.peer:checked ~ .peer-checked\:dark\:fill-neutral-400:where(.dark, .dark *) { + fill: #a3a3a3; } diff --git a/src/www/index.html b/src/www/index.html index 2b1b7ba8..d5fdd956 100644 --- a/src/www/index.html +++ b/src/www/index.html @@ -18,25 +18,54 @@
-
+
- - {{$t("logout")}} - - - - - -

- - WireGuard -

-

- +
+

+ WireGuard +

+ + + + + + {{$t("logout")}} + + + + +
+
diff --git a/src/www/js/app.js b/src/www/js/app.js index a2536ed6..472c6d3d 100644 --- a/src/www/js/app.js +++ b/src/www/js/app.js @@ -23,9 +23,6 @@ function bytes(bytes, decimals, kib, maxunit) { return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; } -const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); -const theme = darkModeMediaQuery.matches ? 'dark' : 'light'; - const i18n = new VueI18n({ locale: localStorage.getItem('lang') || 'en', fallbackLocale: 'en', @@ -40,9 +37,9 @@ const UI_CHART_TYPES = [ ]; const CHART_COLORS = { - rx: { light: 'rgba(0,0,0,0.2)', dark: 'rgba(255,255,255,0.3)' }, - tx: { light: 'rgba(0,0,0,0.3)', dark: 'rgba(255,255,255,0.5)' }, - gradient: { light: ['rgba(0,0,0,0.1)', 'rgba(0,0,0,0)'], dark: ['rgba(255,255,255,0.1)', 'rgba(255,255,255,0)'] }, + rx: { light: 'rgba(128,128,128,0.3)', dark: 'rgba(255,255,255,0.3)' }, + tx: { light: 'rgba(128,128,128,0.4)', dark: 'rgba(255,255,255,0.3)' }, + gradient: { light: ['rgba(0,0,0,1.0)', 'rgba(0,0,0,1.0)'], dark: ['rgba(128,128,128,0)', 'rgba(128,128,128,0)'] }, }; new Vue({ @@ -71,10 +68,12 @@ new Vue({ currentRelease: null, latestRelease: null, - isDark: null, uiTrafficStats: false, uiChartType: 0, + uiShowCharts: localStorage.getItem('uiShowCharts') === "1" ? true : false, + uiTheme: localStorage.theme || 'auto', + prefersDarkScheme: window.matchMedia('(prefers-color-scheme: dark)'), chartOptions: { chart: { @@ -100,11 +99,10 @@ new Vue({ gradient: { shade: 'dark', type: 'vertical', - shadeIntensity: 1, - gradientToColors: CHART_COLORS.gradient[theme], - inverseColors: true, - opacityFrom: 1, - opacityTo: 1, + shadeIntensity: 0, + gradientToColors: CHART_COLORS.gradient[this.theme], + inverseColors: false, + opacityTo: 0, stops: [0, 100], }, }, @@ -301,15 +299,26 @@ new Vue({ .finally(() => this.refresh().catch(console.error)); }, toggleTheme() { - if (this.isDark) { - localStorage.theme = 'light'; - document.documentElement.classList.remove('dark'); - } else { - localStorage.theme = 'dark'; - document.documentElement.classList.add('dark'); + const themes = ['light', 'dark', 'auto']; + const currentIndex = themes.indexOf(this.uiTheme); + const newIndex = (currentIndex + 1) % themes.length; + this.uiTheme = themes[newIndex]; + localStorage.theme = this.uiTheme; + this.setTheme(this.uiTheme); + }, + setTheme(theme) { + const { classList } = document.documentElement; + const shouldAddDarkClass = theme === 'dark' || (theme === 'auto' && this.prefersDarkScheme.matches); + classList.toggle('dark', shouldAddDarkClass); + }, + handlePrefersChange(e) { + if (localStorage.theme === 'auto') { + this.setTheme(e.matches ? 'dark' : 'light'); } - this.isDark = !this.isDark; }, + toggleCharts() { + localStorage.setItem('uiShowCharts', this.uiShowCharts ? 1 : 0); + } }, filters: { bytes, @@ -318,10 +327,8 @@ new Vue({ }, }, mounted() { - this.isDark = false; - if (localStorage.theme === 'dark') { - this.isDark = true; - } + this.prefersDarkScheme.addListener(this.handlePrefersChange); + this.setTheme(this.uiTheme); this.api = new API(); this.api.getSession() @@ -395,7 +402,7 @@ new Vue({ chartOptionsTX() { const opts = { ...this.chartOptions, - colors: [CHART_COLORS.tx[theme]], + colors: [CHART_COLORS.tx[this.theme]], }; opts.chart.type = UI_CHART_TYPES[this.uiChartType].type || false; opts.stroke.width = UI_CHART_TYPES[this.uiChartType].strokeWidth; @@ -404,14 +411,20 @@ new Vue({ chartOptionsRX() { const opts = { ...this.chartOptions, - colors: [CHART_COLORS.rx[theme]], + colors: [CHART_COLORS.rx[this.theme]], }; opts.chart.type = UI_CHART_TYPES[this.uiChartType].type || false; opts.stroke.width = UI_CHART_TYPES[this.uiChartType].strokeWidth; return opts; }, updateCharts() { - return this.uiChartType > 0; + return this.uiChartType > 0 && this.uiShowCharts; + }, + theme() { + if (this.uiTheme === 'auto') { + return this.prefersDarkScheme.matches ? 'dark' : 'light'; + } + return this.uiTheme; }, }, }); diff --git a/src/www/js/i18n.js b/src/www/js/i18n.js index a1962708..82fe64c8 100644 --- a/src/www/js/i18n.js +++ b/src/www/js/i18n.js @@ -28,6 +28,8 @@ const messages = { // eslint-disable-line no-unused-vars downloadConfig: 'Download Configuration', madeBy: 'Made by', donate: 'Donate', + toggleCharts: 'Show/hide Charts', + theme: { dark: 'Dark theme', light: 'Light theme', auto: 'Auto theme' } }, ua: { name: 'Ім`я', From 32fc78589afdd1a83de00cb62a5a596426c05a9a Mon Sep 17 00:00:00 2001 From: Sergei Birukov Date: Sun, 17 Mar 2024 17:30:52 +0300 Subject: [PATCH 10/11] Lint --- src/www/js/app.js | 6 +++--- src/www/js/i18n.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/www/js/app.js b/src/www/js/app.js index 472c6d3d..4a30dc63 100644 --- a/src/www/js/app.js +++ b/src/www/js/app.js @@ -71,7 +71,7 @@ new Vue({ uiTrafficStats: false, uiChartType: 0, - uiShowCharts: localStorage.getItem('uiShowCharts') === "1" ? true : false, + uiShowCharts: localStorage.getItem('uiShowCharts') === '1', uiTheme: localStorage.theme || 'auto', prefersDarkScheme: window.matchMedia('(prefers-color-scheme: dark)'), @@ -189,6 +189,7 @@ new Vue({ // client.transferRx = this.clientsPersist[client.id].transferRxPrevious + Math.random() * 1000; // client.transferTx = this.clientsPersist[client.id].transferTxPrevious + Math.random() * 1000; // client.latestHandshakeAt = new Date(); + // this.requiresPassword = true; this.clientsPersist[client.id].transferRxCurrent = client.transferRx - this.clientsPersist[client.id].transferRxPrevious; this.clientsPersist[client.id].transferRxPrevious = client.transferRx; @@ -211,7 +212,6 @@ new Vue({ name: 'Rx', data: this.clientsPersist[client.id].transferRxHistory, }]; - client.transferTxHistory = this.clientsPersist[client.id].transferTxHistory; client.transferRxHistory = this.clientsPersist[client.id].transferRxHistory; client.transferMax = Math.max(...client.transferTxHistory, ...client.transferRxHistory); @@ -318,7 +318,7 @@ new Vue({ }, toggleCharts() { localStorage.setItem('uiShowCharts', this.uiShowCharts ? 1 : 0); - } + }, }, filters: { bytes, diff --git a/src/www/js/i18n.js b/src/www/js/i18n.js index 82fe64c8..c976a204 100644 --- a/src/www/js/i18n.js +++ b/src/www/js/i18n.js @@ -29,7 +29,7 @@ const messages = { // eslint-disable-line no-unused-vars madeBy: 'Made by', donate: 'Donate', toggleCharts: 'Show/hide Charts', - theme: { dark: 'Dark theme', light: 'Light theme', auto: 'Auto theme' } + theme: { dark: 'Dark theme', light: 'Light theme', auto: 'Auto theme' }, }, ua: { name: 'Ім`я', From 81ccf8762dff986a332de2ea8090b8c4b9ee5160 Mon Sep 17 00:00:00 2001 From: Sergei Birukov Date: Mon, 18 Mar 2024 10:05:12 +0300 Subject: [PATCH 11/11] Header mobile layout --- src/www/index.html | 86 ++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/src/www/index.html b/src/www/index.html index d5fdd956..a3fa6837 100644 --- a/src/www/index.html +++ b/src/www/index.html @@ -18,52 +18,54 @@
-
+
-
-

+
+

WireGuard

- - - -