From b21287262e85e3e09e19217ba4168b83e4fdf4a7 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 24 Aug 2018 07:23:04 +0900 Subject: チャート取得APIを誰でも利用できるようにするなど MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/api/endpoints/chart.ts | 134 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 src/server/api/endpoints/chart.ts (limited to 'src/server/api/endpoints/chart.ts') diff --git a/src/server/api/endpoints/chart.ts b/src/server/api/endpoints/chart.ts new file mode 100644 index 0000000000..a58f0b163e --- /dev/null +++ b/src/server/api/endpoints/chart.ts @@ -0,0 +1,134 @@ +import Stats, { IStats } from '../../../models/stats'; + +type Omit = Pick>; + +export const meta = { +}; + +export default (params: any) => new Promise(async (res, rej) => { + const daysRange = 90; + const hoursRange = 24; + + const now = new Date(); + const y = now.getFullYear(); + const m = now.getMonth(); + const d = now.getDate(); + const h = now.getHours(); + + const [statsPerDay, statsPerHour] = await Promise.all([ + Stats.find({ + span: 'day', + date: { + $gt: new Date(y, m, d - daysRange) + } + }, { + sort: { + date: -1 + }, + fields: { + _id: 0 + } + }), + Stats.find({ + span: 'hour', + date: { + $gt: new Date(y, m, d, h - hoursRange) + } + }, { + sort: { + date: -1 + }, + fields: { + _id: 0 + } + }), + ]); + + const format = (src: IStats[], span: 'day' | 'hour') => { + const chart: Array, 'span'>> = []; + + const range = + span == 'day' ? daysRange : + span == 'hour' ? hoursRange : + null; + + for (let i = (range - 1); i >= 0; i--) { + const current = + span == 'day' ? new Date(y, m, d - i) : + span == 'hour' ? new Date(y, m, d, h - i) : + null; + + const stat = src.find(s => s.date.getTime() == current.getTime()); + + if (stat) { + chart.unshift(stat); + } else { // 隙間埋め + const mostRecent = src.find(s => s.date.getTime() < current.getTime()); + if (mostRecent) { + chart.unshift(Object.assign({}, mostRecent, { + date: current + })); + } else { + chart.unshift({ + date: current, + users: { + local: { + total: 0, + diff: 0 + }, + remote: { + total: 0, + diff: 0 + } + }, + notes: { + local: { + total: 0, + diff: 0, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + }, + remote: { + total: 0, + diff: 0, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + } + }, + drive: { + local: { + totalCount: 0, + totalSize: 0, + diffCount: 0, + diffSize: 0 + }, + remote: { + totalCount: 0, + totalSize: 0, + diffCount: 0, + diffSize: 0 + } + } + }); + } + } + } + + chart.forEach(x => { + delete (x as any).span; + }); + + return chart; + }; + + res({ + perDay: format(statsPerDay, 'day'), + perHour: format(statsPerHour, 'hour') + }); +}); -- cgit v1.2.3-freya From 13624ea7c25870162d1c60e82c3a0e064e8478da Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 24 Aug 2018 14:55:58 +0900 Subject: バグ修正など MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ja-JP.yml | 27 +- .../app/desktop/views/pages/admin/admin.chart.vue | 280 +++++++++++++++------ src/server/api/endpoints/chart.ts | 54 +++- 3 files changed, 268 insertions(+), 93 deletions(-) (limited to 'src/server/api/endpoints/chart.ts') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 3755f55f36..c44254439c 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -946,22 +946,17 @@ desktop/views/pages/admin/admin.chart.vue: notes: "投稿" users: "ユーザー" drive: "ドライブ" - local-notes: "ローカルの投稿の増減" - remote-notes: "リモートの投稿の増減" - local-notes-total: "ローカルの投稿の累計" - remote-notes-total: "リモートの投稿の累計" - local-users: "ローカルのユーザーの増減" - remote-users: "リモートのユーザーの増減" - local-users-total: "ローカルのユーザーの累計" - remote-users-total: "リモートのユーザーの累計" - local-drive: "ローカルのドライブ使用量の増減" - remote-drive: "リモートのドライブ使用量の増減" - local-drive-total: "ローカルのドライブ使用量の累計" - remote-drive-total: "リモートのドライブ使用量の累計" - local-drive-files: "ローカルのドライブのファイル数の増減" - remote-drive-files: "リモートのドライブのファイル数の増減" - local-drive-files-total: "ローカルのドライブのファイル数の累計" - remote-drive-files-total: "リモートのドライブのファイル数の累計" + charts: + notes: "投稿の増減 (統合)" + local-notes: "投稿の増減 (ローカル)" + remote-notes: "投稿の増減 (リモート)" + notes-total: "投稿の累計" + users: "ユーザーの増減" + users-total: "ユーザーの累計" + drive: "ドライブ使用量の増減" + drive-total: "ドライブ使用量の累計" + drive-files: "ドライブのファイル数の増減" + drive-files-total: "ドライブのファイル数の累計" desktop/views/pages/deck/deck.tl-column.vue: is-media-only: "メディア投稿のみ" diff --git a/src/client/app/desktop/views/pages/admin/admin.chart.vue b/src/client/app/desktop/views/pages/admin/admin.chart.vue index 4301f9978b..89e61d4c74 100644 --- a/src/client/app/desktop/views/pages/admin/admin.chart.vue +++ b/src/client/app/desktop/views/pages/admin/admin.chart.vue @@ -4,30 +4,24 @@ %i18n:@title%:
- %i18n:@per-day% | %i18n:@per-hour% + %i18n:@per-day% | %i18n:@per-hour%
@@ -47,7 +41,7 @@ export default Vue.extend({ data() { return { chart: null, - chartType: 'local-notes', + chartType: 'notes', span: 'hour' }; }, @@ -55,22 +49,16 @@ export default Vue.extend({ data(): any { if (this.chart == null) return null; switch (this.chartType) { - case 'local-users': return this.usersChart(true, false); - case 'remote-users': return this.usersChart(false, false); - case 'local-users-total': return this.usersChart(true, true); - case 'remote-users-total': return this.usersChart(false, true); - case 'local-notes': return this.notesChart(true); - case 'remote-notes': return this.notesChart(false); - case 'local-notes-total': return this.notesTotalChart(true); - case 'remote-notes-total': return this.notesTotalChart(false); - case 'local-drive': return this.driveChart(true, false); - case 'remote-drive': return this.driveChart(false, false); - case 'local-drive-total': return this.driveChart(true, true); - case 'remote-drive-total': return this.driveChart(false, true); - case 'local-drive-files': return this.driveFilesChart(true, false); - case 'remote-drive-files': return this.driveFilesChart(false, false); - case 'local-drive-files-total': return this.driveFilesChart(true, true); - case 'remote-drive-files-total': return this.driveFilesChart(false, true); + case 'users': return this.usersChart(false); + case 'users-total': return this.usersChart(true); + 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(false); + case 'drive-total': return this.driveChart(true); + case 'drive-files': return this.driveFilesChart(false); + case 'drive-files-total': return this.driveFilesChart(true); } }, stats(): any[] { @@ -87,13 +75,13 @@ export default Vue.extend({ }); }, methods: { - notesChart(local: boolean): any { + notesChart(type: string): any { const data = this.stats.slice().reverse().map(x => ({ date: new Date(x.date), - normal: local ? x.notes.local.diffs.normal : x.notes.remote.diffs.normal, - reply: local ? x.notes.local.diffs.reply : x.notes.remote.diffs.reply, - renote: local ? x.notes.local.diffs.renote : x.notes.remote.diffs.renote, - all: local ? x.notes.local.diff : x.notes.remote.diff + normal: type == 'local' ? x.notes.local.diffs.normal : type == 'remote' ? x.notes.remote.diffs.normal : x.notes.local.diffs.normal + x.notes.remote.diffs.normal, + reply: type == 'local' ? x.notes.local.diffs.reply : type == 'remote' ? x.notes.remote.diffs.reply : x.notes.local.diffs.reply + x.notes.remote.diffs.reply, + renote: type == 'local' ? x.notes.local.diffs.renote : type == 'remote' ? x.notes.remote.diffs.renote : x.notes.local.diffs.renote + x.notes.remote.diffs.renote, + all: type == 'local' ? x.notes.local.diff : type == 'remote' ? x.notes.remote.diff : x.notes.local.diff + x.notes.remote.diff })); return [{ @@ -107,14 +95,14 @@ export default Vue.extend({ lineTension: 0, data: data.map(x => ({ t: x.date, y: x.all })) }, { - label: 'Normal', + label: 'Renotes', fill: true, - backgroundColor: 'rgba(65, 221, 222, 0.1)', - borderColor: '#41ddde', + backgroundColor: 'rgba(161, 222, 65, 0.1)', + borderColor: '#a1de41', borderWidth: 2, pointBackgroundColor: '#fff', lineTension: 0, - data: data.map(x => ({ t: x.date, y: x.normal })) + data: data.map(x => ({ t: x.date, y: x.renote })) }, { label: 'Replies', fill: true, @@ -125,78 +113,185 @@ export default Vue.extend({ lineTension: 0, data: data.map(x => ({ t: x.date, y: x.reply })) }, { - label: 'Renotes', + label: 'Normal', fill: true, - backgroundColor: 'rgba(161, 222, 65, 0.1)', - borderColor: '#a1de41', + backgroundColor: 'rgba(65, 221, 222, 0.1)', + borderColor: '#41ddde', borderWidth: 2, pointBackgroundColor: '#fff', lineTension: 0, - data: data.map(x => ({ t: x.date, y: x.renote })) + data: data.map(x => ({ t: x.date, y: x.normal })) }] + }, { + scales: { + yAxes: [{ + ticks: { + callback: value => { + return Vue.filter('number')(value); + } + } + }] + }, + tooltips: { + callbacks: { + label: (tooltipItem, data) => { + const label = data.datasets[tooltipItem.datasetIndex].label || ''; + return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; + } + } + } }]; }, - notesTotalChart(local: boolean): any { + notesTotalChart(): any { const data = this.stats.slice().reverse().map(x => ({ date: new Date(x.date), - count: local ? x.notes.local.total : x.notes.remote.total, + localCount: x.notes.local.total, + remoteCount: x.notes.remote.total })); return [{ datasets: [{ - label: local ? 'Local Notes' : 'Remote Notes', + label: 'Notes', + fill: false, + borderColor: '#555', + borderWidth: 2, + borderDash: [4, 4], + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.remoteCount + x.localCount })) + }, { + label: 'Remote Notes', + fill: true, + backgroundColor: 'rgba(65, 221, 222, 0.1)', + borderColor: '#41ddde', + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.remoteCount })) + }, { + label: 'Local Notes', fill: true, backgroundColor: 'rgba(246, 88, 79, 0.1)', borderColor: '#f6584f', borderWidth: 2, pointBackgroundColor: '#fff', lineTension: 0, - data: data.map(x => ({ t: x.date, y: x.count })) + data: data.map(x => ({ t: x.date, y: x.localCount })) }] + }, { + scales: { + yAxes: [{ + ticks: { + callback: value => { + return Vue.filter('number')(value); + } + } + }] + }, + tooltips: { + callbacks: { + label: (tooltipItem, data) => { + const label = data.datasets[tooltipItem.datasetIndex].label || ''; + return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; + } + } + } }]; }, - usersChart(local: boolean, total: boolean): any { + usersChart(total: boolean): any { const data = this.stats.slice().reverse().map(x => ({ date: new Date(x.date), - count: local ? - total ? x.users.local.total : x.users.local.diff : - total ? x.users.remote.total : x.users.remote.diff + localCount: total ? x.users.local.total : x.users.local.diff, + remoteCount: total ? x.users.remote.total : x.users.remote.diff })); return [{ datasets: [{ - label: local ? 'Local Users' : 'Remote Users', + label: 'Users', + fill: false, + borderColor: '#555', + borderWidth: 2, + borderDash: [4, 4], + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.remoteCount + x.localCount })) + }, { + label: 'Remote Users', + fill: true, + backgroundColor: 'rgba(65, 221, 222, 0.1)', + borderColor: '#41ddde', + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.remoteCount })) + }, { + label: 'Local Users', fill: true, backgroundColor: 'rgba(246, 88, 79, 0.1)', borderColor: '#f6584f', borderWidth: 2, pointBackgroundColor: '#fff', lineTension: 0, - data: data.map(x => ({ t: x.date, y: x.count })) + data: data.map(x => ({ t: x.date, y: x.localCount })) }] + }, { + scales: { + yAxes: [{ + ticks: { + callback: value => { + return Vue.filter('number')(value); + } + } + }] + }, + tooltips: { + callbacks: { + label: (tooltipItem, data) => { + const label = data.datasets[tooltipItem.datasetIndex].label || ''; + return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; + } + } + } }]; }, - driveChart(local: boolean, total: boolean): any { + driveChart(total: boolean): any { const data = this.stats.slice().reverse().map(x => ({ date: new Date(x.date), - size: local ? - total ? x.drive.local.totalSize : x.drive.local.diffSize : - total ? x.drive.remote.totalSize : x.drive.remote.diffSize + localSize: total ? x.drive.local.totalSize : x.drive.local.diffSize, + remoteSize: total ? x.drive.remote.totalSize : x.drive.remote.diffSize })); return [{ datasets: [{ - label: local ? 'Local Drive Usage' : 'Remote Drive Usage', + label: 'Drive Usage', + fill: false, + borderColor: '#555', + borderWidth: 2, + borderDash: [4, 4], + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.remoteSize + x.localSize })) + }, { + label: 'Remote Drive Usage', + fill: true, + backgroundColor: 'rgba(65, 221, 222, 0.1)', + borderColor: '#41ddde', + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.remoteSize })) + }, { + label: 'Local Drive Usage', fill: true, backgroundColor: 'rgba(246, 88, 79, 0.1)', borderColor: '#f6584f', borderWidth: 2, pointBackgroundColor: '#fff', lineTension: 0, - data: data.map(x => ({ t: x.date, y: x.size })) + data: data.map(x => ({ t: x.date, y: x.localSize })) }] }, { scales: { @@ -210,35 +305,71 @@ export default Vue.extend({ }, tooltips: { callbacks: { - label: tooltipItem => { - return Vue.filter('bytes')(tooltipItem.yLabel); + label: (tooltipItem, data) => { + const label = data.datasets[tooltipItem.datasetIndex].label || ''; + return `${label}: ${Vue.filter('bytes')(tooltipItem.yLabel)}`; } } } }]; }, - driveFilesChart(local: boolean, total: boolean): any { + driveFilesChart(total: boolean): any { const data = this.stats.slice().reverse().map(x => ({ date: new Date(x.date), - count: local ? - total ? x.drive.local.totalCount : x.drive.local.diffCount : - total ? x.drive.remote.totalCount : x.drive.remote.diffCount + localCount: total ? x.drive.local.totalCount : x.drive.local.diffCount, + remoteCount: total ? x.drive.remote.totalCount : x.drive.remote.diffCount })); return [{ datasets: [{ - label: local ? 'Local Drive Files' : 'Remote Drive Files', + label: 'Drive Files', + fill: false, + borderColor: '#555', + borderWidth: 2, + borderDash: [4, 4], + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.remoteCount + x.localCount })) + }, { + label: 'Remote Drive Files', + fill: true, + backgroundColor: 'rgba(65, 221, 222, 0.1)', + borderColor: '#41ddde', + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.remoteCount })) + }, { + label: 'Local Drive Files', fill: true, backgroundColor: 'rgba(246, 88, 79, 0.1)', borderColor: '#f6584f', borderWidth: 2, pointBackgroundColor: '#fff', lineTension: 0, - data: data.map(x => ({ t: x.date, y: x.count })) + data: data.map(x => ({ t: x.date, y: x.localCount })) }] + }, { + scales: { + yAxes: [{ + ticks: { + callback: value => { + return Vue.filter('number')(value); + } + } + }] + }, + tooltips: { + callbacks: { + label: (tooltipItem, data) => { + const label = data.datasets[tooltipItem.datasetIndex].label || ''; + return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; + } + } + } }]; - }, + } } }); @@ -259,6 +390,11 @@ export default Vue.extend({ > *:last-child margin-left auto + * + &:not(.active) + color $theme-color + cursor pointer + > div > * display block diff --git a/src/server/api/endpoints/chart.ts b/src/server/api/endpoints/chart.ts index a58f0b163e..da3476f067 100644 --- a/src/server/api/endpoints/chart.ts +++ b/src/server/api/endpoints/chart.ts @@ -6,8 +6,8 @@ export const meta = { }; export default (params: any) => new Promise(async (res, rej) => { - const daysRange = 90; - const hoursRange = 24; + const daysRange = 30; + const hoursRange = 30; const now = new Date(); const y = now.getFullYear(); @@ -65,9 +65,53 @@ export default (params: any) => new Promise(async (res, rej) => { } else { // 隙間埋め const mostRecent = src.find(s => s.date.getTime() < current.getTime()); if (mostRecent) { - chart.unshift(Object.assign({}, mostRecent, { - date: current - })); + chart.unshift({ + date: current, + users: { + local: { + total: mostRecent.users.local.total, + diff: 0 + }, + remote: { + total: mostRecent.users.remote.total, + diff: 0 + } + }, + notes: { + local: { + total: mostRecent.notes.local.total, + diff: 0, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + }, + remote: { + total: mostRecent.notes.remote.total, + diff: 0, + diffs: { + normal: 0, + reply: 0, + renote: 0 + } + } + }, + drive: { + local: { + totalCount: mostRecent.drive.local.totalCount, + totalSize: mostRecent.drive.local.totalSize, + diffCount: 0, + diffSize: 0 + }, + remote: { + totalCount: mostRecent.drive.remote.totalCount, + totalSize: mostRecent.drive.remote.totalSize, + diffCount: 0, + diffSize: 0 + } + } + }); } else { chart.unshift({ date: current, -- cgit v1.2.3-freya From 7b6e55047f0bdf02ba0cafd5b90147737a75b556 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 25 Aug 2018 08:35:41 +0900 Subject: #2460 --- .../app/desktop/views/pages/admin/admin.chart.vue | 206 ++++++++++++++++++--- src/models/stats.ts | 106 ++++++++--- src/server/api/endpoints/chart.ts | 77 ++++++-- src/services/update-chart.ts | 88 +++++---- 4 files changed, 374 insertions(+), 103 deletions(-) (limited to 'src/server/api/endpoints/chart.ts') diff --git a/src/client/app/desktop/views/pages/admin/admin.chart.vue b/src/client/app/desktop/views/pages/admin/admin.chart.vue index 89e61d4c74..4310ce03d6 100644 --- a/src/client/app/desktop/views/pages/admin/admin.chart.vue +++ b/src/client/app/desktop/views/pages/admin/admin.chart.vue @@ -55,10 +55,10 @@ export default Vue.extend({ 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(false); - case 'drive-total': return this.driveChart(true); - case 'drive-files': return this.driveFilesChart(false); - case 'drive-files-total': return this.driveFilesChart(true); + 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[] { @@ -81,7 +81,7 @@ export default Vue.extend({ normal: type == 'local' ? x.notes.local.diffs.normal : type == 'remote' ? x.notes.remote.diffs.normal : x.notes.local.diffs.normal + x.notes.remote.diffs.normal, reply: type == 'local' ? x.notes.local.diffs.reply : type == 'remote' ? x.notes.remote.diffs.reply : x.notes.local.diffs.reply + x.notes.remote.diffs.reply, renote: type == 'local' ? x.notes.local.diffs.renote : type == 'remote' ? x.notes.remote.diffs.renote : x.notes.local.diffs.renote + x.notes.remote.diffs.renote, - all: type == 'local' ? x.notes.local.diff : type == 'remote' ? x.notes.remote.diff : x.notes.local.diff + x.notes.remote.diff + all: type == 'local' ? (x.notes.local.inc + -x.notes.local.dec) : type == 'remote' ? (x.notes.remote.inc + -x.notes.remote.dec) : (x.notes.local.inc + -x.notes.local.dec) + (x.notes.remote.inc + -x.notes.remote.dec) })); return [{ @@ -152,7 +152,7 @@ export default Vue.extend({ return [{ datasets: [{ - label: 'Notes', + label: 'Combined', fill: false, borderColor: '#555', borderWidth: 2, @@ -161,7 +161,7 @@ export default Vue.extend({ lineTension: 0, data: data.map(x => ({ t: x.date, y: x.remoteCount + x.localCount })) }, { - label: 'Remote Notes', + label: 'Remote', fill: true, backgroundColor: 'rgba(65, 221, 222, 0.1)', borderColor: '#41ddde', @@ -170,7 +170,7 @@ export default Vue.extend({ lineTension: 0, data: data.map(x => ({ t: x.date, y: x.remoteCount })) }, { - label: 'Local Notes', + label: 'Local', fill: true, backgroundColor: 'rgba(246, 88, 79, 0.1)', borderColor: '#f6584f', @@ -203,13 +203,13 @@ export default Vue.extend({ usersChart(total: boolean): any { const data = this.stats.slice().reverse().map(x => ({ date: new Date(x.date), - localCount: total ? x.users.local.total : x.users.local.diff, - remoteCount: total ? x.users.remote.total : x.users.remote.diff + localCount: total ? x.users.local.total : (x.users.local.inc + -x.users.local.dec), + remoteCount: total ? x.users.remote.total : (x.users.remote.inc + -x.users.remote.dec) })); return [{ datasets: [{ - label: 'Users', + label: 'Combined', fill: false, borderColor: '#555', borderWidth: 2, @@ -218,7 +218,7 @@ export default Vue.extend({ lineTension: 0, data: data.map(x => ({ t: x.date, y: x.remoteCount + x.localCount })) }, { - label: 'Remote Users', + label: 'Remote', fill: true, backgroundColor: 'rgba(65, 221, 222, 0.1)', borderColor: '#41ddde', @@ -227,7 +227,7 @@ export default Vue.extend({ lineTension: 0, data: data.map(x => ({ t: x.date, y: x.remoteCount })) }, { - label: 'Local Users', + label: 'Local', fill: true, backgroundColor: 'rgba(246, 88, 79, 0.1)', borderColor: '#f6584f', @@ -257,16 +257,93 @@ export default Vue.extend({ }]; }, - driveChart(total: boolean): any { + driveChart(): any { const data = this.stats.slice().reverse().map(x => ({ date: new Date(x.date), - localSize: total ? x.drive.local.totalSize : x.drive.local.diffSize, - remoteSize: total ? x.drive.remote.totalSize : x.drive.remote.diffSize + localInc: x.drive.local.incSize, + localDec: -x.drive.local.decSize, + remoteInc: x.drive.remote.incSize, + remoteDec: -x.drive.remote.decSize, })); return [{ datasets: [{ - label: 'Drive Usage', + label: 'All', + fill: false, + borderColor: '#555', + borderWidth: 2, + borderDash: [4, 4], + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.localInc + x.localDec + x.remoteInc + x.remoteDec })) + }, { + label: 'Remote +', + fill: true, + backgroundColor: 'rgba(65, 221, 222, 0.1)', + borderColor: '#41ddde', + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.remoteInc })) + }, { + label: 'Remote -', + fill: true, + backgroundColor: 'rgba(65, 221, 222, 0.1)', + borderColor: '#41ddde', + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.remoteDec })) + }, { + label: 'Local +', + fill: true, + backgroundColor: 'rgba(246, 88, 79, 0.1)', + borderColor: '#f6584f', + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.localInc })) + }, { + label: 'Local -', + fill: true, + backgroundColor: 'rgba(246, 88, 79, 0.1)', + borderColor: '#f6584f', + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.localDec })) + }] + }, { + scales: { + yAxes: [{ + ticks: { + callback: value => { + return Vue.filter('bytes')(value); + } + } + }] + }, + tooltips: { + callbacks: { + label: (tooltipItem, data) => { + const label = data.datasets[tooltipItem.datasetIndex].label || ''; + return `${label}: ${Vue.filter('bytes')(tooltipItem.yLabel)}`; + } + } + } + }]; + }, + + driveTotalChart(): any { + const data = this.stats.slice().reverse().map(x => ({ + date: new Date(x.date), + localSize: x.drive.local.totalSize, + remoteSize: x.drive.remote.totalSize + })); + + return [{ + datasets: [{ + label: 'Combined', fill: false, borderColor: '#555', borderWidth: 2, @@ -275,7 +352,7 @@ export default Vue.extend({ lineTension: 0, data: data.map(x => ({ t: x.date, y: x.remoteSize + x.localSize })) }, { - label: 'Remote Drive Usage', + label: 'Remote', fill: true, backgroundColor: 'rgba(65, 221, 222, 0.1)', borderColor: '#41ddde', @@ -284,7 +361,7 @@ export default Vue.extend({ lineTension: 0, data: data.map(x => ({ t: x.date, y: x.remoteSize })) }, { - label: 'Local Drive Usage', + label: 'Local', fill: true, backgroundColor: 'rgba(246, 88, 79, 0.1)', borderColor: '#f6584f', @@ -314,25 +391,102 @@ export default Vue.extend({ }]; }, - driveFilesChart(total: boolean): any { + driveFilesChart(): any { const data = this.stats.slice().reverse().map(x => ({ date: new Date(x.date), - localCount: total ? x.drive.local.totalCount : x.drive.local.diffCount, - remoteCount: total ? x.drive.remote.totalCount : x.drive.remote.diffCount + localInc: x.drive.local.incCount, + localDec: -x.drive.local.decCount, + remoteInc: x.drive.remote.incCount, + remoteDec: -x.drive.remote.decCount })); return [{ datasets: [{ - label: 'Drive Files', + label: 'All', fill: false, borderColor: '#555', borderWidth: 2, borderDash: [4, 4], pointBackgroundColor: '#fff', lineTension: 0, - data: data.map(x => ({ t: x.date, y: x.remoteCount + x.localCount })) + data: data.map(x => ({ t: x.date, y: x.localInc + x.localDec + x.remoteInc + x.remoteDec })) + }, { + label: 'Remote +', + fill: true, + backgroundColor: 'rgba(65, 221, 222, 0.1)', + borderColor: '#41ddde', + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.remoteInc })) + }, { + label: 'Remote -', + fill: true, + backgroundColor: 'rgba(65, 221, 222, 0.1)', + borderColor: '#41ddde', + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.remoteDec })) + }, { + label: 'Local +', + fill: true, + backgroundColor: 'rgba(246, 88, 79, 0.1)', + borderColor: '#f6584f', + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.localInc })) + }, { + label: 'Local -', + fill: true, + backgroundColor: 'rgba(246, 88, 79, 0.1)', + borderColor: '#f6584f', + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.localDec })) + }] + }, { + scales: { + yAxes: [{ + ticks: { + callback: value => { + return Vue.filter('number')(value); + } + } + }] + }, + tooltips: { + callbacks: { + label: (tooltipItem, data) => { + const label = data.datasets[tooltipItem.datasetIndex].label || ''; + return `${label}: ${Vue.filter('number')(tooltipItem.yLabel)}`; + } + } + } + }]; + }, + + driveFilesTotalChart(): any { + const data = this.stats.slice().reverse().map(x => ({ + date: new Date(x.date), + localCount: x.drive.local.totalCount, + remoteCount: x.drive.remote.totalCount, + })); + + return [{ + datasets: [{ + label: 'Combined', + fill: false, + borderColor: '#555', + borderWidth: 2, + borderDash: [4, 4], + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.localCount + x.remoteCount })) }, { - label: 'Remote Drive Files', + label: 'Remote', fill: true, backgroundColor: 'rgba(65, 221, 222, 0.1)', borderColor: '#41ddde', @@ -341,7 +495,7 @@ export default Vue.extend({ lineTension: 0, data: data.map(x => ({ t: x.date, y: x.remoteCount })) }, { - label: 'Local Drive Files', + label: 'Local', fill: true, backgroundColor: 'rgba(246, 88, 79, 0.1)', borderColor: '#f6584f', diff --git a/src/models/stats.ts b/src/models/stats.ts index c481c3196e..3041d3852b 100644 --- a/src/models/stats.ts +++ b/src/models/stats.ts @@ -8,8 +8,14 @@ export default Stats; export interface IStats { _id: mongo.ObjectID; + /** + * 集計日時 + */ date: Date; + /** + * 集計期間 + */ span: 'day' | 'hour'; /** @@ -18,26 +24,36 @@ export interface IStats { users: { local: { /** - * この日時点での、ローカルのユーザーの総計 + * 集計期間時点での、全ユーザー数 (ローカル) */ total: number; /** - * ローカルのユーザー数の前日比 + * 増加したユーザー数 (ローカル) + */ + inc: number; + + /** + * 減少したユーザー数 (ローカル) */ - diff: number; + dec: number; }; remote: { /** - * この日時点での、リモートのユーザーの総計 + * 集計期間時点での、全ユーザー数 (リモート) */ total: number; /** - * リモートのユーザー数の前日比 + * 増加したユーザー数 (リモート) */ - diff: number; + inc: number; + + /** + * 減少したユーザー数 (リモート) + */ + dec: number; }; }; @@ -47,28 +63,33 @@ export interface IStats { notes: { local: { /** - * この日時点での、ローカルの投稿の総計 + * 集計期間時点での、全投稿数 (ローカル) */ total: number; /** - * ローカルの投稿数の前日比 + * 増加した投稿数 (ローカル) */ - diff: number; + inc: number; + + /** + * 減少した投稿数 (ローカル) + */ + dec: number; diffs: { /** - * ローカルの通常の投稿数の前日比 + * 通常の投稿数の差分 (ローカル) */ normal: number; /** - * ローカルのリプライの投稿数の前日比 + * リプライの投稿数の差分 (ローカル) */ reply: number; /** - * ローカルのRenoteの投稿数の前日比 + * Renoteの投稿数の差分 (ローカル) */ renote: number; }; @@ -76,28 +97,33 @@ export interface IStats { remote: { /** - * この日時点での、リモートの投稿の総計 + * 集計期間時点での、全投稿数 (リモート) */ total: number; /** - * リモートの投稿数の前日比 + * 増加した投稿数 (リモート) + */ + inc: number; + + /** + * 減少した投稿数 (リモート) */ - diff: number; + dec: number; diffs: { /** - * リモートの通常の投稿数の前日比 + * 通常の投稿数の差分 (リモート) */ normal: number; /** - * リモートのリプライの投稿数の前日比 + * リプライの投稿数の差分 (リモート) */ reply: number; /** - * リモートのRenoteの投稿数の前日比 + * Renoteの投稿数の差分 (リモート) */ renote: number; }; @@ -110,46 +136,66 @@ export interface IStats { drive: { local: { /** - * この日時点での、ローカルのドライブファイル数の総計 + * 集計期間時点での、全ドライブファイル数 (ローカル) */ totalCount: number; /** - * この日時点での、ローカルのドライブファイルサイズの総計 + * 集計期間時点での、全ドライブファイルの合計サイズ (ローカル) */ totalSize: number; /** - * ローカルのドライブファイル数の前日比 + * 増加したドライブファイル数 (ローカル) + */ + incCount: number; + + /** + * 増加したドライブ使用量 (ローカル) + */ + incSize: number; + + /** + * 減少したドライブファイル数 (ローカル) */ - diffCount: number; + decCount: number; /** - * ローカルのドライブファイルサイズの前日比 + * 減少したドライブ使用量 (ローカル) */ - diffSize: number; + decSize: number; }; remote: { /** - * この日時点での、リモートのドライブファイル数の総計 + * 集計期間時点での、全ドライブファイル数 (リモート) */ totalCount: number; /** - * この日時点での、リモートのドライブファイルサイズの総計 + * 集計期間時点での、全ドライブファイルの合計サイズ (リモート) */ totalSize: number; /** - * リモートのドライブファイル数の前日比 + * 増加したドライブファイル数 (リモート) + */ + incCount: number; + + /** + * 増加したドライブ使用量 (リモート) + */ + incSize: number; + + /** + * 減少したドライブファイル数 (リモート) */ - diffCount: number; + decCount: number; /** - * リモートのドライブファイルサイズの前日比 + * 減少したドライブ使用量 (リモート) */ - diffSize: number; + decSize: number; }; }; } diff --git a/src/server/api/endpoints/chart.ts b/src/server/api/endpoints/chart.ts index da3476f067..514bacaa84 100644 --- a/src/server/api/endpoints/chart.ts +++ b/src/server/api/endpoints/chart.ts @@ -2,6 +2,31 @@ import Stats, { IStats } from '../../../models/stats'; type Omit = Pick>; +function migrateStats(stats: IStats[]) { + stats.forEach(stat => { + const isOldData = stat.users.local.inc == null; + + if (!isOldData) return; + + stat.users.local.inc = (stat as any).users.local.diff; + stat.users.local.dec = 0; + stat.users.remote.inc = (stat as any).users.remote.diff; + stat.users.remote.dec = 0; + stat.notes.local.inc = (stat as any).notes.local.diff; + stat.notes.local.dec = 0; + stat.notes.remote.inc = (stat as any).notes.remote.diff; + stat.notes.remote.dec = 0; + stat.drive.local.incCount = (stat as any).drive.local.diffCount; + stat.drive.local.decCount = 0; + stat.drive.local.incSize = (stat as any).drive.local.diffSize; + stat.drive.local.decSize = 0; + stat.drive.remote.incCount = (stat as any).drive.remote.diffCount; + stat.drive.remote.decCount = 0; + stat.drive.remote.incSize = (stat as any).drive.remote.diffSize; + stat.drive.remote.decSize = 0; + }); +} + export const meta = { }; @@ -44,6 +69,10 @@ export default (params: any) => new Promise(async (res, rej) => { }), ]); + // 後方互換性のため + migrateStats(statsPerDay); + migrateStats(statsPerHour); + const format = (src: IStats[], span: 'day' | 'hour') => { const chart: Array, 'span'>> = []; @@ -70,17 +99,20 @@ export default (params: any) => new Promise(async (res, rej) => { users: { local: { total: mostRecent.users.local.total, - diff: 0 + inc: 0, + dec: 0 }, remote: { total: mostRecent.users.remote.total, - diff: 0 + inc: 0, + dec: 0 } }, notes: { local: { total: mostRecent.notes.local.total, - diff: 0, + inc: 0, + dec: 0, diffs: { normal: 0, reply: 0, @@ -89,7 +121,8 @@ export default (params: any) => new Promise(async (res, rej) => { }, remote: { total: mostRecent.notes.remote.total, - diff: 0, + inc: 0, + dec: 0, diffs: { normal: 0, reply: 0, @@ -101,14 +134,18 @@ export default (params: any) => new Promise(async (res, rej) => { local: { totalCount: mostRecent.drive.local.totalCount, totalSize: mostRecent.drive.local.totalSize, - diffCount: 0, - diffSize: 0 + incCount: 0, + incSize: 0, + decCount: 0, + decSize: 0 }, remote: { totalCount: mostRecent.drive.remote.totalCount, totalSize: mostRecent.drive.remote.totalSize, - diffCount: 0, - diffSize: 0 + incCount: 0, + incSize: 0, + decCount: 0, + decSize: 0 } } }); @@ -118,17 +155,20 @@ export default (params: any) => new Promise(async (res, rej) => { users: { local: { total: 0, - diff: 0 + inc: 0, + dec: 0 }, remote: { total: 0, - diff: 0 + inc: 0, + dec: 0 } }, notes: { local: { total: 0, - diff: 0, + inc: 0, + dec: 0, diffs: { normal: 0, reply: 0, @@ -137,7 +177,8 @@ export default (params: any) => new Promise(async (res, rej) => { }, remote: { total: 0, - diff: 0, + inc: 0, + dec: 0, diffs: { normal: 0, reply: 0, @@ -149,14 +190,18 @@ export default (params: any) => new Promise(async (res, rej) => { local: { totalCount: 0, totalSize: 0, - diffCount: 0, - diffSize: 0 + incCount: 0, + incSize: 0, + decCount: 0, + decSize: 0 }, remote: { totalCount: 0, totalSize: 0, - diffCount: 0, - diffSize: 0 + incCount: 0, + incSize: 0, + decCount: 0, + decSize: 0 } } }); diff --git a/src/services/update-chart.ts b/src/services/update-chart.ts index 0a0f58bb92..1f8da6be9f 100644 --- a/src/services/update-chart.ts +++ b/src/services/update-chart.ts @@ -48,17 +48,20 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise { users: { local: { total: mostRecentStats.users.local.total, - diff: 0 + inc: 0, + dec: 0 }, remote: { total: mostRecentStats.users.remote.total, - diff: 0 + inc: 0, + dec: 0 } }, notes: { local: { total: mostRecentStats.notes.local.total, - diff: 0, + inc: 0, + dec: 0, diffs: { normal: 0, reply: 0, @@ -67,7 +70,8 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise { }, remote: { total: mostRecentStats.notes.remote.total, - diff: 0, + inc: 0, + dec: 0, diffs: { normal: 0, reply: 0, @@ -79,14 +83,18 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise { local: { totalCount: mostRecentStats.drive.local.totalCount, totalSize: mostRecentStats.drive.local.totalSize, - diffCount: 0, - diffSize: 0 + incCount: 0, + incSize: 0, + decCount: 0, + decSize: 0 }, remote: { totalCount: mostRecentStats.drive.remote.totalCount, totalSize: mostRecentStats.drive.remote.totalSize, - diffCount: 0, - diffSize: 0 + incCount: 0, + incSize: 0, + decCount: 0, + decSize: 0 } } }; @@ -105,17 +113,20 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise { users: { local: { total: 0, - diff: 0 + inc: 0, + dec: 0 }, remote: { total: 0, - diff: 0 + inc: 0, + dec: 0 } }, notes: { local: { total: 0, - diff: 0, + inc: 0, + dec: 0, diffs: { normal: 0, reply: 0, @@ -124,7 +135,8 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise { }, remote: { total: 0, - diff: 0, + inc: 0, + dec: 0, diffs: { normal: 0, reply: 0, @@ -136,14 +148,18 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise { local: { totalCount: 0, totalSize: 0, - diffCount: 0, - diffSize: 0 + incCount: 0, + incSize: 0, + decCount: 0, + decSize: 0 }, remote: { totalCount: 0, totalSize: 0, - diffCount: 0, - diffSize: 0 + incCount: 0, + incSize: 0, + decCount: 0, + decSize: 0 } } }; @@ -174,46 +190,56 @@ function update(inc: any) { } export async function updateUserStats(user: IUser, isAdditional: boolean) { - const amount = isAdditional ? 1 : -1; const origin = isLocalUser(user) ? 'local' : 'remote'; const inc = {} as any; - inc[`users.${origin}.total`] = amount; - inc[`users.${origin}.diff`] = amount; + inc[`users.${origin}.total`] = isAdditional ? 1 : -1; + if (isAdditional) { + inc[`users.${origin}.inc`] = 1; + } else { + inc[`users.${origin}.dec`] = 1; + } await update(inc); } export async function updateNoteStats(note: INote, isAdditional: boolean) { - const amount = isAdditional ? 1 : -1; const origin = isLocalUser(note._user) ? 'local' : 'remote'; const inc = {} as any; - inc[`notes.${origin}.total`] = amount; - inc[`notes.${origin}.diff`] = amount; + inc[`notes.${origin}.total`] = isAdditional ? 1 : -1; + + if (isAdditional) { + inc[`notes.${origin}.inc`] = 1; + } else { + inc[`notes.${origin}.dec`] = 1; + } if (note.replyId != null) { - inc[`notes.${origin}.diffs.reply`] = amount; + inc[`notes.${origin}.diffs.reply`] = isAdditional ? 1 : -1; } else if (note.renoteId != null) { - inc[`notes.${origin}.diffs.renote`] = amount; + inc[`notes.${origin}.diffs.renote`] = isAdditional ? 1 : -1; } else { - inc[`notes.${origin}.diffs.normal`] = amount; + inc[`notes.${origin}.diffs.normal`] = isAdditional ? 1 : -1; } await update(inc); } export async function updateDriveStats(file: IDriveFile, isAdditional: boolean) { - const amount = isAdditional ? 1 : -1; - const size = isAdditional ? file.length : -file.length; const origin = isLocalUser(file.metadata._user) ? 'local' : 'remote'; const inc = {} as any; - inc[`drive.${origin}.totalCount`] = amount; - inc[`drive.${origin}.diffCount`] = amount; - inc[`drive.${origin}.totalSize`] = size; - inc[`drive.${origin}.diffSize`] = size; + inc[`drive.${origin}.totalCount`] = isAdditional ? 1 : -1; + inc[`drive.${origin}.totalSize`] = isAdditional ? file.length : -file.length; + if (isAdditional) { + inc[`drive.${origin}.incCount`] = 1; + inc[`drive.${origin}.incSize`] = file.length; + } else { + inc[`drive.${origin}.decCount`] = 1; + inc[`drive.${origin}.decSize`] = file.length; + } await update(inc); } -- cgit v1.2.3-freya From ef57f5907b115b8047e798f7486bb9ee73d465aa Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 25 Aug 2018 11:10:27 +0900 Subject: Fix bug --- src/server/api/endpoints/chart.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'src/server/api/endpoints/chart.ts') diff --git a/src/server/api/endpoints/chart.ts b/src/server/api/endpoints/chart.ts index 514bacaa84..406ad39946 100644 --- a/src/server/api/endpoints/chart.ts +++ b/src/server/api/endpoints/chart.ts @@ -4,7 +4,23 @@ type Omit = Pick>; function migrateStats(stats: IStats[]) { stats.forEach(stat => { - const isOldData = stat.users.local.inc == null; + const isOldData = + stat.users.local.inc == null || + stat.users.local.dec == null || + stat.users.remote.inc == null || + stat.users.remote.dec == null || + stat.notes.local.inc == null || + stat.notes.local.dec == null || + stat.notes.remote.inc == null || + stat.notes.remote.dec == null || + stat.drive.local.incCount == null || + stat.drive.local.decCount == null || + stat.drive.local.incSize == null || + stat.drive.local.decSize == null || + stat.drive.remote.incCount == null || + stat.drive.remote.decCount == null || + stat.drive.remote.incSize == null || + stat.drive.remote.decSize == null; if (!isOldData) return; -- cgit v1.2.3-freya From 117ab633a151d5d006f5c9dceead4bde1ddea8a1 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 30 Aug 2018 03:49:52 +0900 Subject: Improve API --- src/server/api/endpoints/chart.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'src/server/api/endpoints/chart.ts') diff --git a/src/server/api/endpoints/chart.ts b/src/server/api/endpoints/chart.ts index 406ad39946..521fc42c98 100644 --- a/src/server/api/endpoints/chart.ts +++ b/src/server/api/endpoints/chart.ts @@ -1,4 +1,6 @@ +import $ from 'cafy'; import Stats, { IStats } from '../../../models/stats'; +import getParams from '../../get-params'; type Omit = Pick>; @@ -44,11 +46,26 @@ function migrateStats(stats: IStats[]) { } export const meta = { + desc: { + 'ja-JP': 'インスタンスの統計を取得します。' + }, + + params: { + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数' + } + }), + } }; export default (params: any) => new Promise(async (res, rej) => { - const daysRange = 30; - const hoursRange = 30; + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; + + const daysRange = ps.limit; + const hoursRange = ps.limit; const now = new Date(); const y = now.getFullYear(); -- cgit v1.2.3-freya From 3038434712077cedd92a6cd68d74513f9dbd62bd Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 30 Aug 2018 03:53:26 +0900 Subject: Fix bug --- src/server/api/endpoints/chart.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/server/api/endpoints/chart.ts') diff --git a/src/server/api/endpoints/chart.ts b/src/server/api/endpoints/chart.ts index 521fc42c98..7da970131e 100644 --- a/src/server/api/endpoints/chart.ts +++ b/src/server/api/endpoints/chart.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import Stats, { IStats } from '../../../models/stats'; -import getParams from '../../get-params'; +import getParams from '../get-params'; type Omit = Pick>; -- cgit v1.2.3-freya