mirror of
https://github.com/HangarMC/Hangar.git
synced 2024-11-21 01:21:54 +08:00
Add admin/stats page layout, charts, wooo
This commit is contained in:
parent
6da5ae286e
commit
6424415df7
101
frontend/components/chart/Chart.vue
Normal file
101
frontend/components/chart/Chart.vue
Normal 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>
|
@ -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!',
|
||||
};
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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, {
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user