feat: track and show sent activities as well

This commit is contained in:
Julian Lam
2026-04-01 14:00:04 -04:00
parent cb8b0bb3a3
commit 0b80cf1c92
6 changed files with 66 additions and 33 deletions

View File

@@ -66,5 +66,7 @@
"content.world-default-cid": "Default category ID for "World" page composer",
"analytics.intro": "From this page you can view the state of your instance's federation with other servers",
"analytics.activities": "Received Activities"
"analytics.by-hostname": "Filter by Hostname",
"analytics.activities": "Received Activities",
"analytics.sent": "Sent Activites"
}

View File

@@ -27,17 +27,20 @@ export function init() {
const hostFilterEl = document.getElementById('hostFilter');
if (hostFilterEl) {
hostFilterEl.addEventListener('change', async function () {
const { activities } = await get(`/api${ajaxify.data.url}?host=${this.value}`);
const chart = charts.get('activities');
chart.data.datasets[0].data = activities;
chart.update();
const data = await get(`/api${ajaxify.data.url}?host=${this.value}`);
['activities', 'sent'].forEach((name) => {
const chart = charts.get(name);
chart.data.datasets[0].data = data[name];
chart.update();
});
});
}
}
function initializeCharts() {
const activitiesCanvas = document.getElementById('activities');
// const dailyCanvas = document.getElementById('pageviews:daily');
const sentCanvas = document.getElementById('sent');
// const topicsCanvas = document.getElementById('topics:daily');
// const postsCanvas = document.getElementById('posts:daily');
const hourlyLabels = utils.getHoursArray().map(function (text, idx) {
@@ -73,19 +76,19 @@ function initializeCharts() {
},
],
},
// 'pageviews:daily': {
// labels: dailyLabels,
// datasets: [
// {
// ...commonDataSetOpts,
// 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.analytics['pageviews:daily'],
// },
// ],
// },
'sent': {
labels: hourlyLabels,
datasets: [
{
...commonDataSetOpts,
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.sent,
},
],
},
// 'topics:daily': {
// labels: dailyLabels.slice(-7),
// datasets: [
@@ -115,7 +118,7 @@ function initializeCharts() {
};
activitiesCanvas.width = $(activitiesCanvas).parent().width();
// dailyCanvas.width = $(dailyCanvas).parent().width();
sentCanvas.width = $(sentCanvas).parent().width();
// topicsCanvas.width = $(topicsCanvas).parent().width();
// postsCanvas.width = $(postsCanvas).parent().width();
@@ -135,14 +138,13 @@ function initializeCharts() {
data: data.activities,
options: chartOpts,
})],
['sent', new Chart(sentCanvas.getContext('2d'), {
type: 'line',
data: data.sent,
options: chartOpts,
})],
]);
// new Chart(dailyCanvas.getContext('2d'), {
// type: 'line',
// data: data['pageviews:daily'],
// options: chartOpts,
// });
// new Chart(topicsCanvas.getContext('2d'), {
// type: 'line',
// data: data['topics:daily'],

View File

@@ -381,6 +381,7 @@ ActivityPub._sendMessage = async function (uri, id, type, payload) {
});
if (String(response.statusCode).startsWith('2')) {
ActivityPub.record.send(payload.type, uri);
ActivityPub.helpers.log(`[activitypub/send] Successfully sent ${payload.type} to ${uri}`);
return true;
}
@@ -455,7 +456,9 @@ ActivityPub.send = async (type, id, targets, payload) => {
}).catch(err => winston.error(err.stack));
};
ActivityPub.record = async ({ id, type, actor }) => {
ActivityPub.record = {};
ActivityPub.record.receipt = async ({ id, type, actor }) => {
const now = Date.now();
const { hostname } = new URL(actor);
@@ -466,6 +469,15 @@ ActivityPub.record = async ({ id, type, actor }) => {
]);
};
ActivityPub.record.send = async ({ type, target }) => {
const { hostname } = new URL(target);
await Promise.all([
ActivityPub.instances.log(hostname),
analytics.increment(['ap.out', `ap.out:byType:${type}`, `ap.out:byHost:${hostname}`]),
]);
};
ActivityPub.buildRecipients = async function (object, options) {
/**
* - Builds a list of targets for activitypub.send to consume

View File

@@ -269,7 +269,7 @@ Controller.postInbox = async (req, res) => {
try {
await activitypub.inbox[method](req);
await activitypub.record(req.body);
await activitypub.record.receipt(req.body);
await helpers.formatApiResponse(202, res);
} catch (e) {
helpers.formatApiResponse(500, res, e).catch(err => winston.error(err.stack));

View File

@@ -61,11 +61,14 @@ federationController.analytics = async function (req, res) {
host = undefined;
}
const set = host ? `activities:byHost:${host}` : 'activities';
const sentSet = host ? `ap.out:byHost:${host}` : 'ap:out';
const activities = await analytics.getHourlyStatsForSet(set, Date.now(), 24);
const sent = await analytics.getHourlyStatsForSet(sentSet, Date.now(), 24);
res.render('admin/federation/analytics', {
title: '[[admin/menu:federation/analytics]]',
instances,
activities,
sent,
});
};

View File

@@ -3,12 +3,10 @@
<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:analytics.intro]]</p>
</div>
<div class="mb-4">
<label class="fs-5 fw-bold tracking-tight settings-header mb-3">[[admin/settings/activitypub:analytics.activities]]</label>
<p class="lead">[[admin/settings/activitypub:analytics.intro]]</p>
<label class="fs-5 fw-bold tracking-tight settings-header mb-3">[[admin/settings/activitypub:analytics.by-hostname]] ({instances.length})</label>
<div class="mb-3">
<select class="form-select" autocomplete="off" id="hostFilter">
<option value="">All instances</option>
@@ -17,8 +15,13 @@
{{{ end }}}
</select>
</div>
</div>
<hr />
<div class="mb-4">
<div class="card">
<div class="card-header">[[admin/settings/activitypub:analytics.activities]]</div>
<div class="card-body">
<div class="position-relative" style="aspect-ratio: 2;">
<canvas id="activities" height="250"></canvas>
@@ -26,6 +29,17 @@
</div>
</div>
</div>
<div class="mb-4">
<div class="card">
<div class="card-header">[[admin/settings/activitypub:analytics.sent]]</div>
<div class="card-body">
<div class="position-relative" style="aspect-ratio: 2;">
<canvas id="sent" height="250"></canvas>
</div>
</div>
</div>
</div>
</div>
<!-- IMPORT admin/partials/settings/toc.tpl -->