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 01/19] :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 02/19] :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 03/19] :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 @@