summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/components/chart.vue628
-rw-r--r--src/client/components/instance-stats.vue487
-rw-r--r--src/client/components/number-diff.vue47
-rw-r--r--src/client/pages/instance-info.vue266
-rw-r--r--src/client/pages/instance/instance.vue266
-rw-r--r--src/client/pages/instance/metrics.vue83
-rw-r--r--src/client/pages/instance/overview.vue174
-rw-r--r--src/client/pages/instance/queue.chart.vue2
-rw-r--r--src/client/scripts/hpml/lib.ts7
-rw-r--r--src/queue/index.ts28
-rw-r--r--src/queue/processors/system/index.ts12
-rw-r--r--src/queue/processors/system/resync-charts.ts21
-rw-r--r--src/queue/queues.ts1
-rw-r--r--src/server/api/endpoints/admin/resync-chart.ts4
14 files changed, 948 insertions, 1078 deletions
diff --git a/src/client/components/chart.vue b/src/client/components/chart.vue
new file mode 100644
index 0000000000..3599266cb6
--- /dev/null
+++ b/src/client/components/chart.vue
@@ -0,0 +1,628 @@
+<template>
+<canvas ref="chartEl"></canvas>
+</template>
+
+<script lang="ts">
+import { defineComponent, onMounted, ref, watch, PropType } from 'vue';
+import {
+ Chart,
+ ArcElement,
+ LineElement,
+ BarElement,
+ PointElement,
+ BarController,
+ LineController,
+ CategoryScale,
+ LinearScale,
+ TimeScale,
+ Legend,
+ Title,
+ Tooltip,
+ SubTitle,
+ Filler,
+} from 'chart.js';
+import 'chartjs-adapter-date-fns';
+import { enUS } from 'date-fns/locale';
+import zoomPlugin from 'chartjs-plugin-zoom';
+import * as os from '@client/os';
+import { defaultStore } from '@client/store';
+
+Chart.register(
+ ArcElement,
+ LineElement,
+ BarElement,
+ PointElement,
+ BarController,
+ LineController,
+ CategoryScale,
+ LinearScale,
+ TimeScale,
+ Legend,
+ Title,
+ Tooltip,
+ SubTitle,
+ Filler,
+ zoomPlugin,
+);
+
+const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
+const negate = arr => arr.map(x => -x);
+const alpha = (hex, a) => {
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
+ const r = parseInt(result[1], 16);
+ const g = parseInt(result[2], 16);
+ const b = parseInt(result[3], 16);
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
+};
+
+const colors = ['#008FFB', '#00E396', '#FEB019', '#FF4560'];
+const getColor = (i) => {
+ return colors[i % colors.length];
+};
+
+export default defineComponent({
+ props: {
+ src: {
+ type: String,
+ required: true,
+ },
+ args: {
+ type: Object,
+ required: false,
+ },
+ limit: {
+ type: Number,
+ required: false,
+ default: 90
+ },
+ span: {
+ type: String as PropType<'hour' | 'day'>,
+ required: true,
+ },
+ detailed: {
+ type: Boolean,
+ required: false,
+ default: false
+ },
+ },
+
+ setup(props) {
+ const now = new Date();
+ let chartInstance: Chart = null;
+ let data: {
+ series: {
+ name: string;
+ type: 'line' | 'area';
+ color?: string;
+ borderDash?: number[];
+ hidden?: boolean;
+ data: {
+ x: number;
+ y: number;
+ }[];
+ }[];
+ } = null;
+
+ const chartEl = ref<HTMLCanvasElement>(null);
+ const fetching = ref(true);
+
+ const getDate = (ago: number) => {
+ const y = now.getFullYear();
+ const m = now.getMonth();
+ const d = now.getDate();
+ const h = now.getHours();
+
+ return props.span === 'day' ? new Date(y, m, d - ago) : new Date(y, m, d, h - ago);
+ };
+
+ const format = (arr) => {
+ return arr.map((v, i) => ({
+ x: getDate(i).getTime(),
+ y: v
+ }));
+ };
+
+ const render = () => {
+ if (chartInstance) {
+ chartInstance.destroy();
+ }
+
+ const gridColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
+
+ // フォントカラー
+ Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg');
+
+ chartInstance = new Chart(chartEl.value, {
+ type: 'line',
+ data: {
+ labels: new Array(props.limit).fill(0).map((_, i) => getDate(i).toLocaleString()).slice().reverse(),
+ datasets: data.series.map((x, i) => ({
+ parsing: false,
+ label: x.name,
+ data: x.data.slice().reverse(),
+ pointRadius: 0,
+ tension: 0,
+ borderWidth: 2,
+ borderColor: x.color ? x.color : getColor(i),
+ borderDash: x.borderDash || [],
+ borderJoinStyle: 'round',
+ backgroundColor: alpha(x.color ? x.color : getColor(i), 0.1),
+ fill: x.type === 'area',
+ hidden: !!x.hidden,
+ })),
+ },
+ options: {
+ aspectRatio: 2.5,
+ layout: {
+ padding: {
+ left: 16,
+ right: 16,
+ top: 16,
+ bottom: 8,
+ },
+ },
+ scales: {
+ x: {
+ type: 'time',
+ time: {
+ stepSize: 1,
+ unit: props.span === 'day' ? 'month' : 'day',
+ },
+ grid: {
+ display: props.detailed,
+ color: gridColor,
+ borderColor: 'rgb(0, 0, 0, 0)',
+ },
+ ticks: {
+ display: props.detailed,
+ },
+ adapters: {
+ date: {
+ locale: enUS,
+ },
+ },
+ min: getDate(props.limit).getTime(),
+ },
+ y: {
+ position: 'left',
+ grid: {
+ color: gridColor,
+ borderColor: 'rgb(0, 0, 0, 0)',
+ },
+ ticks: {
+ display: props.detailed,
+ },
+ },
+ },
+ interaction: {
+ intersect: false,
+ },
+ plugins: {
+ legend: {
+ position: 'bottom',
+ labels: {
+ boxWidth: 16,
+ },
+ },
+ tooltip: {
+ mode: 'index',
+ animation: {
+ duration: 0,
+ },
+ },
+ zoom: {
+ pan: {
+ enabled: true,
+ },
+ zoom: {
+ wheel: {
+ enabled: true,
+ },
+ pinch: {
+ enabled: true,
+ },
+ drag: {
+ enabled: false,
+ },
+ mode: 'x',
+ },
+ limits: {
+ x: {
+ min: 'original',
+ max: 'original',
+ },
+ y: {
+ min: 'original',
+ max: 'original',
+ },
+ }
+ },
+ },
+ },
+ });
+ };
+
+ const exportData = () => {
+ // TODO
+ };
+
+ const fetchFederationInstancesChart = async (total: boolean): Promise<typeof data> => {
+ const raw = await os.api('charts/federation', { limit: props.limit, span: props.span });
+ return {
+ series: [{
+ name: 'Instances',
+ type: 'area',
+ data: format(total
+ ? raw.instance.total
+ : sum(raw.instance.inc, negate(raw.instance.dec))
+ ),
+ }],
+ };
+ };
+
+ const fetchNotesChart = async (type: string): Promise<typeof data> => {
+ const raw = await os.api('charts/notes', { limit: props.limit, span: props.span });
+ return {
+ series: [{
+ name: 'All',
+ type: 'line',
+ borderDash: [5, 5],
+ data: format(type == 'combined'
+ ? sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec))
+ : sum(raw[type].inc, negate(raw[type].dec))
+ ),
+ }, {
+ name: 'Renotes',
+ type: 'area',
+ data: format(type == 'combined'
+ ? sum(raw.local.diffs.renote, raw.remote.diffs.renote)
+ : raw[type].diffs.renote
+ ),
+ }, {
+ name: 'Replies',
+ type: 'area',
+ data: format(type == 'combined'
+ ? sum(raw.local.diffs.reply, raw.remote.diffs.reply)
+ : raw[type].diffs.reply
+ ),
+ }, {
+ name: 'Normal',
+ type: 'area',
+ data: format(type == 'combined'
+ ? sum(raw.local.diffs.normal, raw.remote.diffs.normal)
+ : raw[type].diffs.normal
+ ),
+ }],
+ };
+ };
+
+ const fetchNotesTotalChart = async (): Promise<typeof data> => {
+ const raw = await os.api('charts/notes', { limit: props.limit, span: props.span });
+ return {
+ series: [{
+ name: 'Combined',
+ type: 'line',
+ data: format(sum(raw.local.total, raw.remote.total)),
+ }, {
+ name: 'Local',
+ type: 'area',
+ data: format(raw.local.total),
+ }, {
+ name: 'Remote',
+ type: 'area',
+ data: format(raw.remote.total),
+ }],
+ };
+ };
+
+ const fetchUsersChart = async (total: boolean): Promise<typeof data> => {
+ const raw = await os.api('charts/users', { limit: props.limit, span: props.span });
+ return {
+ series: [{
+ name: 'Combined',
+ type: 'line',
+ data: format(total
+ ? sum(raw.local.total, raw.remote.total)
+ : sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec))
+ ),
+ }, {
+ name: 'Local',
+ type: 'area',
+ data: format(total
+ ? raw.local.total
+ : sum(raw.local.inc, negate(raw.local.dec))
+ ),
+ }, {
+ name: 'Remote',
+ type: 'area',
+ data: format(total
+ ? raw.remote.total
+ : sum(raw.remote.inc, negate(raw.remote.dec))
+ ),
+ }],
+ };
+ };
+
+ const fetchActiveUsersChart = async (): Promise<typeof data> => {
+ const raw = await os.api('charts/active-users', { limit: props.limit, span: props.span });
+ return {
+ series: [{
+ name: 'Combined',
+ type: 'line',
+ data: format(sum(raw.local.users, raw.remote.users)),
+ }, {
+ name: 'Local',
+ type: 'area',
+ data: format(raw.local.users),
+ }, {
+ name: 'Remote',
+ type: 'area',
+ data: format(raw.remote.users),
+ }],
+ };
+ };
+
+ const fetchDriveChart = async (): Promise<typeof data> => {
+ const raw = await os.api('charts/drive', { limit: props.limit, span: props.span });
+ return {
+ bytes: true,
+ series: [{
+ name: 'All',
+ type: 'line',
+ borderDash: [5, 5],
+ data: format(
+ sum(
+ raw.local.incSize,
+ negate(raw.local.decSize),
+ raw.remote.incSize,
+ negate(raw.remote.decSize)
+ )
+ ),
+ }, {
+ name: 'Local +',
+ type: 'area',
+ data: format(raw.local.incSize),
+ }, {
+ name: 'Local -',
+ type: 'area',
+ data: format(negate(raw.local.decSize)),
+ }, {
+ name: 'Remote +',
+ type: 'area',
+ data: format(raw.remote.incSize),
+ }, {
+ name: 'Remote -',
+ type: 'area',
+ data: format(negate(raw.remote.decSize)),
+ }],
+ };
+ };
+
+ const fetchDriveTotalChart = async (): Promise<typeof data> => {
+ const raw = await os.api('charts/drive', { limit: props.limit, span: props.span });
+ return {
+ bytes: true,
+ series: [{
+ name: 'Combined',
+ type: 'line',
+ data: format(sum(raw.local.totalSize, raw.remote.totalSize)),
+ }, {
+ name: 'Local',
+ type: 'area',
+ data: format(raw.local.totalSize),
+ }, {
+ name: 'Remote',
+ type: 'area',
+ data: format(raw.remote.totalSize),
+ }],
+ };
+ };
+
+ const fetchDriveFilesChart = async (): Promise<typeof data> => {
+ const raw = await os.api('charts/drive', { limit: props.limit, span: props.span });
+ return {
+ series: [{
+ name: 'All',
+ type: 'line',
+ borderDash: [5, 5],
+ data: format(
+ sum(
+ raw.local.incCount,
+ negate(raw.local.decCount),
+ raw.remote.incCount,
+ negate(raw.remote.decCount)
+ )
+ ),
+ }, {
+ name: 'Local +',
+ type: 'area',
+ data: format(raw.local.incCount),
+ }, {
+ name: 'Local -',
+ type: 'area',
+ data: format(negate(raw.local.decCount)),
+ }, {
+ name: 'Remote +',
+ type: 'area',
+ data: format(raw.remote.incCount),
+ }, {
+ name: 'Remote -',
+ type: 'area',
+ data: format(negate(raw.remote.decCount)),
+ }],
+ };
+ };
+
+ const fetchDriveFilesTotalChart = async (): Promise<typeof data> => {
+ const raw = await os.api('charts/drive', { limit: props.limit, span: props.span });
+ return {
+ series: [{
+ name: 'Combined',
+ type: 'line',
+ data: format(sum(raw.local.totalCount, raw.remote.totalCount)),
+ }, {
+ name: 'Local',
+ type: 'area',
+ data: format(raw.local.totalCount),
+ }, {
+ name: 'Remote',
+ type: 'area',
+ data: format(raw.remote.totalCount),
+ }],
+ };
+ };
+
+ const fetchInstanceRequestsChart = async (): Promise<typeof data> => {
+ const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+ return {
+ series: [{
+ name: 'In',
+ type: 'area',
+ color: '#008FFB',
+ data: format(raw.requests.received)
+ }, {
+ name: 'Out (succ)',
+ type: 'area',
+ color: '#00E396',
+ data: format(raw.requests.succeeded)
+ }, {
+ name: 'Out (fail)',
+ type: 'area',
+ color: '#FEB019',
+ data: format(raw.requests.failed)
+ }]
+ };
+ };
+
+ const fetchInstanceUsersChart = async (total: boolean): Promise<typeof data> => {
+ const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+ return {
+ series: [{
+ name: 'Users',
+ type: 'area',
+ color: '#008FFB',
+ data: format(total
+ ? raw.users.total
+ : sum(raw.users.inc, negate(raw.users.dec))
+ )
+ }]
+ };
+ };
+
+ const fetchInstanceNotesChart = async (total: boolean): Promise<typeof data> => {
+ const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+ return {
+ series: [{
+ name: 'Notes',
+ type: 'area',
+ color: '#008FFB',
+ data: format(total
+ ? raw.notes.total
+ : sum(raw.notes.inc, negate(raw.notes.dec))
+ )
+ }]
+ };
+ };
+
+ const fetchInstanceFfChart = async (total: boolean): Promise<typeof data> => {
+ const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+ return {
+ series: [{
+ name: 'Following',
+ type: 'area',
+ color: '#008FFB',
+ data: format(total
+ ? raw.following.total
+ : sum(raw.following.inc, negate(raw.following.dec))
+ )
+ }, {
+ name: 'Followers',
+ type: 'area',
+ color: '#00E396',
+ data: format(total
+ ? raw.followers.total
+ : sum(raw.followers.inc, negate(raw.followers.dec))
+ )
+ }]
+ };
+ };
+
+ const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof data> => {
+ const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+ return {
+ bytes: true,
+ series: [{
+ name: 'Drive usage',
+ type: 'area',
+ color: '#008FFB',
+ data: format(total
+ ? raw.drive.totalUsage
+ : sum(raw.drive.incUsage, negate(raw.drive.decUsage))
+ )
+ }]
+ };
+ };
+
+ const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof data> => {
+ const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+ return {
+ series: [{
+ name: 'Drive files',
+ type: 'area',
+ color: '#008FFB',
+ data: format(total
+ ? raw.drive.totalFiles
+ : sum(raw.drive.incFiles, negate(raw.drive.decFiles))
+ )
+ }]
+ };
+ };
+
+ const fetchAndRender = async () => {
+ const fetchData = () => {
+ switch (props.src) {
+ case 'federation-instances': return fetchFederationInstancesChart(false);
+ case 'federation-instances-total': return fetchFederationInstancesChart(true);
+ case 'users': return fetchUsersChart(false);
+ case 'users-total': return fetchUsersChart(true);
+ case 'active-users': return fetchActiveUsersChart();
+ case 'notes': return fetchNotesChart('combined');
+ case 'local-notes': return fetchNotesChart('local');
+ case 'remote-notes': return fetchNotesChart('remote');
+ case 'notes-total': return fetchNotesTotalChart();
+ case 'drive': return fetchDriveChart();
+ case 'drive-total': return fetchDriveTotalChart();
+ case 'drive-files': return fetchDriveFilesChart();
+ case 'drive-files-total': return fetchDriveFilesTotalChart();
+
+ case 'instances-requests': return fetchInstanceRequestsChart();
+ case 'instances-users': return fetchInstanceUsersChart(false);
+ case 'instances-users-total': return fetchInstanceUsersChart(true);
+ case 'instances-notes': return fetchInstanceNotesChart(false);
+ case 'instances-notes-total': return fetchInstanceNotesChart(true);
+ case 'instances-ff': return fetchInstanceFfChart(false);
+ case 'instances-ff-total': return fetchInstanceFfChart(true);
+ case 'instances-drive-usage': return fetchInstanceDriveUsageChart(false);
+ case 'instances-drive-usage-total': return fetchInstanceDriveUsageChart(true);
+ case 'instances-drive-files': return fetchInstanceDriveFilesChart(false);
+ case 'instances-drive-files-total': return fetchInstanceDriveFilesChart(true);
+ }
+ };
+ fetching.value = true;
+ data = await fetchData();
+ fetching.value = false;
+ render();
+ };
+
+ watch(() => [props.src, props.span], fetchAndRender);
+
+ onMounted(() => {
+ fetchAndRender();
+ });
+
+ return {
+ chartEl,
+ };
+ },
+});
+</script>
diff --git a/src/client/components/instance-stats.vue b/src/client/components/instance-stats.vue
index 5e7c71ea65..fd0b75609f 100644
--- a/src/client/components/instance-stats.vue
+++ b/src/client/components/instance-stats.vue
@@ -24,35 +24,26 @@
<option value="drive-total">{{ $ts._charts.storageUsageTotal }}</option>
</optgroup>
</MkSelect>
- <MkSelect v-model="chartSpan" style="margin: 0;">
+ <MkSelect v-model="chartSpan" style="margin: 0 0 0 10px;">
<option value="hour">{{ $ts.perHour }}</option>
<option value="day">{{ $ts.perDay }}</option>
</MkSelect>
</div>
- <canvas ref="chart"></canvas>
+ <MkChart :src="chartSrc" :span="chartSpan" :limit="chartLimit" :detailed="detailed"></MkChart>
</div>
</template>
<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
-import Chart from 'chart.js';
-import MkSelect from './form/select.vue';
-import number from '@client/filters/number';
-
-const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
-const negate = arr => arr.map(x => -x);
-const alpha = (hex, a) => {
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
- const r = parseInt(result[1], 16);
- const g = parseInt(result[2], 16);
- const b = parseInt(result[3], 16);
- return `rgba(${r}, ${g}, ${b}, ${a})`;
-};
+import { defineComponent, onMounted, ref, watch } from 'vue';
+import MkSelect from '@client/components/form/select.vue';
+import MkChart from '@client/components/chart.vue';
import * as os from '@client/os';
+import { defaultStore } from '@client/store';
export default defineComponent({
components: {
- MkSelect
+ MkSelect,
+ MkChart,
},
props: {
@@ -68,463 +59,15 @@ export default defineComponent({
},
},
- data() {
- return {
- notesLocalWoW: 0,
- notesLocalDoD: 0,
- notesRemoteWoW: 0,
- notesRemoteDoD: 0,
- usersLocalWoW: 0,
- usersLocalDoD: 0,
- usersRemoteWoW: 0,
- usersRemoteDoD: 0,
- now: null,
- chart: null,
- chartInstance: null,
- chartSrc: 'notes',
- chartSpan: 'hour',
- }
- },
-
- computed: {
- data(): any {
- if (this.chart == null) return null;
- switch (this.chartSrc) {
- case 'federation-instances': return this.federationInstancesChart(false);
- case 'federation-instances-total': return this.federationInstancesChart(true);
- case 'users': return this.usersChart(false);
- case 'users-total': return this.usersChart(true);
- case 'active-users': return this.activeUsersChart();
- case 'notes': return this.notesChart('combined');
- case 'local-notes': return this.notesChart('local');
- case 'remote-notes': return this.notesChart('remote');
- case 'notes-total': return this.notesTotalChart();
- case 'drive': return this.driveChart();
- case 'drive-total': return this.driveTotalChart();
- case 'drive-files': return this.driveFilesChart();
- case 'drive-files-total': return this.driveFilesTotalChart();
- }
- },
-
- stats(): any[] {
- const stats =
- this.chartSpan == 'day' ? this.chart.perDay :
- this.chartSpan == 'hour' ? this.chart.perHour :
- null;
-
- return stats;
- }
- },
-
- watch: {
- chartSrc() {
- this.renderChart();
- },
-
- chartSpan() {
- this.renderChart();
- }
- },
-
- async created() {
- this.now = new Date();
+ setup() {
+ const chartSpan = ref<'hour' | 'day'>('hour');
+ const chartSrc = ref('notes');
- this.fetchChart();
+ return {
+ chartSrc,
+ chartSpan,
+ };
},
-
- methods: {
- async fetchChart() {
- const [perHour, perDay] = await Promise.all([Promise.all([
- os.api('charts/federation', { limit: this.chartLimit, span: 'hour' }),
- os.api('charts/users', { limit: this.chartLimit, span: 'hour' }),
- os.api('charts/active-users', { limit: this.chartLimit, span: 'hour' }),
- os.api('charts/notes', { limit: this.chartLimit, span: 'hour' }),
- os.api('charts/drive', { limit: this.chartLimit, span: 'hour' }),
- ]), Promise.all([
- os.api('charts/federation', { limit: this.chartLimit, span: 'day' }),
- os.api('charts/users', { limit: this.chartLimit, span: 'day' }),
- os.api('charts/active-users', { limit: this.chartLimit, span: 'day' }),
- os.api('charts/notes', { limit: this.chartLimit, span: 'day' }),
- os.api('charts/drive', { limit: this.chartLimit, span: 'day' }),
- ])]);
-
- const chart = {
- perHour: {
- federation: perHour[0],
- users: perHour[1],
- activeUsers: perHour[2],
- notes: perHour[3],
- drive: perHour[4],
- },
- perDay: {
- federation: perDay[0],
- users: perDay[1],
- activeUsers: perDay[2],
- notes: perDay[3],
- drive: perDay[4],
- }
- };
-
- this.chart = chart;
-
- this.renderChart();
- },
-
- renderChart() {
- if (this.chartInstance) {
- this.chartInstance.destroy();
- }
-
- // TODO: var(--panel)の色が暗いか明るいかで判定する
- const gridColor = this.$store.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
-
- Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg');
- this.chartInstance = markRaw(new Chart(this.$refs.chart, {
- type: 'line',
- data: {
- labels: new Array(this.chartLimit).fill(0).map((_, i) => this.getDate(i).toLocaleString()).slice().reverse(),
- datasets: this.data.series.map(x => ({
- label: x.name,
- data: x.data.slice().reverse(),
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: x.color,
- borderDash: x.borderDash || [],
- backgroundColor: alpha(x.color, 0.1),
- fill: x.fill == null ? true : x.fill,
- hidden: !!x.hidden
- }))
- },
- options: {
- aspectRatio: 2.5,
- layout: {
- padding: {
- left: 16,
- right: 16,
- top: 16,
- bottom: 8
- }
- },
- legend: {
- position: 'bottom',
- labels: {
- boxWidth: 16,
- }
- },
- scales: {
- xAxes: [{
- type: 'time',
- time: {
- stepSize: 1,
- unit: this.chartSpan == 'day' ? 'month' : 'day',
- },
- gridLines: {
- display: this.detailed,
- color: gridColor,
- zeroLineColor: gridColor,
- },
- ticks: {
- display: this.detailed
- }
- }],
- yAxes: [{
- position: 'left',
- gridLines: {
- color: gridColor,
- zeroLineColor: gridColor,
- },
- ticks: {
- display: this.detailed
- }
- }]
- },
- tooltips: {
- intersect: false,
- mode: 'index',
- }
- }
- }));
- },
-
- getDate(ago: number) {
- const y = this.now.getFullYear();
- const m = this.now.getMonth();
- const d = this.now.getDate();
- const h = this.now.getHours();
-
- return this.chartSpan == 'day' ? new Date(y, m, d - ago) : new Date(y, m, d, h - ago);
- },
-
- format(arr) {
- const now = Date.now();
- return arr.map((v, i) => ({
- x: new Date(now - ((this.chartSpan == 'day' ? 86400000 :3600000 ) * i)),
- y: v
- }));
- },
-
- federationInstancesChart(total: boolean): any {
- return {
- series: [{
- name: 'Instances',
- color: '#008FFB',
- data: this.format(total
- ? this.stats.federation.instance.total
- : sum(this.stats.federation.instance.inc, negate(this.stats.federation.instance.dec))
- )
- }]
- };
- },
-
- notesChart(type: string): any {
- return {
- series: [{
- name: 'All',
- type: 'line',
- color: '#008FFB',
- borderDash: [5, 5],
- fill: false,
- data: this.format(type == 'combined'
- ? sum(this.stats.notes.local.inc, negate(this.stats.notes.local.dec), this.stats.notes.remote.inc, negate(this.stats.notes.remote.dec))
- : sum(this.stats.notes[type].inc, negate(this.stats.notes[type].dec))
- )
- }, {
- name: 'Renotes',
- type: 'area',
- color: '#00E396',
- data: this.format(type == 'combined'
- ? sum(this.stats.notes.local.diffs.renote, this.stats.notes.remote.diffs.renote)
- : this.stats.notes[type].diffs.renote
- )
- }, {
- name: 'Replies',
- type: 'area',
- color: '#FEB019',
- data: this.format(type == 'combined'
- ? sum(this.stats.notes.local.diffs.reply, this.stats.notes.remote.diffs.reply)
- : this.stats.notes[type].diffs.reply
- )
- }, {
- name: 'Normal',
- type: 'area',
- color: '#FF4560',
- data: this.format(type == 'combined'
- ? sum(this.stats.notes.local.diffs.normal, this.stats.notes.remote.diffs.normal)
- : this.stats.notes[type].diffs.normal
- )
- }]
- };
- },
-
- notesTotalChart(): any {
- return {
- series: [{
- name: 'Combined',
- type: 'line',
- color: '#008FFB',
- data: this.format(sum(this.stats.notes.local.total, this.stats.notes.remote.total))
- }, {
- name: 'Local',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.notes.local.total)
- }, {
- name: 'Remote',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.notes.remote.total)
- }]
- };
- },
-
- usersChart(total: boolean): any {
- return {
- series: [{
- name: 'Combined',
- type: 'line',
- color: '#008FFB',
- data: this.format(total
- ? sum(this.stats.users.local.total, this.stats.users.remote.total)
- : sum(this.stats.users.local.inc, negate(this.stats.users.local.dec), this.stats.users.remote.inc, negate(this.stats.users.remote.dec))
- )
- }, {
- name: 'Local',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(total
- ? this.stats.users.local.total
- : sum(this.stats.users.local.inc, negate(this.stats.users.local.dec))
- )
- }, {
- name: 'Remote',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(total
- ? this.stats.users.remote.total
- : sum(this.stats.users.remote.inc, negate(this.stats.users.remote.dec))
- )
- }]
- };
- },
-
- activeUsersChart(): any {
- return {
- series: [{
- name: 'Combined',
- type: 'line',
- color: '#008FFB',
- data: this.format(sum(this.stats.activeUsers.local.count, this.stats.activeUsers.remote.count))
- }, {
- name: 'Local',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.activeUsers.local.count)
- }, {
- name: 'Remote',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.activeUsers.remote.count)
- }]
- };
- },
-
- driveChart(): any {
- return {
- bytes: true,
- series: [{
- name: 'All',
- type: 'line',
- color: '#09d8e2',
- borderDash: [5, 5],
- fill: false,
- data: this.format(
- sum(
- this.stats.drive.local.incSize,
- negate(this.stats.drive.local.decSize),
- this.stats.drive.remote.incSize,
- negate(this.stats.drive.remote.decSize)
- )
- )
- }, {
- name: 'Local +',
- type: 'area',
- color: '#008FFB',
- data: this.format(this.stats.drive.local.incSize)
- }, {
- name: 'Local -',
- type: 'area',
- color: '#FF4560',
- data: this.format(negate(this.stats.drive.local.decSize))
- }, {
- name: 'Remote +',
- type: 'area',
- color: '#00E396',
- data: this.format(this.stats.drive.remote.incSize)
- }, {
- name: 'Remote -',
- type: 'area',
- color: '#FEB019',
- data: this.format(negate(this.stats.drive.remote.decSize))
- }]
- };
- },
-
- driveTotalChart(): any {
- return {
- bytes: true,
- series: [{
- name: 'Combined',
- type: 'line',
- color: '#008FFB',
- data: this.format(sum(this.stats.drive.local.totalSize, this.stats.drive.remote.totalSize))
- }, {
- name: 'Local',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.drive.local.totalSize)
- }, {
- name: 'Remote',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.drive.remote.totalSize)
- }]
- };
- },
-
- driveFilesChart(): any {
- return {
- series: [{
- name: 'All',
- type: 'line',
- color: '#09d8e2',
- borderDash: [5, 5],
- fill: false,
- data: this.format(
- sum(
- this.stats.drive.local.incCount,
- negate(this.stats.drive.local.decCount),
- this.stats.drive.remote.incCount,
- negate(this.stats.drive.remote.decCount)
- )
- )
- }, {
- name: 'Local +',
- type: 'area',
- color: '#008FFB',
- data: this.format(this.stats.drive.local.incCount)
- }, {
- name: 'Local -',
- type: 'area',
- color: '#FF4560',
- data: this.format(negate(this.stats.drive.local.decCount))
- }, {
- name: 'Remote +',
- type: 'area',
- color: '#00E396',
- data: this.format(this.stats.drive.remote.incCount)
- }, {
- name: 'Remote -',
- type: 'area',
- color: '#FEB019',
- data: this.format(negate(this.stats.drive.remote.decCount))
- }]
- };
- },
-
- driveFilesTotalChart(): any {
- return {
- series: [{
- name: 'Combined',
- type: 'line',
- color: '#008FFB',
- data: this.format(sum(this.stats.drive.local.totalCount, this.stats.drive.remote.totalCount))
- }, {
- name: 'Local',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.drive.local.totalCount)
- }, {
- name: 'Remote',
- type: 'area',
- color: '#008FFB',
- hidden: true,
- data: this.format(this.stats.drive.remote.totalCount)
- }]
- };
- },
-
- number
- }
});
</script>
diff --git a/src/client/components/number-diff.vue b/src/client/components/number-diff.vue
new file mode 100644
index 0000000000..ba7e6964de
--- /dev/null
+++ b/src/client/components/number-diff.vue
@@ -0,0 +1,47 @@
+<template>
+<span class="ceaaebcd" :class="{ isPlus, isMinus, isZero }">
+ <slot name="before"></slot>{{ isPlus ? '+' : isMinus ? '-' : '' }}{{ number(value) }}<slot name="after"></slot>
+</span>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent } from 'vue';
+import number from '@client/filters/number';
+
+export default defineComponent({
+ props: {
+ value: {
+ type: Number,
+ required: true
+ },
+ },
+
+ setup(props) {
+ const isPlus = computed(() => props.value > 0);
+ const isMinus = computed(() => props.value < 0);
+ const isZero = computed(() => props.value === 0);
+ return {
+ isPlus,
+ isMinus,
+ isZero,
+ number,
+ };
+ }
+});
+</script>
+
+<style lang="scss" scoped>
+.ceaaebcd {
+ &.isPlus {
+ color: var(--success);
+ }
+
+ &.isMinus {
+ color: var(--error);
+ }
+
+ &.isZero {
+ opacity: 0.5;
+ }
+}
+</style>
diff --git a/src/client/pages/instance-info.vue b/src/client/pages/instance-info.vue
index 4fbf104f0c..7a4cd5f016 100644
--- a/src/client/pages/instance-info.vue
+++ b/src/client/pages/instance-info.vue
@@ -65,17 +65,17 @@
<div class="_debobigegoPanel cmhjzshl">
<div class="selects">
<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
- <option value="requests">{{ $ts._instanceCharts.requests }}</option>
- <option value="users">{{ $ts._instanceCharts.users }}</option>
- <option value="users-total">{{ $ts._instanceCharts.usersTotal }}</option>
- <option value="notes">{{ $ts._instanceCharts.notes }}</option>
- <option value="notes-total">{{ $ts._instanceCharts.notesTotal }}</option>
- <option value="ff">{{ $ts._instanceCharts.ff }}</option>
- <option value="ff-total">{{ $ts._instanceCharts.ffTotal }}</option>
- <option value="drive-usage">{{ $ts._instanceCharts.cacheSize }}</option>
- <option value="drive-usage-total">{{ $ts._instanceCharts.cacheSizeTotal }}</option>
- <option value="drive-files">{{ $ts._instanceCharts.files }}</option>
- <option value="drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option>
+ <option value="instance-requests">{{ $ts._instanceCharts.requests }}</option>
+ <option value="instance-users">{{ $ts._instanceCharts.users }}</option>
+ <option value="instance-users-total">{{ $ts._instanceCharts.usersTotal }}</option>
+ <option value="instance-notes">{{ $ts._instanceCharts.notes }}</option>
+ <option value="instance-notes-total">{{ $ts._instanceCharts.notesTotal }}</option>
+ <option value="instance-ff">{{ $ts._instanceCharts.ff }}</option>
+ <option value="instance-ff-total">{{ $ts._instanceCharts.ffTotal }}</option>
+ <option value="instance-drive-usage">{{ $ts._instanceCharts.cacheSize }}</option>
+ <option value="instance-drive-usage-total">{{ $ts._instanceCharts.cacheSizeTotal }}</option>
+ <option value="instance-drive-files">{{ $ts._instanceCharts.files }}</option>
+ <option value="instance-drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option>
</MkSelect>
<MkSelect v-model="chartSpan" style="margin: 0;">
<option value="hour">{{ $ts.perHour }}</option>
@@ -83,7 +83,7 @@
</MkSelect>
</div>
<div class="chart">
- <canvas :ref="setChart"></canvas>
+ <MkChart :src="chartSrc" :span="chartSpan" :limit="90" :detailed="true"></MkChart>
</div>
</div>
</div>
@@ -135,7 +135,7 @@
<script lang="ts">
import { defineAsyncComponent, defineComponent } from 'vue';
-import Chart from 'chart.js';
+import MkChart from '@client/components/chart.vue';
import FormObjectView from '@client/components/debobigego/object-view.vue';
import FormTextarea from '@client/components/debobigego/textarea.vue';
import FormLink from '@client/components/debobigego/link.vue';
@@ -151,17 +151,6 @@ import bytes from '@client/filters/bytes';
import * as symbols from '@client/symbols';
import MkInstanceInfo from '@client/pages/instance/instance.vue';
-const chartLimit = 90;
-const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
-const negate = arr => arr.map(x => -x);
-const alpha = hex => {
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
- const r = parseInt(result[1], 16);
- const g = parseInt(result[2], 16);
- const b = parseInt(result[3], 16);
- return `rgba(${r}, ${g}, ${b}, 0.1)`;
-};
-
export default defineComponent({
components: {
FormBase,
@@ -173,6 +162,7 @@ export default defineComponent({
FormKeyValueView,
FormSuspense,
MkSelect,
+ MkChart,
},
props: {
@@ -199,53 +189,11 @@ export default defineComponent({
dnsPromiseFactory: () => os.api('federation/dns', {
host: this.host
}),
- now: null,
- canvas: null,
- chart: null,
- chartInstance: null,
- chartSrc: 'requests',
+ chartSrc: 'instance-requests',
chartSpan: 'hour',
}
},
- computed: {
- data(): any {
- if (this.chart == null) return null;
- switch (this.chartSrc) {
- case 'requests': return this.requestsChart();
- case 'users': return this.usersChart(false);
- case 'users-total': return this.usersChart(true);
- case 'notes': return this.notesChart(false);
- case 'notes-total': return this.notesChart(true);
- case 'ff': return this.ffChart(false);
- case 'ff-total': return this.ffChart(true);
- case 'drive-usage': return this.driveUsageChart(false);
- case 'drive-usage-total': return this.driveUsageChart(true);
- case 'drive-files': return this.driveFilesChart(false);
- case 'drive-files-total': return this.driveFilesChart(true);
- }
- },
-
- stats(): any[] {
- const stats =
- this.chartSpan == 'day' ? this.chart.perDay :
- this.chartSpan == 'hour' ? this.chart.perHour :
- null;
-
- return stats;
- },
- },
-
- watch: {
- chartSrc() {
- this.renderChart();
- },
-
- chartSpan() {
- this.renderChart();
- }
- },
-
mounted() {
this.fetch();
},
@@ -258,190 +206,6 @@ export default defineComponent({
this.instance = await os.api('federation/show-instance', {
host: this.host
});
-
- this.now = new Date();
-
- const [perHour, perDay] = await Promise.all([
- os.api('charts/instance', { host: this.instance.host, limit: chartLimit, span: 'hour' }),
- os.api('charts/instance', { host: this.instance.host, limit: chartLimit, span: 'day' }),
- ]);
-
- const chart = {
- perHour: perHour,
- perDay: perDay
- };
-
- this.chart = chart;
-
- this.renderChart();
- },
-
- setChart(el) {
- this.canvas = el;
- },
-
- renderChart() {
- if (this.chartInstance) {
- this.chartInstance.destroy();
- }
-
- Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg');
- this.chartInstance = new Chart(this.canvas, {
- type: 'line',
- data: {
- labels: new Array(chartLimit).fill(0).map((_, i) => this.getDate(i).toLocaleString()).slice().reverse(),
- datasets: this.data.series.map(x => ({
- label: x.name,
- data: x.data.slice().reverse(),
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: x.color,
- backgroundColor: alpha(x.color),
- }))
- },
- options: {
- aspectRatio: 2.5,
- layout: {
- padding: {
- left: 16,
- right: 16,
- top: 16,
- bottom: 16
- }
- },
- legend: {
- position: 'bottom',
- labels: {
- boxWidth: 16,
- }
- },
- scales: {
- xAxes: [{
- gridLines: {
- display: false
- },
- ticks: {
- display: false
- }
- }],
- yAxes: [{
- position: 'right',
- ticks: {
- display: false
- }
- }]
- },
- tooltips: {
- intersect: false,
- mode: 'index',
- }
- }
- });
- },
-
- getDate(ago: number) {
- const y = this.now.getFullYear();
- const m = this.now.getMonth();
- const d = this.now.getDate();
- const h = this.now.getHours();
-
- return this.chartSpan == 'day' ? new Date(y, m, d - ago) : new Date(y, m, d, h - ago);
- },
-
- format(arr) {
- return arr;
- },
-
- requestsChart(): any {
- return {
- series: [{
- name: 'In',
- color: '#008FFB',
- data: this.format(this.stats.requests.received)
- }, {
- name: 'Out (succ)',
- color: '#00E396',
- data: this.format(this.stats.requests.succeeded)
- }, {
- name: 'Out (fail)',
- color: '#FEB019',
- data: this.format(this.stats.requests.failed)
- }]
- };
- },
-
- usersChart(total: boolean): any {
- return {
- series: [{
- name: 'Users',
- color: '#008FFB',
- data: this.format(total
- ? this.stats.users.total
- : sum(this.stats.users.inc, negate(this.stats.users.dec))
- )
- }]
- };
- },
-
- notesChart(total: boolean): any {
- return {
- series: [{
- name: 'Notes',
- color: '#008FFB',
- data: this.format(total
- ? this.stats.notes.total
- : sum(this.stats.notes.inc, negate(this.stats.notes.dec))
- )
- }]
- };
- },
-
- ffChart(total: boolean): any {
- return {
- series: [{
- name: 'Following',
- color: '#008FFB',
- data: this.format(total
- ? this.stats.following.total
- : sum(this.stats.following.inc, negate(this.stats.following.dec))
- )
- }, {
- name: 'Followers',
- color: '#00E396',
- data: this.format(total
- ? this.stats.followers.total
- : sum(this.stats.followers.inc, negate(this.stats.followers.dec))
- )
- }]
- };
- },
-
- driveUsageChart(total: boolean): any {
- return {
- bytes: true,
- series: [{
- name: 'Drive usage',
- color: '#008FFB',
- data: this.format(total
- ? this.stats.drive.totalUsage
- : sum(this.stats.drive.incUsage, negate(this.stats.drive.decUsage))
- )
- }]
- };
- },
-
- driveFilesChart(total: boolean): any {
- return {
- series: [{
- name: 'Drive files',
- color: '#008FFB',
- data: this.format(total
- ? this.stats.drive.totalFiles
- : sum(this.stats.drive.incFiles, negate(this.stats.drive.decFiles))
- )
- }]
- };
},
info() {
diff --git a/src/client/pages/instance/instance.vue b/src/client/pages/instance/instance.vue
index 6117f090de..5572fbbf75 100644
--- a/src/client/pages/instance/instance.vue
+++ b/src/client/pages/instance/instance.vue
@@ -78,17 +78,17 @@
<span class="label">{{ $ts.charts }}</span>
<div class="selects">
<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
- <option value="requests">{{ $ts._instanceCharts.requests }}</option>
- <option value="users">{{ $ts._instanceCharts.users }}</option>
- <option value="users-total">{{ $ts._instanceCharts.usersTotal }}</option>
- <option value="notes">{{ $ts._instanceCharts.notes }}</option>
- <option value="notes-total">{{ $ts._instanceCharts.notesTotal }}</option>
- <option value="ff">{{ $ts._instanceCharts.ff }}</option>
- <option value="ff-total">{{ $ts._instanceCharts.ffTotal }}</option>
- <option value="drive-usage">{{ $ts._instanceCharts.cacheSize }}</option>
- <option value="drive-usage-total">{{ $ts._instanceCharts.cacheSizeTotal }}</option>
- <option value="drive-files">{{ $ts._instanceCharts.files }}</option>
- <option value="drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option>
+ <option value="instance-requests">{{ $ts._instanceCharts.requests }}</option>
+ <option value="instance-users">{{ $ts._instanceCharts.users }}</option>
+ <option value="instance-users-total">{{ $ts._instanceCharts.usersTotal }}</option>
+ <option value="instance-notes">{{ $ts._instanceCharts.notes }}</option>
+ <option value="instance-notes-total">{{ $ts._instanceCharts.notesTotal }}</option>
+ <option value="instance-ff">{{ $ts._instanceCharts.ff }}</option>
+ <option value="instance-ff-total">{{ $ts._instanceCharts.ffTotal }}</option>
+ <option value="instance-drive-usage">{{ $ts._instanceCharts.cacheSize }}</option>
+ <option value="instance-drive-usage-total">{{ $ts._instanceCharts.cacheSizeTotal }}</option>
+ <option value="instance-drive-files">{{ $ts._instanceCharts.files }}</option>
+ <option value="instance-drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option>
</MkSelect>
<MkSelect v-model="chartSpan" style="margin: 0;">
<option value="hour">{{ $ts.perHour }}</option>
@@ -97,7 +97,7 @@
</div>
</div>
<div class="chart">
- <canvas :ref="setChart"></canvas>
+ <MkChart :src="chartSrc" :span="chartSpan" :limit="90" :detailed="true"></MkChart>
</div>
</div>
<div class="operations section">
@@ -124,28 +124,17 @@
<script lang="ts">
import { defineComponent, markRaw } from 'vue';
-import Chart from 'chart.js';
import XModalWindow from '@client/components/ui/modal-window.vue';
import MkUsersDialog from '@client/components/users-dialog.vue';
import MkSelect from '@client/components/form/select.vue';
import MkButton from '@client/components/ui/button.vue';
import MkSwitch from '@client/components/form/switch.vue';
import MkInfo from '@client/components/ui/info.vue';
+import MkChart from '@client/components/chart.vue';
import bytes from '@client/filters/bytes';
import number from '@client/filters/number';
import * as os from '@client/os';
-const chartLimit = 90;
-const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
-const negate = arr => arr.map(x => -x);
-const alpha = hex => {
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
- const r = parseInt(result[1], 16);
- const g = parseInt(result[2], 16);
- const b = parseInt(result[3], 16);
- return `rgba(${r}, ${g}, ${b}, 0.1)`;
-};
-
export default defineComponent({
components: {
XModalWindow,
@@ -153,6 +142,7 @@ export default defineComponent({
MkButton,
MkSwitch,
MkInfo,
+ MkChart,
},
props: {
@@ -167,42 +157,12 @@ export default defineComponent({
data() {
return {
isSuspended: this.instance.isSuspended,
- now: null,
- canvas: null,
- chart: null,
- chartInstance: null,
chartSrc: 'requests',
chartSpan: 'hour',
};
},
computed: {
- data(): any {
- if (this.chart == null) return null;
- switch (this.chartSrc) {
- case 'requests': return this.requestsChart();
- case 'users': return this.usersChart(false);
- case 'users-total': return this.usersChart(true);
- case 'notes': return this.notesChart(false);
- case 'notes-total': return this.notesChart(true);
- case 'ff': return this.ffChart(false);
- case 'ff-total': return this.ffChart(true);
- case 'drive-usage': return this.driveUsageChart(false);
- case 'drive-usage-total': return this.driveUsageChart(true);
- case 'drive-files': return this.driveFilesChart(false);
- case 'drive-files-total': return this.driveFilesChart(true);
- }
- },
-
- stats(): any[] {
- const stats =
- this.chartSpan == 'day' ? this.chart.perDay :
- this.chartSpan == 'hour' ? this.chart.perHour :
- null;
-
- return stats;
- },
-
meta() {
return this.$instance;
},
@@ -219,49 +179,15 @@ export default defineComponent({
isSuspended: this.isSuspended
});
},
-
- chartSrc() {
- this.renderChart();
- },
-
- chartSpan() {
- this.renderChart();
- }
- },
-
- async created() {
- this.now = new Date();
-
- const [perHour, perDay] = await Promise.all([
- os.api('charts/instance', { host: this.instance.host, limit: chartLimit, span: 'hour' }),
- os.api('charts/instance', { host: this.instance.host, limit: chartLimit, span: 'day' }),
- ]);
-
- const chart = {
- perHour: perHour,
- perDay: perDay
- };
-
- this.chart = chart;
-
- this.renderChart();
},
methods: {
- setChart(el) {
- this.canvas = el;
- },
-
changeBlock(e) {
os.api('admin/update-meta', {
blockedHosts: this.isBlocked ? this.meta.blockedHosts.concat([this.instance.host]) : this.meta.blockedHosts.filter(x => x !== this.instance.host)
});
},
- setSrc(src) {
- this.chartSrc = src;
- },
-
removeAllFollowing() {
os.apiWithDialog('admin/federation/remove-all-following', {
host: this.instance.host
@@ -274,170 +200,6 @@ export default defineComponent({
});
},
- renderChart() {
- if (this.chartInstance) {
- this.chartInstance.destroy();
- }
-
- Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg');
- this.chartInstance = markRaw(new Chart(this.canvas, {
- type: 'line',
- data: {
- labels: new Array(chartLimit).fill(0).map((_, i) => this.getDate(i).toLocaleString()).slice().reverse(),
- datasets: this.data.series.map(x => ({
- label: x.name,
- data: x.data.slice().reverse(),
- pointRadius: 0,
- lineTension: 0,
- borderWidth: 2,
- borderColor: x.color,
- backgroundColor: alpha(x.color),
- }))
- },
- options: {
- aspectRatio: 2.5,
- layout: {
- padding: {
- left: 16,
- right: 16,
- top: 16,
- bottom: 0
- }
- },
- legend: {
- position: 'bottom',
- labels: {
- boxWidth: 16,
- }
- },
- scales: {
- xAxes: [{
- gridLines: {
- display: false
- },
- ticks: {
- display: false
- }
- }],
- yAxes: [{
- position: 'right',
- ticks: {
- display: false
- }
- }]
- },
- tooltips: {
- intersect: false,
- mode: 'index',
- }
- }
- }));
- },
-
- getDate(ago: number) {
- const y = this.now.getFullYear();
- const m = this.now.getMonth();
- const d = this.now.getDate();
- const h = this.now.getHours();
-
- return this.chartSpan == 'day' ? new Date(y, m, d - ago) : new Date(y, m, d, h - ago);
- },
-
- format(arr) {
- return arr;
- },
-
- requestsChart(): any {
- return {
- series: [{
- name: 'In',
- color: '#008FFB',
- data: this.format(this.stats.requests.received)
- }, {
- name: 'Out (succ)',
- color: '#00E396',
- data: this.format(this.stats.requests.succeeded)
- }, {
- name: 'Out (fail)',
- color: '#FEB019',
- data: this.format(this.stats.requests.failed)
- }]
- };
- },
-
- usersChart(total: boolean): any {
- return {
- series: [{
- name: 'Users',
- color: '#008FFB',
- data: this.format(total
- ? this.stats.users.total
- : sum(this.stats.users.inc, negate(this.stats.users.dec))
- )
- }]
- };
- },
-
- notesChart(total: boolean): any {
- return {
- series: [{
- name: 'Notes',
- color: '#008FFB',
- data: this.format(total
- ? this.stats.notes.total
- : sum(this.stats.notes.inc, negate(this.stats.notes.dec))
- )
- }]
- };
- },
-
- ffChart(total: boolean): any {
- return {
- series: [{
- name: 'Following',
- color: '#008FFB',
- data: this.format(total
- ? this.stats.following.total
- : sum(this.stats.following.inc, negate(this.stats.following.dec))
- )
- }, {
- name: 'Followers',
- color: '#00E396',
- data: this.format(total
- ? this.stats.followers.total
- : sum(this.stats.followers.inc, negate(this.stats.followers.dec))
- )
- }]
- };
- },
-
- driveUsageChart(total: boolean): any {
- return {
- bytes: true,
- series: [{
- name: 'Drive usage',
- color: '#008FFB',
- data: this.format(total
- ? this.stats.drive.totalUsage
- : sum(this.stats.drive.incUsage, negate(this.stats.drive.decUsage))
- )
- }]
- };
- },
-
- driveFilesChart(total: boolean): any {
- return {
- series: [{
- name: 'Drive files',
- color: '#008FFB',
- data: this.format(total
- ? this.stats.drive.totalFiles
- : sum(this.stats.drive.incFiles, negate(this.stats.drive.decFiles))
- )
- }]
- };
- },
-
showFollowing() {
os.modal(MkUsersDialog, {
title: this.$ts.instanceFollowing,
diff --git a/src/client/pages/instance/metrics.vue b/src/client/pages/instance/metrics.vue
index 1606063aee..da36f6c688 100644
--- a/src/client/pages/instance/metrics.vue
+++ b/src/client/pages/instance/metrics.vue
@@ -52,7 +52,21 @@
<script lang="ts">
import { defineComponent, markRaw } from 'vue';
-import Chart from 'chart.js';
+import {
+ Chart,
+ ArcElement,
+ LineElement,
+ BarElement,
+ PointElement,
+ BarController,
+ LineController,
+ CategoryScale,
+ LinearScale,
+ Legend,
+ Title,
+ Tooltip,
+ SubTitle
+} from 'chart.js';
import MkButton from '@client/components/ui/button.vue';
import MkSelect from '@client/components/form/select.vue';
import MkInput from '@client/components/form/input.vue';
@@ -64,6 +78,21 @@ import bytes from '@client/filters/bytes';
import number from '@client/filters/number';
import MkInstanceInfo from './instance.vue';
+Chart.register(
+ ArcElement,
+ LineElement,
+ BarElement,
+ PointElement,
+ BarController,
+ LineController,
+ CategoryScale,
+ LinearScale,
+ Legend,
+ Title,
+ Tooltip,
+ SubTitle
+);
+
const alpha = (hex, a) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!;
const r = parseInt(result[1], 16);
@@ -116,7 +145,7 @@ export default defineComponent({
mounted() {
this.fetchJobs();
- Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg');
+ Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg');
os.api('admin/server-info', {}).then(res => {
this.serverInfo = res;
@@ -157,7 +186,7 @@ export default defineComponent({
datasets: [{
label: 'CPU',
pointRadius: 0,
- lineTension: 0,
+ tension: 0,
borderWidth: 2,
borderColor: '#86b300',
backgroundColor: alpha('#86b300', 0.1),
@@ -165,7 +194,7 @@ export default defineComponent({
}, {
label: 'MEM (active)',
pointRadius: 0,
- lineTension: 0,
+ tension: 0,
borderWidth: 2,
borderColor: '#935dbf',
backgroundColor: alpha('#935dbf', 0.02),
@@ -173,7 +202,7 @@ export default defineComponent({
}, {
label: 'MEM (used)',
pointRadius: 0,
- lineTension: 0,
+ tension: 0,
borderWidth: 2,
borderColor: '#935dbf',
borderDash: [5, 5],
@@ -198,7 +227,7 @@ export default defineComponent({
}
},
scales: {
- xAxes: [{
+ x: {
gridLines: {
display: false,
color: this.gridColor,
@@ -207,8 +236,8 @@ export default defineComponent({
ticks: {
display: false,
}
- }],
- yAxes: [{
+ },
+ y: {
position: 'right',
gridLines: {
display: true,
@@ -219,7 +248,7 @@ export default defineComponent({
display: false,
max: 100
}
- }]
+ }
},
tooltips: {
intersect: false,
@@ -238,7 +267,7 @@ export default defineComponent({
datasets: [{
label: 'In',
pointRadius: 0,
- lineTension: 0,
+ tension: 0,
borderWidth: 2,
borderColor: '#94a029',
backgroundColor: alpha('#94a029', 0.1),
@@ -246,7 +275,7 @@ export default defineComponent({
}, {
label: 'Out',
pointRadius: 0,
- lineTension: 0,
+ tension: 0,
borderWidth: 2,
borderColor: '#ff9156',
backgroundColor: alpha('#ff9156', 0.1),
@@ -270,7 +299,7 @@ export default defineComponent({
}
},
scales: {
- xAxes: [{
+ x: {
gridLines: {
display: false,
color: this.gridColor,
@@ -279,8 +308,8 @@ export default defineComponent({
ticks: {
display: false
}
- }],
- yAxes: [{
+ },
+ y: {
position: 'right',
gridLines: {
display: true,
@@ -290,7 +319,7 @@ export default defineComponent({
ticks: {
display: false,
}
- }]
+ }
},
tooltips: {
intersect: false,
@@ -309,7 +338,7 @@ export default defineComponent({
datasets: [{
label: 'Read',
pointRadius: 0,
- lineTension: 0,
+ tension: 0,
borderWidth: 2,
borderColor: '#94a029',
backgroundColor: alpha('#94a029', 0.1),
@@ -317,7 +346,7 @@ export default defineComponent({
}, {
label: 'Write',
pointRadius: 0,
- lineTension: 0,
+ tension: 0,
borderWidth: 2,
borderColor: '#ff9156',
backgroundColor: alpha('#ff9156', 0.1),
@@ -341,7 +370,7 @@ export default defineComponent({
}
},
scales: {
- xAxes: [{
+ x: {
gridLines: {
display: false,
color: this.gridColor,
@@ -350,8 +379,8 @@ export default defineComponent({
ticks: {
display: false
}
- }],
- yAxes: [{
+ },
+ y: {
position: 'right',
gridLines: {
display: true,
@@ -361,7 +390,7 @@ export default defineComponent({
ticks: {
display: false,
}
- }]
+ }
},
tooltips: {
intersect: false,
@@ -371,18 +400,6 @@ export default defineComponent({
}));
},
- async showInstanceInfo(q) {
- let instance = q;
- if (typeof q === 'string') {
- instance = await os.api('federation/show-instance', {
- host: q
- });
- }
- os.popup(MkInstanceInfo, {
- instance: instance
- }, {}, 'closed');
- },
-
fetchJobs() {
os.api('admin/queue/deliver-delayed', {}).then(jobs => {
this.jobs = jobs;
diff --git a/src/client/pages/instance/overview.vue b/src/client/pages/instance/overview.vue
index c6db9d0c04..4a01eeb751 100644
--- a/src/client/pages/instance/overview.vue
+++ b/src/client/pages/instance/overview.vue
@@ -1,61 +1,67 @@
<template>
-<FormBase>
- <FormSuspense :p="init">
- <FormSuspense :p="fetchStats" v-slot="{ result: stats }">
- <FormGroup>
- <FormKeyValueView>
- <template #key>Users</template>
- <template #value>{{ number(stats.originalUsersCount) }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>Notes</template>
- <template #value>{{ number(stats.originalNotesCount) }}</template>
- </FormKeyValueView>
- </FormGroup>
- </FormSuspense>
-
- <div class="_debobigegoItem">
- <div class="_debobigegoPanel">
- <MkInstanceStats :chart-limit="300" :detailed="true"/>
+<div>
+ <MkHeader :info="header"/>
+
+ <div class="edbbcaef">
+ <div class="numbers" v-if="stats">
+ <div class="number _panel">
+ <div class="label">Users</div>
+ <div class="value _monospace">
+ {{ number(stats.originalUsersCount) }}
+ <MkNumberDiff v-if="usersComparedToThePrevDay" class="diff" :value="usersComparedToThePrevDay" v-tooltip="$ts.dayOverDayChanges"><template #before>(</template><template #after>)</template></MkNumberDiff>
+ </div>
+ </div>
+ <div class="number _panel">
+ <div class="label">Notes</div>
+ <div class="value _monospace">
+ {{ number(stats.originalNotesCount) }}
+ <MkNumberDiff v-if="notesComparedToThePrevDay" class="diff" :value="notesComparedToThePrevDay" v-tooltip="$ts.dayOverDayChanges"><template #before>(</template><template #after>)</template></MkNumberDiff>
+ </div>
</div>
</div>
- <XMetrics/>
+ <MkContainer :foldable="true" class="charts">
+ <template #header><i class="fas fa-chart-bar"></i>{{ $ts.charts }}</template>
+ <div style="padding-top: 12px;">
+ <MkInstanceStats :chart-limit="500" :detailed="true"/>
+ </div>
+ </MkContainer>
+
+ <!--<XMetrics/>-->
- <FormSuspense :p="fetchServerInfo" v-slot="{ result: serverInfo }">
- <FormGroup>
- <FormKeyValueView>
- <template #key>Node.js</template>
- <template #value>{{ serverInfo.node }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>PostgreSQL</template>
- <template #value>{{ serverInfo.psql }}</template>
- </FormKeyValueView>
- <FormKeyValueView>
- <template #key>Redis</template>
- <template #value>{{ serverInfo.redis }}</template>
- </FormKeyValueView>
- </FormGroup>
- </FormSuspense>
- </FormSuspense>
-</FormBase>
+ <div class="numbers">
+ <div class="number _panel">
+ <div class="label">Misskey</div>
+ <div class="value _monospace">{{ version }}</div>
+ </div>
+ <div class="number _panel" v-if="serverInfo">
+ <div class="label">Node.js</div>
+ <div class="value _monospace">{{ serverInfo.node }}</div>
+ </div>
+ <div class="number _panel" v-if="serverInfo">
+ <div class="label">PostgreSQL</div>
+ <div class="value _monospace">{{ serverInfo.psql }}</div>
+ </div>
+ <div class="number _panel" v-if="serverInfo">
+ <div class="label">Redis</div>
+ <div class="value _monospace">{{ serverInfo.redis }}</div>
+ </div>
+ <div class="number _panel">
+ <div class="label">Vue</div>
+ <div class="value _monospace">{{ vueVersion }}</div>
+ </div>
+ </div>
+ </div>
+</div>
</template>
<script lang="ts">
-import { computed, defineComponent, markRaw } from 'vue';
+import { computed, defineComponent, version as vueVersion } from 'vue';
import FormKeyValueView from '@client/components/debobigego/key-value-view.vue';
-import FormInput from '@client/components/debobigego/input.vue';
-import FormButton from '@client/components/debobigego/button.vue';
-import FormBase from '@client/components/debobigego/base.vue';
-import FormGroup from '@client/components/debobigego/group.vue';
-import FormTextarea from '@client/components/debobigego/textarea.vue';
-import FormInfo from '@client/components/debobigego/info.vue';
-import FormSuspense from '@client/components/debobigego/suspense.vue';
import MkInstanceStats from '@client/components/instance-stats.vue';
import MkButton from '@client/components/ui/button.vue';
import MkSelect from '@client/components/form/select.vue';
-import MkInput from '@client/components/form/input.vue';
+import MkNumberDiff from '@client/components/number-diff.vue';
import MkContainer from '@client/components/ui/container.vue';
import MkFolder from '@client/components/ui/folder.vue';
import { version, url } from '@client/config';
@@ -68,12 +74,10 @@ import * as symbols from '@client/symbols';
export default defineComponent({
components: {
- FormBase,
- FormSuspense,
- FormGroup,
- FormInfo,
+ MkNumberDiff,
FormKeyValueView,
MkInstanceStats,
+ MkContainer,
XMetrics,
},
@@ -82,17 +86,22 @@ export default defineComponent({
data() {
return {
[symbols.PAGE_INFO]: {
- title: this.$ts.overview,
+ title: this.$ts.dashboard,
icon: 'fas fa-tachometer-alt',
bg: 'var(--bg)',
},
- page: 'index',
+ header: {
+ title: this.$ts.dashboard,
+ icon: 'fas fa-tachometer-alt',
+ },
version,
+ vueVersion,
url,
stats: null,
meta: null,
- fetchStats: () => os.api('stats', {}),
- fetchServerInfo: () => os.api('admin/server-info', {}),
+ serverInfo: null,
+ usersComparedToThePrevDay: null,
+ notesComparedToThePrevDay: null,
fetchJobs: () => os.api('admin/queue/deliver-delayed', {}),
fetchModLogs: () => os.api('admin/show-moderation-logs', {}),
}
@@ -100,13 +109,29 @@ export default defineComponent({
async mounted() {
this.$emit('info', this[symbols.PAGE_INFO]);
+
+ os.api('meta', { detail: true }).then(meta => {
+ this.meta = meta;
+ });
+
+ os.api('stats', {}).then(stats => {
+ this.stats = stats;
+
+ os.api('charts/users', { limit: 2, span: 'day' }).then(chart => {
+ this.usersComparedToThePrevDay = this.stats.originalUsersCount - chart.local.total[1];
+ });
+
+ os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => {
+ this.notesComparedToThePrevDay = this.stats.originalNotesCount - chart.local.total[1];
+ });
+ });
+
+ os.api('admin/server-info', {}).then(serverInfo => {
+ this.serverInfo = serverInfo;
+ });
},
methods: {
- async init() {
- this.meta = await os.api('meta', { detail: true });
- },
-
async showInstanceInfo(q) {
let instance = q;
if (typeof q === 'string') {
@@ -125,3 +150,36 @@ export default defineComponent({
}
});
</script>
+
+<style lang="scss" scoped>
+.edbbcaef {
+ > .numbers {
+ display: grid;
+ grid-gap: 8px;
+ grid-template-columns: repeat(auto-fill,minmax(130px,1fr));
+ margin: 16px;
+
+ > .number {
+ padding: 12px 16px;
+
+ > .label {
+ opacity: 0.7;
+ font-size: 0.8em;
+ }
+
+ > .value {
+ font-weight: bold;
+ font-size: 1.2em;
+
+ > .diff {
+ font-size: 0.8em;
+ }
+ }
+ }
+ }
+
+ > .charts {
+ margin: var(--margin);
+ }
+}
+</style>
diff --git a/src/client/pages/instance/queue.chart.vue b/src/client/pages/instance/queue.chart.vue
index 887fe9a574..4f8fd762bb 100644
--- a/src/client/pages/instance/queue.chart.vue
+++ b/src/client/pages/instance/queue.chart.vue
@@ -67,7 +67,7 @@ export default defineComponent({
// TODO: var(--panel)の色が暗いか明るいかで判定する
const gridColor = this.$store.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
- Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg');
+ Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg');
this.chart = markRaw(new Chart(this.$refs.chart, {
type: 'line',
diff --git a/src/client/scripts/hpml/lib.ts b/src/client/scripts/hpml/lib.ts
index 150a04732f..200faf820b 100644
--- a/src/client/scripts/hpml/lib.ts
+++ b/src/client/scripts/hpml/lib.ts
@@ -1,11 +1,11 @@
import * as tinycolor from 'tinycolor2';
-import Chart from 'chart.js';
import { Hpml } from './evaluator';
import { values, utils } from '@syuilo/aiscript';
import { Fn, HpmlScope } from '.';
import { Expr } from './expr';
import * as seedrandom from 'seedrandom';
+/*
// https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs
Chart.pluginService.register({
beforeDraw: (chart, easing) => {
@@ -18,6 +18,7 @@ Chart.pluginService.register({
}
}
});
+*/
export function initAiLib(hpml: Hpml) {
return {
@@ -49,11 +50,12 @@ export function initAiLib(hpml: Hpml) {
]));
}),
'MkPages:chart': values.FN_NATIVE(([id, opts]) => {
+ /* TODO
utils.assertString(id);
utils.assertObject(opts);
const canvas = hpml.canvases[id.value];
const color = getComputedStyle(document.documentElement).getPropertyValue('--accent');
- Chart.defaults.global.defaultFontColor = '#555';
+ Chart.defaults.color = '#555';
const chart = new Chart(canvas, {
type: opts.value.get('type').value,
data: {
@@ -122,6 +124,7 @@ export function initAiLib(hpml: Hpml) {
})
}
});
+ */
})
};
}
diff --git a/src/queue/index.ts b/src/queue/index.ts
index 43c062bae7..37eb809604 100644
--- a/src/queue/index.ts
+++ b/src/queue/index.ts
@@ -10,7 +10,7 @@ import procesObjectStorage from './processors/object-storage/index';
import { queueLogger } from './logger';
import { DriveFile } from '@/models/entities/drive-file';
import { getJobInfo } from './get-job-info';
-import { dbQueue, deliverQueue, inboxQueue, objectStorageQueue } from './queues';
+import { systemQueue, dbQueue, deliverQueue, inboxQueue, objectStorageQueue } from './queues';
import { ThinUser } from './types';
import { IActivity } from '@/remote/activitypub/type';
@@ -22,11 +22,20 @@ function renderError(e: Error): any {
};
}
+const systemLogger = queueLogger.createSubLogger('system');
const deliverLogger = queueLogger.createSubLogger('deliver');
const inboxLogger = queueLogger.createSubLogger('inbox');
const dbLogger = queueLogger.createSubLogger('db');
const objectStorageLogger = queueLogger.createSubLogger('objectStorage');
+systemQueue
+ .on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`))
+ .on('active', (job) => systemLogger.debug(`active id=${job.id}`))
+ .on('completed', (job, result) => systemLogger.debug(`completed(${result}) id=${job.id}`))
+ .on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
+ .on('error', (job: any, err: Error) => systemLogger.error(`error ${err}`, { job, e: renderError(err) }))
+ .on('stalled', (job) => systemLogger.warn(`stalled id=${job.id}`));
+
deliverQueue
.on('waiting', (jobId) => deliverLogger.debug(`waiting id=${jobId}`))
.on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
@@ -220,12 +229,17 @@ export function createCleanRemoteFilesJob() {
}
export default function() {
- if (!envOption.onlyServer) {
- deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver);
- inboxQueue.process(config.inboxJobConcurrency || 16, processInbox);
- processDb(dbQueue);
- procesObjectStorage(objectStorageQueue);
- }
+ if (envOption.onlyServer) return;
+
+ deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver);
+ inboxQueue.process(config.inboxJobConcurrency || 16, processInbox);
+ processDb(dbQueue);
+ procesObjectStorage(objectStorageQueue);
+
+ systemQueue.add('resyncCharts', {
+ }, {
+ repeat: { cron: '0 0 * * *' }
+ });
}
export function destroy() {
diff --git a/src/queue/processors/system/index.ts b/src/queue/processors/system/index.ts
new file mode 100644
index 0000000000..52b7868105
--- /dev/null
+++ b/src/queue/processors/system/index.ts
@@ -0,0 +1,12 @@
+import * as Bull from 'bull';
+import { resyncCharts } from './resync-charts';
+
+const jobs = {
+ resyncCharts,
+} as Record<string, Bull.ProcessCallbackFunction<{}> | Bull.ProcessPromiseFunction<{}>>;
+
+export default function(dbQueue: Bull.Queue<{}>) {
+ for (const [k, v] of Object.entries(jobs)) {
+ dbQueue.process(k, v);
+ }
+}
diff --git a/src/queue/processors/system/resync-charts.ts b/src/queue/processors/system/resync-charts.ts
new file mode 100644
index 0000000000..b36b024cfb
--- /dev/null
+++ b/src/queue/processors/system/resync-charts.ts
@@ -0,0 +1,21 @@
+import * as Bull from 'bull';
+
+import { queueLogger } from '../../logger';
+import { driveChart, notesChart, usersChart } from '@/services/chart/index';
+
+const logger = queueLogger.createSubLogger('resync-charts');
+
+export default async function resyncCharts(job: Bull.Job<{}>, done: any): Promise<void> {
+ logger.info(`Resync charts...`);
+
+ // TODO: ユーザーごとのチャートも更新する
+ // TODO: インスタンスごとのチャートも更新する
+ await Promise.all([
+ driveChart.resync(),
+ notesChart.resync(),
+ usersChart.resync(),
+ ]);
+
+ logger.succ(`All charts successfully resynced.`);
+ done();
+}
diff --git a/src/queue/queues.ts b/src/queue/queues.ts
index d8c09ef86e..a66a7ca451 100644
--- a/src/queue/queues.ts
+++ b/src/queue/queues.ts
@@ -2,6 +2,7 @@ import config from '@/config/index';
import { initialize as initializeQueue } from './initialize';
import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData } from './types';
+export const systemQueue = initializeQueue<{}>('system');
export const deliverQueue = initializeQueue<DeliverJobData>('deliver', config.deliverJobPerSec || 128);
export const inboxQueue = initializeQueue<InboxJobData>('inbox', config.inboxJobPerSec || 16);
export const dbQueue = initializeQueue<DbJobData>('db');
diff --git a/src/server/api/endpoints/admin/resync-chart.ts b/src/server/api/endpoints/admin/resync-chart.ts
index b0e687333f..e01dfce1b6 100644
--- a/src/server/api/endpoints/admin/resync-chart.ts
+++ b/src/server/api/endpoints/admin/resync-chart.ts
@@ -1,5 +1,5 @@
import define from '../../define';
-import { driveChart, notesChart, usersChart, instanceChart } from '@/services/chart/index';
+import { driveChart, notesChart, usersChart } from '@/services/chart/index';
import { insertModerationLog } from '@/services/insert-moderation-log';
export const meta = {
@@ -15,7 +15,7 @@ export default define(meta, async (ps, me) => {
driveChart.resync();
notesChart.resync();
usersChart.resync();
- instanceChart.resync();
// TODO: ユーザーごとのチャートもキューに入れて更新する
+ // TODO: インスタンスごとのチャートもキューに入れて更新する
});