🚧 Refactor + more widgets

* ♻️ segment into smaller widgets, improve mixin
* ♻️ change NextcloudInfo to NextcloudUser
  * a small widget showing branding and uesr info, including quota
*  add NextcloudNotifications widget
  * show and delete Nextcloud notifications
*  add NextcloudUserStatus widget
  * display user statuses of selected users
*  add NextcloudStats widget (admin only)
  * display Nextcloud usage statistics (users, files, shares)
*  add NextcloudSystem widget (admin only)
  * visualise cpu load and memory utilisation, show server versions
*  add NextcloudPhpOpcache widget (admin only)
  * show statistics about php opcache performance
*  add a misc helper for formatting nunbers
* 🌐 add translations to widget templates
* 🌐 add translation entries for en
* 🍱 add scss styles file, shared by all widgets
This commit is contained in:
Marcell Fülöp
2022-06-19 12:06:43 +00:00
parent a43988f3cd
commit 821af62426
12 changed files with 1558 additions and 442 deletions

View File

@@ -0,0 +1,195 @@
<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>&nbsp;
<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 ' + getStatusClass(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';
// //import { NcdStatusAll, NcdStatusSingle } from '@/utils/ncd';
// 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;
},
},
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)
// //Promise.resolve(NcdStatusAll)
.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)
// //Promise.resolve(NcdStatusSingle)
.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;
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) {
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')}`
+ ` ${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: .25rem;
margin: 0;
}
}
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 {
color: #aaaaaa;
top: 0;
opacity: .5;
margin: 0;
}
}
> div {
display: table-cell;
text-align: left;
}
}
div.user hr {
margin-top: 0.3rem;
margin-bottom: 0.3rem;
}
}
</style>