chore: refactor stats generator to use an enum for types

fix: saved data not being converted to JSON automatically when using SQLite (and possibly MSSQL)
feat: display when a stat was generated in the section's header
fix: not being able to click through the footer (sorry IE11 users, you still won't be able to click through it)
fix: add is-mobile to columns so they don't stack on top of each other
feat: add text next to the NSFW toggle and make it look like the elements around it (begone round iOS edges)
This commit is contained in:
Zephyrrus 2021-01-07 23:55:37 +02:00
parent 34bd71948e
commit 925080f6a0
13 changed files with 93 additions and 28 deletions

View File

@ -1,5 +1,6 @@
const Route = require('../../structures/Route');
const StatsGenerator = require('../../utils/StatsGenerator');
const moment = require('moment');
// Thank you Bobby for the stats code https://github.com/BobbyWibowo/lolisafe/blob/safe.fiery.me/controllers/utilsController.js
class filesGET extends Route {
@ -9,12 +10,23 @@ class filesGET extends Route {
async run(req, res, db) {
const cachedStats = await db('statistics')
.select('type', 'data', 'batchId')
.select('type', 'data', 'batchId', 'createdAt')
.where('batchId', '=', db('statistics').max('batchId'));
let stats = cachedStats.reduce((acc, { type, data }) => {
let stats = cachedStats.reduce((acc, { type, data, createdAt }) => {
try {
acc[type] = JSON.parse(data);
// pg returns json, sqlite retuns a string...
if (typeof data === 'string' || data instanceof String) {
acc[type] = JSON.parse(data);
} else {
acc[type] = data;
}
acc[type].meta = {
cached: true,
generatedOn: moment(createdAt).format('MMMM Do YYYY, h:mm:ss a z'), // pg returns this as a date, sqlite3 returns an unix timestamp :<
type: StatsGenerator.Type.HIDDEN
};
} catch (e) {
console.error(e);
}
@ -24,10 +36,12 @@ class filesGET extends Route {
stats = { ...stats, ...(await StatsGenerator.getMissingStats(db, Object.keys(stats))) };
return res.json(StatsGenerator.keyOrder.reduce((acc, k) => {
const ordered = StatsGenerator.keyOrder.reduce((acc, k) => {
acc[k] = stats[k];
return acc;
}, {}));
}, {});
return res.json({ statistics: ordered });
}
}

View File

@ -1,6 +1,22 @@
const si = require('systeminformation');
class StatsGenerator {
// symbols would be better because they're unique, but harder to serialize them
static Type = Object.freeze({
// should contain key value: number
TIME: 'time',
// should contain key value: number
BYTE: 'byte',
// should contain key value: { used: number, total: number }
BYTE_USAGE: 'byteUsage',
// should contain key data: Array<{ key: string, value: number | string }>
// and optionally a count/total
DETAILED: 'detailed',
// hidden type should be skipped during iteration, can contain anything
// these should be treated on a case by case basis on the frontend
HIDDEN: 'hidden'
});
static statGenerators = {
system: StatsGenerator.getSystemInfo,
fileSystems: StatsGenerator.getFileSystemsInfo,
@ -30,20 +46,20 @@ class StatsGenerator {
used: mem.active,
total: mem.total
},
type: 'byteUsage'
type: StatsGenerator.Type.BYTE_USAGE
},
'Memory Usage': {
value: process.memoryUsage().rss,
type: 'byte'
type: StatsGenerator.Type.BYTE
},
'System Uptime': {
value: time.uptime,
type: 'time'
type: StatsGenerator.Type.TIME
},
'Node.js': `${process.versions.node}`,
'Service Uptime': {
value: Math.floor(nodeUptime),
type: 'time'
type: StatsGenerator.Type.TIME
}
};
}
@ -58,7 +74,7 @@ class StatsGenerator {
total: fs.size,
used: fs.used
},
type: 'byteUsage'
type: StatsGenerator.Type.BYTE_USAGE
};
}
@ -73,11 +89,11 @@ class StatsGenerator {
'Others': {
data: {},
count: 0,
type: 'detailed'
type: StatsGenerator.Type.DETAILED
},
'Size in DB': {
value: 0,
type: 'byte'
type: StatsGenerator.Type.BYTE
}
};
@ -88,7 +104,7 @@ class StatsGenerator {
'Total': uploads.length,
'Size in DB': {
value: uploads.reduce((acc, upload) => acc + parseInt(upload.size, 10), 0),
type: 'byte'
type: StatsGenerator.Type.BYTE
}
};
};
@ -127,7 +143,7 @@ class StatsGenerator {
Others: {
data,
count,
type: 'detailed'
type: StatsGenerator.Type.DETAILED
}
};
};

View File

@ -311,11 +311,18 @@ class Util {
return extname + multi;
}
static async saveStatsToDb() {
// TODO: Allow choosing what to save to db and what stats we care about in general
// TODO: if a stat is not saved to db but selected to be shows on the dashboard, it will be generated during the request
static async saveStatsToDb(force) {
// If there were no changes since the instance started, don't generate new stats
// OR
// if we alredy saved a stats to the db, and there were no new changes to the db since then
// skip generating and saving new stats.
// XXX: Should we save non-db related statistics to the database anyway? (like performance, disk usage)
if (statsLastSavedTime && statsLastSavedTime > db.userParams.lastMutationTime) {
if (!force &&
(!db.userParams.lastMutationTime ||
(statsLastSavedTime && statsLastSavedTime > db.userParams.lastMutationTime)
)
) {
return;
}

View File

@ -101,7 +101,13 @@
<div class="level-item">
<b-switch
:value="nsfw"
@input="toggleNsfw()" />
:rounded="false"
type="is-warning"
class="has-text-light"
left-label
@input="toggleNsfw()">
NSFW
</b-switch>
</div>
<div class="level-item">
<button

View File

@ -72,6 +72,9 @@ export default {
<style lang="scss" scoped>
@import '~/assets/styles/_colors.scss';
footer {
pointer-events: none;
touch-action: none;
@media screen and (min-width: 1025px) {
position: fixed;
bottom: 0;
@ -84,6 +87,9 @@ export default {
.container {
.column {
pointer-events: auto;
touch-action: auto;
text-align: center;
@media screen and (min-width: 1025px) {
margin-right: 2rem;

View File

@ -29,7 +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 icon="chart-line" label="Statistics" tag="nuxt-link" to="/dashboard/admin/statistics" exact />
</b-menu-item>
<b-menu-item
class="item"

View File

@ -1,6 +1,6 @@
<template>
<div>
<div class="columns">
<div class="columns is-mobile">
<div class="column is-2">
{{ title }}
</div>

View File

@ -1,6 +1,6 @@
<template>
<div>
<div class="columns">
<div class="columns is-mobile">
<div class="column is-2">
{{ title }}
</div>

View File

@ -1,11 +1,11 @@
<template>
<div>
<div class="columns">
<div class="columns is-mobile">
<div class="column is-2">
{{ title }}
</div>
<div class="column">
<b-table :data="data || []" :mobile-cards="true">
<b-table v-if="data.length" :data="data || []" :mobile-cards="true" narrowed class="details">
<b-table-column v-slot="props" field="type" label="Type">
{{ props.row.key }}
</b-table-column>
@ -13,6 +13,9 @@
{{ props.row.value }}
</b-table-column>
</b-table>
<template v-else>
-
</template>
</div>
</div>
</div>
@ -31,3 +34,13 @@ export default {
}
};
</script>
<style lang="scss" scoped>
.details ::v-deep .table-wrapper {
box-shadow: none;
.table {
border-radius: unset;
background: #2A2E3C;
}
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div>
<div class="columns">
<div class="columns is-mobile">
<div class="column is-2">
{{ title }}
</div>

View File

@ -1,6 +1,6 @@
<template>
<div>
<div class="columns">
<div class="columns is-mobile">
<div class="column is-2">
{{ title }}
</div>

View File

@ -14,8 +14,11 @@
<div :key="category"
class="stats-container">
<h2 class="title">
{{ category }}
{{ category }} <span v-if="stats[category].meta" class="is-size-7 is-pulled-right is-family-monospace has-text-grey-light">
generated on {{ stats[category].meta.generatedOn }}
</span>
</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'">
@ -25,7 +28,7 @@
</template>
<!-- If it's an object then we need to do some magic -->
<template v-else-if="typeof stats[category][item] === 'object'">
<template v-else-if="typeof stats[category][item] === 'object' && stats[category][item].type !== 'hidden'">
<byteUsage v-if="stats[category][item].type === 'byteUsage'"
:key="item"
:title="item"

View File

@ -105,7 +105,7 @@ export default {
div.search-container {
padding: 1rem 2rem;
background-color: $base-2;
// background-color: $base-2;
}
div.column > h2.subtitle { padding-top: 1px; }