Add stats page (status counts by scope)

This commit is contained in:
Maxim Filippov 2019-12-19 19:55:24 +03:00
parent 7731d5c158
commit c1de536cc0
11 changed files with 158 additions and 33 deletions

View file

@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Ability to confirm users' emails and resend confirmation emails - Ability to confirm users' emails and resend confirmation emails
- Report notes - Report notes
- Ability to moderate users on the statuses page - Ability to moderate users on the statuses page
- Stats page: status counts are displayed here
### Fixed ### Fixed

View file

@ -48,6 +48,7 @@ Features, that can be disabled:
- moderation log: `DISABLED_FEATURES: '["moderationLog"]'` - moderation log: `DISABLED_FEATURES: '["moderationLog"]'`
- settings: `DISABLED_FEATURES: '["settings"]'` - settings: `DISABLED_FEATURES: '["settings"]'`
- emoji packs: `DISABLED_FEATURES: '["emojiPacks"]'` - emoji packs: `DISABLED_FEATURES: '["emojiPacks"]'`
- stats: `DISABLED_FEATURES: '["stats"]'`
Of course, you can disable multiple features just by adding to the array, e.g. `DISABLED_FEATURES: '["emojiPacks", "settings"]'` will have both emoji packs and settings disabled. Of course, you can disable multiple features just by adding to the array, e.g. `DISABLED_FEATURES: '["emojiPacks", "settings"]'` will have both emoji packs and settings disabled.

View file

