diff --git a/CHANGELOG.md b/CHANGELOG.md index 9026cec5182d2b447671e9c341577c269e7b2a0a..71b111d1e1ed2a704f8d03f18848b5ff2854f62e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 - Report notes - Ability to moderate users on the statuses page +- Stats page: status counts are displayed here ### Fixed diff --git a/README.md b/README.md index 84ce067d3887335c0f60f316784519787c22fc62..413802c2645ae9407a56a11de879334883572afd 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ Features, that can be disabled: - moderation log: `DISABLED_FEATURES: '["moderationLog"]'` - settings: `DISABLED_FEATURES: '["settings"]'` - 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. diff --git a/src/api/peers.js b/src/api/instance.js similarity index 66% rename from src/api/peers.js rename to src/api/instance.js index 4b80d7abe27413fecbbe02fdce271737b64591d5..a4e3ad9bc045002f5a1410db8ee411af9111bb4e 100644 --- a/src/api/peers.js +++ b/src/api/instance.js @@ -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()}` } : {} diff --git a/src/lang/en.js b/src/lang/en.js index 3dcd9bddd672efafaf27ebdbb9ad24ba53b22c30..d9b696b5d0a9bb8fec26f170ffae81cd7297ebbd 100644 --- a/src/lang/en.js +++ b/src/lang/en.js @@ -67,7 +67,8 @@ export default { reports: 'Reports', settings: 'Settings', moderationLog: 'Moderation Log', - 'emoji-packs': 'Emoji packs' + 'emoji-packs': 'Emoji packs', + stats: 'Stats' }, navbar: { logOut: 'Log Out', @@ -432,5 +433,14 @@ export default { emailSent: 'Invite was sent', 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`' + }, + stats: { + stats: 'Instance stats', + statusCounts: 'Status counts', + all: 'All', + public: 'Public', + unlisted: 'Unlisted', + direct: 'Direct', + private: 'Private' } } diff --git a/src/router/index.js b/src/router/index.js index 8a7f9e36bf90f50815bd6d845700399969e7afa7..c4b483852e5bd4509aa3b2034909fb2142614321 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -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 = [ { path: '/redirect', @@ -145,6 +159,7 @@ export const asyncRouterMap = [ ...(invitesDisabled ? [] : [invites]), ...(moderationLogDisabled ? [] : [moderationLog]), ...(settingsDisabled ? [] : [settings]), + ...(statsDisabled ? [] : [stats]), { path: '/users/:id', component: Layout, diff --git a/src/store/getters.js b/src/store/getters.js index 159fb48c1c103b6e7242cc62e1d524cdba7c6d01..0722b4c270fa7cc43988b8e39f69a5ec83cc38fa 100644 --- a/src/store/getters.js +++ b/src/store/getters.js @@ -49,7 +49,7 @@ const getters = { http: state => state.settings.settings['http'], httpSecurity: state => state.settings.settings['http_security'], instance: state => state.settings.settings['instance'], - instances: state => state.peers.fetchedPeers, + instances: state => state.instance.fetchedPeers, kocaptcha: state => state.settings.settings['Pleroma.Captcha.Kocaptcha'], level: state => state.settings.settings['level'], ldap: state => state.settings.settings['ldap'], @@ -84,6 +84,7 @@ const getters = { suggestions: state => state.settings.settings['suggestions'], scheduledActivity: state => state.settings.settings['Pleroma.ScheduledActivity'], statuses: state => state.status.fetchedStatuses, + statusCounts: state => state.instance.fetchedStatusCounts, teslaAdapter: state => state.settings.settings['adapter'], twitter: state => state.settings.settings['Ueberauth.Strategy.Twitter.OAuth'], ueberauth: state => state.settings.settings['Ueberauth'], diff --git a/src/store/index.js b/src/store/index.js index 586f2b072af3620f4ac3cbf91c2fcd25ed7c5ad0..bb654676e7bd0fbe3d6ebadc65e8ee8c4523da3d 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -4,7 +4,7 @@ import app from './modules/app' import errorLog from './modules/errorLog' import moderationLog from './modules/moderationLog' import invites from './modules/invites' -import peers from './modules/peers' +import instance from './modules/instance' import permission from './modules/permission' import relays from './modules/relays' import reports from './modules/reports' @@ -25,7 +25,7 @@ const store = new Vuex.Store({ errorLog, moderationLog, invites, - peers, + instance, permission, relays, reports, diff --git a/src/store/modules/instance.js b/src/store/modules/instance.js new file mode 100644 index 0000000000000000000000000000000000000000..cec33c915ef1eade548cfe578834a93b471130c1 --- /dev/null +++ b/src/store/modules/instance.js @@ -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 diff --git a/src/store/modules/peers.js b/src/store/modules/peers.js deleted file mode 100644 index fa37a1d0dee988baca282de55b8579050de67f34..0000000000000000000000000000000000000000 --- a/src/store/modules/peers.js +++ /dev/null @@ -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 diff --git a/src/views/stats/index.vue b/src/views/stats/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..7b58b0afe8c7e655f94bddaa6480c477bd9459bf --- /dev/null +++ b/src/views/stats/index.vue @@ -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> diff --git a/src/views/statuses/index.vue b/src/views/statuses/index.vue index 408d3e24bc2c2319ed926959c489327772cfe570..0651d2476fa371dbf912ce69f704d3989595b87a 100644 --- a/src/views/statuses/index.vue +++ b/src/views/statuses/index.vue @@ -49,7 +49,7 @@ export default { }, computed: { loadingPeers() { - return this.$store.state.peers.loading + return this.$store.state.instance.peersLoading }, ...mapGetters([ 'instances',