From 330d812bfd868c01fd88e564e82232b9256ff5c0 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Fri, 10 Jun 2022 14:36:17 +0100 Subject: [PATCH 01/49] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Deletes=20rogue?= =?UTF-8?q?=20font=20import=20(#521)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/styles/color-themes.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/styles/color-themes.scss b/src/styles/color-themes.scss index 3e736892..7d5449ae 100644 --- a/src/styles/color-themes.scss +++ b/src/styles/color-themes.scss @@ -1543,8 +1543,6 @@ html[data-theme="oblivion-scotch"] { --primary: #d69e3a; } -@import url('https://fonts.googleapis.com/css2?family=Shrikhand&display=swap'); - html[data-theme='lissy'] { // --primary: #f0f; --primary: #ffffffcc; From b10b587442e8ae30bc98b9c78f5b90817e1aef6e Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Jun 2022 14:06:31 +0100 Subject: [PATCH 02/49] =?UTF-8?q?=F0=9F=8F=97=20=20Updates=20Heroku=20buil?= =?UTF-8?q?d=20files=20(#723)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Procfile | 5 +---- app.json | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Procfile b/Procfile index 4d76c4cd..7ddabf6b 100644 --- a/Procfile +++ b/Procfile @@ -1,4 +1 @@ -# Heroku config - Specifies the commands to execute when the app starts -# See docs for more info: https://devcenter.heroku.com/articles/procfile - -web: node server.js \ No newline at end of file +web: npm run build-and-start diff --git a/app.json b/app.json index 6d674ad9..ced11578 100644 --- a/app.json +++ b/app.json @@ -1,5 +1,6 @@ { "name": "Dashy", + "website": "https://dashy.to/", "description": "A Dashboard for your Homelab 🚀", "repository": "https://github.com/lissy93/dashy", "logo": "https://raw.githubusercontent.com/Lissy93/dashy/master/docs/assets/logo.png", @@ -13,4 +14,4 @@ "lissy93" ], "stack": "heroku-20" -} \ No newline at end of file +} From da72f680b3c8bbafb19a83b435ebe9e1a4f4bc8c Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Jun 2022 14:16:22 +0100 Subject: [PATCH 03/49] =?UTF-8?q?=F0=9F=8F=97=20=20Heroku=20set=20OpenSSL?= =?UTF-8?q?=20Provider=20(#723)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Procfile | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Procfile b/Procfile index 7ddabf6b..1b9edfba 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: npm run build-and-start +web: npm run heroku-start diff --git a/package.json b/package.json index 64f6d7d0..59fe5c5f 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "build-and-start": "npm-run-all --parallel build-watch start", "validate-config": "node services/config-validator", "health-check": "node services/healthcheck", - "dependency-audit": "npx improved-yarn-audit --ignore-dev-deps" + "dependency-audit": "npx improved-yarn-audit --ignore-dev-deps", + "heroku-start": "NODE_OPTIONS=--openssl-legacy-provider; npm run build-and-start" }, "dependencies": { "@formschema/native": "^2.0.0-beta.6", From e24fa10f0f972dc3f6227a086b4e1966de40b451 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Jun 2022 14:21:53 +0100 Subject: [PATCH 04/49] =?UTF-8?q?=E2=86=AA=20REVERT:=20Heroku=20set=20Open?= =?UTF-8?q?SSL=20Provider=20(#723)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Procfile | 2 +- package.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Procfile b/Procfile index 1b9edfba..7ddabf6b 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: npm run heroku-start +web: npm run build-and-start diff --git a/package.json b/package.json index 59fe5c5f..64f6d7d0 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,7 @@ "build-and-start": "npm-run-all --parallel build-watch start", "validate-config": "node services/config-validator", "health-check": "node services/healthcheck", - "dependency-audit": "npx improved-yarn-audit --ignore-dev-deps", - "heroku-start": "NODE_OPTIONS=--openssl-legacy-provider; npm run build-and-start" + "dependency-audit": "npx improved-yarn-audit --ignore-dev-deps" }, "dependencies": { "@formschema/native": "^2.0.0-beta.6", From 9786c8b5545b0e262a743a2690cb85a7c91016da Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 11 Jun 2022 21:24:19 +0100 Subject: [PATCH 05/49] =?UTF-8?q?=F0=9F=A4=96=20Updates=20bot=20action=20f?= =?UTF-8?q?or=20new=20users?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/issue-spam-control.yml | 37 ------------------------ .github/workflows/new-issues-check.yml | 22 ++++++++++++++ 2 files changed, 22 insertions(+), 37 deletions(-) delete mode 100644 .github/workflows/issue-spam-control.yml create mode 100644 .github/workflows/new-issues-check.yml diff --git a/.github/workflows/issue-spam-control.yml b/.github/workflows/issue-spam-control.yml deleted file mode 100644 index 13d6357b..00000000 --- a/.github/workflows/issue-spam-control.yml +++ /dev/null @@ -1,37 +0,0 @@ -# Will add a comment and close any new issues opened by -# users who have not yet committed to, or starred the repo -name: 🎯 Issue Spam Control -on: - issues: - types: [opened, reopened] -jobs: - check-user: - if: > - ${{ - ! contains( github.event.issue.labels.*.name, '📌 Keep Open') && - ! contains( github.event.issue.labels.*.name, '🌈 Feedback') && - ! contains( github.event.issue.labels.*.name, '💯 Showcase') && - github.event.comment.author_association != 'CONTRIBUTOR' - }} - runs-on: ubuntu-latest - name: Close issue opened by non-stargazer - steps: - - name: close - uses: uhyo/please-star-first@v1.0.1 - with: - token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - message: | - Welcome to Dashy 👋 - It's great to have you here, but unfortunately your ticket has been closed to prevent spam. Before reopening this issue, please ensure the following criteria are met. - - Issues are sometimes closed when users: - - Have only recently joined GitHub - - Have not yet stared this repository - - Have not previously interacted with the repo - - Before you reopen this issue, please also ensure that: - - You have checked that a similar issue does not already exist - - You have checked the documentation for an existing solution - - You have completed the relevant sections in the Issue template - - Once you have verified the above standards are met, you may reopen this issue. Sorry for any inconvenience caused, I'm just a bot, and sometimes make mistakes 🤖 diff --git a/.github/workflows/new-issues-check.yml b/.github/workflows/new-issues-check.yml new file mode 100644 index 00000000..e90314bf --- /dev/null +++ b/.github/workflows/new-issues-check.yml @@ -0,0 +1,22 @@ +name: ⭐ Hello non-Stargazers +on: + issues: + types: [opened, reopened] +jobs: + check-user: + if: > + ${{ + ! contains( github.event.issue.labels.*.name, '📌 Keep Open') && + ! contains( github.event.issue.labels.*.name, '🌈 Feedback') && + ! contains( github.event.issue.labels.*.name, '💯 Showcase') && + github.event.comment.author_association != 'CONTRIBUTOR' + }} + runs-on: ubuntu-latest + name: Add comment to issues opened by non-stargazers + steps: + - name: comment + uses: qxip/please-star-light@v4 + with: + token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + autoclose: false + message: "If you're enjoying Dashy, consider dropping us a ⭐
_🤖 I'm a bot, and this message was automated_" From 0bf6fee1807c14f695be02691d7f738184a5a373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Sat, 11 Jun 2022 23:41:40 +0000 Subject: [PATCH 06/49] :tada: Add Nextcloud widget Add a widget supporting the `capabilites`, `user` and `serverinfo` Nextcloud APIs. Basic branding, user and quota information is always displayed and when the provided credentials are for and admin user then server information is also displayed. APIs: * [capabilities](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html#capabilities-api) * [user](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html#user-metadata) * [serverinfo](https://github.com/nextcloud/serverinfo) --- src/assets/locales/en.json | 4 + src/components/Widgets/NextcloudInfo.vue | 398 +++++++++++++++++++++++ src/components/Widgets/WidgetBase.vue | 8 + src/mixins/NextcloudMixin.js | 82 +++++ src/utils/MiscHelpers.js | 9 + 5 files changed, 501 insertions(+) create mode 100644 src/components/Widgets/NextcloudInfo.vue create mode 100644 src/mixins/NextcloudMixin.js diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index a064eb67..2860c52c 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -303,6 +303,10 @@ "remaining": "Remaining", "up": "Up", "down": "Down" + }, + "nextcloud-info": { + "label-version": "Nextcloud version", + "label-last-login": "Last login" } } } diff --git a/src/components/Widgets/NextcloudInfo.vue b/src/components/Widgets/NextcloudInfo.vue new file mode 100644 index 00000000..ff19043b --- /dev/null +++ b/src/components/Widgets/NextcloudInfo.vue @@ -0,0 +1,398 @@ + + + + + diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue index d34d135d..6551f265 100644 --- a/src/components/Widgets/WidgetBase.vue +++ b/src/components/Widgets/WidgetBase.vue @@ -321,6 +321,13 @@ @error="handleError" :ref="widgetRef" /> + import('@/components/Widgets/NdLoadHistory.vue'), NdRamHistory: () => import('@/components/Widgets/NdRamHistory.vue'), NewsHeadlines: () => import('@/components/Widgets/NewsHeadlines.vue'), + NextcloudInfo: () => import('@/components/Widgets/NextcloudInfo.vue'), PiHoleStats: () => import('@/components/Widgets/PiHoleStats.vue'), PiHoleTopQueries: () => import('@/components/Widgets/PiHoleTopQueries.vue'), PiHoleTraffic: () => import('@/components/Widgets/PiHoleTraffic.vue'), diff --git a/src/mixins/NextcloudMixin.js b/src/mixins/NextcloudMixin.js new file mode 100644 index 00000000..6e040afb --- /dev/null +++ b/src/mixins/NextcloudMixin.js @@ -0,0 +1,82 @@ +import { serviceEndpoints } from '@/utils/defaults'; +import { convertBytes, formatNumber, getTimeAgo } from '@/utils/MiscHelpers'; +// //import { NcdCap } from '@/utils/ncd'; + +/** Reusable mixin for Nextcloud widgets */ +export default { + data() { + return { + capabilities: { + notifications: null, + activity: null, + }, + capabilitiesLastUpdated: 0, + }; + }, + computed: { + hostname() { + if (!this.options.hostname) this.error('A hostname is required'); + return this.options.hostname; + }, + username() { + if (!this.options.username) this.error('A username is required'); + return this.options.username; + }, + password() { + if (!this.options.password) this.error('An app-password is required'); + return this.options.password; + }, + headers() { + return { + 'OCS-APIREQUEST': true, + Accept: 'application/json', + Authorization: `Basic ${window.btoa(`${this.username}:${this.password}`)}`, + }; + }, + proxyReqEndpoint() { + const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin; + return `${baseUrl}${serviceEndpoints.corsProxy}`; + }, + }, + methods: { + endpoint(id) { + const endpoints = { + capabilities: `${this.hostname}/ocs/v1.php/cloud/capabilities`, + user: `${this.hostname}/ocs/v1.php/cloud/users/${this.username}`, + serverinfo: `${this.hostname}/ocs/v2.php/apps/serverinfo/api/v1/info`, + }; + return endpoints[id]; + }, + fetchCapabilities() { + const promise = Promise.resolve(); + if ((new Date().getTime()) - this.capabilitiesLastUpdated > 3600000) { + promise.then(() => this.makeRequest(this.endpoint('capabilities'), this.headers)) + // //promise.then(() => NcdCap) + .then(this.processCapabilities); + } + return promise; + }, + processCapabilities(data) { + const ocdata = data?.ocs?.data; + if (!ocdata) { + this.error('Invalid response'); + return; + } + this.branding = ocdata?.capabilities?.theming; + this.capabilities.notifications = ocdata?.capabilities?.notifications?.['ocs-endpoints']; + this.capabilities.activity = ocdata?.capabilities?.activity?.apiv2; + this.version.string = ocdata?.version?.string; + this.version.edition = ocdata?.version?.edition; + this.capabilitiesLastUpdated = new Date().getTime(); + }, + formatNumber(number) { + return formatNumber(number); + }, + convertBytes(bytes) { + return convertBytes(bytes); + }, + getTimeAgo(time) { + return getTimeAgo(time); + }, + }, +}; diff --git a/src/utils/MiscHelpers.js b/src/utils/MiscHelpers.js index c3f59a8b..6a65222f 100644 --- a/src/utils/MiscHelpers.js +++ b/src/utils/MiscHelpers.js @@ -105,6 +105,15 @@ export const convertBytes = (bytes, decimals = 2) => { const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / (k ** i)).toFixed(decimals))} ${sizes[i]}`; }; +/* Returns a numbers shortened version with suffixes for thousand, million, billion + and trillion, e.g. 105_411 => 105.4K, 4_294_967_295 => 4.3B */ +export const formatNumber = (number) => { + if (number > -1000 && number < 1000) return number; + const k = 1000; + const units = ['', 'K', 'M', 'B', 'T']; + const i = Math.floor(Math.log(number) / Math.log(k)); + return `${(number / (k ** i)).toFixed(1)}${units[i]}`; +}; /* Round price to appropriate number of decimals */ export const roundPrice = (price) => { From ff1bcdbab8e651b8ec9eb8d7c015fa8ec5d36fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Sun, 12 Jun 2022 00:18:17 +0000 Subject: [PATCH 07/49] :adhesive_bandage: Handle plural/singular for availabel updates --- src/assets/locales/en.json | 3 ++- src/components/Widgets/NextcloudInfo.vue | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index 2860c52c..b22974e4 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -306,7 +306,8 @@ }, "nextcloud-info": { "label-version": "Nextcloud version", - "label-last-login": "Last login" + "label-last-login": "Last login", + "updates-available": "update{plural} available" } } } diff --git a/src/components/Widgets/NextcloudInfo.vue b/src/components/Widgets/NextcloudInfo.vue index ff19043b..cc82cd2d 100644 --- a/src/components/Widgets/NextcloudInfo.vue +++ b/src/components/Widgets/NextcloudInfo.vue @@ -47,7 +47,10 @@ data-has-updates v-tooltip="appUpdatesTooltip()"> {{ server.nextcloud.system.apps.num_updates_available }} - {{ $t('updates available') }} + + {{ $t('widgets.nextcloud-info.updates-available', + {plural: server.nextcloud.system.apps.num_updates_available > 1 ? 's' : ''}) }} + {{ $t('no pending updates') }} From caf131df233929d8cc2e5b954800b8ae5128b29a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Sun, 12 Jun 2022 01:10:14 +0000 Subject: [PATCH 08/49] :art: Add HTML comments --- src/components/Widgets/NextcloudInfo.vue | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/Widgets/NextcloudInfo.vue b/src/components/Widgets/NextcloudInfo.vue index cc82cd2d..71f6ae35 100644 --- a/src/components/Widgets/NextcloudInfo.vue +++ b/src/components/Widgets/NextcloudInfo.vue @@ -1,5 +1,6 @@ - - - - diff --git a/src/components/Widgets/NextcloudNotifications.vue b/src/components/Widgets/NextcloudNotifications.vue new file mode 100644 index 00000000..d68ad1b8 --- /dev/null +++ b/src/components/Widgets/NextcloudNotifications.vue @@ -0,0 +1,192 @@ + + + + diff --git a/src/components/Widgets/NextcloudPhpOpcache.vue b/src/components/Widgets/NextcloudPhpOpcache.vue new file mode 100644 index 00000000..b64bdaf1 --- /dev/null +++ b/src/components/Widgets/NextcloudPhpOpcache.vue @@ -0,0 +1,205 @@ + + + + + diff --git a/src/components/Widgets/NextcloudStats.vue b/src/components/Widgets/NextcloudStats.vue new file mode 100644 index 00000000..a03f247f --- /dev/null +++ b/src/components/Widgets/NextcloudStats.vue @@ -0,0 +1,203 @@ + + + + + diff --git a/src/components/Widgets/NextcloudSystem.vue b/src/components/Widgets/NextcloudSystem.vue new file mode 100644 index 00000000..1e4e5bde --- /dev/null +++ b/src/components/Widgets/NextcloudSystem.vue @@ -0,0 +1,234 @@ + + + + + diff --git a/src/components/Widgets/NextcloudUser.vue b/src/components/Widgets/NextcloudUser.vue new file mode 100644 index 00000000..832fa3ba --- /dev/null +++ b/src/components/Widgets/NextcloudUser.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/src/components/Widgets/NextcloudUserStatus.vue b/src/components/Widgets/NextcloudUserStatus.vue new file mode 100644 index 00000000..738997f5 --- /dev/null +++ b/src/components/Widgets/NextcloudUserStatus.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue index 6551f265..7abb9790 100644 --- a/src/components/Widgets/WidgetBase.vue +++ b/src/components/Widgets/WidgetBase.vue @@ -321,8 +321,43 @@ @error="handleError" :ref="widgetRef" /> - + + + + + import('@/components/Widgets/NdLoadHistory.vue'), NdRamHistory: () => import('@/components/Widgets/NdRamHistory.vue'), NewsHeadlines: () => import('@/components/Widgets/NewsHeadlines.vue'), - NextcloudInfo: () => import('@/components/Widgets/NextcloudInfo.vue'), + NextcloudNotifications: () => import('@/components/Widgets/NextcloudNotifications.vue'), + NextcloudPhpOpcache: () => import('@/components/Widgets/NextcloudPhpOpcache.vue'), + NextcloudStats: () => import('@/components/Widgets/NextcloudStats.vue'), + NextcloudSystem: () => import('@/components/Widgets/NextcloudSystem.vue'), + NextcloudUser: () => import('@/components/Widgets/NextcloudUser.vue'), + NextcloudUserStatus: () => import('@/components/Widgets/NextcloudUserStatus.vue'), PiHoleStats: () => import('@/components/Widgets/PiHoleStats.vue'), PiHoleTopQueries: () => import('@/components/Widgets/PiHoleTopQueries.vue'), PiHoleTraffic: () => import('@/components/Widgets/PiHoleTraffic.vue'), diff --git a/src/mixins/NextcloudMixin.js b/src/mixins/NextcloudMixin.js index a64f1387..0d933f85 100644 --- a/src/mixins/NextcloudMixin.js +++ b/src/mixins/NextcloudMixin.js @@ -1,47 +1,64 @@ import { serviceEndpoints } from '@/utils/defaults'; -import { convertBytes, formatNumber, getTimeAgo } from '@/utils/MiscHelpers'; -// //import { NcdCap, NcdUsr } from '@/utils/ncd'; +import { + convertBytes, formatNumber, getTimeAgo, timestampToDateTime, +} from '@/utils/MiscHelpers'; +// //import { NcdCap } from '@/utils/ncd'; -/** Reusable mixin for Nextcloud widgets */ +/** + * Reusable mixin for Nextcloud widgets + * Nextcloud APIs + * - capabilities: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html#capabilities-api + * - userstatus: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-status-api.html#user-status-retrieve-statuses + * - user: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-api-overview.html#user-metadata + * - notifications: https://github.com/nextcloud/notifications/blob/master/docs/ocs-endpoint-v2.md + * - serverinfo: https://github.com/nextcloud/serverinfo + */ export default { data() { return { + validCredentials: null, capabilities: { - notifications: null, - activity: null, + notifications: { + enabled: null, + features: [], + }, + userStatus: null, }, capabilitiesLastUpdated: 0, - user: { - id: null, - isAdmin: false, - displayName: null, - email: null, - quota: { - relative: null, - total: null, - used: null, - free: null, - quota: null, - }, + branding: { + name: null, + logo: null, + url: null, + slogan: null, + }, + version: { + string: null, + edition: null, }, }; }, computed: { + /* The user provided Nextcloud hostname */ hostname() { if (!this.options.hostname) this.error('A hostname is required'); return this.options.hostname; }, + /* The user provided Nextcloud username */ username() { if (!this.options.username) this.error('A username is required'); return this.options.username; }, + /* The user provided Nextcloud password */ password() { if (!this.options.password) this.error('An app-password is required'); + // reject Nextcloud user passord (enforce 'app-password') if (!/^([a-z0-9]{5}-){4}[a-z0-9]{5}$/i.test(this.options.password)) { - this.error('Please use an app-password for this widget, not your login password.'); + this.error('Please use a Nextcloud app-password, not your login password.'); + return ''; } return this.options.password; }, + /* HTTP headers for Nextcloud API requests */ headers() { return { 'OCS-APIREQUEST': true, @@ -49,6 +66,7 @@ export default { Authorization: `Basic ${window.btoa(`${this.username}:${this.password}`)}`, }; }, + /* TTL for data delivered by the capabilities endpoint, ms */ capabilitiesTtl() { return (parseInt(this.options.capabilitiesTtl, 10) || 3600) * 1000; }, @@ -58,6 +76,7 @@ export default { }, }, methods: { + /* Nextcloud API endpoints */ endpoint(id) { switch (id) { case 'capabilities': @@ -65,10 +84,65 @@ export default { return `${this.hostname}/ocs/v1.php/cloud/capabilities`; case 'user': return `${this.hostname}/ocs/v1.php/cloud/users/${this.username}`; + case 'userstatus': + return `${this.hostname}/ocs/v2.php/apps/user_status/api/v1/statuses`; case 'serverinfo': return `${this.hostname}/ocs/v2.php/apps/serverinfo/api/v1/info`; + case 'notifications': + return `${this.hostname}/ocs/v2.php/apps/notifications/api/v2/notifications`; } }, + /* Helper for widgets to terminate {fetchData} early */ + hasValidCredentials() { + return this.validCredentials !== false + && this.username.length > 0 + && this.password.length > 0; + }, + /* Primary handler for every Nextcloud API response */ + validateResponse(response) { + const data = response?.ocs?.data; + let meta = response?.ocs?.meta; + const error = response?.error; // Dashy error when cors-proxied + if (error && error.status) { + meta = { statuscode: error.status }; + } + if (!meta || !meta.statuscode || !data) { + this.error('Invalid response'); + } + switch (meta.statuscode) { + case 401: + this.validCredentials = false; + this.error( + `Access denied for user ${this.username}.` + + ' Note that some Nextcloud widgets only work with an admin user.', + ); + break; + case 429: + this.validCredentials = false; + this.error( + 'The server indicated \'rate-limit reached\' error (HTTP 429).' + + ' The server-info API may return this error for incorrect user/password.', + ); + break; + case 993: + case 997: + case 998: + this.validCredentials = false; + this.error( + 'The provided app-password is not permitted to access the requested resource or it has' + + ' been revoked, or the username/password combination is incorrect', + ); + break; + default: + this.validCredentials = true; + if (!this.allowedStatuscodes().includes(meta.statuscode)) { + this.error('Unexpected response'); + } + break; + } + return data; + }, + /* Process the capabilities endpoint if {capabilitiesTtl} has expired */ loadCapabilities() { if ((new Date().getTime()) - this.capabilitiesLastUpdated > this.capabilitiesTtl) { return this.makeRequest(this.endpoint('capabilities'), this.headers) @@ -77,44 +151,59 @@ export default { } return Promise.resolve(); }, - processCapabilities(data) { - const ocdata = data?.ocs?.data; - if (!ocdata) { - this.error('Invalid response'); - return; - } + /* Update the sate based on the capabilites response */ + processCapabilities(capResponse) { + const ocdata = this.validateResponse(capResponse); + const capNotif = ocdata?.capabilities?.notifications?.['ocs-endpoints']; this.branding = ocdata?.capabilities?.theming; - this.capabilities.notifications = ocdata?.capabilities?.notifications?.['ocs-endpoints']; - this.capabilities.activity = ocdata?.capabilities?.activity?.apiv2; + this.capabilities.notifications.enabled = !!(capNotif?.length); + this.capabilities.notifications.features = capNotif || []; + this.capabilities.userStatus = !!(ocdata?.capabilities?.user_status?.enabled); this.version.string = ocdata?.version?.string; this.version.edition = ocdata?.version?.edition; this.capabilitiesLastUpdated = new Date().getTime(); }, - loadUser() { - return this.makeRequest(this.endpoint('user'), this.headers).then(this.processUser); - // //return Promise.resolve(NcdUsr).then(this.processUser); - }, - processUser(userData) { - const user = userData?.ocs?.data; - if (!user) { - this.error('Invalid response'); - return; - } - this.user.id = user.id; - this.user.email = user.email; - this.user.quota = user.quota; - this.user.displayName = user.displayname; - this.user.lastLogin = user.lastLogin; - this.user.isAdmin = user.groups && user.groups.includes('admin'); - }, - formatNumber(number) { - return formatNumber(number); - }, - convertBytes(bytes) { - return convertBytes(bytes); - }, + /* Shared template helpers */ getTimeAgo(time) { return getTimeAgo(time); }, + formatDateTime(time) { + return timestampToDateTime(time); + }, + /* Add additional formatting to {MiscHelpers.convertBytes()} */ + convertBytes(bytes, decimals = 2, formatHtml = true) { + const formatted = convertBytes(bytes, decimals).toString(); + if (!formatHtml) return formatted; + const m = formatted.match(/(-?[0-9]+)((\.[0-9]+)?\s(([KMGTPEZY]B|Bytes)))/); + return `${m[1]}${m[2]}`; + }, + /* Add additional formatting to {MiscHelpers.formatNumber()} */ + formatNumber(number, decimals = 1, formatHtml = true) { + const formatted = formatNumber(number, decimals).toString(); + if (!formatHtml) return formatted; + const m = formatted.match(/([0-9]+)((\.[0-9]+)?([KMBT]?))/); + return `${m[1]}${m[2]}`; + }, + /* Format a number as percentage value */ + formatPercent(number, decimals = 2) { + const n = parseFloat(number).toFixed(decimals).split('.'); + const d = n.length > 1 ? `.${n[1]}` : ''; + return `${n[0]}${d}%`; + }, + /* Similar to {MiscHelpers.getValueFromCss()} but uses the widget root node to get + * the computed style so widget color is respected in variable widget color themes. */ + getValueFromCss(colorVar) { + const cssProps = getComputedStyle(this.$el || document.documentElement); + return cssProps.getPropertyValue(`--${colorVar}`).trim(); + }, + /* Get {colorVar} CSS property value and return as rgba() */ + getColorRgba(colorVar, alpha = 1) { + const [r, g, b] = this.getValueFromCss(colorVar).match(/\w\w/g).map(x => parseInt(x, 16)); + return `rgba(${r},${g},${b},${alpha})`; + }, + /* Translation shorthand with key prefix */ + tt(key, options = null) { + return this.$t(`widgets.nextcloud.${key}`, options); + }, }, }; diff --git a/src/styles/widgets/nextcloud-shared.scss b/src/styles/widgets/nextcloud-shared.scss new file mode 100644 index 00000000..204479a5 --- /dev/null +++ b/src/styles/widgets/nextcloud-shared.scss @@ -0,0 +1,77 @@ +.nextcloud-widget { + p { + color: var(--widget-text-color); + margin: 0.5rem 0; + } + + a { + color: var(--widget-text-color); + } + + p i { + font-size: 110%; + min-width: 22px; + text-align: center; + } + + p em { + font-size: 110%; + margin: 0 4px; + font-weight: 800; + } + + strong { + font-weight: 800; + font-size: 105%; + margin-left: .25rem; + } + + small { + opacity: .66; + } + + hr { + color: var(--widget-text-color); + border: none; + border-top: 1px solid; + margin-top: 0.8rem; + margin-bottom: 0.8rem; + opacity: .25; + clear: both; + } + + div.sep { + border-top: 1px dashed var(--widget-text-color); + width: 100%; + padding: .4rem 0; + margin: .85em 0 0 0; + > div:not(:first-child) { + width: 100%; + position: relative; + } + } + + .success { + color: var(--success); + } + + .warning { + color: #ff9000; + } + + .danger { + color: var(--danger); + } + + .disabled { + color: #818181; + } + + ::v-deep span.decimals { + font-size: 85%; + } + + ::v-deep div.percentage-chart { + margin: 0; + } +} diff --git a/src/utils/MiscHelpers.js b/src/utils/MiscHelpers.js index 6a65222f..14e6c808 100644 --- a/src/utils/MiscHelpers.js +++ b/src/utils/MiscHelpers.js @@ -105,14 +105,17 @@ export const convertBytes = (bytes, decimals = 2) => { const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / (k ** i)).toFixed(decimals))} ${sizes[i]}`; }; -/* Returns a numbers shortened version with suffixes for thousand, million, billion - and trillion, e.g. 105_411 => 105.4K, 4_294_967_295 => 4.3B */ -export const formatNumber = (number) => { + +/* Round a number to thousands, millions, billions or trillions and suffix + * with K, M, B or T respectively, e.g. 4_294_967_295 => 4.3B */ +export const formatNumber = (number, decimals = 1) => { if (number > -1000 && number < 1000) return number; - const k = 1000; const units = ['', 'K', 'M', 'B', 'T']; + const k = 1000; const i = Math.floor(Math.log(number) / Math.log(k)); - return `${(number / (k ** i)).toFixed(1)}${units[i]}`; + const f = number / (k ** i); + const d = f.toFixed(decimals) % 1.0 === 0 ? 0 : decimals; // number of decimals, omit .0 + return `${f.toFixed(d)}${units[i]}`; }; /* Round price to appropriate number of decimals */ From e76f5528301bc3c26683a72c7f06750f54767105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Sun, 19 Jun 2022 13:39:23 +0000 Subject: [PATCH 21/49] :sparkles: Add showEmpty option for NextcloudUserStatus widget When a user status doesn't include a status message, it still may have a status emoji and it always has a status indicator (e.g. online). When {showEmpty=true} then statuses without a message are shown, otherwise hidden. Defaults to {false}. --- src/components/Widgets/NextcloudUserStatus.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/Widgets/NextcloudUserStatus.vue b/src/components/Widgets/NextcloudUserStatus.vue index 738997f5..0e2e1c02 100644 --- a/src/components/Widgets/NextcloudUserStatus.vue +++ b/src/components/Widgets/NextcloudUserStatus.vue @@ -74,6 +74,9 @@ export default { if (this.options.users.length > 100) return this.options.users.slice(0, 100); return this.options.users; }, + showEmpty() { + return !!this.options.showEmpty; + }, }, data() { return { @@ -119,6 +122,7 @@ export default { const newStatuses = {}; Object.values(statuses).forEach((status) => { if (!this.users.includes(status.userId)) return; + if (!status.message && !this.showEmpty) return; newStatuses[status.userId] = status; }); this.statuses = newStatuses; @@ -126,7 +130,7 @@ export default { processStatus(response) { const raw = this.validateResponse(response); const status = Array.isArray(raw) && raw.length ? raw[0] : raw; - if (status) { + if (status && (status.message || this.showEmpty)) { this.newStatuses[status.userId] = status; } }, From 4c015bb25d023b79acac6a4d5130f913354542e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Sun, 19 Jun 2022 13:51:40 +0000 Subject: [PATCH 22/49] :sparkles: Add limit option for NextcloudNotifications widget Limit displayed notifications either by count or by age. An integer value is interpeted as count limit, a number suffixed with 'm', 'h' or 'd' is converted to minutes, hours or days, respectively, and older notifications are not shown. --- .../Widgets/NextcloudNotifications.vue | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/components/Widgets/NextcloudNotifications.vue b/src/components/Widgets/NextcloudNotifications.vue index d68ad1b8..f8b90bb7 100644 --- a/src/components/Widgets/NextcloudNotifications.vue +++ b/src/components/Widgets/NextcloudNotifications.vue @@ -54,6 +54,23 @@ export default { notifications: [], }; }, + computed: { + /* Parse the limit user option to either an integer or to an integer + 'm', 'h' or 'd' */ + limit() { + const lim = this.options.limit; + const defaultLimit = [0, false]; + if (typeof lim === 'string') { + const k = { m: 60, h: 60 * 60, d: 60 * 60 * 24 }; + const m = lim.match(/(\d+)([hmd])/); + if (m.length !== 3) return defaultLimit; + return [false, m[1] * k[m[2]] * 1000]; + } + if (typeof lim === 'number') { + return [parseInt(this.options.limit, 10) || 0, false]; + } + return defaultLimit; + }, + }, methods: { allowedStatuscodes() { return [100, 200]; @@ -72,8 +89,15 @@ export default { }, processNotifications(response) { const notifications = this.validateResponse(response); + const [limitCount, limitTime] = this.limit; this.notifications = []; notifications.forEach((notification) => { + if (limitCount && this.notifications.length === limitCount) return; // count limit + const notiDate = Date.parse(notification.datetime); + const now = new Date().getTime(); + if (limitTime && notiDate && now - notiDate > limitTime) { // time limit + return; + } this.notifications.push(notification); }); }, From 4b930939c795a2cbea2f8c90f613a1bfb1dcc9db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Sun, 19 Jun 2022 15:21:38 +0000 Subject: [PATCH 23/49] :pencil2: Fix typo in translation --- src/assets/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index f368f61e..efdaf631 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -339,7 +339,7 @@ "local-shares": "Local shares", "local": "local", "max-keys": "max keys", - "memory-used": "memory-used", + "memory-used": "memory used", "memory-utilisation": "memory utilisation", "memory": "memory", "misses": "misses", From db0fc0454d353246b3c62eb4c98dc25a0de5a894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Sun, 19 Jun 2022 16:20:16 +0000 Subject: [PATCH 24/49] :fire: Remove references to mock data --- src/components/Widgets/NextcloudNotifications.vue | 4 ---- src/components/Widgets/NextcloudStats.vue | 2 -- src/components/Widgets/NextcloudSystem.vue | 2 -- src/components/Widgets/NextcloudUser.vue | 2 -- src/components/Widgets/NextcloudUserStatus.vue | 3 --- src/mixins/NextcloudMixin.js | 2 -- 6 files changed, 15 deletions(-) diff --git a/src/components/Widgets/NextcloudNotifications.vue b/src/components/Widgets/NextcloudNotifications.vue index f8b90bb7..b05c983a 100644 --- a/src/components/Widgets/NextcloudNotifications.vue +++ b/src/components/Widgets/NextcloudNotifications.vue @@ -38,7 +38,6 @@ @@ -139,20 +155,20 @@ export default { border-top: none; } div.percentage-chart-wrapper { - margin: 0 0.75rem; + margin: 0 .75em; width: 5em; position: relative; - top: 0.2rem; + top: .2em; float: right; } div.logo { width: 40%; text-align: center; img { - width: 8rem; + width: 8em; } p { - font-size: 90%; + font-size: .9em; opacity: .85; } } @@ -166,23 +182,23 @@ export default { } p.brand { margin: 0; - font-size: 135%; + font-size: 1.35em; font-weight: 800; letter-spacing: 3px; } p.version small { - font-size: 75%; + font-size: .75em; } p.username { - font-size: 110%; + font-size: 1.1em; em { - font-size: 90%; + font-size: .9em; } } p.login { span { - font-size: 90%; - margin-right: .25rem; + font-size: .9em; + margin-right: .25em; } } } diff --git a/src/components/Widgets/NextcloudUserStatus.vue b/src/components/Widgets/NextcloudUserStatus.vue index 5cfac344..dd45a06d 100644 --- a/src/components/Widgets/NextcloudUserStatus.vue +++ b/src/components/Widgets/NextcloudUserStatus.vue @@ -19,7 +19,7 @@

- + @@ -131,14 +131,6 @@ export default { this.newStatuses[status.userId] = status; } }, - getStatusClass(status) { - switch (status) { - case 'online': return 'success'; - case 'away': return 'warning'; - case 'dnd': return 'danger'; - case 'offline': default: return 'disabled'; - } - }, /* Tooltip generators */ clearAtTooltip(clearAtTime) { const content = clearAtTime ? `${this.tt('until')}` @@ -165,6 +157,18 @@ export default { margin: 0; } } + .online { + color: var(--success); + } + .offline { + color: var(--medium-grey); + } + .away { + color: var(--error); + } + .dnd { + color: var(--danger); + } div.user > div { display: table; width: 100%; @@ -177,7 +181,7 @@ export default { } > div:nth-child(2) { p small i { - color: #aaaaaa; + color: var(--medium-grey); top: 0; opacity: .5; margin: 0; @@ -189,8 +193,11 @@ export default { } } div.user hr { - margin-top: 0.3rem; - margin-bottom: 0.3rem; + margin-top: .3em; + margin-bottom: .3em; + } + div.user hr:last-child { + margin-bottom: 0; } } diff --git a/src/styles/widgets/nextcloud-shared.scss b/src/styles/widgets/nextcloud-shared.scss index 204479a5..77360681 100644 --- a/src/styles/widgets/nextcloud-shared.scss +++ b/src/styles/widgets/nextcloud-shared.scss @@ -1,7 +1,7 @@ .nextcloud-widget { p { color: var(--widget-text-color); - margin: 0.5rem 0; + margin: .5em 0; } a { @@ -9,21 +9,21 @@ } p i { - font-size: 110%; + font-size: 1.1em; min-width: 22px; text-align: center; } p em { - font-size: 110%; - margin: 0 4px; + font-size: 1.1em; + margin: 0 .24em; font-weight: 800; } strong { font-weight: 800; - font-size: 105%; - margin-left: .25rem; + font-size: 1.05em; + margin-left: .25em; } small { @@ -34,16 +34,19 @@ color: var(--widget-text-color); border: none; border-top: 1px solid; - margin-top: 0.8rem; - margin-bottom: 0.8rem; + margin-top: .8em; + margin-bottom: .8em; opacity: .25; clear: both; } + hr:last-child { + margin-bottom: 0; + } div.sep { border-top: 1px dashed var(--widget-text-color); width: 100%; - padding: .4rem 0; + padding: .4em 0 0 0; margin: .85em 0 0 0; > div:not(:first-child) { width: 100%; @@ -51,22 +54,6 @@ } } - .success { - color: var(--success); - } - - .warning { - color: #ff9000; - } - - .danger { - color: var(--danger); - } - - .disabled { - color: #818181; - } - ::v-deep span.decimals { font-size: 85%; } From f00b76299d5150b422960cc366c7cb88075f4f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Mon, 20 Jun 2022 18:43:29 +0000 Subject: [PATCH 33/49] :lipstick: Facelifto --- src/components/Widgets/NextcloudUserStatus.vue | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/Widgets/NextcloudUserStatus.vue b/src/components/Widgets/NextcloudUserStatus.vue index dd45a06d..37f8ae7c 100644 --- a/src/components/Widgets/NextcloudUserStatus.vue +++ b/src/components/Widgets/NextcloudUserStatus.vue @@ -153,7 +153,7 @@ export default { float: right; i { position: relative; - top: .25rem; + top: .15rem; margin: 0; } } @@ -181,7 +181,6 @@ export default { } > div:nth-child(2) { p small i { - color: var(--medium-grey); top: 0; opacity: .5; margin: 0; @@ -196,7 +195,7 @@ export default { margin-top: .3em; margin-bottom: .3em; } - div.user hr:last-child { + div.user > div > div:last-child hr { margin-bottom: 0; } } From 2ee853d33f2ab9fe661b029c8584cf7dd42e3572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Mon, 20 Jun 2022 18:49:22 +0000 Subject: [PATCH 34/49] :memo: Add widget documentation --- docs/widgets.md | 224 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) diff --git a/docs/widgets.md b/docs/widgets.md index d5f7d92a..1bbd9e65 100644 --- a/docs/widgets.md +++ b/docs/widgets.md @@ -48,6 +48,12 @@ Dashy has support for displaying dynamic content in the form of widgets. There a - [AdGuard Home Filters](#adguard-home-filters) - [AdGuard Home DNS Info](#adguard-home-dns-info) - [AdGuard Home Top Domains](#adguard-home-top-domains) + - [Nextcloud User](#nextcloud-user) + - [Nextcloud User Statuses](#nextcloud-user-statuses) + - [Nextcloud Notifications](#nextcloud-notifications) + - [Nextcloud System](#nextcloud-system) + - [Nextcloud Stats](#nextcloud-stats) + - [Nextcloud PHP Opcache](#nextcloud-php-opcache-stats) - **[System Resource Monitoring](#system-resource-monitoring)** - [CPU Usage Current](#current-cpu-usage) - [CPU Usage Per Core](#cpu-usage-per-core) @@ -1564,6 +1570,224 @@ Fetches data from your [AdGuard Home](https://adguard.com/en/adguard-home/overvi --- +### Nextcloud User + +Nextcloud is a [self hosted](https://nextcloud.com/install/#instructions-server) productivity platform, it can also be used free of charge with [hundreds of existing hosting providers](https://nextcloud.com/sign-up/) that offer a free Nextcloud account. + +Displays branding information of a Nextcloud server (logo, url, slogan) and some user details (name, login name, last login, disk space or quota). Use with regular or admin user. + +Shows quota usage when quota is enabled for the user or disk usage when not enabled. + +Known issues: the User API incorrectly reports available disk space as total for admin users when quota is not enabled (which usually is the case for admins). + +

nextcloud-user

+ +##### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`hostname`** | `string` | Required | The URL of the Nextcloud server +**`username`** | `string` | Required | Nextcloud username +**`password`** | `string` | Required | Nextcloud app-password (create one in Settings -> Security) + + +##### Example + +```yaml +- type: nextcloud-user + useProxy: true + options: + hostname: https://nextcloud.example.com + username: alice + password: xxxxx-xxxxx-xxxxx-xxxxx +``` + +##### Info +- **CORS**: 🟠 Proxied +- **Auth**: 🟢 Required +- **Price**: 🟢 Free +- **Host**: Self-Hosted (see [Nextcloud](https://nextcloud.com)) +- **Privacy**: _See [Nextcloud Privacy Policy](https://nextcloud.com/privacy)_ + +--- + +### Nextcloud User Statuses + +Show user statuses for selected users. + +

nextcloud-userstatus

+ +##### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`hostname`** | `string` | Required | The URL of the Nextcloud server +**`username`** | `string` | Required | Nextcloud username +**`password`** | `string` | Required | Nextcloud app-password (create one in Settings -> Security) +**`users`** | `array` | Required | Nextcloud User IDs to show statuses for, list size between `1` and `100` +**`showEmpty`** | `boolean` | _Optional_ | Show statuses without a message, defaults to `true` + + +##### Example + +```yaml +- type: nextcloud-userstatus + useProxy: true + options: + hostname: https://nextcloud.example.com + username: alice + password: xxxxx-xxxxx-xxxxx-xxxxx + users: ['bob', 'alice'] +``` + +##### Info +- **CORS**: 🟠 Proxied +- **Auth**: 🟢 Required +- **Price**: 🟢 Free +- **Host**: Self-Hosted (see [Nextcloud](https://nextcloud.com)) +- **Privacy**: _See [Nextcloud Privacy Policy](https://nextcloud.com/privacy)_ + +--- + +### Nextcloud Notifications + +Displays your notifications and allows deleting them. + +

nextcloud-notifications

+ +##### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`hostname`** | `string` | Required | The URL of the Nextcloud server +**`username`** | `string` | Required | Nextcloud username +**`password`** | `string` | Required | Nextcloud app-password (create one in Settings -> Security) +**`limit`** | `number\|string` | _Optional_ | Limit displayed notifications either by count, e.g. `5` to show the 5 most recent, or by age, e.g. `1d` to only show notifications not older than a day. Accepted suffixes for age limit are `m`, `h` and `d`. + + +##### Example + +```yaml +- type: nextcloud-userstatus + useProxy: true + options: + hostname: https://nextcloud.example.com + username: alice + password: xxxxx-xxxxx-xxxxx-xxxxx + limit: 6h +``` + +##### Info +- **CORS**: 🟠 Proxied +- **Auth**: 🟢 Required +- **Price**: 🟢 Free +- **Host**: Self-Hosted (see [Nextcloud](https://nextcloud.com)) +- **Privacy**: _See [Nextcloud Privacy Policy](https://nextcloud.com/privacy)_ + +--- + +### Nextcloud System + +Visualises overall memory utilisation and CPU load averages, shows server versions. + +

nextcloud-system

+ +##### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`hostname`** | `string` | Required | The URL of the Nextcloud server +**`username`** | `string` | Required | Must be a Nextcloud admin user +**`password`** | `string` | Required | Nextcloud app-password (create one in Settings -> Security) + +##### Example + +```yaml +- type: nextcloud-system + useProxy: true + options: + hostname: https://nextcloud.example.com + username: alice + password: xxxxx-xxxxx-xxxxx-xxxxx +``` + +##### Info +- **CORS**: 🟠 Proxied +- **Auth**: 🟢 Required +- **Price**: 🟢 Free +- **Host**: Self-Hosted (see [Nextcloud](https://nextcloud.com)) +- **Privacy**: _See [Nextcloud Privacy Policy](https://nextcloud.com/privacy)_ + +--- + +### Nextcloud Stats + +Shows key usage statistics about your Nextcloud server. + +

nextcloud-stats

+ +##### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`hostname`** | `string` | Required | The URL of the Nextcloud server +**`username`** | `string` | Required | Must be a Nextcloud admin user +**`password`** | `string` | Required | Nextcloud app-password (create one in Settings -> Security) + +##### Example + +```yaml +- type: nextcloud-stats + useProxy: true + options: + hostname: https://nextcloud.example.com + username: alice + password: xxxxx-xxxxx-xxxxx-xxxxx +``` + +##### Info +- **CORS**: 🟠 Proxied +- **Auth**: 🟢 Required +- **Price**: 🟢 Free +- **Host**: Self-Hosted (see [Nextcloud](https://nextcloud.com)) +- **Privacy**: _See [Nextcloud Privacy Policy](https://nextcloud.com/privacy)_ + +--- + +### Nextcloud PHP Opcache Stats + +Shows statistics about PHP Opcache perforamnce on your Nextcloud server. + +

nextcloud-phpopcache

+ +##### Options + +**Field** | **Type** | **Required** | **Description** +--- | --- | --- | --- +**`hostname`** | `string` | Required | The URL of the Nextcloud server +**`username`** | `string` | Required | Must be a Nextcloud admin user +**`password`** | `string` | Required | Nextcloud app-password (create one in Settings -> Security) + +##### Example + +```yaml +- type: nextcloud-stats + useProxy: true + options: + hostname: https://nextcloud.example.com + username: alice + password: xxxxx-xxxxx-xxxxx-xxxxx +``` + +##### Info +- **CORS**: 🟠 Proxied +- **Auth**: 🟢 Required +- **Price**: 🟢 Free +- **Host**: Self-Hosted (see [Nextcloud](https://nextcloud.com)) +- **Privacy**: _See [Nextcloud Privacy Policy](https://nextcloud.com/privacy)_ + +--- + ## System Resource Monitoring The easiest method for displaying system info and resource usage in Dashy is with [Glances](https://nicolargo.github.io/glances/). From c9cd8da2d9d795142688aaa6f8937f14efbd466e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Mon, 20 Jun 2022 18:59:47 +0000 Subject: [PATCH 35/49] :lipstick: Missed style update --- src/components/Widgets/NextcloudNotifications.vue | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Widgets/NextcloudNotifications.vue b/src/components/Widgets/NextcloudNotifications.vue index 719f208d..dd1d4f34 100644 --- a/src/components/Widgets/NextcloudNotifications.vue +++ b/src/components/Widgets/NextcloudNotifications.vue @@ -193,8 +193,7 @@ export default { text-align: left; > img { float: right; - width: 16px; - height: 16px; + width: 1em; position: relative; top: 1em; opacity: .75; From bbb0f16ead11c57e76b3c9730e32f40e0934b30e Mon Sep 17 00:00:00 2001 From: k073l <21180271+k073l@users.noreply.github.com> Date: Tue, 21 Jun 2022 13:02:26 +0200 Subject: [PATCH 36/49] Change APOD API endpoint --- src/utils/defaults.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/defaults.js b/src/utils/defaults.js index e1b41128..128a3572 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -217,7 +217,7 @@ module.exports = { /* API endpoints for widgets that need to fetch external data */ widgetApiEndpoints: { anonAddy: 'https://app.anonaddy.com', - astronomyPictureOfTheDay: 'https://apodapi.herokuapp.com/api', + astronomyPictureOfTheDay: 'https://go-apod.herokuapp.com/apod', blacklistCheck: 'https://api.blacklistchecker.com/check', codeStats: 'https://codestats.net/', covidStats: 'https://disease.sh/v3/covid-19', From 45900da025aadf59364ecfc01cc136d94da5279d Mon Sep 17 00:00:00 2001 From: k073l <21180271+k073l@users.noreply.github.com> Date: Tue, 21 Jun 2022 13:57:33 +0200 Subject: [PATCH 37/49] :lipstick: Update Apod.vue to new API --- src/components/Widgets/Apod.vue | 36 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/components/Widgets/Apod.vue b/src/components/Widgets/Apod.vue index 2d53c39e..cbf68189 100644 --- a/src/components/Widgets/Apod.vue +++ b/src/components/Widgets/Apod.vue @@ -1,15 +1,15 @@ @@ -24,17 +24,17 @@ export default { data() { return { title: null, - image: null, - hdImage: null, - link: null, - description: null, + url: null, + hdurl: null, + link: 'https://apod.nasa.gov/apod/astropix.html', + explanation: null, copyright: null, - showFullDesc: false, + showFullExp: false, }; }, computed: { - truncatedDescription() { - return this.showFullDesc ? this.description : `${this.description.substring(0, 100)}...`; + truncatedExplanation() { + return this.showFullExp ? this.explanation : `${this.explanation.substring(0, 100)}...`; }, }, methods: { @@ -52,14 +52,14 @@ export default { }, processData(data) { this.title = data.title; - this.image = data.url; - this.hdImage = data.hdurl; - this.link = data.apod_site; - this.description = data.description; + this.url = data.url; + this.hdurl = data.hdurl; + this.link = data.link; + this.explanation = data.explanation; this.copyright = data.copyright; }, toggleShowFull() { - this.showFullDesc = !this.showFullDesc; + this.showFullExp = !this.showFullExp; }, }, }; @@ -85,7 +85,7 @@ export default { opacity: var(--dimming-factor); color: var(--widget-text-color); } - p.description { + p.explanation { color: var(--widget-text-color); font-size: 1rem; margin: 0.5rem 0; From f21f44ce2178ba3969a376f3655cf94d2bd7b456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Tue, 21 Jun 2022 19:22:01 +0000 Subject: [PATCH 38/49] :adhesive_bandage: Add back some styling These weren't correctly updated afeter the style refactor (Stats and PhpOpcache widgets). --- src/components/Widgets/NextcloudPhpOpcache.vue | 17 ++++++++++++++--- src/components/Widgets/NextcloudStats.vue | 3 ++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/Widgets/NextcloudPhpOpcache.vue b/src/components/Widgets/NextcloudPhpOpcache.vue index 645cac07..f361d7b0 100644 --- a/src/components/Widgets/NextcloudPhpOpcache.vue +++ b/src/components/Widgets/NextcloudPhpOpcache.vue @@ -5,11 +5,11 @@

PHP opcache  - + {{ tt('enabled') }} - {{ tt('disabled') }}  - + {{ tt('disabled') }}  + {{ tt('cache-full') }}

@@ -200,4 +200,15 @@ export default { diff --git a/src/components/Widgets/NextcloudStats.vue b/src/components/Widgets/NextcloudStats.vue index 5d43bc60..8860434f 100644 --- a/src/components/Widgets/NextcloudStats.vue +++ b/src/components/Widgets/NextcloudStats.vue @@ -18,7 +18,7 @@ {{ tt('applications') }} - + {{ apps.num_updates_available }} {{ tt('updates-available', @@ -192,6 +192,7 @@ export default { @import '@/styles/widgets/nextcloud-shared.scss'; .nextcloud-stats-wrapper { div.server-info .nc-updates { + color: var(--success); margin-left: .5em; } } From 2933df20dd0dc0496794e75aa8b2a324c8ae947e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Wed, 22 Jun 2022 17:13:42 +0000 Subject: [PATCH 39/49] :sparkles: Allow pages to override theme --- src/store.js | 15 +++++++++++++++ src/utils/defaults.js | 1 + 2 files changed, 16 insertions(+) diff --git a/src/store.js b/src/store.js index 4eea3763..a8d92f3a 100644 --- a/src/store.js +++ b/src/store.js @@ -10,6 +10,7 @@ import { applyItemId } from '@/utils/SectionHelpers'; import filterUserSections from '@/utils/CheckSectionVisibility'; import ErrorHandler, { InfoHandler, InfoKeys } from '@/utils/ErrorHandler'; import { isUserAdmin } from '@/utils/Auth'; +import { localStorageKeys } from './utils/defaults'; Vue.use(Vuex); @@ -295,6 +296,11 @@ const store = new Vuex.Store({ state.navigateConfToTab = index; }, [SET_CURRENT_SUB_PAGE](state, subPageObject) { + if (!subPageObject) { + // Set theme back to primary when navigating to index page + const defaulTheme = localStorage.getItem(localStorageKeys.PRIMARY_THEME); + if (defaulTheme) state.config.appConfig.theme = defaulTheme; + } state.currentConfigInfo = subPageObject; }, [USE_MAIN_CONFIG](state) { @@ -312,13 +318,22 @@ const store = new Vuex.Store({ commit(SET_REMOTE_CONFIG, yaml.load((await axios.get('/conf.yml')).data)); const deepCopy = (json) => JSON.parse(JSON.stringify(json)); const config = deepCopy(new ConfigAccumulator().config()); + if (config.appConfig?.theme) { + // Save theme defined in conf.yml as primary + localStorage.setItem(localStorageKeys.PRIMARY_THEME, config.appConfig.theme); + // This will set theme back to primary in case we were on a themed page + // and the index page is loaded w/o navigation (e.g. modifying browser location) + localStorage.setItem(localStorageKeys.THEME, config.appConfig.theme); + } commit(SET_CONFIG, config); }, /* Fetch config for a sub-page (sections and pageInfo only) */ async [INITIALIZE_MULTI_PAGE_CONFIG]({ commit, state }, configPath) { axios.get(configPath).then((response) => { const subConfig = yaml.load(response.data); + const pageTheme = subConfig.appConfig?.theme; subConfig.appConfig = state.config.appConfig; // Always use parent appConfig + if (pageTheme) subConfig.appConfig.theme = pageTheme; // Apply page theme override commit(SET_CONFIG, subConfig); }).catch((err) => { ErrorHandler(`Unable to load config from '${configPath}'`, err); diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 128a3572..ba6336cc 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -120,6 +120,7 @@ module.exports = { COLLAPSE_STATE: 'collapseState', ICON_SIZE: 'iconSize', THEME: 'theme', + PRIMARY_THEME: 'primaryTheme', CUSTOM_COLORS: 'customColors', CONF_SECTIONS: 'confSections', CONF_WIDGETS: 'confSections', From 4c87092be5ed741afc7fa38c8d610455099aa73d Mon Sep 17 00:00:00 2001 From: patrickheeney Date: Thu, 23 Jun 2022 20:02:19 -0700 Subject: [PATCH 40/49] :sparkles: Remove component repetition --- src/components/Widgets/WidgetBase.vue | 597 ++++---------------------- 1 file changed, 79 insertions(+), 518 deletions(-) diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue index 7abb9790..353b2768 100644 --- a/src/components/Widgets/WidgetBase.vue +++ b/src/components/Widgets/WidgetBase.vue @@ -20,463 +20,13 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ handleError('Widget type was not found') }}
@@ -489,6 +39,74 @@ import UpdateIcon from '@/assets/interface-icons/widget-update.svg'; import OpenIcon from '@/assets/interface-icons/open-new-tab.svg'; import LoadingAnimation from '@/assets/interface-icons/loader.svg'; +const COMPAT = { + 'adguard-dns-info': 'AdGuardDnsInfo', + 'adguard-filter-status': 'AdGuardFilterStatus', + 'adguard-stats': 'AdGuardStats', + 'adguard-top-domains': 'AdGuardTopDomains', + anonaddy: 'AnonAddy', + apod: 'Apod', + 'blacklist-check': 'BlacklistCheck', + clock: 'Clock', + 'crypto-price-chart': 'CryptoPriceChart', + 'crypto-watch-list': 'CryptoWatchList', + 'cve-vulnerabilities': 'CveVulnerabilities', + 'domain-monitor': 'DomainMonitor', + 'code-stats': 'CodeStats', + 'covid-stats': 'CovidStats', + embed: 'EmbedWidget', + 'eth-gas-prices': 'EthGasPrices', + 'exchange-rates': 'ExchangeRates', + 'flight-data': 'Flights', + 'github-profile-stats': 'GitHubProfile', + 'github-trending-repos': 'GitHubTrending', + 'gl-alerts': 'GlAlerts', + 'gl-current-cores': 'GlCpuCores', + 'gl-current-cpu': 'GlCpuGauge', + 'gl-cpu-history': 'GlCpuHistory', + 'gl-disk-io': 'GlDiskIo', + 'gl-disk-space': 'GlDiskSpace', + 'gl-ip-address': 'GlIpAddress', + 'gl-load-history': 'GlLoadHistory', + 'gl-current-mem': 'GlMemGauge', + 'gl-mem-history': 'GlMemHistory', + 'gl-network-interfaces': 'GlNetworkInterfaces', + 'gl-network-traffic': 'GlNetworkTraffic', + 'gl-system-load': 'GlSystemLoad', + 'gl-cpu-temp': 'GlCpuTemp', + 'health-checks': 'HealthChecks', + iframe: 'IframeWidget', + image: 'ImageWidget', + joke: 'Jokes', + 'mullvad-status': 'MullvadStatus', + 'nd-cpu-history': 'NdCpuHistory', + 'nd-load-history': 'NdLoadHistory', + 'nd-ram-history': 'NdRamHistory', + 'news-headlines': 'NewsHeadlines', + 'nextcloud-notifications': 'NextcloudNotifications', + 'nextcloud-php-opcache': 'NextcloudPhpOpcache', + 'nextcloud-stats': 'NextcloudStats', + 'nextcloud-system': 'NextcloudSystem', + 'nextcloud-user': 'NextcloudUser', + 'nextcloud-user-status': 'NextcloudUserStatus', + 'pi-hole-stats': 'PiHoleStats', + 'pi-hole-top-queries': 'PiHoleTopQueries', + 'pi-hole-traffic': 'PiHoleTraffic', + 'public-holidays': 'PublicHolidays', + 'public-ip': 'PublicIp', + 'rss-feed': 'RssFeed', + 'sports-scores': 'SportsScores', + 'stat-ping': 'StatPing', + 'stock-price-chart': 'StockPriceChart', + 'synology-download': 'SynologyDownload', + 'system-info': 'SystemInfo', + 'tfl-status': 'TflStatus', + 'wallet-balance': 'WalletBalance', + weather: 'Weather', + 'weather-forecast': 'WeatherForecast', + 'xkcd-comic': 'XkcdComic', +}; + export default { name: 'Widget', components: { @@ -497,72 +115,6 @@ export default { UpdateIcon, OpenIcon, LoadingAnimation, - // Register widget components - AdGuardDnsInfo: () => import('@/components/Widgets/AdGuardDnsInfo.vue'), - AdGuardFilterStatus: () => import('@/components/Widgets/AdGuardFilterStatus.vue'), - AdGuardStats: () => import('@/components/Widgets/AdGuardStats.vue'), - AdGuardTopDomains: () => import('@/components/Widgets/AdGuardTopDomains.vue'), - AnonAddy: () => import('@/components/Widgets/AnonAddy.vue'), - Apod: () => import('@/components/Widgets/Apod.vue'), - BlacklistCheck: () => import('@/components/Widgets/BlacklistCheck.vue'), - Clock: () => import('@/components/Widgets/Clock.vue'), - CodeStats: () => import('@/components/Widgets/CodeStats.vue'), - CovidStats: () => import('@/components/Widgets/CovidStats.vue'), - CryptoPriceChart: () => import('@/components/Widgets/CryptoPriceChart.vue'), - CryptoWatchList: () => import('@/components/Widgets/CryptoWatchList.vue'), - CveVulnerabilities: () => import('@/components/Widgets/CveVulnerabilities.vue'), - DomainMonitor: () => import('@/components/Widgets/DomainMonitor.vue'), - EmbedWidget: () => import('@/components/Widgets/EmbedWidget.vue'), - EthGasPrices: () => import('@/components/Widgets/EthGasPrices.vue'), - ExchangeRates: () => import('@/components/Widgets/ExchangeRates.vue'), - Flights: () => import('@/components/Widgets/Flights.vue'), - GitHubTrending: () => import('@/components/Widgets/GitHubTrending.vue'), - GitHubProfile: () => import('@/components/Widgets/GitHubProfile.vue'), - GlAlerts: () => import('@/components/Widgets/GlAlerts.vue'), - GlCpuCores: () => import('@/components/Widgets/GlCpuCores.vue'), - GlCpuGauge: () => import('@/components/Widgets/GlCpuGauge.vue'), - GlCpuHistory: () => import('@/components/Widgets/GlCpuHistory.vue'), - GlDiskIo: () => import('@/components/Widgets/GlDiskIo.vue'), - GlDiskSpace: () => import('@/components/Widgets/GlDiskSpace.vue'), - GlIpAddress: () => import('@/components/Widgets/GlIpAddress.vue'), - GlLoadHistory: () => import('@/components/Widgets/GlLoadHistory.vue'), - GlMemGauge: () => import('@/components/Widgets/GlMemGauge.vue'), - GlMemHistory: () => import('@/components/Widgets/GlMemHistory.vue'), - GlNetworkInterfaces: () => import('@/components/Widgets/GlNetworkInterfaces.vue'), - GlNetworkTraffic: () => import('@/components/Widgets/GlNetworkTraffic.vue'), - GlSystemLoad: () => import('@/components/Widgets/GlSystemLoad.vue'), - GlCpuTemp: () => import('@/components/Widgets/GlCpuTemp.vue'), - HealthChecks: () => import('@/components/Widgets/HealthChecks.vue'), - IframeWidget: () => import('@/components/Widgets/IframeWidget.vue'), - ImageWidget: () => import('@/components/Widgets/ImageWidget.vue'), - Jokes: () => import('@/components/Widgets/Jokes.vue'), - MullvadStatus: () => import('@/components/Widgets/MullvadStatus.vue'), - NdCpuHistory: () => import('@/components/Widgets/NdCpuHistory.vue'), - NdLoadHistory: () => import('@/components/Widgets/NdLoadHistory.vue'), - NdRamHistory: () => import('@/components/Widgets/NdRamHistory.vue'), - NewsHeadlines: () => import('@/components/Widgets/NewsHeadlines.vue'), - NextcloudNotifications: () => import('@/components/Widgets/NextcloudNotifications.vue'), - NextcloudPhpOpcache: () => import('@/components/Widgets/NextcloudPhpOpcache.vue'), - NextcloudStats: () => import('@/components/Widgets/NextcloudStats.vue'), - NextcloudSystem: () => import('@/components/Widgets/NextcloudSystem.vue'), - NextcloudUser: () => import('@/components/Widgets/NextcloudUser.vue'), - NextcloudUserStatus: () => import('@/components/Widgets/NextcloudUserStatus.vue'), - PiHoleStats: () => import('@/components/Widgets/PiHoleStats.vue'), - PiHoleTopQueries: () => import('@/components/Widgets/PiHoleTopQueries.vue'), - PiHoleTraffic: () => import('@/components/Widgets/PiHoleTraffic.vue'), - PublicHolidays: () => import('@/components/Widgets/PublicHolidays.vue'), - PublicIp: () => import('@/components/Widgets/PublicIp.vue'), - RssFeed: () => import('@/components/Widgets/RssFeed.vue'), - SportsScores: () => import('@/components/Widgets/SportsScores.vue'), - StatPing: () => import('@/components/Widgets/StatPing.vue'), - StockPriceChart: () => import('@/components/Widgets/StockPriceChart.vue'), - SynologyDownload: () => import('@/components/Widgets/SynologyDownload.vue'), - SystemInfo: () => import('@/components/Widgets/SystemInfo.vue'), - TflStatus: () => import('@/components/Widgets/TflStatus.vue'), - WalletBalance: () => import('@/components/Widgets/WalletBalance.vue'), - Weather: () => import('@/components/Widgets/Weather.vue'), - WeatherForecast: () => import('@/components/Widgets/WeatherForecast.vue'), - XkcdComic: () => import('@/components/Widgets/XkcdComic.vue'), }, props: { widget: Object, @@ -603,6 +155,15 @@ export default { hideControls() { return this.widget.hideControls; }, + component() { + const type = COMPAT[this.widgetType] || this.widget.type; + if (!type) { + ErrorHandler('Widget type was not found'); + return null; + } + // eslint-disable-next-line prefer-template + return () => import('@/components/Widgets/' + type + '.vue'); + }, }, methods: { /* Calls update data method on widget */ From 542a9fe9bd07fd2606eaf11a4732b36fe803862b Mon Sep 17 00:00:00 2001 From: patrickheeney Date: Fri, 24 Jun 2022 08:00:50 -0700 Subject: [PATCH 41/49] :adhesive_bandage: Handle failed component load. --- src/components/Widgets/WidgetBase.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue index 353b2768..0e590d12 100644 --- a/src/components/Widgets/WidgetBase.vue +++ b/src/components/Widgets/WidgetBase.vue @@ -162,7 +162,7 @@ export default { return null; } // eslint-disable-next-line prefer-template - return () => import('@/components/Widgets/' + type + '.vue'); + return () => import('@/components/Widgets/' + type + '.vue').catch(() => import('@/components/Widgets/Blank.vue')); }, }, methods: { From 91d4fd55c0f21a939d0c265911a85650206d8a36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Fri, 24 Jun 2022 19:28:46 +0000 Subject: [PATCH 42/49] :adhesive_bandage: Ensure stat finished before startSSLServer() The two chained stat() promises may not have finished by the time `enableSSL` is evaluated in case of a slow file system (e.g. on a Raspberry Pi where the only block device is an SD card). --- services/ssl-server.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/services/ssl-server.js b/services/ssl-server.js index 13695bd6..c63b7db3 100644 --- a/services/ssl-server.js +++ b/services/ssl-server.js @@ -24,21 +24,23 @@ const printSuccess = () => { // Check if the SSL certs are present and SSL should be enabled let enableSSL = false; -stat(httpsCerts.public).then(() => { - stat(httpsCerts.private).then(() => { +const checkCertificateFiles = stat(httpsCerts.public).then(() => { + return stat(httpsCerts.private).then(() => { enableSSL = true; }).catch(() => { printNotSoGood('Private key not present'); }); }).catch(() => { printNotSoGood('Public key not present'); }); const startSSLServer = (app) => { - // If SSL should be enabled, create a secured server and start it - if (enableSSL) { - const httpsServer = https.createServer({ - key: fs.readFileSync(httpsCerts.private), - cert: fs.readFileSync(httpsCerts.public), - }, app); - httpsServer.listen(SSLPort, () => { printSuccess(); }); - } + checkCertificateFiles.then(() => { + // If SSL should be enabled, create a secured server and start it + if (enableSSL) { + const httpsServer = https.createServer({ + key: fs.readFileSync(httpsCerts.private), + cert: fs.readFileSync(httpsCerts.public), + }, app); + httpsServer.listen(SSLPort, () => { printSuccess(); }); + } + }); }; const middleware = (req, res, next) => { From 138ea8bd018176b11b08fcbf1c7f0729c92656ab Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 25 Jun 2022 21:14:26 +0100 Subject: [PATCH 43/49] :memo: Expands on the 404 fix --- docs/troubleshooting.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index c23fc908..f27553eb 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -1,8 +1,9 @@ # Troubleshooting -> _**This document contains common problems and their solutions.**_ +> _**This document contains common problems and their solutions.**_
+> Please ensure your issue isn't listed here, before opening a new ticket. > -> _If you came across an issue where the solution was not immediately obvious, consider adding it to this list to help other users._ +> _If you come across an issue not listed below, consider adding it, to help other users._ ### Contents - [Refused to Connect in Web Content View](#refused-to-connect-in-modal-or-workspace-view) @@ -94,10 +95,18 @@ If this works, but you wish to continue using HTML5 history mode, then a bit of ## 404 after Launch from Mobile Home Screen -Similar to the above issue, if you get a 404 after using iOS's “add to Home Screen” feature, then this is caused by Vue router. +Similar to the above issue, if you get a 404 after using iOS and Android's “Add to Home Screen” feature, then this is caused by Vue router. It can be fixed by setting `appConfig.routingMode` to `hash` -See also: [#628](https://github.com/Lissy93/dashy/issues/628) +See also: [#628](https://github.com/Lissy93/dashy/issues/628), [#762](https://github.com/Lissy93/dashy/issues/762) + +--- + +## 404 On Multi-Page Apps + +Similar to above, if you get a 404 error when visiting a page directly on multi-page apps, then this can be fixed under `appConfig`, by setting `routingMode` to `hash`. Then rebuilding, and refreshing the page. + +See also: [#670](https://github.com/Lissy93/dashy/issues/670), [#763](https://github.com/Lissy93/dashy/issues/763) --- From cb80dd195c68d103b82523e84dfb5971331baee2 Mon Sep 17 00:00:00 2001 From: repo-visualizer Date: Sun, 26 Jun 2022 01:31:06 +0000 Subject: [PATCH 44/49] :yellow_heart: Updates repo diagram --- docs/assets/repo-visualization.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/assets/repo-visualization.svg b/docs/assets/repo-visualization.svg index 49767897..1ca5a198 100644 --- a/docs/assets/repo-visualization.svg +++ b/docs/assets/repo-visualization.svg @@ -1 +1 @@ -viewsviewsutilsutilsstylesstylesmixinsmixinsdirectivesdirectivescomponentscomponentsassetsassetsWorkspaceWorkspaceWidgetsWidgetsSettingsSettingsPageStrcturePageStrctureMinimalViewMinimalViewLinkItemsLinkItemsInteractiveEditorInteractiveEditorFormElementsFormElementsConfigurationConfigurationChartsChartslocaleslocalesinterface-iconsinterface-iconsLogin.vueLogin.vueLogin.vueemojis.jsonemojis.jsonemojis.jsonConfigSch...ConfigSch...ConfigSch...defaults.jsdefaults.jsdefaults.jscolor-the...color-the...color-the...store.jsstore.jsstore.jsWidgetBas...WidgetBas...WidgetBas...AnonAddy.vueAnonAddy.vueAnonAddy.vueCustomTh...CustomTh...CustomTh...Section.vueSection.vueSection.vueItemIcon...ItemIcon...ItemIcon...Item.vueItem.vueItem.vueEditItem...EditItem...EditItem...ConfigCo...ConfigCo...ConfigCo...Gauge.vueGauge.vueGauge.vuebg.jsonbg.jsonbg.jsonhi.jsonhi.jsonhi.jsonko.jsonko.jsonko.jsonpt.jsonpt.jsonpt.jsonit.jsonit.jsonit.jsonfr.jsonfr.jsonfr.jsonen.jsonen.jsonen.jsonsv.jsonsv.jsonsv.jsonzh-CN.jsonzh-CN.jsonzh-CN.jsonru.jsonru.jsonru.jsonsl.jsonsl.jsonsl.json.js.json.scss.svg.vueeach dot sized by file size \ No newline at end of file +viewsviewsutilsutilsstylesstylesmixinsmixinsdirectivesdirectivescomponentscomponentsassetsassetsWorkspaceWorkspaceWidgetsWidgetsSettingsSettingsPageStrcturePageStrctureMinimalViewMinimalViewLinkItemsLinkItemsInteractiveEditorInteractiveEditorFormElementsFormElementsConfigurationConfigurationChartsChartslocaleslocalesinterface-iconsinterface-iconsemojis.jsonemojis.jsonemojis.jsonConfigSch...ConfigSch...ConfigSch...color-the...color-the...color-the...store.jsstore.jsstore.jsAnonAddy...AnonAddy...AnonAddy...CustomTh...CustomTh...CustomTh...Section.vueSection.vueSection.vueItemIcon...ItemIcon...ItemIcon...Item.vueItem.vueItem.vueEditItem...EditItem...EditItem...ConfigCo...ConfigCo...ConfigCo...en.jsonen.jsonen.jsonbg.jsonbg.jsonbg.jsonhi.jsonhi.jsonhi.jsonko.jsonko.jsonko.jsonpt.jsonpt.jsonpt.jsonit.jsonit.jsonit.jsonfr.jsonfr.jsonfr.jsonsv.jsonsv.jsonsv.jsonzh-CN.jsonzh-CN.jsonzh-CN.jsonru.jsonru.jsonru.jsonsl.jsonsl.jsonsl.json.js.json.scss.svg.vueeach dot sized by file size \ No newline at end of file From 5c81d53606bfbfdca6acd544873d53b38573aca6 Mon Sep 17 00:00:00 2001 From: Alicia Bot <87835202+liss-bot@users.noreply.github.com> Date: Sun, 26 Jun 2022 02:34:49 +0100 Subject: [PATCH 45/49] :blue_heart: Updates contributor SVG --- docs/assets/CONTRIBUTORS.svg | 93 ++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/docs/assets/CONTRIBUTORS.svg b/docs/assets/CONTRIBUTORS.svg index 51b2bb7e..47481da1 100644 --- a/docs/assets/CONTRIBUTORS.svg +++ b/docs/assets/CONTRIBUTORS.svg @@ -6,127 +6,136 @@ + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + \ No newline at end of file From f9195ac33a40833680ba05755edee55da3d99cba Mon Sep 17 00:00:00 2001 From: Alicia Bot <87835202+liss-bot@users.noreply.github.com> Date: Sun, 26 Jun 2022 02:34:55 +0100 Subject: [PATCH 46/49] :blue_heart: Makes author list --- .github/AUTHORS.txt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/AUTHORS.txt b/.github/AUTHORS.txt index 6077f606..e99650f7 100644 --- a/.github/AUTHORS.txt +++ b/.github/AUTHORS.txt @@ -13,7 +13,7 @@ Jeremy - 1 commits Kieren - 1 commits Leonardo - 1 commits M - 1 commits -Marcell <ülö> - 1 commits +Markus - 1 commits PlusaN <61884717+PlusaN@users.noreply.github.com> - 1 commits Rune - 1 commits Ryan - 1 commits @@ -35,7 +35,9 @@ Brendan <'Lear> - 2 commits CHAIYEON - 2 commits Dan - 2 commits Ruben - 2 commits +k073l <21180271+k073l@users.noreply.github.com> - 2 commits liss-bot <87835202+liss-bot@users.noreply.github.com> - 2 commits +patrickheeney - 2 commits ᗪєνιη <υн> - 2 commits Walkx <71191962+walkxcode@users.noreply.github.com> - 3 commits aterox - 3 commits @@ -57,13 +59,14 @@ github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> - 16 snyk-bot - 18 commits aterox - 19 commits EVOTk <45015615+EVOTk@users.noreply.github.com> - 22 commits +Marcell <ülö> - 23 commits Alicia - 28 commits -repo-visualizer - 41 commits +repo-visualizer - 43 commits snyk-bot - 50 commits Lissy93 - 78 commits -Alicia - 82 commits -liss-bot - 93 commits -Alicia - 136 commits +Alicia - 84 commits +liss-bot - 95 commits +Alicia - 148 commits Lissy93 - 208 commits Alicia - 440 commits Alicia - 1488 commits \ No newline at end of file From 4b65fedfe40ea9b136eecc8106840389ea3f06d5 Mon Sep 17 00:00:00 2001 From: Doug Lock Date: Sun, 26 Jun 2022 17:12:57 +0100 Subject: [PATCH 47/49] Update tflStatus API endpoint to include all modes of transport from the "normal" TFL status updates page here: https://tfl.gov.uk/tube-dlr-overground/status/ --- src/utils/defaults.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/defaults.js b/src/utils/defaults.js index ba6336cc..273dde06 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -243,7 +243,7 @@ module.exports = { rssToJson: 'https://api.rss2json.com/v1/api.json', sportsScores: 'https://www.thesportsdb.com/api/v1/json', stockPriceChart: 'https://www.alphavantage.co/query', - tflStatus: 'https://api.tfl.gov.uk/line/mode/tube/status', + tflStatus: 'https://api.tfl.gov.uk/line/mode/dlr,elizabeth-line,overground,tram,tube/status', walletBalance: 'https://api.blockcypher.com/v1', walletQrCode: 'https://www.bitcoinqrcodemaker.com/api', weather: 'https://api.openweathermap.org/data/2.5/weather', From 736f0e95ed52e775679c1a781cf8a1cf1c2154bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcell=20F=C3=BCl=C3=B6p?= Date: Sun, 26 Jun 2022 17:25:08 +0000 Subject: [PATCH 48/49] :adhesive_bandage: Stop status-check when Item is destroyed --- src/components/LinkItems/Item.vue | 6 +++++- src/mixins/ItemMixin.js | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/LinkItems/Item.vue b/src/components/LinkItems/Item.vue index 75cf3563..24df237e 100644 --- a/src/components/LinkItems/Item.vue +++ b/src/components/LinkItems/Item.vue @@ -175,9 +175,13 @@ export default { if (this.enableStatusCheck) this.checkWebsiteStatus(); // If continious status checking is enabled, then start ever-lasting loop if (this.statusCheckInterval > 0) { - setInterval(this.checkWebsiteStatus, this.statusCheckInterval * 1000); + this.intervalId = setInterval(this.checkWebsiteStatus, this.statusCheckInterval * 1000); } }, + beforeDestroy() { + // Stop periodic status-check when item is destroyed (e.g. navigating in multi-page setup) + if (this.intervalId) clearInterval(this.intervalId); + }, }; diff --git a/src/mixins/ItemMixin.js b/src/mixins/ItemMixin.js index 47148c4c..83113752 100644 --- a/src/mixins/ItemMixin.js +++ b/src/mixins/ItemMixin.js @@ -22,6 +22,7 @@ export default { return { statusResponse: undefined, contextMenuOpen: false, + intervalId: undefined, // status-check setInterval() id contextPos: { posX: undefined, posY: undefined, From 2daabed9dd562aee3e3e69f89dd4bdb916d85a49 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Wed, 29 Jun 2022 13:36:19 +0100 Subject: [PATCH 49/49] =?UTF-8?q?=F0=9F=93=9D=20Adds=20Reset=20Local=20Set?= =?UTF-8?q?tings=20to=20Troubleshooting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/troubleshooting.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index f27553eb..d464a919 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -33,6 +33,7 @@ - [Weather Forecast Widget 401](#weather-forecast-widget-401) - [Font Awesome Icons not Displaying](#font-awesome-icons-not-displaying) - [Copy to Clipboard not Working](#copy-to-clipboard-not-working) +- [How to Reset Local Settings](#how-to-reset-local-settings) - [How-To Open Browser Console](#how-to-open-browser-console) - [Git Contributions not Displaying](#git-contributions-not-displaying) @@ -457,6 +458,21 @@ As a workaround, you could either: --- +## How to Reset Local Settings + +Some settings are stored locally, in the browser's storage. + +In some instances cached assets can prevent your settings from being updated, in which case you may wish to reset local data. + +To clear all local data from the UI, head to the Config Menu, then click "Reset Local Settings", and Confirm when prompted. +This will not affect your config file. But be sure that you keep a backup of your config, if you've not written changes it to disk. + +You can also view any and all data that Dashy is storing, using the developer tools. Open your browser's dev tools (usually F12), in Chromium head to the Application tab, or in Firefox go to the Storage tab. Select Local Storage, then scroll down the the URL Dashy is running on. You should now see all data being stored, and you can select and delete any fields you wish. + +For a full list of all data that may be cached, see the [Privacy Docs](/docs/privacy.md#browser-storage). + +--- + ## How-To Open Browser Console When raising a bug, one crucial piece of info needed is the browser's console output. This will help the developer diagnose and fix the issue.