🔀 Merge branch 'master' of github.com:Lissy93/dashy into REFACTOR/widget-and-docs-improvments
This commit is contained in:
@@ -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);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="apod-wrapper" v-if="image">
|
||||
<div class="apod-wrapper" v-if="url">
|
||||
<a :href="link" class="title" target="__blank" title="View Article">
|
||||
{{ title }}
|
||||
</a>
|
||||
<a :href="hdImage" title="View HD Image" class="picture" target="__blank">
|
||||
<img :src="image" :alt="title" />
|
||||
<a :href="hdurl" title="View HD Image" class="picture" target="__blank">
|
||||
<img :src="url" :alt="title" />
|
||||
</a>
|
||||
<p class="copyright">{{ copyright }}</p>
|
||||
<p class="description">{{ truncatedDescription }}</p>
|
||||
<p class="explanation">{{ truncatedExplanation }}</p>
|
||||
<p @click="toggleShowFull" class="expend-details-btn">
|
||||
{{ showFullDesc ? $t('widgets.general.show-less') : $t('widgets.general.show-more') }}
|
||||
{{ showFullExp ? $t('widgets.general.show-less') : $t('widgets.general.show-more') }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -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;
|
||||
|
||||
208
src/components/Widgets/NextcloudNotifications.vue
Normal file
208
src/components/Widgets/NextcloudNotifications.vue
Normal file
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<div class="nextcloud-widget nextcloud-status-wrapper">
|
||||
<div v-if="notifications.length">
|
||||
<!-- group actions: delete all -->
|
||||
<p v-if="canDeleteNotification('delete-all')" class="group-action">
|
||||
<span class="action secondary" @click="deleteNotifications">{{ tt('delete-all') }}</span>
|
||||
</p>
|
||||
<hr/>
|
||||
<!-- notifications list -->
|
||||
<div v-for="(notification, idx) in notifications" :key="idx" class="notification">
|
||||
<div><img :src="notificationIcon(notification.icon)" /></div>
|
||||
<div>
|
||||
<p>
|
||||
<small class="date" v-tooltip="dateTooltip(notification)">
|
||||
{{ getTimeAgo(Date.parse(notification.datetime)) }}
|
||||
</small> <span v-tooltip="subjectTooltip(notification)">{{ notification.subject }} </span>
|
||||
<!-- notifications item: action links -->
|
||||
<span v-if="notification.actions.length">
|
||||
<span v-for="(action, idx) in notification.actions" :key="idx">
|
||||
<a :href="action.link" class="action" target="_blank">{{ action.label }}</a>
|
||||
</span>
|
||||
</span>
|
||||
<span v-if="canDeleteNotification('delete')">
|
||||
<a @click="deleteNotification(notification.notification_id)"
|
||||
class="action secondary">{{ tt('delete-notification') }}</a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- empty notifications list -->
|
||||
<div v-else class="sep">
|
||||
<p>{{ tt('no-notifications') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import NextcloudMixin from '@/mixins/NextcloudMixin';
|
||||
|
||||
/**
|
||||
* NextcloudNotifications widget - Displays the user's notifications
|
||||
* Used endpoints
|
||||
* - capabilities: to determine if the User Notification API is enabled
|
||||
* - notifications: to fetch list of notifications, delete all or a single notification
|
||||
*/
|
||||
export default {
|
||||
mixins: [WidgetMixin, NextcloudMixin],
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
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];
|
||||
},
|
||||
async fetchData() {
|
||||
if (!this.hasValidCredentials()) return;
|
||||
await this.loadCapabilities();
|
||||
if (!this.capabilities?.notifications?.enabled) {
|
||||
this.error('This Nextcloud server doesn\'t support the Notifications API');
|
||||
return;
|
||||
}
|
||||
this.makeRequest(this.endpoint('notifications'), this.headers)
|
||||
.then(this.processNotifications)
|
||||
.finally(this.finishLoading);
|
||||
},
|
||||
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 notiTime = Date.parse(notification.datetime);
|
||||
const nowTime = new Date().getTime();
|
||||
if (limitTime && notiTime && nowTime - notiTime > limitTime) return; // time limit
|
||||
this.notifications.push(notification);
|
||||
});
|
||||
},
|
||||
/* Transform icon URL to SVG Color API request URL
|
||||
* @see https://docs.nextcloud.com/server/latest/developer_manual/html_css_design/icons.html */
|
||||
notificationIcon(url) {
|
||||
const color = this.getValueFromCss('widget-text-color').replace('#', '');
|
||||
return url.replace('core/img', 'svg/core')
|
||||
.replace(/extra-apps\/([^/]+)\/img/, 'svg/$1')
|
||||
.replace(/apps\/([^/]+)\/img/, 'svg/$1')
|
||||
.replace('.svg', `?color=${color}`);
|
||||
},
|
||||
/* Notification actions */
|
||||
canDeleteNotification(deleteTarget) {
|
||||
const capNotif = this.capabilities?.notifications?.features;
|
||||
return Array.isArray(capNotif) && capNotif.includes(deleteTarget);
|
||||
},
|
||||
deleteNotifications() {
|
||||
this.makeRequest(this.endpoint('notifications'), this.headers, 'DELETE')
|
||||
.then(() => {
|
||||
this.notifications = [];
|
||||
});
|
||||
},
|
||||
deleteNotification(id) {
|
||||
this.makeRequest(`${this.endpoint('notifications')}/${id}`, this.headers, 'DELETE')
|
||||
.then(this.fetchData);
|
||||
},
|
||||
/* Tooltip generators */
|
||||
subjectTooltip(notification) {
|
||||
const content = notification.message;
|
||||
return {
|
||||
content, trigger: 'hover focus', delay: 250, classes: 'nc-tooltip',
|
||||
};
|
||||
},
|
||||
dateTooltip(notification) {
|
||||
const content = new Date(Date.parse(notification.datetime)).toLocaleString();
|
||||
return {
|
||||
content, trigger: 'hover focus', delay: 250, classes: 'nc-tooltip',
|
||||
};
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.overrideUpdateInterval = 60;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/styles/widgets/nextcloud-shared.scss';
|
||||
.nextcloud-status-wrapper {
|
||||
|
||||
div p small i {
|
||||
position: relative;
|
||||
top: .25em;
|
||||
}
|
||||
small.date {
|
||||
background: var(--widget-text-color);
|
||||
color: var(--widget-accent-color);
|
||||
border-radius: .25em;
|
||||
padding: .15em .3em;
|
||||
margin: .25em .25em .25em 0;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
}
|
||||
p.group-action {
|
||||
margin-top: 0;
|
||||
}
|
||||
span.action, span a.action {
|
||||
cursor: pointer;
|
||||
margin: .1em .5em .1em 0;
|
||||
padding: .15em;
|
||||
border-radius: .25em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
span.action:hover, span a.action:hover {
|
||||
background: var(--widget-text-color);
|
||||
color: var(--widget-accent-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
.secondary {
|
||||
opacity: .5;
|
||||
font-size: 75%;
|
||||
margin-left: .2rem;
|
||||
}
|
||||
div.notification {
|
||||
display: table;
|
||||
width: 100%;
|
||||
> div:first-child {
|
||||
float: right;
|
||||
}
|
||||
> div:nth-child(2) {
|
||||
float: left;
|
||||
width: 93%;
|
||||
}
|
||||
> div {
|
||||
display: table-cell;
|
||||
text-align: left;
|
||||
> img {
|
||||
float: right;
|
||||
width: 1em;
|
||||
position: relative;
|
||||
top: 1em;
|
||||
opacity: .75;
|
||||
}
|
||||
}
|
||||
}
|
||||
div hr {
|
||||
margin-top: .3em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
214
src/components/Widgets/NextcloudPhpOpcache.vue
Normal file
214
src/components/Widgets/NextcloudPhpOpcache.vue
Normal file
@@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<div v-if="didLoadData" class="nextcloud-widget nextcloud-phpopcache-wrapper">
|
||||
<div class="sep">
|
||||
<!-- PHP opcache enabled and cache full -->
|
||||
<p v-tooltip="opcacheStartTimeTooltip()">
|
||||
<i class="fal fa-microchip"></i>
|
||||
<strong>PHP opcache</strong>
|
||||
<em v-if="opcache.opcache_enabled" class="oc-enabled">
|
||||
{{ tt('enabled') }}
|
||||
</em>
|
||||
<em v-else class="oc-disabled">{{ tt('disabled') }}</em>
|
||||
<strong v-if="opcache.cache_full" class="oc-full">
|
||||
<i class="far fa-siren-on"></i>{{ tt('cache-full') }}
|
||||
</strong>
|
||||
</p>
|
||||
<hr/>
|
||||
<!-- PHP opcache stats -->
|
||||
<div v-if="opcache.opcache_enabled">
|
||||
<!-- PHP opcache stats: hit/miss -->
|
||||
<p v-tooltip="opcacheStatsTooltip()">
|
||||
<i class="fal fa-bullseye-arrow"></i>
|
||||
<em v-html="formatNumber(opcache_stats.hits)"></em>
|
||||
<small>{{ tt('hits') }}</small>
|
||||
<em v-html="formatNumber(opcache_stats.misses)"></em>
|
||||
<small>{{ tt('misses') }}</small>
|
||||
<em v-html="formatPercent(opcache_stats.opcache_hit_rate, 3)"></em>
|
||||
<small>{{ tt('hit-rate') }}</small>
|
||||
</p>
|
||||
<hr/>
|
||||
<!-- PHP opcache stats: memory -->
|
||||
<p v-tooltip="opcacheMemoryUsageTooltip()">
|
||||
<i class="fal fa-memory"></i>
|
||||
<em v-html="formatPercent(opcache.memory_usage.used_memory_percentage, 1)"></em>
|
||||
<small>of</small>
|
||||
<em v-html="convertBytes(opcache.memory_usage.total_memory)"></em>
|
||||
<small>{{ tt('memory-used') }}</small>
|
||||
</p>
|
||||
<hr/>
|
||||
<!-- PHP opcache stats: interned strings -->
|
||||
<p v-tooltip="opcacheInternedStringsTooltip()">
|
||||
<i class="fal fa-puzzle-piece"></i>
|
||||
<em v-html="formatNumber(opcache.interned_strings_usage.number_of_strings, 1, true)"></em>
|
||||
<small>{{ tt('strings-use') }}</small>
|
||||
<em v-html="formatPercent(opcache.interned_strings_usage.used_memory_percentage)"></em>
|
||||
<small>{{ tt('of') }}</small>
|
||||
<em v-html="convertBytes(opcache.interned_strings_usage.total_memory)"></em>
|
||||
</p>
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import NextcloudMixin from '@/mixins/NextcloudMixin';
|
||||
|
||||
/**
|
||||
* NextcloudPhpOpcache widget - Shows statistics about PHP opcache performance
|
||||
* Used endpoints
|
||||
* - serverinfo: requires Nextcloud admin user
|
||||
*/
|
||||
export default {
|
||||
mixins: [WidgetMixin, NextcloudMixin],
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
opcache: {
|
||||
opcache_enabled: null,
|
||||
full: null,
|
||||
opcache_statistics: {
|
||||
num_cached_scripts: null,
|
||||
num_cached_keys: null,
|
||||
max_cached_keys: null,
|
||||
hits: null,
|
||||
start_time: null,
|
||||
last_restart_time: null,
|
||||
misses: null,
|
||||
opcache_hit_rate: null,
|
||||
},
|
||||
memory_usage: {
|
||||
used_memory: null,
|
||||
free_memory: null,
|
||||
total_memory: null,
|
||||
wasted_memory: null,
|
||||
used_memory_percentage: null,
|
||||
current_wasted_percentage: null,
|
||||
},
|
||||
interned_strings_usage: {
|
||||
buffer_size: null,
|
||||
used_memory: null,
|
||||
total_memory: null,
|
||||
free_memory: null,
|
||||
number_of_strings: null,
|
||||
used_memory_percentage: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
didLoadData() {
|
||||
return typeof (this?.opcache?.opcache_enabled) === 'boolean';
|
||||
},
|
||||
// shortcuts to data members
|
||||
opcache_stats() {
|
||||
return this.opcache.opcache_statistics;
|
||||
},
|
||||
opcache_interned() {
|
||||
return this.opcache.interned_strings_usage;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
allowedStatuscodes() {
|
||||
return [200];
|
||||
},
|
||||
fetchData() {
|
||||
if (!this.hasValidCredentials()) return;
|
||||
this.makeRequest(this.endpoint('serverinfo'), this.headers)
|
||||
.then(this.processServerInfo)
|
||||
.finally(() => this.finishLoading());
|
||||
},
|
||||
processServerInfo(serverData) {
|
||||
const data = this.validateResponse(serverData);
|
||||
this.opcache = data.server?.php?.opcache;
|
||||
if (!this.opcache) return;
|
||||
this.updateOpcacheMemory();
|
||||
this.updateOpcacheInterned();
|
||||
},
|
||||
updateOpcacheMemory() {
|
||||
this.opcache_stats.opcache_hit_rate = parseFloat(
|
||||
this.opcache_stats.opcache_hit_rate,
|
||||
).toFixed(3);
|
||||
this.opcache.memory_usage.total_memory = (
|
||||
this.opcache.memory_usage.used_memory + this.opcache.memory_usage.free_memory
|
||||
);
|
||||
this.opcache.memory_usage.used_memory_percentage = parseFloat(
|
||||
(this.opcache.memory_usage.used_memory / this.opcache.memory_usage.total_memory) * 100,
|
||||
).toFixed(1);
|
||||
},
|
||||
updateOpcacheInterned() {
|
||||
this.opcache_interned.total_memory = (
|
||||
this.opcache_interned.used_memory + this.opcache_interned.free_memory
|
||||
);
|
||||
this.opcache_interned.used_memory_percentage = parseFloat(
|
||||
(this.opcache_interned.used_memory / this.opcache_interned.total_memory) * 100,
|
||||
).toFixed(5);
|
||||
},
|
||||
/* Tooltip generators */
|
||||
opcacheStartTimeTooltip() {
|
||||
let content = `${this.tt('started')} `
|
||||
+ `${new Date(this.opcache_stats.start_time * 1000).toLocaleString()}`;
|
||||
if (this.opcache_stats.last_restart_time) {
|
||||
content = content.concat(
|
||||
`<br><br>${this.tt('last-restart')} `
|
||||
+ `${new Date(this.opcache_stats.last_restart_time * 1000).toLocaleString()}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
content, html: true, trigger: 'hover focus', delay: 250, classes: 'nc-tooltip',
|
||||
};
|
||||
},
|
||||
opcacheStatsTooltip() {
|
||||
const content = `${parseFloat(this.opcache_stats.hits).toLocaleString()} ${this.tt('hits')}<br>`
|
||||
+ `${parseFloat(this.opcache_stats.misses).toLocaleString()} ${this.tt('misses')}<br><br>`
|
||||
+ `${parseFloat(this.opcache_stats.num_cached_scripts).toLocaleString()} ${this.tt('scripts')}<br>`
|
||||
+ `${parseFloat(this.opcache_stats.num_cached_keys).toLocaleString()} ${this.tt('keys')}<br>`
|
||||
+ `${parseFloat(this.opcache_stats.max_cached_keys).toLocaleString()} ${this.tt('max-keys')}<br>`;
|
||||
return {
|
||||
content, html: true, trigger: 'hover focus', delay: 250, classes: 'nc-tooltip',
|
||||
};
|
||||
},
|
||||
opcacheMemoryUsageTooltip() {
|
||||
const content = `PHP opcache ${this.tt('memory-utilisation')}<br><br>`
|
||||
+ `${this.convertBytes(this.opcache.memory_usage.total_memory)} ${this.tt('total')}<br>`
|
||||
+ `${this.convertBytes(this.opcache.memory_usage.used_memory)} ${this.tt('used')}<br>`
|
||||
+ `${this.convertBytes(this.opcache.memory_usage.free_memory)} ${this.tt('free')}<br><br>`
|
||||
+ `${this.convertBytes(this.opcache.memory_usage.wasted_memory)} (`
|
||||
+ `${parseFloat(this.opcache.memory_usage.current_wasted_percentage).toFixed(1)}%) ${this.tt('wasted')}`;
|
||||
return {
|
||||
content, html: true, trigger: 'hover focus', delay: 250, classes: 'nc-tooltip',
|
||||
};
|
||||
},
|
||||
opcacheInternedStringsTooltip() {
|
||||
const content = 'PHP opcache interned strings<br><br>'
|
||||
+ `${this.convertBytes(this.opcache_interned.buffer_size)} ${this.tt('total')} ${this.tt('memory')}<br>`
|
||||
+ `${this.convertBytes(this.opcache_interned.used_memory)} ${this.tt('used')} ${this.tt('memory')}<br>`
|
||||
+ `${this.convertBytes(this.opcache_interned.free_memory)} ${this.tt('free')} ${this.tt('memory')}<br><br>`
|
||||
+ `${parseFloat(this.opcache_interned.number_of_strings).toLocaleString()}`
|
||||
+ ' strings';
|
||||
return {
|
||||
content, html: true, trigger: 'hover focus', delay: 250, classes: 'nc-tooltip',
|
||||
};
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.overrideUpdateInterval = 60;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/styles/widgets/nextcloud-shared.scss';
|
||||
.nextcloud-phpopcache-wrapper {
|
||||
.oc-enabled {
|
||||
color: var(--success);
|
||||
}
|
||||
.oc-disabled {
|
||||
color: var(--neutral);
|
||||
}
|
||||
.oc-full {
|
||||
color: var(--danger);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
199
src/components/Widgets/NextcloudStats.vue
Normal file
199
src/components/Widgets/NextcloudStats.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<div v-if="didLoadData" class="nextcloud-widget nextcloud-stats-wrapper">
|
||||
<div class="server-info sep">
|
||||
<!-- server info: users -->
|
||||
<div v-if="activeUsers">
|
||||
<p v-tooltip="activeUsersTooltip()">
|
||||
<i class="fal fa-user"></i>
|
||||
<em v-html="formatNumber(storage.num_users)"></em>
|
||||
<strong>{{ tt('total-users') }}</strong> <small>{{ tt('of-which') }}</small>
|
||||
<em v-html="formatNumber(activeUsers.last24hours)"></em>
|
||||
<strong>{{ tt('active') }}</strong> <small>({{ tt('last-24-hours') }})</small>
|
||||
</p>
|
||||
</div>
|
||||
<hr />
|
||||
<div v-if="nextcloud">
|
||||
<!-- server info: apps -->
|
||||
<p v-tooltip="appUpdatesTooltip()">
|
||||
<i class="fal fa-browser"></i>
|
||||
<em v-html="formatNumber(apps.num_installed)"></em>
|
||||
<strong>{{ tt('applications') }}</strong>
|
||||
<span v-if="apps.num_updates_available" class="nc-updates">
|
||||
<i class="fal fa-download"></i><em>{{ apps.num_updates_available }}</em>
|
||||
<strong>
|
||||
{{ tt('updates-available',
|
||||
{plural: apps.num_updates_available > 1 ? 's' : ''}) }}
|
||||
</strong>
|
||||
</span>
|
||||
<small v-else data-nc-updates class="disabled">{{ tt('no-pending-updates') }}</small>
|
||||
</p>
|
||||
<hr />
|
||||
<!-- server info: storage -->
|
||||
<p v-tooltip="storagesTooltip()">
|
||||
<i class="fal fa-file"></i><em v-html="formatNumber(storage.num_files)"></em>
|
||||
<strong>{{ tt('files', { plural: storage.num_files > 1 ? 's' : '' }) }}</strong>
|
||||
<small>{{ tt('in') }}</small><em>{{ storage.num_storages }}</em>
|
||||
<strong>{{ tt('storages', { plural: storage.num_storages > 1 ? 's' : '' }) }}</strong>
|
||||
• <strong v-html="convertBytes(system.freespace)"></strong>
|
||||
<small>{{ tt('free') }}</small>
|
||||
</p>
|
||||
<hr />
|
||||
<!-- server info: shares -->
|
||||
<p v-tooltip="sharesTooltip()">
|
||||
<i class="fal fa-share"></i>
|
||||
<em v-html="formatNumber(shares.num_shares)"></em>
|
||||
<strong>{{ tt('local') }}</strong> <small> {{ tt('and') }}</small>
|
||||
<em v-html="formatNumber(shares.num_fed_shares_sent
|
||||
+ shares.num_fed_shares_received)"></em>
|
||||
<strong>
|
||||
{{ tt('federated-shares') }}
|
||||
</strong>
|
||||
</p>
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import NextcloudMixin from '@/mixins/NextcloudMixin';
|
||||
|
||||
/**
|
||||
* NextcloudStats widget - Shows statistics about Nextcloud usage
|
||||
* Used endpoints
|
||||
* - serverinfo: requires Nextcloud admin user
|
||||
*/
|
||||
|
||||
export default {
|
||||
mixins: [WidgetMixin, NextcloudMixin],
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
nextcloud: {
|
||||
system: {
|
||||
freespace: null,
|
||||
apps: {
|
||||
num_installed: null,
|
||||
num_updates_available: 0,
|
||||
app_updates: [],
|
||||
},
|
||||
},
|
||||
storage: {
|
||||
num_users: null,
|
||||
num_files: null,
|
||||
num_storages: null,
|
||||
},
|
||||
shares: {
|
||||
num_shares: null,
|
||||
num_shares_user: null,
|
||||
num_shares_groups: null,
|
||||
num_shares_link: null,
|
||||
num_shares_mail: null,
|
||||
num_shares_room: null,
|
||||
num_shares_link_no_password: null,
|
||||
num_fed_shares_sent: null,
|
||||
num_fed_shares_received: null,
|
||||
},
|
||||
},
|
||||
activeUsers: {
|
||||
last5minutes: null,
|
||||
last1hour: null,
|
||||
last24hours: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
didLoadData() {
|
||||
return !!(this?.system?.freespace);
|
||||
},
|
||||
// data shortcuts
|
||||
system() {
|
||||
return this.nextcloud.system;
|
||||
},
|
||||
storage() {
|
||||
return this.nextcloud.storage;
|
||||
},
|
||||
shares() {
|
||||
return this.nextcloud.shares;
|
||||
},
|
||||
apps() {
|
||||
return this.nextcloud.system.apps;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
allowedStatuscodes() {
|
||||
return [200];
|
||||
},
|
||||
fetchData() {
|
||||
if (!this.hasValidCredentials()) return;
|
||||
this.makeRequest(this.endpoint('serverinfo'), this.headers)
|
||||
.then(this.processServerInfo)
|
||||
.finally(this.finishLoading);
|
||||
},
|
||||
processServerInfo(serverResponse) {
|
||||
const data = this.validateResponse(serverResponse);
|
||||
this.nextcloud = data.nextcloud;
|
||||
this.activeUsers = data.activeUsers;
|
||||
},
|
||||
/* Tooltip generators */
|
||||
activeUsersTooltip() {
|
||||
const content = `${parseFloat(this.activeUsers.last5minutes).toLocaleString()}`
|
||||
+ ` ${this.tt('last-5-minutes')}<br>`
|
||||
+ `${parseFloat(this.activeUsers.last1hour).toLocaleString()}`
|
||||
+ ` ${this.tt('last-hour')}<br>`;
|
||||
return {
|
||||
content, html: true, trigger: 'hover focus', delay: 250, classes: 'nc-tooltip',
|
||||
};
|
||||
},
|
||||
appUpdatesTooltip() {
|
||||
let content = `<strong>${this.tt('updates-available-for')}</strong><ul>`;
|
||||
Object.entries(this.system.apps.app_updates).forEach(([app, version]) => {
|
||||
content += `<li>${app}: ${version}</li>`;
|
||||
});
|
||||
content += '</ul>';
|
||||
return {
|
||||
content, html: true, trigger: 'hover focus', delay: 250, classes: 'nc-tooltip',
|
||||
};
|
||||
},
|
||||
storagesTooltip() {
|
||||
const content = `<strong>${this.tt('storages-by-type')}</strong><ul><li>`
|
||||
+ `${parseFloat(this.storage.num_storages_local).toLocaleString()} ${this.tt('local')}</li><li>`
|
||||
+ `${parseFloat(this.storage.num_storages_home).toLocaleString()} ${this.tt('home')}</li><li>`
|
||||
+ `${parseFloat(this.storage.num_storages_other).toLocaleString()} ${this.tt('other')}</li></ul>`
|
||||
+ `${parseFloat(this.storage.num_files).toLocaleString()} ${this.tt('total-files')}`;
|
||||
return {
|
||||
content, html: true, trigger: 'hover focus', delay: 250, classes: 'nc-tooltip',
|
||||
};
|
||||
},
|
||||
sharesTooltip() {
|
||||
const content = `<strong>${this.tt('local-shares')}</strong><ul><li>`
|
||||
+ `${parseFloat(this.shares.num_shares_user).toLocaleString()} ${this.tt('user')}</li><li>`
|
||||
+ `${parseFloat(this.shares.num_shares_groups).toLocaleString()} ${this.tt('groups')}</li><li>`
|
||||
+ `${parseFloat(this.shares.num_shares_mail).toLocaleString()} ${this.tt('email')}</li><li>`
|
||||
+ `${parseFloat(this.shares.num_shares_room).toLocaleString()} ${this.tt('chat-room')}</li><li>`
|
||||
+ `${parseFloat(this.shares.num_shares_link).toLocaleString()} ${this.tt('private-link')}</li><li>`
|
||||
+ `${parseFloat(this.shares.num_shares_link_no_password).toLocaleString()} ${this.tt('public-link')}</li></ul>`
|
||||
+ `<strong>${this.tt('federated-shares-ucfirst')}</strong><ul><li>`
|
||||
+ `${parseFloat(this.shares.num_fed_shares_sent).toLocaleString()} ${this.tt('sent')}</li><li>`
|
||||
+ `${parseFloat(this.shares.num_fed_shares_received).toLocaleString()} ${this.tt('received')}</li></ul>`;
|
||||
return {
|
||||
content, html: true, trigger: 'hover focus', delay: 250, classes: 'nc-tooltip',
|
||||
};
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.overrideUpdateInterval = 20;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/styles/widgets/nextcloud-shared.scss';
|
||||
.nextcloud-stats-wrapper {
|
||||
div.server-info .nc-updates {
|
||||
color: var(--success);
|
||||
margin-left: .5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
230
src/components/Widgets/NextcloudSystem.vue
Normal file
230
src/components/Widgets/NextcloudSystem.vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<div v-if="didLoadData" class="nextcloud-widget nextcloud-system-wrapper">
|
||||
<div class="charts">
|
||||
<!-- memory gauge -->
|
||||
<div class="chart-container">
|
||||
<small>{{ tt('overall') }} {{ tt('memory-utilisation') }}</small>
|
||||
<GaugeChart :value="memoryGauge.value"
|
||||
:baseColor="memoryGauge.background"
|
||||
:gaugeColor="memoryGauge.color">
|
||||
<p class="percentage">{{ memoryGauge.value }}%</p>
|
||||
</GaugeChart>
|
||||
<small>{{ getMemoryGaugeLabel() }}</small>
|
||||
</div>
|
||||
<!-- cpu load chart -->
|
||||
<div>
|
||||
<div
|
||||
:id="cpuLoadChartId" class="load-chart"
|
||||
v-tooltip="$t('widgets.glances.system-load-desc')"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<!-- server info: server -->
|
||||
<hr />
|
||||
<p>
|
||||
<i class="fal fa-server"></i>
|
||||
<strong>Nextcloud</strong>
|
||||
<em>{{ server.nextcloud.system.version }}</em> <small>• </small>
|
||||
<strong>{{ server.server.webserver }}/PHP</strong>
|
||||
<em>{{ server.server.php.version }}</em>
|
||||
</p>
|
||||
<hr />
|
||||
<!-- server info: database -->
|
||||
<p>
|
||||
<i class="fal fa-database"></i>
|
||||
<strong>{{ server.server.database.type }}</strong>
|
||||
<em>{{ server.server.database.version }}</em> <small>{{ tt('using') }}</small>
|
||||
<em v-html="convertBytes(server.server.database.size)"></em>
|
||||
</p>
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import NextcloudMixin from '@/mixins/NextcloudMixin';
|
||||
import GaugeChart from '@/components/Charts/Gauge';
|
||||
import ChartingMixin from '@/mixins/ChartingMixin';
|
||||
|
||||
/**
|
||||
* NextcloudSystem widget - Visualises CPU load and memory utilisation and shows server versions
|
||||
* Used endpoints
|
||||
* - serverinfo: requires Nextcloud admin user
|
||||
*/
|
||||
export default {
|
||||
mixins: [WidgetMixin, NextcloudMixin, ChartingMixin],
|
||||
components: { GaugeChart },
|
||||
data() {
|
||||
return {
|
||||
server: {
|
||||
server: {
|
||||
database: {
|
||||
type: null,
|
||||
version: null,
|
||||
size: null,
|
||||
},
|
||||
webserver: null,
|
||||
php: {
|
||||
version: null,
|
||||
},
|
||||
},
|
||||
nextcloud: {
|
||||
system: {
|
||||
version: null,
|
||||
freespace: null,
|
||||
cpuload: [],
|
||||
mem_total: null,
|
||||
mem_free: null,
|
||||
mem_percent: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
memoryGauge: {
|
||||
value: 0,
|
||||
color: '#272f4d',
|
||||
showMoreInfo: false,
|
||||
moreInfo: null,
|
||||
background: '#16161d',
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
cpuLoadChartId() {
|
||||
return `nextcloud-cpu-load-chart-${Math.random().toString().slice(-4)}`;
|
||||
},
|
||||
didLoadData() {
|
||||
return !!(this.server?.nextcloud?.system?.version);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
allowedStatuscodes() {
|
||||
return [200];
|
||||
},
|
||||
async fetchData() {
|
||||
if (!this.hasValidCredentials()) return;
|
||||
this.makeRequest(this.endpoint('serverinfo'), this.headers)
|
||||
.then(this.processServerInfo)
|
||||
.finally(() => this.finishLoading());
|
||||
},
|
||||
processServerInfo(serverData) {
|
||||
const data = this.validateResponse(serverData);
|
||||
if (!data || data.length === 0) return;
|
||||
this.server.nextcloud.system = data.nextcloud?.system;
|
||||
this.server.server.php.version = data.server?.php?.version;
|
||||
this.server.server.database = data.server?.database;
|
||||
this.server.server.webserver = data.server?.webserver;
|
||||
},
|
||||
updateMemoryGauge(sys) {
|
||||
this.memoryGauge.value = parseFloat(
|
||||
(((sys.mem_total - sys.mem_free) / sys.mem_total) * 100).toFixed(2),
|
||||
);
|
||||
this.memoryGauge.color = this.getMemoryGaugeColor(this.memoryGauge.value);
|
||||
},
|
||||
updateOpcacheMemory() {
|
||||
this.opcache_stats.opcache_hit_rate = parseFloat(
|
||||
this.opcache_stats.opcache_hit_rate,
|
||||
).toFixed(3);
|
||||
this.opcache.memory_usage.total_memory = (
|
||||
this.opcache.memory_usage.used_memory + this.opcache.memory_usage.free_memory
|
||||
);
|
||||
this.opcache.memory_usage.used_memory_percentage = parseFloat(
|
||||
(this.opcache.memory_usage.used_memory / this.opcache.memory_usage.total_memory) * 100,
|
||||
).toFixed(1);
|
||||
},
|
||||
updateOpcacheInterned() {
|
||||
this.opcache.interned_strings_usage.total_memory = (
|
||||
this.opcache.interned_strings_usage.used_memory
|
||||
+ this.opcache.interned_strings_usage.free_memory
|
||||
);
|
||||
this.opcache.interned_strings_usage.used_memory_percentage = parseFloat(
|
||||
(this.opcache.interned_strings_usage.used_memory
|
||||
/ this.opcache.interned_strings_usage.total_memory) * 100,
|
||||
).toFixed(5);
|
||||
},
|
||||
getMemoryGaugeColor(memPercent) {
|
||||
if (memPercent < 50) return this.getColorRgba('widget-text-color', 0.6);
|
||||
if (memPercent < 60) return this.getColorRgba('warning', 0.75);
|
||||
if (memPercent < 80) return this.getColorRgba('error', 0.9);
|
||||
if (memPercent < 100) return this.getColorRgba('danger');
|
||||
return this.getColorRgba('background');
|
||||
},
|
||||
getMemoryGaugeLabel() {
|
||||
const sys = this.server.nextcloud.system;
|
||||
return `${this.convertBytes((sys.mem_total - sys.mem_free) * 1024, 2, false)} / `
|
||||
+ `${this.convertBytes(sys.mem_total * 1024, 2, false)}`;
|
||||
},
|
||||
updateCpuLoad(load) {
|
||||
const chartData = {
|
||||
labels: ['1m', '5m', '15m'],
|
||||
datasets: [{ values: [load[0], load[1], load[2]] }],
|
||||
};
|
||||
const chartTitle = this.tt('load-averages');
|
||||
this.renderCpuLoadChart(chartData, chartTitle);
|
||||
},
|
||||
renderCpuLoadChart(loadBarChartData, chartTitle) {
|
||||
return new this.Chart(`#${this.cpuLoadChartId}`, {
|
||||
title: chartTitle,
|
||||
data: loadBarChartData,
|
||||
type: 'bar',
|
||||
height: 180,
|
||||
colors: [this.getColorRgba('widget-text-color', 0.6)],
|
||||
barOptions: {
|
||||
spaceRatio: 0.2,
|
||||
},
|
||||
tooltipOptions: {
|
||||
formatTooltipY: d => `${d} ${this.tt('tasks')}`,
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.overrideUpdateInterval = 30;
|
||||
},
|
||||
updated() {
|
||||
const load = this.server?.nextcloud?.system?.cpuload;
|
||||
if (load) this.updateCpuLoad(load);
|
||||
const sys = this.server.nextcloud.system;
|
||||
if (sys) this.updateMemoryGauge(sys);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/styles/widgets/nextcloud-shared.scss';
|
||||
.nextcloud-system-wrapper {
|
||||
div.charts {
|
||||
> div {
|
||||
float: left;
|
||||
}
|
||||
> div:first-child {
|
||||
max-width: 44%;
|
||||
small {
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin: .9em 0 1.4em 0;
|
||||
opacity: 1;
|
||||
}
|
||||
small:last-child {
|
||||
margin-top: 2em;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
> div:nth-child(2) {
|
||||
min-width: 55%;
|
||||
}
|
||||
p.percentage {
|
||||
color: var(--widget-text-color);
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
font-size: 1.3em;
|
||||
margin: .5em 0;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
206
src/components/Widgets/NextcloudUser.vue
Normal file
206
src/components/Widgets/NextcloudUser.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div v-if="didLoadData" class="nextcloud-widget nextcloud-info-wrapper">
|
||||
<!-- logo, branding, user info -->
|
||||
<div>
|
||||
<div class="logo">
|
||||
<a :href="branding.url" target="_blank">
|
||||
<img :src="branding.logo" />
|
||||
</a>
|
||||
<p>{{ branding.slogan }}</p>
|
||||
</div>
|
||||
<div class="info">
|
||||
<p class="brand">{{ branding.name }}</p>
|
||||
<p class="version" v-if="version.string">
|
||||
<small>Nextcloud {{ tt('version') }} {{ version.string }}</small>
|
||||
</p>
|
||||
<p class="username">{{ user.displayName }} <em>({{ user.id }})</em></p>
|
||||
<p class="login" v-tooltip="lastLoginTooltip()">
|
||||
<span>{{ tt('last-login') }}</span>
|
||||
<small>{{ getTimeAgo(user.lastLogin) }}</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- disk space/quota -->
|
||||
<div>
|
||||
<div v-tooltip="quotaTooltip()">
|
||||
<hr/>
|
||||
<p>
|
||||
<i class="fal fa-disc-drive"></i>
|
||||
<strong v-if="user.quota.quota > 0">{{ tt('disk-quota') }}</strong>
|
||||
<strong v-else>{{ tt('disk-space') }}</strong>
|
||||
<em v-html="formatPercent(user.quota.relative)"></em>
|
||||
<small>{{ tt('of') }}</small><strong v-html="convertBytes(user.quota.total)"></strong>
|
||||
<span v-if="quotaChart.dataLoaded">
|
||||
<PercentageChart :values="quotaChart.data" :showLegend="false" />
|
||||
</span>
|
||||
</p>
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import NextcloudMixin from '@/mixins/NextcloudMixin';
|
||||
import PercentageChart from '@/components/Charts/PercentageChart';
|
||||
|
||||
/**
|
||||
* NextcloudUser widget - Displays branding and user information
|
||||
* Used endpoints
|
||||
* - capabilities: this delivers branding info (server name, logo, slogan, etc.)
|
||||
* - user: name, last login, disk quota info
|
||||
*/
|
||||
export default {
|
||||
mixins: [WidgetMixin, NextcloudMixin],
|
||||
components: { PercentageChart },
|
||||
data() {
|
||||
return {
|
||||
user: {
|
||||
id: null,
|
||||
displayName: null,
|
||||
email: null,
|
||||
quota: {
|
||||
relative: null,
|
||||
total: null,
|
||||
used: null,
|
||||
free: null,
|
||||
quota: null,
|
||||
},
|
||||
},
|
||||
quotaChart: {
|
||||
dataLoaded: false,
|
||||
data: [
|
||||
{ label: null, size: null, color: null },
|
||||
{ label: null, size: null, color: null },
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
didLoadData() {
|
||||
return !!this.user.id;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
allowedStatuscodes() {
|
||||
return [100, 200];
|
||||
},
|
||||
fetchData() {
|
||||
if (!this.hasValidCredentials()) return;
|
||||
this.loadCapabilities()
|
||||
.then(this.loadUser)
|
||||
.finally(this.finishLoading);
|
||||
},
|
||||
loadUser() {
|
||||
return this.makeRequest(this.endpoint('user'), this.headers)
|
||||
.then(this.processUser);
|
||||
},
|
||||
processUser(userResponse) {
|
||||
const user = this.validateResponse(userResponse);
|
||||
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;
|
||||
},
|
||||
getQuotaChartColorUsed(percent) {
|
||||
if (percent < 0.75) return this.getValueFromCss('widget-text-color');
|
||||
if (percent < 0.85) return this.getValueFromCss('warning');
|
||||
if (percent < 0.95) return this.getValueFromCss('error');
|
||||
return this.getValueFromCss('danger');
|
||||
},
|
||||
updateQuotaChart() {
|
||||
const used = parseFloat(this.user.quota.used / this.user.quota.total);
|
||||
const free = parseFloat(this.user.quota.free / this.user.quota.total);
|
||||
const d = this.quotaChart.data;
|
||||
d[0] = { label: this.tt('used'), size: used, color: this.getQuotaChartColorUsed(used) };
|
||||
d[1] = { label: this.tt('available'), size: free, color: this.getValueFromCss('success') };
|
||||
this.quotaChart.dataLoaded = true;
|
||||
},
|
||||
/* Tooltip generators */
|
||||
quotaTooltip() {
|
||||
const quotaEnabled = this.user.quota.quota > 0;
|
||||
const content = `${this.tt('quota-enabled', { not: quotaEnabled ? '' : 'not ' })}`
|
||||
+ `<br><br>${this.convertBytes(this.user.quota.used)} ${this.tt('used')}<br>`
|
||||
+ `${this.convertBytes(this.user.quota.free)} ${this.tt('free')}<br>`
|
||||
+ `${this.convertBytes(this.user.quota.total)} ${this.tt('total')}`;
|
||||
return {
|
||||
content, html: true, trigger: 'hover focus', delay: 250, classes: 'nc-tooltip',
|
||||
};
|
||||
},
|
||||
lastLoginTooltip() {
|
||||
const content = new Date(this.user.lastLogin).toLocaleString();
|
||||
return {
|
||||
content, html: true, trigger: 'hover focus', delay: 250, classes: 'nc-tooltip',
|
||||
};
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.overrideUpdateInterval = 120;
|
||||
},
|
||||
updated() {
|
||||
this.updateQuotaChart();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/styles/widgets/nextcloud-shared.scss';
|
||||
.nextcloud-info-wrapper {
|
||||
> div:first-child {
|
||||
display: flex;
|
||||
}
|
||||
> div:nth-child(2) {
|
||||
border-top: none;
|
||||
}
|
||||
div.percentage-chart-wrapper {
|
||||
margin: 0 .75em;
|
||||
width: 5em;
|
||||
position: relative;
|
||||
top: .2em;
|
||||
float: right;
|
||||
}
|
||||
div.logo {
|
||||
width: 40%;
|
||||
text-align: center;
|
||||
img {
|
||||
width: 8em;
|
||||
}
|
||||
p {
|
||||
font-size: .9em;
|
||||
opacity: .85;
|
||||
}
|
||||
}
|
||||
div.info {
|
||||
width: 56%;
|
||||
p {
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
p:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
p.brand {
|
||||
margin: 0;
|
||||
font-size: 1.35em;
|
||||
font-weight: 800;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
p.version small {
|
||||
font-size: .75em;
|
||||
}
|
||||
p.username {
|
||||
font-size: 1.1em;
|
||||
em {
|
||||
font-size: .9em;
|
||||
}
|
||||
}
|
||||
p.login {
|
||||
span {
|
||||
font-size: .9em;
|
||||
margin-right: .25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
202
src/components/Widgets/NextcloudUserStatus.vue
Normal file
202
src/components/Widgets/NextcloudUserStatus.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<div class="nextcloud-widget nextcloud-user-status-wrapper">
|
||||
<div v-if="didLoadData" class="sep">
|
||||
<!-- user statuses: list -->
|
||||
<div v-for="(status, userId) in statuses" :key="userId" class="user">
|
||||
<div>
|
||||
<!-- user status: emoji -->
|
||||
<div>
|
||||
<i>{{ status.icon }}</i>
|
||||
</div>
|
||||
<!-- user status: message -->
|
||||
<div>
|
||||
<p v-tooltip="clearAtTooltip(status.clearAt)">
|
||||
<strong>{{ status.userId }}</strong>
|
||||
<small v-if="status.clearAt"><i class="fal fa-clock"></i></small>
|
||||
<span v-else-if="status.message">•</span><em>{{ status.message }}</em>
|
||||
</p>
|
||||
</div>
|
||||
<!-- user status: status -->
|
||||
<div>
|
||||
<p>
|
||||
<small :class="`status ${status.status}`">
|
||||
<i v-if="status.status === 'online' || status.status === 'dnd'"
|
||||
class="fas fa-circle" v-tooltip="tt(status.status)"></i>
|
||||
<i v-else class="far fa-circle" v-tooltip="tt(status.status)"></i>
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- user statuses: no content -->
|
||||
<div v-else class="sep"><p>{{ tt('nothing-to-show') }}</p></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import NextcloudMixin from '@/mixins/NextcloudMixin';
|
||||
|
||||
// Nextcloud User Status API supports getting all user statuses at once
|
||||
// or a single user's status. {fetchStrategy} determines which of these methods to use.
|
||||
const fetchStrategies = {
|
||||
allAtOnce: 'AllAtOnce',
|
||||
oneByOne: 'OneByOne',
|
||||
};
|
||||
|
||||
/**
|
||||
* NextcloudUserStatus widget - Displays user statuses
|
||||
* Used endpoints
|
||||
* - capabilities: to determine if the User Status API is enabled
|
||||
* - userstatus: to fetch a single or all user statuses
|
||||
*/
|
||||
export default {
|
||||
mixins: [WidgetMixin, NextcloudMixin],
|
||||
components: {},
|
||||
computed: {
|
||||
didLoadData() {
|
||||
return !!Object.keys(this?.statuses || {}).length;
|
||||
},
|
||||
fetchStrategy() {
|
||||
if (!this.options.fetchStrategy) {
|
||||
return fetchStrategies.allAtOnce;
|
||||
}
|
||||
if (!Object.values(fetchStrategies).includes(this.options.fetchStrategy)) {
|
||||
return fetchStrategies.allAtOnce;
|
||||
}
|
||||
return this.options.fetchStrategy;
|
||||
},
|
||||
users() {
|
||||
if (!this.options.users || !Array.isArray(this.options.users)) return [];
|
||||
if (this.options.users.length > 100) return this.options.users.slice(0, 100);
|
||||
return this.options.users;
|
||||
},
|
||||
showEmpty() {
|
||||
return !!this.options.showEmpty;
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statuses: {},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
allowedStatuscodes() {
|
||||
return [100, 200];
|
||||
},
|
||||
async fetchData() {
|
||||
if (!this.hasValidCredentials() || !this.users.length) return;
|
||||
await this.loadCapabilities();
|
||||
if (!this.capabilities?.userStatus) {
|
||||
this.error('This Nextcloud server doesn\'t support the User Status API');
|
||||
return;
|
||||
}
|
||||
if (this.fetchStrategy === fetchStrategies.allAtOnce) {
|
||||
this.makeRequest(this.endpoint('userstatus'), this.headers)
|
||||
.then(this.processStatuses)
|
||||
.finally(this.finishLoading);
|
||||
} else {
|
||||
const promises = [];
|
||||
this.newStatuses = {};
|
||||
this.users.forEach((user) => {
|
||||
promises.push(
|
||||
this.makeRequest(`${this.endpoint('userstatus')}/${user}`, this.headers)
|
||||
.then(this.processStatus),
|
||||
);
|
||||
});
|
||||
Promise.all(promises)
|
||||
.then(() => {
|
||||
this.statuses = this.newStatuses;
|
||||
delete this.newStatuses;
|
||||
})
|
||||
.finally(this.finishLoading);
|
||||
}
|
||||
},
|
||||
processStatuses(response) {
|
||||
const statuses = this.validateResponse(response);
|
||||
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;
|
||||
},
|
||||
processStatus(response) {
|
||||
const raw = this.validateResponse(response);
|
||||
const status = Array.isArray(raw) && raw.length ? raw[0] : raw;
|
||||
if (status && (status.message || this.showEmpty)) {
|
||||
this.newStatuses[status.userId] = status;
|
||||
}
|
||||
},
|
||||
/* Tooltip generators */
|
||||
clearAtTooltip(clearAtTime) {
|
||||
const content = clearAtTime ? `${this.tt('until')}`
|
||||
+ ` ${new Date(clearAtTime * 1000).toLocaleString()}` : '';
|
||||
return {
|
||||
content, html: true, trigger: 'hover focus', delay: 250, classes: 'nc-tooltip',
|
||||
};
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.overrideUpdateInterval = 60;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/styles/widgets/nextcloud-shared.scss';
|
||||
.nextcloud-user-status-wrapper {
|
||||
.status {
|
||||
float: right;
|
||||
i {
|
||||
position: relative;
|
||||
top: .15rem;
|
||||
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%;
|
||||
> div:first-child {
|
||||
width: 1.75em;
|
||||
text-align: center;
|
||||
> i {
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
> div:nth-child(2) {
|
||||
p small i {
|
||||
top: 0;
|
||||
opacity: .5;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
> div {
|
||||
display: table-cell;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
div.user hr {
|
||||
margin-top: .3em;
|
||||
margin-bottom: .3em;
|
||||
}
|
||||
div.user > div > div:last-child hr {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,421 +20,13 @@
|
||||
</div>
|
||||
<!-- Widget -->
|
||||
<div :class="`widget-wrap ${ error ? 'has-error' : '' }`">
|
||||
<AdGuardDnsInfo
|
||||
v-if="widgetType === 'adguard-dns-info'"
|
||||
<component
|
||||
v-bind:is="component"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<AdGuardFilterStatus
|
||||
v-else-if="widgetType === 'adguard-filter-status'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<AdGuardStats
|
||||
v-else-if="widgetType === 'adguard-stats'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<AdGuardTopDomains
|
||||
v-else-if="widgetType === 'adguard-top-domains'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<AnonAddy
|
||||
v-else-if="widgetType === 'anonaddy'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<Apod
|
||||
v-else-if="widgetType === 'apod'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<BlacklistCheck
|
||||
v-else-if="widgetType === 'blacklist-check'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<Clock
|
||||
v-else-if="widgetType === 'clock'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<CryptoPriceChart
|
||||
v-else-if="widgetType === 'crypto-price-chart'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<CryptoWatchList
|
||||
v-else-if="widgetType === 'crypto-watch-list'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<CveVulnerabilities
|
||||
v-else-if="widgetType === 'cve-vulnerabilities'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<DomainMonitor
|
||||
v-else-if="widgetType === 'domain-monitor'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<CodeStats
|
||||
v-else-if="widgetType === 'code-stats'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<CovidStats
|
||||
v-else-if="widgetType === 'covid-stats'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<EmbedWidget
|
||||
v-else-if="widgetType === 'embed'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<EthGasPrices
|
||||
v-else-if="widgetType === 'eth-gas-prices'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<ExchangeRates
|
||||
v-else-if="widgetType === 'exchange-rates'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<Flights
|
||||
v-else-if="widgetType === 'flight-data'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GitHubProfile
|
||||
v-else-if="widgetType === 'github-profile-stats'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GitHubTrending
|
||||
v-else-if="widgetType === 'github-trending-repos'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlAlerts
|
||||
v-else-if="widgetType === 'gl-alerts'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlCpuCores
|
||||
v-else-if="widgetType === 'gl-current-cores'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlCpuGauge
|
||||
v-else-if="widgetType === 'gl-current-cpu'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlCpuHistory
|
||||
v-else-if="widgetType === 'gl-cpu-history'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlDiskIo
|
||||
v-else-if="widgetType === 'gl-disk-io'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlDiskSpace
|
||||
v-else-if="widgetType === 'gl-disk-space'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlIpAddress
|
||||
v-else-if="widgetType === 'gl-ip-address'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlLoadHistory
|
||||
v-else-if="widgetType === 'gl-load-history'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlMemGauge
|
||||
v-else-if="widgetType === 'gl-current-mem'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlMemHistory
|
||||
v-else-if="widgetType === 'gl-mem-history'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlNetworkInterfaces
|
||||
v-else-if="widgetType === 'gl-network-interfaces'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlNetworkTraffic
|
||||
v-else-if="widgetType === 'gl-network-traffic'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlSystemLoad
|
||||
v-else-if="widgetType === 'gl-system-load'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlCpuTemp
|
||||
v-else-if="widgetType === 'gl-cpu-temp'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<HealthChecks
|
||||
v-else-if="widgetType === 'health-checks'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<IframeWidget
|
||||
v-else-if="widgetType === 'iframe'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<ImageWidget
|
||||
v-else-if="widgetType === 'image'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<Jokes
|
||||
v-else-if="widgetType === 'joke'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<MullvadStatus
|
||||
v-else-if="widgetType === 'mullvad-status'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<NdCpuHistory
|
||||
v-else-if="widgetType === 'nd-cpu-history'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<NdLoadHistory
|
||||
v-else-if="widgetType === 'nd-load-history'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<NdRamHistory
|
||||
v-else-if="widgetType === 'nd-ram-history'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<NewsHeadlines
|
||||
v-else-if="widgetType === 'news-headlines'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<PiHoleStats
|
||||
v-else-if="widgetType === 'pi-hole-stats'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<PiHoleTopQueries
|
||||
v-else-if="widgetType === 'pi-hole-top-queries'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<PiHoleTraffic
|
||||
v-else-if="widgetType === 'pi-hole-traffic'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<PublicHolidays
|
||||
v-else-if="widgetType === 'public-holidays'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<PublicIp
|
||||
v-else-if="widgetType === 'public-ip'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<RssFeed
|
||||
v-else-if="widgetType === 'rss-feed'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<SportsScores
|
||||
v-else-if="widgetType === 'sports-scores'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<StatPing
|
||||
v-else-if="widgetType === 'stat-ping'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<StockPriceChart
|
||||
v-else-if="widgetType === 'stock-price-chart'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<SynologyDownload
|
||||
v-else-if="widgetType === 'synology-download'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<SystemInfo
|
||||
v-else-if="widgetType === 'system-info'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<TflStatus
|
||||
v-else-if="widgetType === 'tfl-status'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<WalletBalance
|
||||
v-else-if="widgetType === 'wallet-balance'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<Weather
|
||||
v-else-if="widgetType === 'weather'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<WeatherForecast
|
||||
v-else-if="widgetType === 'weather-forecast'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<XkcdComic
|
||||
v-else-if="widgetType === 'xkcd-comic'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<!-- No widget type specified -->
|
||||
<div v-else>{{ handleError('Widget type was not found') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -447,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: {
|
||||
@@ -455,66 +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'),
|
||||
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,
|
||||
@@ -556,6 +156,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').catch(() => import('@/components/Widgets/Blank.vue'));
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/* Calls update data method on widget */
|
||||
|
||||
Reference in New Issue
Block a user