@logboard/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +37 -0
- package/README.md +200 -0
- package/bin/logboard +536 -0
- package/client/logger.js +309 -0
- package/config/index.js +142 -0
- package/config.js +2 -0
- package/controllers/AnalyticsController.js +46 -0
- package/controllers/ApiAnalyticsController.js +129 -0
- package/controllers/ApiKeyController.js +58 -0
- package/controllers/AuthController.js +131 -0
- package/controllers/HealthController.js +56 -0
- package/controllers/LogController.js +197 -0
- package/controllers/OrgController.js +152 -0
- package/controllers/RoleConfigController.js +20 -0
- package/controllers/SettingsController.js +39 -0
- package/controllers/StreamController.js +55 -0
- package/controllers/UiController.js +789 -0
- package/controllers/UserController.js +79 -0
- package/lib/batchWriter.js +57 -0
- package/lib/cleanup.js +67 -0
- package/lib/ejs.js +103 -0
- package/lib/emitter.js +5 -0
- package/lib/healthMonitor.js +245 -0
- package/lib/logger.js +21 -0
- package/lib/streams.js +32 -0
- package/lib/theme.js +77 -0
- package/lib/userStore.js +13 -0
- package/lib/utils.js +44 -0
- package/middleware/apiKey.js +82 -0
- package/middleware/auth.js +55 -0
- package/middleware/ipWhitelist.js +59 -0
- package/middleware/org.js +85 -0
- package/middleware/pageAccess.js +20 -0
- package/middleware/rateLimit.js +29 -0
- package/middleware/roles.js +11 -0
- package/package.json +77 -0
- package/routes/alerts.js +18 -0
- package/routes/analytics.js +26 -0
- package/routes/api-analytics.js +30 -0
- package/routes/api-keys.js +12 -0
- package/routes/archive.js +91 -0
- package/routes/audit.js +50 -0
- package/routes/auth.js +22 -0
- package/routes/bookmarks.js +13 -0
- package/routes/health.js +11 -0
- package/routes/logs.js +88 -0
- package/routes/metrics.js +66 -0
- package/routes/notifications.js +14 -0
- package/routes/orgs.js +98 -0
- package/routes/registration.js +202 -0
- package/routes/role-config.js +97 -0
- package/routes/saved-searches.js +12 -0
- package/routes/server.js +151 -0
- package/routes/settings.js +28 -0
- package/routes/status.js +21 -0
- package/routes/stream.js +11 -0
- package/routes/super.js +129 -0
- package/routes/ui.js +120 -0
- package/routes/users.js +13 -0
- package/server.js +172 -0
- package/services/AlertRulesService.js +323 -0
- package/services/AnalyticsService.js +665 -0
- package/services/ApiAnalyticsService.js +471 -0
- package/services/ApiKeyService.js +166 -0
- package/services/AuditService.js +249 -0
- package/services/AuthService.js +234 -0
- package/services/BookmarkService.js +49 -0
- package/services/GlobalSettingsService.js +44 -0
- package/services/LogService.js +1066 -0
- package/services/MetricsService.js +116 -0
- package/services/NotificationService.js +70 -0
- package/services/OrgService.js +217 -0
- package/services/ReportService.js +247 -0
- package/services/RoleConfigService.js +201 -0
- package/services/SavedSearchService.js +63 -0
- package/services/SettingsService.js +220 -0
- package/services/UserService.js +121 -0
- package/setup.js +132 -0
- package/views/404.ejs +8 -0
- package/views/alerts.ejs +190 -0
- package/views/analytics.ejs +209 -0
- package/views/api-analytics.ejs +660 -0
- package/views/api-keys.ejs +150 -0
- package/views/archive.ejs +123 -0
- package/views/audit.ejs +314 -0
- package/views/bookmarks.ejs +54 -0
- package/views/custom-dashboard.ejs +162 -0
- package/views/dashboard.ejs +186 -0
- package/views/diff.ejs +98 -0
- package/views/health.ejs +269 -0
- package/views/heatmap.ejs +126 -0
- package/views/insights.ejs +334 -0
- package/views/invite.ejs +74 -0
- package/views/live.ejs +299 -0
- package/views/login.ejs +64 -0
- package/views/logo.png +0 -0
- package/views/logs.ejs +754 -0
- package/views/notifications.ejs +58 -0
- package/views/partials/head.ejs +282 -0
- package/views/partials/sidebar.ejs +168 -0
- package/views/register.ejs +100 -0
- package/views/roles.ejs +279 -0
- package/views/saved-searches.ejs +51 -0
- package/views/service-map.ejs +142 -0
- package/views/settings.ejs +1159 -0
- package/views/sidebar.ejs +129 -0
- package/views/status.ejs +100 -0
- package/views/super-admin-admins.ejs +58 -0
- package/views/super-admin-analytics.ejs +49 -0
- package/views/super-admin-orgs.ejs +310 -0
- package/views/super-admin-profile.ejs +77 -0
- package/views/super-admin-settings.ejs +108 -0
- package/views/super-admin-system.ejs +46 -0
- package/views/users.ejs +153 -0
|
@@ -0,0 +1,789 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const config = require('../config');
|
|
3
|
+
const { getToday } = require('../lib/utils');
|
|
4
|
+
|
|
5
|
+
class UiController {
|
|
6
|
+
constructor (logService, analyticsService) {
|
|
7
|
+
this.logs = logService;
|
|
8
|
+
this.analytics = analyticsService;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Returns an org-scoped LogService for the current request
|
|
12
|
+
_logSvc (req) {
|
|
13
|
+
const LogService = require('../services/LogService');
|
|
14
|
+
return new LogService(this.logs.batchWriter, req.org);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Returns an org-scoped AnalyticsService for the current request
|
|
18
|
+
_analyticsSvc (req) {
|
|
19
|
+
const AnalyticsService = require('../services/AnalyticsService');
|
|
20
|
+
return new AnalyticsService(req.org);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async _base (req, extra = {}) {
|
|
24
|
+
try {
|
|
25
|
+
const { RoleConfigService } = require('../services/RoleConfigService');
|
|
26
|
+
const SettingsService = require('../services/SettingsService');
|
|
27
|
+
const org = req.org || null;
|
|
28
|
+
const role = req.user?.role || 'viewer';
|
|
29
|
+
|
|
30
|
+
// Super-admin: no org context, use global settings only
|
|
31
|
+
if (role === 'super-admin') {
|
|
32
|
+
const { buildThemeSnippet } = require('../lib/theme');
|
|
33
|
+
let settings = {};
|
|
34
|
+
try { settings = await new SettingsService(null).get(); } catch {}
|
|
35
|
+
return {
|
|
36
|
+
user: req.user, allowedPages: [], allowedCards: [],
|
|
37
|
+
settings, appName: settings.appName || 'LogBoard',
|
|
38
|
+
appLogoUrl: settings.appLogoUrl || '/public/logo.png',
|
|
39
|
+
themeSnippet: buildThemeSnippet(settings), ...extra,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const rc = new RoleConfigService(org);
|
|
44
|
+
const [allowedPages, allowedCards, settings] = await Promise.all([
|
|
45
|
+
rc.getAllowedPages(role),
|
|
46
|
+
rc.getAllowedCards(role),
|
|
47
|
+
new SettingsService(org).get(),
|
|
48
|
+
]);
|
|
49
|
+
const { buildThemeSnippet } = require('../lib/theme');
|
|
50
|
+
return {
|
|
51
|
+
user: req.user,
|
|
52
|
+
allowedPages,
|
|
53
|
+
allowedCards,
|
|
54
|
+
settings,
|
|
55
|
+
appName: settings.appName || 'LogBoard',
|
|
56
|
+
appLogoUrl: settings.appLogoUrl || '/public/logo.png',
|
|
57
|
+
themeSnippet: buildThemeSnippet(settings),
|
|
58
|
+
...extra,
|
|
59
|
+
};
|
|
60
|
+
} catch {
|
|
61
|
+
return {
|
|
62
|
+
user: req.user,
|
|
63
|
+
allowedPages: null,
|
|
64
|
+
allowedCards: [],
|
|
65
|
+
settings: {},
|
|
66
|
+
appName: 'LogBoard',
|
|
67
|
+
appLogoUrl: '/public/logo.png',
|
|
68
|
+
themeSnippet: '',
|
|
69
|
+
...extra,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async loginPage (req, res) {
|
|
75
|
+
try {
|
|
76
|
+
const SettingsService = require('../services/SettingsService');
|
|
77
|
+
const s = await new SettingsService(req.org).get();
|
|
78
|
+
res.render('login', {
|
|
79
|
+
title: 'Login',
|
|
80
|
+
error: null,
|
|
81
|
+
settings: s,
|
|
82
|
+
themeSnippet: require('../lib/theme').buildThemeSnippet(s),
|
|
83
|
+
appName: s.appName || 'LogBoard',
|
|
84
|
+
appLogoUrl: s.appLogoUrl || '/public/logo.png',
|
|
85
|
+
});
|
|
86
|
+
} catch {
|
|
87
|
+
res.render('login', {
|
|
88
|
+
title: 'Login',
|
|
89
|
+
error: null,
|
|
90
|
+
settings: {},
|
|
91
|
+
appName: 'LogBoard',
|
|
92
|
+
appLogoUrl: '/public/logo.png',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async dashboard (req, res) {
|
|
98
|
+
try {
|
|
99
|
+
const { RoleConfigService } = require('../services/RoleConfigService');
|
|
100
|
+
const AnalyticsService = require('../services/AnalyticsService');
|
|
101
|
+
const role = req.user?.role || 'viewer';
|
|
102
|
+
const allowedApps = await new RoleConfigService(req.org).getAllowedApps(
|
|
103
|
+
role,
|
|
104
|
+
);
|
|
105
|
+
const [overview, hourly, errors, trend] = await Promise.all([
|
|
106
|
+
new AnalyticsService(req.org).getOverview(allowedApps),
|
|
107
|
+
new AnalyticsService(req.org).getHourlyVolume(
|
|
108
|
+
null,
|
|
109
|
+
getToday(),
|
|
110
|
+
allowedApps,
|
|
111
|
+
),
|
|
112
|
+
new AnalyticsService(req.org).getRecentErrors(10, allowedApps),
|
|
113
|
+
new AnalyticsService(req.org).getDailyTrend(null, 7, allowedApps),
|
|
114
|
+
]);
|
|
115
|
+
const bp = await this._base(req);
|
|
116
|
+
// Load per-user dashboard order
|
|
117
|
+
let dashOrder = null;
|
|
118
|
+
try {
|
|
119
|
+
const path = require('path'),
|
|
120
|
+
fsp = require('fs').promises;
|
|
121
|
+
const orderFile = path.join(
|
|
122
|
+
require('../config').DATA_DIR,
|
|
123
|
+
`dashorder-${ req.user.username }.json`,
|
|
124
|
+
);
|
|
125
|
+
const raw = await fsp.readFile(orderFile, 'utf8');
|
|
126
|
+
dashOrder = JSON.parse(raw);
|
|
127
|
+
} catch {}
|
|
128
|
+
res.render('dashboard', {
|
|
129
|
+
...bp,
|
|
130
|
+
title: 'Dashboard',
|
|
131
|
+
overview,
|
|
132
|
+
hourly,
|
|
133
|
+
errors,
|
|
134
|
+
trend,
|
|
135
|
+
today: getToday(),
|
|
136
|
+
dashOrder,
|
|
137
|
+
});
|
|
138
|
+
} catch (err) {
|
|
139
|
+
const bp = await this._base(req);
|
|
140
|
+
res.render('dashboard', {
|
|
141
|
+
...bp,
|
|
142
|
+
title: 'Dashboard',
|
|
143
|
+
overview: null,
|
|
144
|
+
hourly: null,
|
|
145
|
+
errors: [],
|
|
146
|
+
trend: [],
|
|
147
|
+
today: getToday(),
|
|
148
|
+
dashOrder: null,
|
|
149
|
+
renderError: err.message,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async logsPage (req, res) {
|
|
155
|
+
try {
|
|
156
|
+
const allServices = await this._logSvc(req).getServices();
|
|
157
|
+
// Per-service access control: filter by role's allowedApps
|
|
158
|
+
const { RoleConfigService } = require('../services/RoleConfigService');
|
|
159
|
+
const role = req.user?.role || 'viewer';
|
|
160
|
+
const allowedApps = await new RoleConfigService(req.org).getAllowedApps(
|
|
161
|
+
role,
|
|
162
|
+
);
|
|
163
|
+
const services = allowedApps.length
|
|
164
|
+
? allServices.filter((s) => allowedApps.includes(s.appName))
|
|
165
|
+
: allServices; // empty = all allowed
|
|
166
|
+
const { service, date, level, q, fromTime, toTime, fromDate, toDate }
|
|
167
|
+
= req.query;
|
|
168
|
+
let results = null;
|
|
169
|
+
// Enforce service access
|
|
170
|
+
if (service && allowedApps.length && !allowedApps.includes(service)) {
|
|
171
|
+
const bp2 = await this._base(req);
|
|
172
|
+
return res.render('logs', {
|
|
173
|
+
...bp2,
|
|
174
|
+
title: 'Logs',
|
|
175
|
+
services,
|
|
176
|
+
selected: {
|
|
177
|
+
service: '',
|
|
178
|
+
date: date || getToday(),
|
|
179
|
+
level: level || '',
|
|
180
|
+
q: q || '',
|
|
181
|
+
fromTime: '',
|
|
182
|
+
toTime: '',
|
|
183
|
+
fromDate: '',
|
|
184
|
+
toDate: '',
|
|
185
|
+
},
|
|
186
|
+
results: null,
|
|
187
|
+
today: getToday(),
|
|
188
|
+
accessError: `You do not have access to service: ${ service}`,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const effectiveDate
|
|
192
|
+
= date || (service ? getToday() : !fromDate ? getToday() : '');
|
|
193
|
+
// Search across all services when none selected but query/level provided
|
|
194
|
+
if (!service && (q || level) && effectiveDate) {
|
|
195
|
+
// Search all accessible services for the query
|
|
196
|
+
const allLines = [];
|
|
197
|
+
for (const { appName: svcName } of services.slice(0, 20)) {
|
|
198
|
+
try {
|
|
199
|
+
const r = await this._logSvc(req).search({
|
|
200
|
+
service: svcName,
|
|
201
|
+
date: effectiveDate,
|
|
202
|
+
level,
|
|
203
|
+
q,
|
|
204
|
+
limit: 50,
|
|
205
|
+
});
|
|
206
|
+
if (r && r.lines) {
|
|
207
|
+
// Tag each line with service name if not already tagged
|
|
208
|
+
r.lines.forEach((line) => {
|
|
209
|
+
try {
|
|
210
|
+
const p = JSON.parse(line);
|
|
211
|
+
if (!p.appName) { p.appName = svcName; }
|
|
212
|
+
allLines.push(JSON.stringify(p));
|
|
213
|
+
} catch {
|
|
214
|
+
allLines.push(line);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
} catch {}
|
|
219
|
+
}
|
|
220
|
+
results = { lines: allLines.slice(0, 500), total: allLines.length };
|
|
221
|
+
} else if (service && effectiveDate) {
|
|
222
|
+
if (fromTime || toTime) {
|
|
223
|
+
results = await this._logSvc(req).searchTimeRange(service, effectiveDate, {
|
|
224
|
+
fromTime,
|
|
225
|
+
toTime,
|
|
226
|
+
level,
|
|
227
|
+
q,
|
|
228
|
+
limit: 500,
|
|
229
|
+
});
|
|
230
|
+
} else if (level || q) {
|
|
231
|
+
results = await this._logSvc(req).search({
|
|
232
|
+
service,
|
|
233
|
+
date: effectiveDate,
|
|
234
|
+
level,
|
|
235
|
+
q,
|
|
236
|
+
limit: 500,
|
|
237
|
+
});
|
|
238
|
+
} else {
|
|
239
|
+
results = await this._logSvc(req).tail(service, effectiveDate, 200);
|
|
240
|
+
}
|
|
241
|
+
} else if (service && fromDate && toDate) {
|
|
242
|
+
results = await this._logSvc(req).searchMultiDate(service, fromDate, toDate, {
|
|
243
|
+
level,
|
|
244
|
+
q,
|
|
245
|
+
limit: 500,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
const bp = await this._base(req);
|
|
249
|
+
res.render('logs', {
|
|
250
|
+
...bp,
|
|
251
|
+
title: 'Logs',
|
|
252
|
+
services,
|
|
253
|
+
selected: {
|
|
254
|
+
service: service || '',
|
|
255
|
+
date: effectiveDate || getToday(),
|
|
256
|
+
level: level || '',
|
|
257
|
+
q: q || '',
|
|
258
|
+
fromTime: fromTime || '',
|
|
259
|
+
toTime: toTime || '',
|
|
260
|
+
fromDate: fromDate || '',
|
|
261
|
+
toDate: toDate || '',
|
|
262
|
+
},
|
|
263
|
+
results,
|
|
264
|
+
today: getToday(),
|
|
265
|
+
});
|
|
266
|
+
} catch (err) {
|
|
267
|
+
const bp = await this._base(req);
|
|
268
|
+
res.render('logs', {
|
|
269
|
+
...bp,
|
|
270
|
+
title: 'Logs',
|
|
271
|
+
services: [],
|
|
272
|
+
selected: {
|
|
273
|
+
service: '',
|
|
274
|
+
date: getToday(),
|
|
275
|
+
level: '',
|
|
276
|
+
q: '',
|
|
277
|
+
fromTime: '',
|
|
278
|
+
toTime: '',
|
|
279
|
+
fromDate: '',
|
|
280
|
+
toDate: '',
|
|
281
|
+
},
|
|
282
|
+
results: null,
|
|
283
|
+
today: getToday(),
|
|
284
|
+
renderError: err.message,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async livePage (req, res) {
|
|
290
|
+
try {
|
|
291
|
+
const services = await this._logSvc(req).getServices();
|
|
292
|
+
const bp = await this._base(req);
|
|
293
|
+
res.render('live', { ...bp, title: 'Live Stream', services });
|
|
294
|
+
} catch {
|
|
295
|
+
const bp = await this._base(req);
|
|
296
|
+
res.render('live', { ...bp, title: 'Live Stream', services: [] });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async insightsPage (req, res) {
|
|
301
|
+
try {
|
|
302
|
+
const { service, date } = req.query;
|
|
303
|
+
const today = getToday();
|
|
304
|
+
const selDate = date || today;
|
|
305
|
+
const ApiAnalyticsService = require('../services/ApiAnalyticsService');
|
|
306
|
+
const AnalyticsService = require('../services/AnalyticsService');
|
|
307
|
+
const _allowedInsights = await (async () => {
|
|
308
|
+
try {
|
|
309
|
+
const {
|
|
310
|
+
RoleConfigService,
|
|
311
|
+
} = require('../services/RoleConfigService');
|
|
312
|
+
return await new RoleConfigService(req.org).getAllowedApps(
|
|
313
|
+
req.user?.role || 'viewer',
|
|
314
|
+
);
|
|
315
|
+
} catch {
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
})();
|
|
319
|
+
const [logServices, apiServicesList, hourly, breakdown, topSvcs, trend]
|
|
320
|
+
= await Promise.all([
|
|
321
|
+
this._logSvc(req).getServices(),
|
|
322
|
+
new ApiAnalyticsService().getApiServices().catch(() => []),
|
|
323
|
+
new AnalyticsService(req.org).getHourlyVolume(
|
|
324
|
+
service || null,
|
|
325
|
+
selDate,
|
|
326
|
+
_allowedInsights,
|
|
327
|
+
),
|
|
328
|
+
new AnalyticsService(req.org).getLevelBreakdown(
|
|
329
|
+
service || null,
|
|
330
|
+
selDate,
|
|
331
|
+
),
|
|
332
|
+
new AnalyticsService(req.org).getTopServices(
|
|
333
|
+
selDate,
|
|
334
|
+
10,
|
|
335
|
+
_allowedInsights,
|
|
336
|
+
),
|
|
337
|
+
new AnalyticsService(req.org).getDailyTrend(
|
|
338
|
+
service || null,
|
|
339
|
+
7,
|
|
340
|
+
_allowedInsights,
|
|
341
|
+
),
|
|
342
|
+
]);
|
|
343
|
+
const bp = await this._base(req);
|
|
344
|
+
res.render('insights', {
|
|
345
|
+
...bp,
|
|
346
|
+
title: 'Insights',
|
|
347
|
+
logServices,
|
|
348
|
+
apiServices: apiServicesList,
|
|
349
|
+
selected: { service: service || '', date: selDate },
|
|
350
|
+
hourly,
|
|
351
|
+
breakdown,
|
|
352
|
+
topSvcs,
|
|
353
|
+
trend,
|
|
354
|
+
today,
|
|
355
|
+
});
|
|
356
|
+
} catch (err) {
|
|
357
|
+
const bp = await this._base(req);
|
|
358
|
+
res.render('insights', {
|
|
359
|
+
...bp,
|
|
360
|
+
title: 'Insights',
|
|
361
|
+
logServices: [],
|
|
362
|
+
apiServices: [],
|
|
363
|
+
selected: {},
|
|
364
|
+
hourly: null,
|
|
365
|
+
breakdown: null,
|
|
366
|
+
topSvcs: [],
|
|
367
|
+
trend: [],
|
|
368
|
+
today: getToday(),
|
|
369
|
+
renderError: err.message,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async healthPage (req, res) {
|
|
375
|
+
try {
|
|
376
|
+
const AnalyticsService = require('../services/AnalyticsService');
|
|
377
|
+
const { RoleConfigService } = require('../services/RoleConfigService');
|
|
378
|
+
let serviceHealth = [];
|
|
379
|
+
try {
|
|
380
|
+
const rawHealth = await new AnalyticsService(req.org).getServiceHealth();
|
|
381
|
+
const allowedApps2 = await new RoleConfigService(req.org).getAllowedApps(req.user?.role || 'viewer');
|
|
382
|
+
serviceHealth = allowedApps2.length ? rawHealth.filter(s => allowedApps2.includes(s.appName)) : rawHealth;
|
|
383
|
+
} catch {}
|
|
384
|
+
const bp = await this._base(req);
|
|
385
|
+
res.render('health', {
|
|
386
|
+
...bp,
|
|
387
|
+
title: 'Health',
|
|
388
|
+
serviceHealth,
|
|
389
|
+
});
|
|
390
|
+
} catch (err) {
|
|
391
|
+
const bp = await this._base(req);
|
|
392
|
+
res.render('health', { ...bp, title: 'Health', serviceHealth: [], renderError: err.message });
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async settingsPage (req, res) {
|
|
397
|
+
try {
|
|
398
|
+
const SettingsService = require('../services/SettingsService');
|
|
399
|
+
const AuthService = require('../services/AuthService');
|
|
400
|
+
const { PRESETS } = require('../lib/theme');
|
|
401
|
+
const [settings, totpStatus] = await Promise.all([
|
|
402
|
+
new SettingsService(req.org).get(),
|
|
403
|
+
new AuthService().getTotpStatus(req.user.username),
|
|
404
|
+
]);
|
|
405
|
+
const bp = await this._base(req);
|
|
406
|
+
res.render('settings', {
|
|
407
|
+
...bp,
|
|
408
|
+
title: 'Settings',
|
|
409
|
+
isAdmin: req.user.role === 'admin' || req.user.role === 'super-admin',
|
|
410
|
+
settings,
|
|
411
|
+
totpEnabled: totpStatus.enabled,
|
|
412
|
+
PRESETS,
|
|
413
|
+
});
|
|
414
|
+
} catch (err) {
|
|
415
|
+
res
|
|
416
|
+
.status(500)
|
|
417
|
+
.render('404', {
|
|
418
|
+
title: 'Error',
|
|
419
|
+
user: req.user || null,
|
|
420
|
+
settings: {},
|
|
421
|
+
appName: 'LogBoard',
|
|
422
|
+
appLogoUrl: '/public/logo.png',
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async usersPage (req, res) {
|
|
428
|
+
try {
|
|
429
|
+
const UserService = require('../services/UserService');
|
|
430
|
+
const { RoleConfigService } = require('../services/RoleConfigService');
|
|
431
|
+
const [users, rolesData] = await Promise.all([
|
|
432
|
+
new UserService().getAll(),
|
|
433
|
+
new RoleConfigService(req.org).getRoles(),
|
|
434
|
+
]);
|
|
435
|
+
const bp = await this._base(req);
|
|
436
|
+
res.render('users', {
|
|
437
|
+
...bp,
|
|
438
|
+
title: 'Users',
|
|
439
|
+
users,
|
|
440
|
+
roles: Object.keys(rolesData),
|
|
441
|
+
rolesData,
|
|
442
|
+
});
|
|
443
|
+
} catch (err) {
|
|
444
|
+
res
|
|
445
|
+
.status(500)
|
|
446
|
+
.render('404', {
|
|
447
|
+
title: 'Error',
|
|
448
|
+
user: req.user || null,
|
|
449
|
+
settings: {},
|
|
450
|
+
appName: 'LogBoard',
|
|
451
|
+
appLogoUrl: '/public/logo.png',
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async apiKeysPage (req, res) {
|
|
457
|
+
try {
|
|
458
|
+
const ApiKeyService = require('../services/ApiKeyService');
|
|
459
|
+
const keys = await new ApiKeyService().list();
|
|
460
|
+
const bp = await this._base(req);
|
|
461
|
+
const SCOPES = [
|
|
462
|
+
'logs:write',
|
|
463
|
+
'logs:read',
|
|
464
|
+
'analytics:read',
|
|
465
|
+
'health:read',
|
|
466
|
+
'stream:read',
|
|
467
|
+
];
|
|
468
|
+
res.render('api-keys', { ...bp, title: 'API Keys', keys, SCOPES });
|
|
469
|
+
} catch (err) {
|
|
470
|
+
res
|
|
471
|
+
.status(500)
|
|
472
|
+
.render('404', {
|
|
473
|
+
title: 'Error',
|
|
474
|
+
user: req.user || null,
|
|
475
|
+
settings: {},
|
|
476
|
+
appName: 'LogBoard',
|
|
477
|
+
appLogoUrl: '/public/logo.png',
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async rolesPage (req, res) {
|
|
483
|
+
try {
|
|
484
|
+
const {
|
|
485
|
+
RoleConfigService,
|
|
486
|
+
ALL_PAGES,
|
|
487
|
+
ALL_CARDS,
|
|
488
|
+
} = require('../services/RoleConfigService');
|
|
489
|
+
const rolesData = await new RoleConfigService(req.org).getRoles();
|
|
490
|
+
const bp = await this._base(req);
|
|
491
|
+
res.render('roles', {
|
|
492
|
+
...bp,
|
|
493
|
+
title: 'Role Config',
|
|
494
|
+
rolesData,
|
|
495
|
+
allPages: ALL_PAGES,
|
|
496
|
+
allCards: ALL_CARDS,
|
|
497
|
+
});
|
|
498
|
+
} catch (err) {
|
|
499
|
+
res
|
|
500
|
+
.status(500)
|
|
501
|
+
.render('404', {
|
|
502
|
+
title: 'Error',
|
|
503
|
+
user: req.user || null,
|
|
504
|
+
settings: {},
|
|
505
|
+
appName: 'LogBoard',
|
|
506
|
+
appLogoUrl: '/public/logo.png',
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
async auditPage (req, res) {
|
|
512
|
+
try {
|
|
513
|
+
const AuditService = require('../services/AuditService'); const audit = new AuditService(req.org); const { q = '', limit = 200, action = '' } = req.query;
|
|
514
|
+
const { entries, total } = await audit.list({
|
|
515
|
+
limit: Number(limit),
|
|
516
|
+
q,
|
|
517
|
+
action,
|
|
518
|
+
});
|
|
519
|
+
const bp = await this._base(req);
|
|
520
|
+
res.render('audit', {
|
|
521
|
+
...bp,
|
|
522
|
+
title: 'Audit Log',
|
|
523
|
+
entries,
|
|
524
|
+
total,
|
|
525
|
+
q,
|
|
526
|
+
action,
|
|
527
|
+
});
|
|
528
|
+
} catch (err) {
|
|
529
|
+
res
|
|
530
|
+
.status(500)
|
|
531
|
+
.render('404', {
|
|
532
|
+
title: 'Error',
|
|
533
|
+
user: req.user || null,
|
|
534
|
+
settings: {},
|
|
535
|
+
appName: 'LogBoard',
|
|
536
|
+
appLogoUrl: '/public/logo.png',
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async alertsPage (req, res) {
|
|
542
|
+
try {
|
|
543
|
+
const svc = require('../services/AlertRulesService');
|
|
544
|
+
const logSvcs = await this._logSvc(req).getServices();
|
|
545
|
+
const [rules, history] = await Promise.all([
|
|
546
|
+
svc.listRules(),
|
|
547
|
+
svc.getHistory(50),
|
|
548
|
+
]);
|
|
549
|
+
const bp = await this._base(req);
|
|
550
|
+
res.render('alerts', {
|
|
551
|
+
...bp,
|
|
552
|
+
title: 'Alert Rules',
|
|
553
|
+
rules,
|
|
554
|
+
history,
|
|
555
|
+
logServices: logSvcs,
|
|
556
|
+
});
|
|
557
|
+
} catch (err) {
|
|
558
|
+
console.log(err);
|
|
559
|
+
res
|
|
560
|
+
.status(500)
|
|
561
|
+
.render('404', {
|
|
562
|
+
title: 'Error',
|
|
563
|
+
user: req.user || null,
|
|
564
|
+
settings: {},
|
|
565
|
+
appName: 'LogBoard',
|
|
566
|
+
appLogoUrl: '/public/logo.png',
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async archivePage (req, res) {
|
|
572
|
+
try {
|
|
573
|
+
const path = require('path'),
|
|
574
|
+
fs = require('fs');
|
|
575
|
+
const dir = config.ARCHIVE_DIR;
|
|
576
|
+
const files = fs.existsSync(dir)
|
|
577
|
+
? fs
|
|
578
|
+
.readdirSync(dir)
|
|
579
|
+
.filter((f) => f.endsWith('.tar.gz') || f.endsWith('.zip'))
|
|
580
|
+
.map((f) => {
|
|
581
|
+
const fp = path.join(dir, f),
|
|
582
|
+
st = fs.statSync(fp);
|
|
583
|
+
return { name: f, size: st.size, created: st.birthtime };
|
|
584
|
+
})
|
|
585
|
+
.sort((a, b) => new Date(b.created) - new Date(a.created))
|
|
586
|
+
: [];
|
|
587
|
+
const logSvcs = await this._logSvc(req).getServices();
|
|
588
|
+
const bp = await this._base(req);
|
|
589
|
+
res.render('archive', {
|
|
590
|
+
...bp,
|
|
591
|
+
title: 'Log Archive',
|
|
592
|
+
archives: files,
|
|
593
|
+
logServices: logSvcs,
|
|
594
|
+
});
|
|
595
|
+
} catch (err) {
|
|
596
|
+
res
|
|
597
|
+
.status(500)
|
|
598
|
+
.render('404', {
|
|
599
|
+
title: 'Error',
|
|
600
|
+
user: req.user || null,
|
|
601
|
+
settings: {},
|
|
602
|
+
appName: 'LogBoard',
|
|
603
|
+
appLogoUrl: '/public/logo.png',
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// ── NEW PAGES ─────────────────────────────────────────────────────────────
|
|
609
|
+
|
|
610
|
+
async heatmapPage (req, res) {
|
|
611
|
+
try {
|
|
612
|
+
const { service } = req.query;
|
|
613
|
+
const services = await this._logSvc(req).getServices();
|
|
614
|
+
const AnalyticsService = require('../services/AnalyticsService');
|
|
615
|
+
const heatmapData = service
|
|
616
|
+
? await new AnalyticsService(req.org).getHeatmap(service)
|
|
617
|
+
: null;
|
|
618
|
+
const bp = await this._base(req);
|
|
619
|
+
res.render('heatmap', {
|
|
620
|
+
...bp,
|
|
621
|
+
title: 'Error Heatmap',
|
|
622
|
+
services,
|
|
623
|
+
service: service || '',
|
|
624
|
+
heatmapData,
|
|
625
|
+
});
|
|
626
|
+
} catch (err) {
|
|
627
|
+
const bp = await this._base(req);
|
|
628
|
+
res.render('heatmap', {
|
|
629
|
+
...bp,
|
|
630
|
+
title: 'Error Heatmap',
|
|
631
|
+
services: [],
|
|
632
|
+
service: '',
|
|
633
|
+
heatmapData: null,
|
|
634
|
+
renderError: err.message,
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
async diffPage (req, res) {
|
|
640
|
+
try {
|
|
641
|
+
const { service, dateA, dateB } = req.query;
|
|
642
|
+
const services = await this._logSvc(req).getServices();
|
|
643
|
+
let diffData = null;
|
|
644
|
+
if (service && dateA && dateB) { diffData = await this._logSvc(req).getDiff(service, dateA, dateB); }
|
|
645
|
+
const bp = await this._base(req);
|
|
646
|
+
res.render('diff', {
|
|
647
|
+
...bp,
|
|
648
|
+
title: 'Log Diff',
|
|
649
|
+
services,
|
|
650
|
+
service: service || '',
|
|
651
|
+
dateA: dateA || '',
|
|
652
|
+
dateB: dateB || '',
|
|
653
|
+
diffData,
|
|
654
|
+
today: getToday(),
|
|
655
|
+
});
|
|
656
|
+
} catch (err) {
|
|
657
|
+
const bp = await this._base(req);
|
|
658
|
+
res.render('diff', {
|
|
659
|
+
...bp,
|
|
660
|
+
title: 'Log Diff',
|
|
661
|
+
services: [],
|
|
662
|
+
service: '',
|
|
663
|
+
dateA: '',
|
|
664
|
+
dateB: '',
|
|
665
|
+
diffData: null,
|
|
666
|
+
today: getToday(),
|
|
667
|
+
renderError: err.message,
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async serviceMapPage (req, res) {
|
|
673
|
+
try {
|
|
674
|
+
const { date } = req.query;
|
|
675
|
+
const today = getToday();
|
|
676
|
+
const mapData = await this._logSvc(req).getServiceMap(date || today);
|
|
677
|
+
const bp = await this._base(req);
|
|
678
|
+
res.render('service-map', {
|
|
679
|
+
...bp,
|
|
680
|
+
title: 'Service Map',
|
|
681
|
+
mapData,
|
|
682
|
+
date: date || today,
|
|
683
|
+
today,
|
|
684
|
+
});
|
|
685
|
+
} catch (err) {
|
|
686
|
+
const bp = await this._base(req);
|
|
687
|
+
res.render('service-map', {
|
|
688
|
+
...bp,
|
|
689
|
+
title: 'Service Map',
|
|
690
|
+
mapData: null,
|
|
691
|
+
date: getToday(),
|
|
692
|
+
today: getToday(),
|
|
693
|
+
renderError: err.message,
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
async customDashboardPage (req, res) {
|
|
699
|
+
try {
|
|
700
|
+
const DashboardService = require('../services/DashboardService');
|
|
701
|
+
const logSvcs = await this._logSvc(req).getServices();
|
|
702
|
+
const layout = await new DashboardService().getLayout(req.user.username);
|
|
703
|
+
const bp = await this._base(req);
|
|
704
|
+
res.render('custom-dashboard', {
|
|
705
|
+
...bp,
|
|
706
|
+
title: 'My Dashboard',
|
|
707
|
+
layout,
|
|
708
|
+
logServices: logSvcs,
|
|
709
|
+
today: getToday(),
|
|
710
|
+
});
|
|
711
|
+
} catch (err) {
|
|
712
|
+
const bp = await this._base(req);
|
|
713
|
+
res.render('custom-dashboard', {
|
|
714
|
+
...bp,
|
|
715
|
+
title: 'My Dashboard',
|
|
716
|
+
layout: [],
|
|
717
|
+
logServices: [],
|
|
718
|
+
today: getToday(),
|
|
719
|
+
renderError: err.message,
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
async bookmarksPage (req, res) {
|
|
725
|
+
try {
|
|
726
|
+
const BookmarkService = require('../services/BookmarkService');
|
|
727
|
+
const bookmarks = await BookmarkService.getAll(req.user.username);
|
|
728
|
+
const bp = await this._base(req);
|
|
729
|
+
res.render('bookmarks', { ...bp, title: 'Bookmarks', bookmarks });
|
|
730
|
+
} catch (err) {
|
|
731
|
+
const bp = await this._base(req);
|
|
732
|
+
res.render('bookmarks', {
|
|
733
|
+
...bp,
|
|
734
|
+
title: 'Bookmarks',
|
|
735
|
+
bookmarks: [],
|
|
736
|
+
renderError: err.message,
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async savedSearchesPage (req, res) {
|
|
742
|
+
try {
|
|
743
|
+
const SavedSearchService = require('../services/SavedSearchService');
|
|
744
|
+
const services = await this._logSvc(req).getServices();
|
|
745
|
+
const searches = await SavedSearchService.getAll(req.user.username);
|
|
746
|
+
const bp = await this._base(req);
|
|
747
|
+
res.render('saved-searches', {
|
|
748
|
+
...bp,
|
|
749
|
+
title: 'Saved Searches',
|
|
750
|
+
searches,
|
|
751
|
+
logServices: services,
|
|
752
|
+
today: getToday(),
|
|
753
|
+
});
|
|
754
|
+
} catch (err) {
|
|
755
|
+
const bp = await this._base(req);
|
|
756
|
+
res.render('saved-searches', {
|
|
757
|
+
...bp,
|
|
758
|
+
title: 'Saved Searches',
|
|
759
|
+
searches: [],
|
|
760
|
+
logServices: [],
|
|
761
|
+
today: getToday(),
|
|
762
|
+
renderError: err.message,
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
async notificationsPage (req, res) {
|
|
768
|
+
try {
|
|
769
|
+
const NotifService = require('../services/NotificationService');
|
|
770
|
+
const notifs = await NotifService.getAll(100);
|
|
771
|
+
const bp = await this._base(req);
|
|
772
|
+
res.render('notifications', {
|
|
773
|
+
...bp,
|
|
774
|
+
title: 'Notifications',
|
|
775
|
+
notifications: notifs,
|
|
776
|
+
});
|
|
777
|
+
} catch (err) {
|
|
778
|
+
const bp = await this._base(req);
|
|
779
|
+
res.render('notifications', {
|
|
780
|
+
...bp,
|
|
781
|
+
title: 'Notifications',
|
|
782
|
+
notifications: [],
|
|
783
|
+
renderError: err.message,
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
module.exports = UiController;
|