diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/api/routes/verifyGET.js | 3 | ||||
| -rw-r--r-- | src/site/components/footer/Footer.vue | 15 | ||||
| -rw-r--r-- | src/site/components/home/links/Links.vue | 12 | ||||
| -rw-r--r-- | src/site/components/sidebar/Sidebar.vue | 1 | ||||
| -rw-r--r-- | src/site/components/statistics/byte.vue | 26 | ||||
| -rw-r--r-- | src/site/components/statistics/byteUsage.vue | 31 | ||||
| -rw-r--r-- | src/site/components/statistics/detailed.vue | 33 | ||||
| -rw-r--r-- | src/site/components/statistics/generic.vue | 26 | ||||
| -rw-r--r-- | src/site/components/statistics/time.vue | 46 | ||||
| -rw-r--r-- | src/site/pages/dashboard/admin/statistics.vue | 103 | ||||
| -rw-r--r-- | src/site/store/admin.js | 12 |
11 files changed, 294 insertions, 14 deletions
diff --git a/src/api/routes/verifyGET.js b/src/api/routes/verifyGET.js index 2f370e8..c94c155 100644 --- a/src/api/routes/verifyGET.js +++ b/src/api/routes/verifyGET.js @@ -11,7 +11,8 @@ class verifyGET extends Route { user: { id: user.id, username: user.username, - isAdmin: user.isAdmin + isAdmin: user.isAdmin, + apiKey: user.apiKey } }); } diff --git a/src/site/components/footer/Footer.vue b/src/site/components/footer/Footer.vue index 38e3f07..96774e7 100644 --- a/src/site/components/footer/Footer.vue +++ b/src/site/components/footer/Footer.vue @@ -35,11 +35,12 @@ import { saveAs } from 'file-saver'; export default { computed: { - ...mapGetters({ loggedIn: 'auth/isLoggedIn' }), + ...mapGetters({ + loggedIn: 'auth/isLoggedIn', + apiKey: 'auth/getApiKey' + }), ...mapState({ - version: state => state.config.version, - serviceName: state => state.config.serviceName, - token: state => state.auth.token + version: state => state.config.version }), getYear() { return new Date().getFullYear(); @@ -48,18 +49,18 @@ export default { methods: { createShareXThing() { const sharexFile = `{ - "Name": "${this.serviceName}", + "Name": "${this.$store.state.config.serviceName}", "DestinationType": "ImageUploader, FileUploader", "RequestType": "POST", "RequestURL": "${location.origin}/api/upload", "FileFormName": "files[]", "Headers": { - "authorization": "Bearer ${this.token}", + "token": "${this.apiKey}", "accept": "application/vnd.chibisafe.json" }, "ResponseType": "Text", "URL": "$json:url$", - "ThumbnailURL": "$json:url$" + "ThumbnailURL": "$json:thumb$" }`; const sharexBlob = new Blob([sharexFile], { type: 'application/octet-binary' }); saveAs(sharexBlob, `${location.hostname}.sxcu`); diff --git a/src/site/components/home/links/Links.vue b/src/site/components/home/links/Links.vue index 05915b9..6777f44 100644 --- a/src/site/components/home/links/Links.vue +++ b/src/site/components/home/links/Links.vue @@ -46,13 +46,15 @@ </div> </template> <script> +import { mapGetters } from 'vuex'; import { saveAs } from 'file-saver'; export default { computed: { - loggedIn() { - return this.$store.state.auth.loggedIn; - } + ...mapGetters({ + loggedIn: 'auth/isLoggedIn', + apiKey: 'auth/getApiKey' + }) }, methods: { createShareXThing() { @@ -63,12 +65,12 @@ export default { "RequestURL": "${location.origin}/api/upload", "FileFormName": "files[]", "Headers": { - "authorization": "Bearer ${this.$store.state.token}", + "token": "${this.apiKey}", "accept": "application/vnd.chibisafe.json" }, "ResponseType": "Text", "URL": "$json:url$", - "ThumbnailURL": "$json:url$" + "ThumbnailURL": "$json:thumb$" }`; const sharexBlob = new Blob([sharexFile], { type: 'application/octet-binary' }); saveAs(sharexBlob, `${location.hostname}.sxcu`); diff --git a/src/site/components/sidebar/Sidebar.vue b/src/site/components/sidebar/Sidebar.vue index dc261ee..98c3c81 100644 --- a/src/site/components/sidebar/Sidebar.vue +++ b/src/site/components/sidebar/Sidebar.vue @@ -29,6 +29,7 @@ </template> <b-menu-item icon="account" label="Users" tag="nuxt-link" to="/dashboard/admin/users" exact /> <b-menu-item icon="cog-outline" label="Settings" tag="nuxt-link" to="/dashboard/admin/settings" exact /> + <!--<b-menu-item icon="chart-line" label="Statistics" tag="nuxt-link" to="/dashboard/admin/statistics" exact />--> </b-menu-item> <b-menu-item class="item" diff --git a/src/site/components/statistics/byte.vue b/src/site/components/statistics/byte.vue new file mode 100644 index 0000000..05852cd --- /dev/null +++ b/src/site/components/statistics/byte.vue @@ -0,0 +1,26 @@ +<template> + <div> + <div class="columns"> + <div class="column is-2"> + {{ title }} + </div> + <div class="column"> + {{ (value / 1024 / 1024 / 1024).toFixed(2) }} GiB + </div> + </div> + </div> +</template> +<script> +export default { + props: { + title: { + 'type': String, + 'default': null + }, + value: { + 'type': Number, + 'default': null + } + } +}; +</script> diff --git a/src/site/components/statistics/byteUsage.vue b/src/site/components/statistics/byteUsage.vue new file mode 100644 index 0000000..740feff --- /dev/null +++ b/src/site/components/statistics/byteUsage.vue @@ -0,0 +1,31 @@ +<template> + <div> + <div class="columns"> + <div class="column is-2"> + {{ title }} + </div> + <div class="column"> + {{ (used / 1024 / 1024 / 1024).toFixed(2) }} GiB / + {{ (total / 1024 / 1024 / 1024).toFixed(2) }} GiB ({{ ((used * 100) / total).toFixed(2) }}%) + </div> + </div> + </div> +</template> +<script> +export default { + props: { + title: { + 'type': String, + 'default': null + }, + used: { + 'type': Number, + 'default': null + }, + total: { + 'type': Number, + 'default': null + } + } +}; +</script> diff --git a/src/site/components/statistics/detailed.vue b/src/site/components/statistics/detailed.vue new file mode 100644 index 0000000..8a0722e --- /dev/null +++ b/src/site/components/statistics/detailed.vue @@ -0,0 +1,33 @@ +<template> + <div> + <div class="columns"> + <div class="column is-2"> + {{ title }} + </div> + <div class="column"> + <b-table :data="data || []" :mobile-cards="true"> + <b-table-column v-slot="props" field="type" label="Type"> + {{ props.row.key }} + </b-table-column> + <b-table-column v-slot="props" field="count" label="Count"> + {{ props.row.value }} + </b-table-column> + </b-table> + </div> + </div> + </div> +</template> +<script> +export default { + props: { + title: { + 'type': String, + 'default': null + }, + data: { + 'type': Array, + 'default': () => [] + } + } +}; +</script> diff --git a/src/site/components/statistics/generic.vue b/src/site/components/statistics/generic.vue new file mode 100644 index 0000000..704be7a --- /dev/null +++ b/src/site/components/statistics/generic.vue @@ -0,0 +1,26 @@ +<template> + <div> + <div class="columns"> + <div class="column is-2"> + {{ title }} + </div> + <div class="column"> + {{ value }} + </div> + </div> + </div> +</template> +<script> +export default { + props: { + title: { + 'type': String, + 'default': null + }, + value: { + 'type': [Number, String], + 'default': null + } + } +}; +</script> diff --git a/src/site/components/statistics/time.vue b/src/site/components/statistics/time.vue new file mode 100644 index 0000000..ff1bb8d --- /dev/null +++ b/src/site/components/statistics/time.vue @@ -0,0 +1,46 @@ +<template> + <div> + <div class="columns"> + <div class="column is-2"> + {{ title }} + </div> + <div class="column"> + {{ parsedTime }} + </div> + </div> + </div> +</template> +<script> +export default { + props: { + title: { + 'type': String, + 'default': null + }, + value: { + 'type': Number, + 'default': null + } + }, + computed: { + parsedTime() { + let seconds = this.value; + const days = Math.floor(seconds / 86400); + seconds %= 86400; + let hours = Math.floor(seconds / 3600); + seconds %= 3600; + let minutes = Math.floor(seconds / 60); + seconds %= 60; + + if (hours < 10) hours = `0${hours}`; + if (minutes < 10) minutes = `0${minutes}`; + if (seconds < 10) seconds = `0${seconds}`; + + if (days > 0) { + return `${days}d ${hours}:${minutes}:${seconds}`; + } + return `${hours}:${minutes}:${seconds}`; + } + } +}; +</script> diff --git a/src/site/pages/dashboard/admin/statistics.vue b/src/site/pages/dashboard/admin/statistics.vue new file mode 100644 index 0000000..4d601cf --- /dev/null +++ b/src/site/pages/dashboard/admin/statistics.vue @@ -0,0 +1,103 @@ +<template> + <section class="section is-fullheight dashboard"> + <div class="container"> + <div class="columns"> + <div class="column is-narrow"> + <Sidebar /> + </div> + <div class="column"> + <h2 class="subtitle"> + Statistics + </h2> + <hr> + <template v-for="category in Object.keys(stats)"> + <div :key="category" + class="stats-container"> + <h2 class="title"> + {{ category }} + </h2> + <template v-for="item in Object.keys(stats[category])"> + <!-- If it's plain text or a number, just print it --> + <template v-if="typeof stats[category][item] === 'string' || typeof stats[category][item] === 'number'"> + <generic :key="item" + :title="item" + :value="stats[category][item]" /> + </template> + + <!-- If it's an object then we need to do some magic --> + <template v-else-if="typeof stats[category][item] === 'object'"> + <byteUsage v-if="stats[category][item].type === 'byteUsage'" + :key="item" + :title="item" + :used="stats[category][item].value.used" + :total="stats[category][item].value.total" /> + <byte v-else-if="stats[category][item].type === 'byte'" + :key="item" + :title="item" + :value="stats[category][item].value" /> + <beat v-else-if="stats[category][item].type === 'time'" + :key="item" + :title="item" + :value="stats[category][item].value" /> + <detailed v-else-if="stats[category][item].type === 'detailed'" + :key="item" + :title="item" + :data="stats[category][item].data" /> + </template> + </template> + </div> + </template> + </div> + </div> + </div> + </section> +</template> + +<script> +import { mapState } from 'vuex'; +import Sidebar from '~/components/sidebar/Sidebar.vue'; +import byteUsage from '~/components/statistics/byteUsage.vue'; +import byte from '~/components/statistics/byte.vue'; +import beat from '~/components/statistics/time.vue'; +import detailed from '~/components/statistics/detailed.vue'; +import generic from '~/components/statistics/generic.vue'; + +export default { + components: { + Sidebar, + byteUsage, + byte, + beat, + detailed, + generic + }, + middleware: ['auth', 'admin', ({ store }) => { + try { + store.dispatch('admin/fetchStatistics'); + } catch (e) { + console.error(e); + } + }], + computed: mapState({ + stats: state => state.admin.statistics + }), + methods: {}, + head() { + return { + title: 'Service statistics' + }; + } +}; +</script> +<style lang="scss" scoped> +@import '~/assets/styles/_colors.scss'; +h2.title { + color: $defaultTextColor; +} +div.stats-container { + padding: 1rem; + background: #1c1e23; + box-shadow: rgba(15, 17, 21, 0.35) 0px 6px 9px 0px; + margin-bottom: 1rem; +} +</style> diff --git a/src/site/store/admin.js b/src/site/store/admin.js index 58b63b5..b2d1926 100644 --- a/src/site/store/admin.js +++ b/src/site/store/admin.js @@ -11,7 +11,8 @@ export const state = () => ({ files: [] }, file: {}, - settings: {} + settings: {}, + statistics: {} }); export const actions = { @@ -21,6 +22,12 @@ export const actions = { return response; }, + async fetchStatistics({ commit }) { + const response = await this.$axios.$get('service/statistics'); + commit('setStatistics', response); + + return response; + }, async fetchUsers({ commit }) { const response = await this.$axios.$get('admin/users'); commit('setUsers', response); @@ -89,6 +96,9 @@ export const mutations = { setSettings(state, { config }) { state.settings = config; }, + setStatistics(state, { statistics }) { + state.statistics = statistics; + }, setUsers(state, { users }) { state.users = users; }, |