diff --git a/app/controllers/admin.js b/app/controllers/admin.js index 7317bab..b0173ea 100644 --- a/app/controllers/admin.js +++ b/app/controllers/admin.js @@ -44,11 +44,12 @@ class AdminController extends SiteController { }), ); - router.use('/host',await this.loadChild(path.join(__dirname, 'admin', 'host'))); - router.use('/job-queue',await this.loadChild(path.join(__dirname, 'admin', 'job-queue'))); - router.use('/link',await this.loadChild(path.join(__dirname, 'admin', 'link'))); - router.use('/newsletter',await this.loadChild(path.join(__dirname, 'admin', 'newsletter'))); - router.use('/settings',await this.loadChild(path.join(__dirname, 'admin', 'settings'))); + router.use('/host', await this.loadChild(path.join(__dirname, 'admin', 'host'))); + router.use('/job-queue', await this.loadChild(path.join(__dirname, 'admin', 'job-queue'))); + router.use('/link', await this.loadChild(path.join(__dirname, 'admin', 'link'))); + router.use('/log', await this.loadChild(path.join(__dirname, 'admin', 'log'))); + router.use('/newsletter', await this.loadChild(path.join(__dirname, 'admin', 'newsletter'))); + router.use('/settings', await this.loadChild(path.join(__dirname, 'admin', 'settings'))); router.use('/user', await this.loadChild(path.join(__dirname, 'admin', 'user'))); router.get('/diagnostics', this.getDiagnostics.bind(this)); diff --git a/app/controllers/admin/log.js b/app/controllers/admin/log.js new file mode 100644 index 0000000..a6c9e56 --- /dev/null +++ b/app/controllers/admin/log.js @@ -0,0 +1,57 @@ +// admin/log.js +// Copyright (C) 2021 Digital Telepresence, LLC +// License: Apache-2.0 + +'use strict'; + +const DTP_COMPONENT_NAME = 'admin:log'; +const express = require('express'); + +const { SiteController } = require('../../../lib/site-lib'); + +class LogController extends SiteController { + + constructor (dtp) { + super(dtp, DTP_COMPONENT_NAME); + } + + async start ( ) { + const router = express.Router(); + router.use(async (req, res, next) => { + res.locals.currentView = 'admin'; + res.locals.adminView = 'log'; + return next(); + }); + + router.get('/', this.getIndex.bind(this)); + + return router; + } + + async getIndex (req, res, next) { + const { log: logService } = this.dtp.services; + try { + res.locals.query = req.query; + + res.locals.components = await logService.getComponentNames(); + res.locals.pagination = this.getPaginationParameters(req, 25); + + const search = { }; + if (req.query.component) { + search.componentName = req.query.component; + } + res.locals.logs = await logService.getRecords(search, res.locals.pagination); + + res.locals.totalLogCount = await logService.getTotalCount(); + + res.render('admin/log/index'); + } catch (error) { + return next(error); + } + } +} + +module.exports = async (dtp) => { + let controller = new LogController(dtp); + return controller; +}; \ No newline at end of file diff --git a/app/models/link-category.js b/app/models/link-category.js new file mode 100644 index 0000000..f91c5d5 --- /dev/null +++ b/app/models/link-category.js @@ -0,0 +1,17 @@ +// link-category.js +// Copyright (C) 2021 Digital Telepresence, LLC +// License: Apache-2.0 + +'use strict'; + +const mongoose = require('mongoose'); + +const Schema = mongoose.Schema; + +const LinkCategorySchema = new Schema({ + user: { type: Schema.ObjectId, required: true, index: 1, ref: 'User' }, + name: { type: String, required: true }, + order: { type: Number, default: 0, required: true }, +}); + +module.exports = mongoose.model('LinkCategory', LinkCategorySchema); \ No newline at end of file diff --git a/app/models/link.js b/app/models/link.js index 468889a..64cf2d6 100644 --- a/app/models/link.js +++ b/app/models/link.js @@ -11,7 +11,7 @@ const Schema = mongoose.Schema; const { ResourceStats, ResourceStatsDefaults } = require('./lib/resource-stats'); const LinkSchema = new Schema({ - created: { type: Date, required: true, default: Date.now, index: -1, expires: '7d' }, + created: { type: Date, required: true, default: Date.now, index: -1 }, user: { type: Schema.ObjectId, required: true, index: 1, ref: 'User' }, label: { type: String, required: true, maxlength: 100 }, href: { type: String, required: true, maxlength: 255 }, diff --git a/app/services/log.js b/app/services/log.js new file mode 100644 index 0000000..f6f5e57 --- /dev/null +++ b/app/services/log.js @@ -0,0 +1,44 @@ +// log.js +// Copyright (C) 2021 Digital Telepresence, LLC +// License: Apache-2.0 + +'use strict'; + +const mongoose = require('mongoose'); + +const Log = mongoose.model('Log'); + +const { SiteService } = require('../../lib/site-lib'); + +class SystemLogService extends SiteService { + + constructor (dtp) { + super(dtp, module.exports); + } + + async getRecords (search, pagination) { + const logs = await Log + .find(search) + .sort({ created: -1 }) + .skip(pagination.skip) + .limit(pagination.cpp) + .lean(); + return logs; + } + + async getComponentNames ( ) { + return await Log.distinct('componentName'); + } + + async getTotalCount ( ) { + const count = await Log.estimatedDocumentCount(); + this.log.debug('log message total count', { count }); + return count; + } +} + +module.exports = { + slug: 'log', + name: 'log', + create: (dtp) => { return new SystemLogService(dtp); }, +}; \ No newline at end of file diff --git a/app/views/admin/components/menu.pug b/app/views/admin/components/menu.pug index 00aeb19..494c9f6 100644 --- a/app/views/admin/components/menu.pug +++ b/app/views/admin/components/menu.pug @@ -36,6 +36,11 @@ ul.uk-nav.uk-nav-default span.nav-item-icon i.fas.fa-microchip span.uk-margin-small-left Jobs + li(class={ 'uk-active': (adminView === 'log') }) + a(href="/admin/log") + span.nav-item-icon + i.fas.fa-clipboard-list + span.uk-margin-small-left Logs li.uk-nav-divider diff --git a/app/views/admin/layouts/main.pug b/app/views/admin/layouts/main.pug index 1ce341c..3e38246 100644 --- a/app/views/admin/layouts/main.pug +++ b/app/views/admin/layouts/main.pug @@ -1,4 +1,7 @@ extends ../../layouts/main +block vendorcss + link(rel="stylesheet", href="/highlight.js/styles/default.css") + block content-container block page-header diff --git a/app/views/admin/log/index.pug b/app/views/admin/log/index.pug new file mode 100644 index 0000000..802b040 --- /dev/null +++ b/app/views/admin/log/index.pug @@ -0,0 +1,46 @@ +extends ../layouts/main +block content + + include ../../components/pagination-bar + + .uk-margin + form(method="GET", action="/admin/log").uk-form + div(uk-grid).uk-grid-small.uk-flex-middle + .uk-width-expand + h1 Log #[span.uk-text-small.uk-text-muted #{numeral(totalLogCount).format('0,0')} records] + + .uk-width-auto + - + var urlParams = ''; + if (query.component) { + urlParams += `&component=${query.component}`; + } + +renderPaginationBar(`/admin/log`, totalLogCount, urlParams) + + .uk-width-auto + select(id="component", name="component").uk-select + each componentName in components + option(value= componentName, selected= (query.component === componentName))= componentName + .uk-width-auto + button(type="submit").uk-button.dtp-button-primary Filter + + if Array.isArray(logs) && (logs.length > 0) + table.uk-table.uk-table-small.uk-table-divider + thead + tr + th Timestamp + th Level + th Component + th Message + tbody + each log in logs + tr + td= moment(log.created).format('YYYY-MM-DD hh:mm:ss.SSS') + td= log.level + td= log.componentName + td + div= log.message + if log.metadata + .uk-text-small(style="font-family: Courier New;")!= hljs.highlightAuto(JSON.stringify(log.metadata, null, 1)).value + else + div There are no logs. \ No newline at end of file diff --git a/lib/site-platform.js b/lib/site-platform.js index 57efded..77d3895 100644 --- a/lib/site-platform.js +++ b/lib/site-platform.js @@ -210,11 +210,13 @@ module.exports.startWebServer = async (dtp) => { * Expose useful modules and information */ module.app.locals.DTP_SCRIPT_DEBUG = (process.env.NODE_ENV === 'local'); + module.app.locals.dtp = dtp; module.app.locals.pkg = require(path.join(dtp.config.root, 'package.json')); module.app.locals.moment = require('moment'); module.app.locals.numeral = require('numeral'); module.app.locals.phoneNumberJS = require('libphonenumber-js'); module.app.locals.anchorme = require('anchorme').default; + module.app.locals.hljs = require('highlight.js'); /* * Set up the protected markdown renderer that will refuse to process links and images @@ -262,6 +264,7 @@ module.exports.startWebServer = async (dtp) => { module.app.use('/mpegts', cacheOneDay, express.static(path.join(dtp.config.root, 'node_modules', 'mpegts.js', 'dist'))); module.app.use('/numeral', cacheOneDay, express.static(path.join(dtp.config.root, 'node_modules', 'numeral', 'min'))); module.app.use('/tinymce', cacheOneDay, express.static(path.join(dtp.config.root, 'node_modules', 'tinymce'))); + module.app.use('/highlight.js', cacheOneDay, express.static(path.join(dtp.config.root, 'node_modules', 'highlight.js'))); /* * ExpressJS middleware