Skip to content

Commit c03ade5

Browse files
authored
Add log records and list them in plugin admin (#153)
1 parent 4dbc454 commit c03ade5

File tree

25 files changed

+632
-4
lines changed

25 files changed

+632
-4
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseActivityPub
4+
module Admin
5+
class LogController < DiscourseActivityPub::Admin::AdminController
6+
ORDER_BY = { "level" => "level" }
7+
8+
def index
9+
logs = DiscourseActivityPubLog.all
10+
11+
offset = params[:offset].to_i || 0
12+
load_more_query_params = { offset: offset + 1 }
13+
load_more_query_params[:order] = params[:order] if !params[:order].nil?
14+
load_more_query_params[:asc] = params[:asc] if !params[:asc].nil?
15+
16+
total = logs.count
17+
order = ORDER_BY.fetch(params[:order], "created_at")
18+
direction = params[:asc] == "true" ? "ASC" : "DESC"
19+
logs = logs.order("#{order} #{direction}").limit(page_limit).offset(offset * page_limit)
20+
21+
load_more_url = URI("/admin/plugins/ap/log.json")
22+
load_more_url.query = ::URI.encode_www_form(load_more_query_params)
23+
24+
render_serialized(
25+
logs,
26+
DiscourseActivityPub::Admin::LogSerializer,
27+
root: "logs",
28+
meta: {
29+
total: total,
30+
load_more_url: load_more_url.to_s,
31+
},
32+
)
33+
end
34+
35+
protected
36+
37+
def page_limit
38+
30
39+
end
40+
end
41+
end
42+
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# frozen_string_literal: true
2+
3+
module Jobs
4+
class DiscourseActivityPubLogRotate < ::Jobs::Scheduled
5+
every 1.day
6+
7+
def execute(args)
8+
DiscourseActivityPubLog.where(
9+
"created_at < ?",
10+
SiteSetting.activity_pub_logs_max_days_old.days.ago,
11+
).destroy_all
12+
end
13+
end
14+
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
3+
class DiscourseActivityPubLog < ActiveRecord::Base
4+
enum :level, %i[info warn error]
5+
6+
validates :message, presence: true
7+
validates :level, presence: true
8+
end
9+
10+
# == Schema Information
11+
#
12+
# Table name: discourse_activity_pub_logs
13+
#
14+
# id :bigint not null, primary key
15+
# level :integer
16+
# message :string
17+
# json :json
18+
# created_at :datetime not null
19+
# updated_at :datetime not null
20+
#
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseActivityPub
4+
class Admin::LogSerializer < ActiveModel::Serializer
5+
attributes :id, :created_at, :level, :message, :json
6+
end
7+
end

assets/javascripts/discourse/activity-pub-admin-route-map.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export default {
55
this.route("activityPub", { path: "/ap" }, function () {
66
this.route("actor");
77
this.route("actorShow", { path: "/actor/:actor_id" });
8+
this.route("log");
89
});
910
},
1011
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<DModal
2+
@closeModal={{@closeModal}}
3+
@title={{i18n "admin.discourse_activity_pub.log.json.title"}}
4+
class="activity-pub-json-modal"
5+
>
6+
<:body>
7+
<div class="activity-pub-json-modal-header">
8+
<div class="activity-pub-json-modal-title">
9+
{{htmlSafe
10+
(i18n
11+
"admin.discourse_activity_pub.log.json.logged_at"
12+
logged_at=(formatDate
13+
@model.log.created_at format="medium" leaveAgo="true"
14+
)
15+
)
16+
}}
17+
</div>
18+
<div class="activity-pub-json-modal-buttons">
19+
{{#if this.copied}}
20+
<span class="activity-pub-json-copy-status success">
21+
{{i18n "admin.discourse_activity_pub.log.json.copy.success"}}
22+
</span>
23+
{{/if}}
24+
<DButton
25+
@action={{this.copyToClipboard}}
26+
@icon="copy"
27+
@label="admin.discourse_activity_pub.log.json.copy.label"
28+
@title="admin.discourse_activity_pub.log.json.copy.title"
29+
class="activity-pub-json-copy-btn btn-default"
30+
/>
31+
</div>
32+
</div>
33+
<pre class="activity-pub-json-display">{{this.jsonDisplay}}</pre>
34+
</:body>
35+
</DModal>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Component from "@glimmer/component";
2+
import { tracked } from "@glimmer/tracking";
3+
import { action } from "@ember/object";
4+
import { clipboardCopy } from "discourse/lib/utilities";
5+
import discourseLater from "discourse-common/lib/later";
6+
7+
export default class ActivityPubLogJson extends Component {
8+
@tracked copied = false;
9+
10+
get jsonDisplay() {
11+
return JSON.stringify(this.args.model.log.json, null, 4);
12+
}
13+
14+
@action
15+
copyToClipboard() {
16+
clipboardCopy(this.args.model.log.json);
17+
this.copied = true;
18+
discourseLater(() => {
19+
this.copied = false;
20+
}, 3000);
21+
}
22+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { tracked } from "@glimmer/tracking";
2+
import Controller from "@ember/controller";
3+
import { action } from "@ember/object";
4+
import { notEmpty } from "@ember/object/computed";
5+
import { service } from "@ember/service";
6+
import { ajax } from "discourse/lib/ajax";
7+
import { popupAjaxError } from "discourse/lib/ajax-error";
8+
import ActivityPubLogJsonModal from "../components/modal/activity-pub-log-json";
9+
import ActivityPubLog from "../models/activity-pub-log";
10+
11+
export default class AdminPluginsActivityPubLog extends Controller {
12+
@service modal;
13+
@service router;
14+
@tracked order = "";
15+
@tracked asc = null;
16+
loadMoreUrl = "";
17+
total = "";
18+
19+
@notEmpty("logs") hasLogs;
20+
21+
queryParams = ["order", "asc"];
22+
23+
@action
24+
loadMore() {
25+
if (!this.loadMoreUrl || this.total <= this.logs.length) {
26+
return;
27+
}
28+
29+
this.set("loadingMore", true);
30+
31+
return ajax(this.loadMoreUrl)
32+
.then((response) => {
33+
if (response) {
34+
this.logs.pushObjects(
35+
(response.logs || []).map((log) => {
36+
return ActivityPubLog.create(log);
37+
})
38+
);
39+
this.setProperties({
40+
loadMoreUrl: response.meta.load_more_url,
41+
total: response.meta.total,
42+
loadingMore: false,
43+
});
44+
}
45+
})
46+
.catch(popupAjaxError);
47+
}
48+
49+
@action
50+
updateOrder(field, asc) {
51+
this.setProperties({
52+
order: field,
53+
asc,
54+
});
55+
}
56+
57+
@action
58+
showJson(log) {
59+
this.modal.show(ActivityPubLogJsonModal, {
60+
model: {
61+
log,
62+
},
63+
});
64+
}
65+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import EmberObject from "@ember/object";
2+
import { ajax } from "discourse/lib/ajax";
3+
import { popupAjaxError } from "discourse/lib/ajax-error";
4+
5+
export const logAdminPath = "/admin/plugins/ap/log";
6+
7+
class ActivityPubLog extends EmberObject {}
8+
9+
ActivityPubLog.reopenClass({
10+
list(params) {
11+
const queryParams = new URLSearchParams();
12+
13+
if (params.order) {
14+
queryParams.set("order", params.order);
15+
}
16+
17+
if (params.asc) {
18+
queryParams.set("asc", params.asc);
19+
}
20+
21+
const path = logAdminPath;
22+
23+
let url = `${path}.json`;
24+
if (queryParams.size) {
25+
url += `?${queryParams.toString()}`;
26+
}
27+
28+
return ajax(url).catch(popupAjaxError);
29+
},
30+
});
31+
32+
export default ActivityPubLog;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { A } from "@ember/array";
2+
import DiscourseRoute from "discourse/routes/discourse";
3+
import ActivityPubLog from "../models/activity-pub-log";
4+
5+
export default class AdminPluginsActivityPubLogRoute extends DiscourseRoute {
6+
queryParams = {
7+
order: { refreshModel: true },
8+
asc: { refreshModel: true },
9+
};
10+
11+
model(params) {
12+
return ActivityPubLog.list(params);
13+
}
14+
15+
setupController(controller, model) {
16+
controller.setProperties({
17+
logs: A(
18+
(model.logs || []).map((actor) => {
19+
return ActivityPubLog.create(actor);
20+
})
21+
),
22+
});
23+
}
24+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<div class="admin-title activity-pub-log-title">
2+
<h2>{{i18n "admin.discourse_activity_pub.log.title"}}</h2>
3+
</div>
4+
5+
<LoadMore
6+
@selector=".directory-table .directory-table__cell"
7+
@action={{action "loadMore"}}
8+
class="activity-pub-logs-container"
9+
>
10+
{{#if this.hasLogs}}
11+
<ResponsiveTable @className="activity-pub-log-table">
12+
<:header>
13+
<TableHeaderToggle
14+
@onToggle={{this.updateOrder}}
15+
@field="created_at"
16+
@labelKey="admin.discourse_activity_pub.log.created_at"
17+
@automatic={{true}}
18+
@order={{this.order}}
19+
@asc={{this.asc}}
20+
/>
21+
<TableHeaderToggle
22+
@onToggle={{this.updateOrder}}
23+
@field="level"
24+
@labelKey="admin.discourse_activity_pub.log.level"
25+
@automatic={{true}}
26+
@order={{this.order}}
27+
@asc={{this.asc}}
28+
/>
29+
<TableHeaderToggle
30+
@field="message"
31+
@labelKey="admin.discourse_activity_pub.log.message"
32+
@automatic={{true}}
33+
@order={{this.order}}
34+
@asc={{this.asc}}
35+
/>
36+
<div
37+
class="activity-pub-json-table-json directory-table__column-header"
38+
>
39+
<div class="header-contents">
40+
{{i18n "admin.discourse_activity_pub.log.json.label"}}
41+
</div>
42+
</div>
43+
</:header>
44+
<:body>
45+
{{#each this.logs as |log|}}
46+
<div class="directory-table__row activity-pub-log-row">
47+
<div class="directory-table__cell activity-pub-log-created-at">
48+
{{formatDate log.created_at leaveAgo="true"}}
49+
</div>
50+
<div class="directory-table__cell activity-pub-log-level">
51+
{{log.level}}
52+
</div>
53+
<div class="directory-table__cell activity-pub-log-message">
54+
{{log.message}}
55+
</div>
56+
<div class="directory-table__cell activity-pub-log-json">
57+
{{#if log.json}}
58+
<DButton
59+
@action={{action "showJson" log}}
60+
@icon="code"
61+
@label="admin.discourse_activity_pub.log.json.show.label"
62+
@title="admin.discourse_activity_pub.log.json.show.title"
63+
class="activity-pub-log-show-json-btn"
64+
/>
65+
{{/if}}
66+
</div>
67+
</div>
68+
{{/each}}
69+
</:body>
70+
</ResponsiveTable>
71+
72+
<ConditionalLoadingSpinner @condition={{this.loadingMore}} />
73+
{{else}}
74+
<p>{{i18n "search.no_results"}}</p>
75+
{{/if}}
76+
</LoadMore>

assets/javascripts/discourse/templates/admin-plugins-activity-pub.hbs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
<span>{{i18n "admin.discourse_activity_pub.actor.tag.label"}}</span>
1616
</LinkTo>
1717
</li>
18+
<li>
19+
<LinkTo @route="adminPlugins.activityPub.log">
20+
<span>{{i18n "admin.discourse_activity_pub.log.label"}}</span>
21+
</LinkTo>
22+
</li>
1823
<li class="activity-pub-add-actor">
1924
<LinkTo
2025
@route="adminPlugins.activityPub.actorShow"

0 commit comments

Comments
 (0)