@ -11,4 +11,13 @@ export async function fetchPeers(authHost, token) {
}) })
} }
export async function fetchInstanceInfo(authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/v1/instance`,
method: 'get',
headers: authHeaders(token)
})
}
const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {} const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {}

View file

@ -67,7 +67,8 @@ export default {
reports: 'Reports', reports: 'Reports',
settings: 'Settings', settings: 'Settings',
moderationLog: 'Moderation Log', moderationLog: 'Moderation Log',
'emoji-packs': 'Emoji packs' 'emoji-packs': 'Emoji packs',
stats: 'Stats'
}, },
navbar: { navbar: {
logOut: 'Log Out', logOut: 'Log Out',
@ -432,5 +433,14 @@ export default {
emailSent: 'Invite was sent', emailSent: 'Invite was sent',
submitFormError: 'There are invalid values in the form. Please fix them before continuing.', submitFormError: 'There are invalid values in the form. Please fix them before continuing.',
inviteViaEmailAlert: 'To send invite via email make sure to enable `invites_enabled` and disable `registrations_open`' inviteViaEmailAlert: 'To send invite via email make sure to enable `invites_enabled` and disable `registrations_open`'
},
stats: {
stats: 'Instance stats',
statusCounts: 'Status counts',
all: 'All',
public: 'Public',
unlisted: 'Unlisted',
direct: 'Direct',
private: 'Private'
} }
} }

View file

@ -77,6 +77,20 @@ const moderationLog = {
] ]
} }
const statsDisabled = disabledFeatures.includes('stats')
const stats = {
path: '/stats',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/stats/index'),
name: 'Stats',
meta: { title: 'stats', icon: 'chart', noCache: true }
}
]
}
export const constantRouterMap = [ export const constantRouterMap = [
{ {
path: '/redirect', path: '/redirect',
@ -145,6 +159,7 @@ export const asyncRouterMap = [
...(invitesDisabled ? [] : [invites]), ...(invitesDisabled ? [] : [invites]),
...(moderationLogDisabled ? [] : [moderationLog]), ...(moderationLogDisabled ? [] : [moderationLog]),
...(settingsDisabled ? [] : [settings]), ...(settingsDisabled ? [] : [settings]),
...(statsDisabled ? [] : [stats]),
{ {
path: '/users/:id', path: '/users/:id',
component: Layout, component: Layout,

View file

@ -49,7 +49,7 @@ const getters = {
http: state => state.settings.settings['http'], http: state => state.settings.settings['http'],
httpSecurity: state => state.settings.settings['http_security'], httpSecurity: state => state.settings.settings['http_security'],
instance: state => state.settings.settings['instance'], instance: state => state.settings.settings['instance'],
instances: state => state.peers.fetchedPeers, instances: state => state.instance.fetchedPeers,
kocaptcha: state => state.settings.settings['Pleroma.Captcha.Kocaptcha'], kocaptcha: state => state.settings.settings['Pleroma.Captcha.Kocaptcha'],
level: state => state.settings.settings['level'], level: state => state.settings.settings['level'],
ldap: state => state.settings.settings['ldap'], ldap: state => state.settings.settings['ldap'],
@ -84,6 +84,7 @@ const getters = {
suggestions: state => state.settings.settings['suggestions'], suggestions: state => state.settings.settings['suggestions'],
scheduledActivity: state => state.settings.settings['Pleroma.ScheduledActivity'], scheduledActivity: state => state.settings.settings['Pleroma.ScheduledActivity'],
statuses: state => state.status.fetchedStatuses, statuses: state => state.status.fetchedStatuses,
statusCounts: state => state.instance.fetchedStatusCounts,
teslaAdapter: state => state.settings.settings['adapter'], teslaAdapter: state => state.settings.settings['adapter'],
twitter: state => state.settings.settings['Ueberauth.Strategy.Twitter.OAuth'], twitter: state => state.settings.settings['Ueberauth.Strategy.Twitter.OAuth'],
ueberauth: state => state.settings.settings['Ueberauth'], ueberauth: state => state.settings.settings['Ueberauth'],

View file

@ -4,7 +4,7 @@ import app from './modules/app'
import errorLog from './modules/errorLog' import errorLog from './modules/errorLog'
import moderationLog from './modules/moderationLog' import moderationLog from './modules/moderationLog'
import invites from './modules/invites' import invites from './modules/invites'
import peers from './modules/peers' import instance from './modules/instance'
import permission from './modules/permission' import permission from './modules/permission'
import relays from './modules/relays' import relays from './modules/relays'
import reports from './modules/reports' import reports from './modules/reports'
@ -25,7 +25,7 @@ const store = new Vuex.Store({
errorLog, errorLog,
moderationLog, moderationLog,
invites, invites,
peers, instance,
permission, permission,
relays, relays,
reports, reports,

View file

@ -0,0 +1,40 @@
import { fetchPeers, fetchInstanceInfo } from '@/api/instance'
const instance = {
state: {
fetchedPeers: [],
peersLoading: true,
fetchedStatusCounts: {},
statusCountsLoading: true
},
mutations: {
SET_PEERS: (state, peers) => {
state.fetchedPeers = peers
},
SET_PEERS_LOADING: (state, status) => {
state.peersLoading = status
},
SET_STATUS_COUNTS: (state, counts) => {
state.fetchedStatusCounts = counts
},
SET_STATUS_COUNTS_LOADING: (state, status) => {
state.statusCountsLoading = status
}
},
actions: {
async FetchPeers({ commit, getters }) {
const peers = await fetchPeers(getters.authHost, getters.token)
commit('SET_PEERS', peers.data)
commit('SET_PEERS_LOADING', false)
},
async FetchStatusCounts({ commit, getters }) {
const info = await fetchInstanceInfo(getters.authHost, getters.token)
commit('SET_STATUS_COUNTS', info.data.stats.status_count)
commit('SET_STATUS_COUNTS_LOADING', false)
}
}
}
export default instance

View file

@ -1,28 +0,0 @@
import { fetchPeers } from '@/api/peers'
const peers = {
state: {
fetchedPeers: [],
loading: true
},
mutations: {
SET_PEERS: (state, peers) => {
state.fetchedPeers = peers
},
SET_LOADING: (state, status) => {
state.loading = status
}
},
actions: {
async FetchPeers({ commit, getters }) {
const peers = await fetchPeers(getters.authHost, getters.token)
commit('SET_PEERS', peers.data)
commit('SET_LOADING', false)
}
}
}
export default peers

76
src/views/stats/index.vue Normal file
View file

@ -0,0 +1,76 @@
<template>
<div class="stats-container">
<h1>{{ $t('stats.stats') }}</h1>
<el-row>
<el-col :span="5">
<el-card v-if="!loadingStatusCounts" class="box-card">
<div slot="header">
<h4>{{ $t('stats.statusCounts') }}</h4>
</div>
<table>
<tr>
<td>{{ $t('stats.all') }}</td>
<td class="number">{{ formatNumber(statusCounts.all) }}</td>
</tr>
<tr>
<td>{{ $t('stats.public') }}</td>
<td class="number">{{ formatNumber(statusCounts.public) }}</td>
</tr>
<tr>
<td>{{ $t('stats.unlisted') }}</td>
<td class="number">{{ formatNumber(statusCounts.unlisted) }}</td>
</tr>
<tr>
<td>{{ $t('stats.direct') }}</td>
<td class="number">{{ formatNumber(statusCounts.direct) }}</td>
</tr>
<tr>
<td>{{ $t('stats.private') }}</td>
<td class="number">{{ formatNumber(statusCounts.private) }}</td>
</tr>
</table>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import numeral from 'numeral'
import { mapGetters } from 'vuex'
export default {
name: 'Stats',
computed: {
loadingStatusCounts() {
return this.$store.state.instance.statusCountsLoading
},
...mapGetters([
'statusCounts'
])
},
mounted() {
this.$store.dispatch('FetchStatusCounts')
},
methods: {
formatNumber(num) {
return numeral(num).format('0,0')
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
.stats-container {
padding: 0 15px;
}
table {
width: 100%;
td.number {
text-align: right;
}
}
h4 {
margin: 0;
}
</style>

View file

@ -49,7 +49,7 @@ export default {
}, },
computed: { computed: {
loadingPeers() { loadingPeers() {
return this.$store.state.peers.loading return this.$store.state.instance.peersLoading
}, },
...mapGetters([ ...mapGetters([
'instances', 'instances',