Add admin/stats page layout, charts, wooo

This commit is contained in:
MiniDigger 2021-02-20 11:36:24 +01:00
parent 6da5ae286e
commit 6424415df7
7 changed files with 271 additions and 6 deletions

View File

@ -0,0 +1,101 @@
<template>
<div :id="id" class="ct-chart"></div>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator';
import { IChartistBase, IChartistData, IChartOptions } from 'chartist';
import * as Chartist from 'chartist';
import { Prop } from 'vue-property-decorator';
import { PropType } from 'vue';
require('chartist-plugin-legend');
@Component
export default class Chart extends Vue {
@Prop({ required: true })
id!: string;
@Prop({ required: true })
barType!: PropType<'pie' | 'bar' | 'line' | 'candle'>;
@Prop({ required: true })
data!: IChartistData;
@Prop()
options!: IChartOptions;
chart!: IChartistBase<IChartOptions>;
mounted() {
const type = (this.barType as unknown) as string;
if (type === 'pie') {
this.chart = new Chartist.Pie('#' + this.id, this.data, this.options);
} else if (type === 'bar') {
this.chart = new Chartist.Bar('#' + this.id, this.data, this.options);
} else if (type === 'line') {
this.chart = new Chartist.Line('#' + this.id, this.data, this.options);
} else if (type === 'candle') {
this.chart = new Chartist.Candle('#' + this.id, this.data, this.options);
} else {
console.log('unknown bar type ', type);
}
}
}
</script>
<style lang="scss">
@import 'node_modules/chartist/dist/scss/chartist.scss';
.ct-label {
color: rgba(255, 255, 255, 0.7);
}
.ct-grid {
stroke: rgba(255, 255, 255, 0.2);
}
// ct legend plugin stuff
.ct-legend {
position: relative;
z-index: 10;
list-style: none;
text-align: center;
li {
position: relative;
padding-right: 10px;
padding-left: 23px;
margin-bottom: 3px;
cursor: pointer;
display: inline-block;
}
li:before {
width: 12px;
height: 12px;
position: absolute;
left: 0;
content: '';
border: 3px solid transparent;
border-radius: 2px;
}
li.inactive:before {
background: transparent;
}
&.ct-legend-inside {
position: absolute;
top: 0;
right: 0;
}
@for $i from 0 to length($ct-series-colors) {
.ct-series-#{$i}:before {
background-color: nth($ct-series-colors, $i + 1);
border-color: nth($ct-series-colors, $i + 1);
}
}
}
</style>

View File

@ -308,6 +308,18 @@ const msgs: LocaleMessageObject = {
notes: 'Notes',
placeholder: 'Add a note...',
},
stats: {
title: 'Stats',
plugins: 'Plugins',
reviews: 'Reviews',
uploads: 'Uploads',
downloads: 'Downloads',
totalDownloads: 'Total Downloads',
unsafeDownloads: 'Unsafe Downloads',
flags: 'Flags',
openedFlags: 'Opened Flags',
closedFlags: 'Closed Flags',
},
message: 'Good morning!',
};

View File

@ -22,6 +22,8 @@
"@nuxtjs/axios": "^5.12.5",
"@nuxtjs/proxy": "^2.1.0",
"@nuxtjs/pwa": "^3.3.4",
"chartist": "^0.11.4",
"chartist-plugin-legend": "^0.6.2",
"cookie-universal-nuxt": "^2.1.4",
"core-js": "^3.8.2",
"filesize": "^6.1.0",
@ -41,6 +43,7 @@
"@nuxtjs/eslint-config-typescript": "^5.0.0",
"@nuxtjs/eslint-module": "^3.0.2",
"@nuxtjs/vuetify": "^1.11.3",
"@types/chartist": "^0.11.0",
"@types/lodash-es": "^4.17.4",
"@types/swagger-ui-dist": "^3.30.0",
"eslint": "^7.18.0",

View File

@ -19,7 +19,7 @@
</template>
<v-row>
<v-col cols="1">
<UserAvatar :username="project.namespace.owner" :avatar-url="project.iconUrl" clazz="user-avatar-sm"></UserAvatar>
<UserAvatar :username="project.namespace.owner" :avatar-url="project.iconUrl" clazz="user-avatar-md"></UserAvatar>
</v-col>
<v-col cols="auto">
<h1 class="d-inline">

View File

