Files
wekan/client/components/settings/problemsSummary.js
T
Lauri Ojansivu 5a1c9a61e9 Admin Panel / Problems "Status" overview + snap run wekan.problems: show migrations/repairs in progress and login-page "Must be logged in" causes; run repairs server-side.
Motivated by the 10.16-10.21 reports where, after an upgrade, the login page
showed "Must be logged in" or sat on the logo + "Loading, please wait." spinner,
pages took 5+ minutes, All Boards counts stayed 0 and i18n showed raw keys — the
FerretDB v1 (SQLite) process pegged ~2 cores for a long time under WeKan's poll
load, i.e. a migration/repair or a saturated backend, with no way to see it
without the Admin Panel.

Server-side, independent of any client / progress page:
 - server/startup/repairBoardsOnStartup.js runs the shared board data-repairs once
   (version-gated, background, 30s after boot) so corrupted data is fixed even if
   no user opens a board and nobody watches a progress page.
 - The MongoDB <-> SQLite text migration and the startup repair now PERSIST their
   progress to the text_migration_status collection (models/textMigrationStatus.js),
   so a separate process can see "a migration/repair is in progress" (an in-memory
   object could not).

Status overview (server/lib/systemStatus.js), shown in Admin Panel / Problems ->
Summary and via the method systemStatusReport (admin-gated, server/methods/
systemStatus.js):
 - everything in progress (text migration, board repair, recovery, cron migration,
   attachment migration) aggregated from the persisted status collections;
 - detected problems (broken cards; failing login-page checks);
 - login-page "Must be logged in" / "Loading, please wait" cause checklist
   (migration in progress, ROOT_URL, LDAP, Sandstorm) — models/lib/loginProblems.js;
 - a CPU detail area that calls out a pegged ferretdb process and the mitigation.

Snap: `snap run wekan.problems` (snap-src/bin/wekan-problems[.mjs], snapcraft app
`problems`) prints the same overview as text — problems, migrations/repairs in
progress, login checks — so an admin with only server access can see it WITHOUT
Admin Panel access. Sub-commands: migrations | login | broken-cards | cpu | help.

Pure helpers models/lib/loginProblems.js and models/lib/problemsOverview.js are
unit-tested; tests/problemsStatus.test.cjs (14 checks) also source-guards the
server hub, method, startup pass, migration persistence and snap command.

Thanks to xet7 !
2026-07-22 16:22:39 +03:00

72 lines
2.4 KiB
JavaScript

import { TAPi18n } from '/imports/i18n';
// Admin Panel → Problems → Summary page: a checkbox list of problem areas
// (Security / Speed / Tests) with each area's menu path and its count of NEW
// (unacknowledged) problems, plus ONE Acknowledge button that acknowledges every
// checked area. This is the ONLY place problems are acknowledged — the Security /
// Speed / Tests detail pages are read-only.
// Design: docs/Security/Remediation/WeKan.md. Data: the admin-only
// eventLogProblemAreas / acknowledgeEventLog methods (models/eventLog.js).
const STREAM_LABEL_KEY = {
security: 'securityReportTitle',
speed: 'speedReportTitle',
tests: 'testsReportTitle',
};
Template.problemsSummary.onCreated(function () {
this.areas = new ReactiveVar([]);
// Status overview: everything in progress (migrations / repairs) + detected
// problems (broken cards, login-page "Must be logged in" causes). Same data as
// `snap run wekan.problems`.
this.status = new ReactiveVar(null);
this.reload = () => {
Meteor.call('eventLogProblemAreas', (err, res) => {
if (!err) this.areas.set(Array.isArray(res) ? res : []);
});
Meteor.call('systemStatusReport', (err, res) => {
if (!err) this.status.set(res || null);
});
};
this.reload();
this.timer = Meteor.setInterval(this.reload, 30000);
});
Template.problemsSummary.onDestroyed(function () {
if (this.timer) Meteor.clearInterval(this.timer);
});
Template.problemsSummary.helpers({
areas() {
return Template.instance().areas.get();
},
hasProblems() {
return (Template.instance().areas.get() || []).length > 0;
},
areaLabel(stream) {
return TAPi18n.__(STREAM_LABEL_KEY[stream] || stream);
},
statusOverview() {
const s = Template.instance().status.get();
return s && s.overview ? s.overview : null;
},
inProgress() {
const s = Template.instance().status.get();
return s && s.overview ? s.overview.inProgress : [];
},
statusProblems() {
const s = Template.instance().status.get();
return s && s.overview ? s.overview.problems : [];
},
});
Template.problemsSummary.events({
'click .js-ack-checked'(event, templateInstance) {
const streams = Array.from(templateInstance.findAll('.js-problem-check:checked'))
.map(el => el.getAttribute('data-stream'))
.filter(Boolean);
if (!streams.length) return;
Meteor.call('acknowledgeEventLog', streams, () => templateInstance.reload());
},
});