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:
parent
34bd71948e
commit
925080f6a0
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="columns">
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-2">
|
||||
{{ title }}
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="columns">
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-2">
|
||||
{{ title }}
|
||||
</div>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="columns">
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-2">
|
||||
{{ title }}
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="columns">
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-2">
|
||||
{{ title }}
|
||||
</div>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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; }
|
||||
|
|
Loading…
Reference in New Issue