mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-05-06 17:16:28 +02:00
feat: ACP chart to show ap relay analytics
This commit is contained in:
@@ -3,9 +3,53 @@
|
||||
import { post, del } from 'api';
|
||||
import { error } from 'alerts';
|
||||
import { render } from 'benchpress';
|
||||
import { get } from 'api';
|
||||
import { translate } from 'translator';
|
||||
import {
|
||||
Chart,
|
||||
LineController,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
LineElement,
|
||||
PointElement,
|
||||
Tooltip,
|
||||
Filler,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
|
||||
export function init() {
|
||||
Chart.register(
|
||||
LineController,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
LineElement,
|
||||
PointElement,
|
||||
Tooltip,
|
||||
Filler,
|
||||
Legend
|
||||
);
|
||||
|
||||
let chart;
|
||||
const labels = new Map([
|
||||
['hourly', utils.getHoursArray().map(function (text, idx) {
|
||||
return idx % 3 ? '' : text;
|
||||
})],
|
||||
['daily', utils.getDaysArray().map(function (text, idx) {
|
||||
return idx % 3 ? '' : text;
|
||||
})],
|
||||
]);
|
||||
|
||||
export async function init() {
|
||||
setupRelays();
|
||||
chart = await initializeCharts();
|
||||
|
||||
const hostFilterEl = document.getElementById('hostFilter');
|
||||
const termEl = document.getElementById('term');
|
||||
if (hostFilterEl) {
|
||||
hostFilterEl.addEventListener('change', updateCharts);
|
||||
}
|
||||
if (termEl) {
|
||||
termEl.addEventListener('change', updateCharts);
|
||||
}
|
||||
};
|
||||
|
||||
function setupRelays() {
|
||||
@@ -70,4 +114,79 @@ function throwModal() {
|
||||
modal.find('input').focus();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function updateCharts() {
|
||||
const hostFilterEl = document.getElementById('hostFilter');
|
||||
const termEl = document.getElementById('term');
|
||||
console.log(hostFilterEl.value, termEl.value);
|
||||
const data = await get(`/api${ajaxify.data.url}?host=${hostFilterEl.value}&term=${termEl.value}`);
|
||||
|
||||
chart.data.labels = labels.get(termEl.value || 'hourly');
|
||||
chart.data.datasets[0].data = data.data.in;
|
||||
chart.data.datasets[1].data = data.data.out;
|
||||
chart.update();
|
||||
}
|
||||
|
||||
async function initializeCharts() {
|
||||
const canvas = document.querySelector('canvas');
|
||||
|
||||
if (utils.isMobile()) {
|
||||
Chart.defaults.plugins.tooltip.enabled = false;
|
||||
}
|
||||
|
||||
const commonDataSetOpts = {
|
||||
label: '',
|
||||
fill: true,
|
||||
tension: 0.25,
|
||||
pointHoverBackgroundColor: '#fff',
|
||||
pointBorderColor: '#fff',
|
||||
};
|
||||
|
||||
const data = {
|
||||
labels: labels.get('hourly'),
|
||||
datasets: [
|
||||
{
|
||||
...commonDataSetOpts,
|
||||
label: await translate('[[admin/settings/activitypub:analytics.in]]'),
|
||||
backgroundColor: 'rgba(161,181,108,0.2)',
|
||||
borderColor: 'rgba(161,181,108,1)',
|
||||
pointBackgroundColor: 'rgba(161,181,108,1)',
|
||||
pointHoverBorderColor: 'rgba(161,181,108,1)',
|
||||
data: ajaxify.data.data.in,
|
||||
},
|
||||
{
|
||||
...commonDataSetOpts,
|
||||
label: await translate('[[admin/settings/activitypub:analytics.out]]'),
|
||||
backgroundColor: 'rgba(151,187,205,0.2)',
|
||||
borderColor: 'rgba(151,187,205,1)',
|
||||
pointBackgroundColor: 'rgba(151,187,205,1)',
|
||||
pointHoverBorderColor: 'rgba(151,187,205,1)',
|
||||
data: ajaxify.data.data.out,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
canvas.width = $(canvas).parent().width();
|
||||
|
||||
const chartOpts = {
|
||||
responsive: true,
|
||||
animation: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return new Chart(canvas.getContext('2d'), {
|
||||
type: 'line',
|
||||
data,
|
||||
options: chartOpts,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,11 +32,27 @@ federationController.rules = async function (req, res) {
|
||||
|
||||
federationController.relays = async function (req, res) {
|
||||
const relays = await activitypub.relays.list();
|
||||
const urls = relays.map(({ url }) => url);
|
||||
|
||||
let { host, term } = req.query;
|
||||
if (!urls.includes(host)) {
|
||||
host = undefined;
|
||||
}
|
||||
let method = 'getHourlyStatsForSet';
|
||||
let count = 24;
|
||||
if (term === 'daily') {
|
||||
method = 'getDailyStatsForSet';
|
||||
count = 30;
|
||||
}
|
||||
const inSet = host ? `ap.relayIn:byHost:${host}` : 'ap.relayIn';
|
||||
const outSet = host ? `ap.relayOut:byHost:${host}` : 'ap.relayOut';
|
||||
const incoming = await analytics[method](inSet, Date.now(), count);
|
||||
const out = await analytics[method](outSet, Date.now(), count);
|
||||
|
||||
res.render(`admin/federation/relays`, {
|
||||
title: '[[admin/menu:federation/relays]]',
|
||||
relays,
|
||||
hideSave: true,
|
||||
data: { in: incoming, out },
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,39 +1,62 @@
|
||||
<div class="acp-page-container">
|
||||
<!-- IMPORT admin/partials/settings/header.tpl -->
|
||||
|
||||
<div class="row settings m-0">
|
||||
<div id="spy-container" class="col-12 col-md-8 px-0 mb-4" tabindex="0">
|
||||
<div id="relays" class="mb-4">
|
||||
<p class="lead">[[admin/settings/activitypub:relays.intro]]</p>
|
||||
<p class="text-warning">[[admin/settings/activitypub:relays.warning]]</p>
|
||||
<div class="mb-3 table-responsive-md">
|
||||
<table class="table table-striped" id="relays">
|
||||
<thead>
|
||||
<th>[[admin/settings/activitypub:relays.relay]]</th>
|
||||
<th>[[admin/settings/activitypub:relays.state]]</th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{{ each relays }}}
|
||||
<tr data-url="{./url}">
|
||||
<td>{./url}</td>
|
||||
<td>{./label}</td>
|
||||
<td><a href="#" data-action="relays.remove"><i class="fa fa-trash link-danger"></i></a></td>
|
||||
</tr>
|
||||
{{{ end }}}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
<button class="btn btn-sm btn-primary" data-action="relays.add">[[admin/settings/activitypub:relays.add]]</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IMPORT admin/partials/settings/toc.tpl -->
|
||||
<div component="settings/main/header" class="row border-bottom py-2 m-0 mb-3 sticky-top acp-page-main-header align-items-center">
|
||||
<div class="col-12 col-md-8 px-0 mb-1 mb-md-0">
|
||||
<h4 class="fw-bold tracking-tight mb-0">{title}</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row flex-column-reverse flex-md-row">
|
||||
<div id="relays" class="col-12 col-md-4">
|
||||
<p>[[admin/settings/activitypub:relays.intro]]</p>
|
||||
<p class="text-warning">[[admin/settings/activitypub:relays.warning]]</p>
|
||||
<div class="mb-3 table-responsive-md">
|
||||
<table class="table table-striped" id="relays">
|
||||
<thead>
|
||||
<th>[[admin/settings/activitypub:relays.relay]]</th>
|
||||
<th>[[admin/settings/activitypub:relays.state]]</th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{{ each relays }}}
|
||||
<tr data-url="{./url}">
|
||||
<td>{./url}</td>
|
||||
<td>{./label}</td>
|
||||
<td><a href="#" data-action="relays.remove"><i class="fa fa-trash link-danger"></i></a></td>
|
||||
</tr>
|
||||
{{{ end }}}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
<button class="btn btn-sm btn-primary" data-action="relays.add">[[admin/settings/activitypub:relays.add]]</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="mb-3 row">
|
||||
<div class="col-6">
|
||||
<label class="form-label" for="hostFilter">[[admin/settings/activitypub:analytics.by-hostname]] ({relays.length})</label>
|
||||
<select class="form-select" autocomplete="off" id="hostFilter">
|
||||
<option value="">All relays</option>
|
||||
{{{ each relays }}}
|
||||
<option value="{./url}">{./url}</option>
|
||||
{{{ end }}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="form-label" for="term">[[admin/settings/activitypub:analytics.term]]</label>
|
||||
<select class="form-select" autocomplete="off" id="term">
|
||||
<option value="hourly">[[admin/settings/activitypub:analytics.hourly]]</option>
|
||||
<option value="daily">[[admin/settings/activitypub:analytics.daily]]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<canvas height="350"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user