diff --git a/packages/frontend/src/components/MkActiveUsersHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue similarity index 77% rename from packages/frontend/src/components/MkActiveUsersHeatmap.vue rename to packages/frontend/src/components/MkHeatmap.vue index 744193dd5f..078d0721da 100644 --- a/packages/frontend/src/components/MkActiveUsersHeatmap.vue +++ b/packages/frontend/src/components/MkHeatmap.vue @@ -8,7 +8,7 @@ </template> <script lang="ts" setup> -import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; +import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'; import { Chart, ArcElement, @@ -54,6 +54,10 @@ Chart.register( MatrixController, MatrixElement, ); +const props = defineProps<{ + src: string; +}>(); + const rootEl = $ref<HTMLDivElement>(null); const chartEl = $ref<HTMLCanvasElement>(null); const now = new Date(); @@ -96,7 +100,24 @@ async function renderChart() { }); }; - const raw = await os.api('charts/active-users', { limit: chartLimit, span: 'day' }); + let values; + + if (props.src === 'active-users') { + const raw = await os.api('charts/active-users', { limit: chartLimit, span: 'day' }); + values = raw.readWrite; + } else if (props.src === 'notes') { + const raw = await os.api('charts/notes', { limit: chartLimit, span: 'day' }); + values = raw.local.inc; + } else if (props.src === 'ap-requests-inbox-received') { + const raw = await os.api('charts/ap-request', { limit: chartLimit, span: 'day' }); + values = raw.inboxReceived; + } else if (props.src === 'ap-requests-deliver-succeeded') { + const raw = await os.api('charts/ap-request', { limit: chartLimit, span: 'day' }); + values = raw.deliverSucceeded; + } else if (props.src === 'ap-requests-deliver-failed') { + const raw = await os.api('charts/ap-request', { limit: chartLimit, span: 'day' }); + values = raw.deliverFailed; + } fetching = false; @@ -110,7 +131,9 @@ async function renderChart() { const color = defaultStore.state.darkMode ? '#b4e900' : '#86b300'; // 視覚上の分かりやすさのため上から最も大きい3つの値の平均を最大値とする - const max = raw.readWrite.slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3; + const max = values.slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3; + + const min = Math.max(0, Math.min(...values) - 1); const marginEachCell = 4; @@ -119,14 +142,17 @@ async function renderChart() { data: { datasets: [{ label: 'Read & Write', - data: format(raw.readWrite), + data: format(values), pointRadius: 0, borderWidth: 0, borderJoinStyle: 'round', borderRadius: 3, backgroundColor(c) { const value = c.dataset.data[c.dataIndex].v; - const a = value / max; + let a = (value - min) / max; + if (value !== 0) { // 0でない限りは完全に不可視にはしない + a = Math.max(a, 0.05); + } return alpha(color, a); }, fill: true, @@ -222,6 +248,11 @@ async function renderChart() { }); } +watch(() => props.src, () => { + fetching = true; + renderChart(); +}); + onMounted(async () => { renderChart(); }); diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue index f01bb7426c..3500a340a8 100644 --- a/packages/frontend/src/components/MkInstanceStats.vue +++ b/packages/frontend/src/components/MkInstanceStats.vue @@ -38,8 +38,15 @@ <MkFolder class="item"> <template #header>Active users heatmap</template> + <MkSelect v-model="heatmapSrc" style="margin: 0 0 12px 0;"> + <option value="active-users">Active users</option> + <option value="notes">Notes</option> + <option value="ap-requests-inbox-received">AP Requests: inboxReceived</option> + <option value="ap-requests-deliver-succeeded">AP Requests: deliverSucceeded</option> + <option value="ap-requests-deliver-failed">AP Requests: deliverFailed</option> + </MkSelect> <div class="_panel" :class="$style.heatmap"> - <MkActiveUsersHeatmap/> + <MkHeatmap :src="heatmapSrc"/> </div> </MkFolder> @@ -93,7 +100,7 @@ import MkChart from '@/components/MkChart.vue'; import { useChartTooltip } from '@/scripts/use-chart-tooltip'; import * as os from '@/os'; import { i18n } from '@/i18n'; -import MkActiveUsersHeatmap from '@/components/MkActiveUsersHeatmap.vue'; +import MkHeatmap from '@/components/MkHeatmap.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue'; @@ -115,15 +122,10 @@ Chart.register( Filler, ); -const props = withDefaults(defineProps<{ - chartLimit?: number; - detailed?: boolean; -}>(), { - chartLimit: 90, -}); - -const chartSpan = $ref<'hour' | 'day'>('hour'); -const chartSrc = $ref('active-users'); +const chartLimit = 90; +let chartSpan = $ref<'hour' | 'day'>('hour'); +let chartSrc = $ref('active-users'); +let heatmapSrc = $ref('active-users'); let subDoughnutEl = $ref<HTMLCanvasElement>(); let pubDoughnutEl = $ref<HTMLCanvasElement>(); diff --git a/packages/frontend/src/pages/admin/overview.heatmap.vue b/packages/frontend/src/pages/admin/overview.heatmap.vue index 16d1c83b9f..4d56d2a51f 100644 --- a/packages/frontend/src/pages/admin/overview.heatmap.vue +++ b/packages/frontend/src/pages/admin/overview.heatmap.vue @@ -1,11 +1,21 @@ <template> <div class="_panel" :class="$style.root"> - <MkActiveUsersHeatmap/> + <MkSelect v-model="src" style="margin: 0 0 12px 0;" small> + <option value="active-users">Active users</option> + <option value="notes">Notes</option> + <option value="ap-requests-inbox-received">AP Requests: inboxReceived</option> + <option value="ap-requests-deliver-succeeded">AP Requests: deliverSucceeded</option> + <option value="ap-requests-deliver-failed">AP Requests: deliverFailed</option> + </MkSelect> + <MkHeatmap :src="src"/> </div> </template> <script lang="ts" setup> -import MkActiveUsersHeatmap from '@/components/MkActiveUsersHeatmap.vue'; +import MkHeatmap from '@/components/MkHeatmap.vue'; +import MkSelect from '@/components/form/select.vue'; + +let src = $ref('active-users'); </script> <style lang="scss" module>