@ -1,14 +1,148 @@
<template>
<div>{{ $nuxt.$route.name }}</div>
<v-card>
<v-card-title>
{{ $t('stats.title') }}
</v-card-title>
<v-card-text>
<v-menu ref="menu" v-model="dateMenu" :close-on-content-click="false" transition="scale-transition" offset-y min-width="auto">
<template #activator="{ on, attrs }">
<v-text-field v-model="dateRangeText" prepend-icon="mdi-calendar" readonly v-bind="attrs" v-on="on"></v-text-field>
</template>
<v-date-picker ref="picker" v-model="dates" range></v-date-picker>
</v-menu>
<h2>{{ $t('stats.plugins') }}</h2>
<Chart id="stats" :data="pluginData" :options="options" bar-type="line"></Chart>
<h2>{{ $t('stats.downloads') }}</h2>
<Chart id="downloads" :data="downloadData" :options="options" bar-type="line"></Chart>
<h2>{{ $t('stats.flags') }}</h2>
<Chart id="flags" :data="flagData" :options="options" bar-type="line"></Chart>
</v-card-text>
</v-card>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator';
import * as Chartist from 'chartist';
import { IChartistData, ILineChartOptions } from 'chartist';
import Chart from '~/components/chart/Chart.vue';
// TODO implement AdminStatsPage
@Component({
components: { Chart },
})
export default class AdminStatsPage extends Vue {
dates: Array<Date> | null;
dateMenu = false;
@Component
export default class AdminStatsPage extends Vue {}
constructor() {
super();
this.dates = null;
// TODO init properly
// this.dates = [];
// this.dates.push(new Date());
// this.dates.push(new Date());
// this.dates[0].setDate(this.dates[0].getMonth() - 1);
}
get dateRangeText() {
return this.dates && this.dates.length > 1 ? this.$util.prettyDate(this.dates[0]) + ' ~ ' + this.$util.prettyDate(this.dates[1]) : null;
}
options: ILineChartOptions = {
axisX: {
type: Chartist.FixedScaleAxis,
divisor: 5,
labelInterpolationFnc: (value: string | Date) => {
return this.$util.prettyDate(value);
},
},
plugins: [Chartist.plugins.legend()],
};
pluginData: IChartistData = {
series: [
{
name: this.$t('stats.reviews') as string,
data: [
{ x: new Date(143134652600), y: 53 },
{ x: new Date(143234652600), y: 40 },
{ x: new Date(143340052600), y: 45 },
{ x: new Date(143366652600), y: 40 },
{ x: new Date(143410652600), y: 20 },
{ x: new Date(143508652600), y: 32 },
{ x: new Date(143569652600), y: 18 },
{ x: new Date(143579652600), y: 11 },
],
},
{
name: this.$t('stats.uploads') as string,
data: [
{ x: new Date(143134652600), y: 53 },
{ x: new Date(143234652600), y: 35 },
{ x: new Date(143334652600), y: 30 },
{ x: new Date(143384652600), y: 30 },
{ x: new Date(143568652600), y: 10 },
],
},
],
};
downloadData: IChartistData = {
series: [
{
name: this.$t('stats.totalDownloads') as string,
data: [
{ x: new Date(143134652600), y: 53 },
{ x: new Date(143234652600), y: 40 },
{ x: new Date(143340052600), y: 45 },
{ x: new Date(143366652600), y: 40 },
{ x: new Date(143410652600), y: 20 },
{ x: new Date(143508652600), y: 32 },
{ x: new Date(143569652600), y: 18 },
{ x: new Date(143579652600), y: 11 },
],
},
{
name: this.$t('stats.unsafeDownloads') as string,
data: [
{ x: new Date(143134652600), y: 53 },
{ x: new Date(143234652600), y: 35 },
{ x: new Date(143334652600), y: 30 },
{ x: new Date(143384652600), y: 30 },
{ x: new Date(143568652600), y: 10 },
],
},
],
};
flagData: IChartistData = {
series: [
{
name: this.$t('stats.openedFlags') as string,
data: [
{ x: new Date(143134652600), y: 53 },
{ x: new Date(143234652600), y: 40 },
{ x: new Date(143340052600), y: 45 },
{ x: new Date(143366652600), y: 40 },
{ x: new Date(143410652600), y: 20 },
{ x: new Date(143508652600), y: 32 },
{ x: new Date(143569652600), y: 18 },
{ x: new Date(143579652600), y: 11 },
],
},
{
name: this.$t('stats.closedFlags') as string,
data: [
{ x: new Date(143134652600), y: 53 },
{ x: new Date(143234652600), y: 35 },
{ x: new Date(143334652600), y: 30 },
{ x: new Date(143384652600), y: 30 },
{ x: new Date(143568652600), y: 10 },
],
},
],
};
}
</script>
<style lang="scss" scoped></style>

View File

@ -78,8 +78,8 @@ const createUtil = ({ store, error, app: { i18n } }: Context) => {
return 'https://papermc.io/forums/u/' + name;
}
prettyDate(date: Date | string): string {
if (typeof date === 'string') {
prettyDate(date: Date | string | number): string {
if (typeof date === 'string' || typeof date === 'number') {
date = new Date(date);
}
return date.toLocaleDateString(undefined, {

View File

@ -1434,6 +1434,11 @@
resolved "https://registry.yarnpkg.com/@types/browserslist/-/browserslist-4.8.0.tgz#60489aefdf0fcb56c2d8eb65267ff08dad7a526d"
integrity sha512-4PyO9OM08APvxxo1NmQyQKlJdowPCOQIy5D/NLO3aO0vGC57wsMptvGp3b8IbYnupFZr92l1dlVief1JvS6STQ==
"@types/chartist@^0.11.0":
version "0.11.0"
resolved "https://registry.yarnpkg.com/@types/chartist/-/chartist-0.11.0.tgz#d64416f3b3781057301b3e22987eea4516fccaf8"
integrity sha512-YDJuUm0TkKj2WW6GlYmhOuBkaYzZBGJMvZz1X+Qp0Oj8oY3aozQ/YeWw4aNhfpyk5df0DKf6psjMftJI+GThtA==
"@types/clean-css@*":
version "4.2.3"
resolved "https://registry.yarnpkg.com/@types/clean-css/-/clean-css-4.2.3.tgz#12c13cc815f5e793014ee002c6324455907d851c"
@ -2881,6 +2886,16 @@ chardet@^0.7.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
chartist-plugin-legend@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/chartist-plugin-legend/-/chartist-plugin-legend-0.6.2.tgz#53bf2771ddc1dc288c8abc16c151788f0b750244"
integrity sha1-U78ncd3B3CiMirwWwVF4jwt1AkQ=
chartist@^0.11.4:
version "0.11.4"
resolved "https://registry.yarnpkg.com/chartist/-/chartist-0.11.4.tgz#e96e1c573d8b67478920a3a6ae52359d9fc8d8b7"
integrity sha512-H4AimxaUD738/u9Mq8t27J4lh6STsLi4BQHt65nOtpLk3xyrBPaLiLMrHw7/WV9CmsjGA02WihjuL5qpSagLYw==
check-types@^8.0.3:
version "8.0.3"
resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552"