diff --git a/app/controllers/admin.js b/app/controllers/admin.js index 4a4a387..71c1d2d 100644 --- a/app/controllers/admin.js +++ b/app/controllers/admin.js @@ -74,9 +74,11 @@ class AdminController extends SiteController { async getHomeView (req, res) { const { + chat: chatService, coreNode: coreNodeService, dashboard: dashboardService, logan: loganService, + user: userService, } = this.dtp.services; res.locals.stats = { @@ -87,6 +89,12 @@ class AdminController extends SiteController { res.locals.pageTitle = `Admin Dashbord for ${this.dtp.config.site.name}`; + res.locals.recentMembers = await userService.getRecent(10); + res.locals.admins = await userService.getAdmins(); + res.locals.moderators = await userService.getModerators(); + + res.locals.recentChat = await chatService.getRecent(10); + loganService.sendRequestEvent(module.exports, req, { level: 'info', event: 'getHomeView', diff --git a/app/controllers/chat.js b/app/controllers/chat.js index 82cf2a8..d3518b9 100644 --- a/app/controllers/chat.js +++ b/app/controllers/chat.js @@ -366,16 +366,21 @@ class ChatController extends SiteController { async getRoomEditor (req, res) { const { logan: loganService } = this.dtp.services; + + const logData = { }; + if (res.locals.room) { + logData.room = { + _id: res.locals.room._id, + name: res.locals.room.name, + }; + } + loganService.sendRequestEvent(module.exports, req, { level: 'info', event: 'getRoomEditor', - data: { - room: { - _id: res.locals.room._id, - name: res.locals.room.name, - }, - }, + data: logData, }); + res.render('chat/room/editor'); } diff --git a/app/services/chat.js b/app/services/chat.js index 64a24c3..cc1da01 100644 --- a/app/services/chat.js +++ b/app/services/chat.js @@ -785,6 +785,16 @@ class ChatService extends SiteService { return reaction.toObject(); } + async getRecent (limit = 10) { + const messages = await ChatMessage + .find({ }) + .sort({ created: -1 }) + .limit(limit) + .populate(this.populateChatMessage) + .lean(); + return messages; + } + async removeForUser (user) { const { logan: loganService } = this.dtp.services; diff --git a/app/services/user.js b/app/services/user.js index 28c1dea..03f4a0f 100644 --- a/app/services/user.js +++ b/app/services/user.js @@ -508,6 +508,24 @@ class UserService extends SiteService { return users; } + async getAdmins ( ) { + const admins = await User + .find({ 'flags.isAdmin': true }) + .select(UserService.USER_SELECT) + .sort({ username: 1 }) + .lean(); + return admins; + } + + async getModerators ( ) { + const moderators = await User + .find({ 'flags.isModerator': true }) + .select(UserService.USER_SELECT) + .sort({ username: 1 }) + .lean(); + return moderators; + } + async setUserSettings (user, settings) { const { crypto: cryptoService, diff --git a/app/views/admin/index.pug b/app/views/admin/index.pug index 6efb3d8..86ff8f1 100644 --- a/app/views/admin/index.pug +++ b/app/views/admin/index.pug @@ -1,6 +1,9 @@ extends layouts/main block content + include user/components/list-item + include ../chat/components/message + div(uk-grid) div(class="uk-width-1-1 uk-width-auto@m") h3= site.name @@ -25,6 +28,53 @@ block content h3 Hourly Sign-Ups canvas(id="hourly-signups") + .uk-margin + div(uk-grid) + div(class="uk-width-1-1 uk-width-1-3@l") + h3 Admins + if Array.isArray(admins) && (admins.length > 0) + ul.uk-list.uk-list-divider + each member in admins + li + +renderUserListItem(member) + else + div There are no system admins. + + h3 Moderators + if Array.isArray(moderators) && (moderators.length > 0) + ul.uk-list.uk-list-divider + each member in moderators + li + +renderUserListItem(member) + else + div There are no system-level moderators. + + div(class="uk-width-1-1 uk-width-1-3@l") + h3 Recent Chat + if Array.isArray(recentChat) && (recentChat.length > 0) + ul.uk-list.uk-list-divider + each message in recentChat + li + div(uk-grid).uk-grid-small + .uk-width-expand + +renderChatMessage(message, { fullWidth: true }) + .uk-width-auto + a(href=`/admin/user/local/${message.author._id}`, uk-tooltip={ title: 'Manage user account' }).uk-button.uk-button-default.uk-button-small.uk-border-rounded + span + i.fas.fa-wrench + else + div There is no recent chat. + + div(class="uk-width-1-1 uk-width-1-3@l") + h3 Recent Members + if Array.isArray(recentMembers) && (recentMembers.length > 0) + ul.uk-list.uk-list-divider + each member in recentMembers + li + +renderUserListItem(member) + else + div There are no recent members. + block viewjs script(src="/chart.js/chart.min.js") script(src="/chartjs-adapter-moment/chartjs-adapter-moment.min.js") diff --git a/app/views/admin/user/components/list-item.pug b/app/views/admin/user/components/list-item.pug new file mode 100644 index 0000000..123f74b --- /dev/null +++ b/app/views/admin/user/components/list-item.pug @@ -0,0 +1,14 @@ +mixin renderUserListItem (user) + div(uk-grid).uk-grid-small + .uk-width-auto + +renderProfileIcon(user) + .uk-width-expand + .uk-text-bold(style="line-height: 1;").uk-text-truncate= user.displayName || user.username + .uk-text-small.uk-text-muted + a(href= getUserProfileUrl(user))= user.username + .uk-text-small.uk-text-truncate= user.bio + .uk-text-small.uk-text-muted created #{moment(user.created).fromNow()} + .uk-width-auto + a(href=`/admin/user/local/${user._id}`, uk-tooltip={ title: 'Manage user account' }).uk-button.uk-button-default.uk-button-small.uk-border-rounded + span + i.fas.fa-wrench \ No newline at end of file diff --git a/app/views/chat/components/message.pug b/app/views/chat/components/message.pug index ea5f39b..017c94a 100644 --- a/app/views/chat/components/message.pug +++ b/app/views/chat/components/message.pug @@ -2,8 +2,10 @@ include ../../sticker/components/sticker mixin renderChatMessage (message, options = { }) - var authorName = message.author.displayName || message.author.username; div( - data-message-id= message._id, data-author-id= message.author._id - ).chat-message + data-message-id= message._id, + data-author-id= message.author._id, + class={ 'full-width': options.fullWidth }, + ).site-chat-message .uk-margin-small div(uk-grid).uk-grid-small .uk-width-auto @@ -34,7 +36,7 @@ mixin renderChatMessage (message, options = { }) //- "time" is filled in by the JavaScript client using the browser's locale //- information so that "time" is always in the user's display timezone. - .chat-timestamp(data-dtp-timestamp= message.created).uk-text-small + .chat-timestamp(data-dtp-timestamp= message.created).uk-text-small.uk-text-muted if Array.isArray(message.stickers) && (message.stickers.length > 0) each sticker in message.stickers diff --git a/client/js/site-chat.js b/client/js/site-chat.js index 56c7a8f..226dd9f 100644 --- a/client/js/site-chat.js +++ b/client/js/site-chat.js @@ -35,7 +35,6 @@ export default class SiteChat { if (this.ui.messageList) { this.ui.messageList.addEventListener('scroll', this.onChatMessageListScroll.bind(this)); - this.updateTimestamps(); setTimeout(( ) => { this.log.info('constructor', 'scrolling chat', { top: this.ui.messageList.scrollHeight }); this.ui.messageList.scrollTo({ top: this.ui.messageList.scrollHeight, behavior: 'instant' }); @@ -62,6 +61,8 @@ export default class SiteChat { this.mutedUsers = window.localStorage.mutedUsers ? JSON.parse(window.localStorage.mutedUsers) : [ ]; this.filterChatView(); } + + this.updateTimestamps(); } async filterChatView ( ) { diff --git a/client/less/site/chat.less b/client/less/site/chat.less index b227219..5863c73 100644 --- a/client/less/site/chat.less +++ b/client/less/site/chat.less @@ -13,6 +13,97 @@ background-color: @content-background-color; } +.site-chat-message { + padding: @grid-small-gutter-vertical @grid-small-gutter-horizontal; + margin: (@grid-small-gutter-vertical / 2) @grid-small-gutter-horizontal; + + border: solid 1px @content-border-color; + border-radius: 8px; + + background: @content-background-color; + color: inherit; + font-size: var(--dtp-chat-font-size); + + &.full-width { + margin-left: 0; + margin-right: 0; + + &:first-of-type { margin-top: 0; } + + &:last-of-type { margin-bottom: 0; } + } + + &.system-message { + background: #e8e8e8; + color: #1a1a1a; + + &[data-message-type="info"] { + background: #068be4; + color: white; + } + &[data-message-type="warning"] { + background: #e4c306; + color: white; + } + &[data-message-type="error"] { + background: #ff00131a; + color: white; + } + } + + .chat-username { + font-weight: bold; + font-size: var(--dtp-chat-font-size); + line-height: 1; + color: var(--dtp-chat-username-color); + } + + img.chat-author-image { + width: auto; + height: 40px; + border-radius: 4px; + } + + .chat-content { + line-height: 1.2em; + font-size: var(--dtp-chat-font-size); + color: inherit; + overflow-wrap: break-word; + + p:last-child { + margin-bottom: 0; + } + } + + .chat-timestamp { + color: var(--dtp-chat-timestamp-color); + } + + .chat-sticker { + display: inline-block; + margin-top: 4px; + margin-right: 8px; + color: inherit; + + video { + width: auto; + height: 100px; + } + } + + .chat-user-menu { + + button.chat-menu-button { + padding: 0; + margin: 0; + background: transparent; + outline: none; + border: none; + line-height: 1; + } + } +} + #site-chat-container { overflow: auto; background-color: @content-container-color; @@ -145,88 +236,6 @@ background-color: @scrollbar-thumb-color; } - - .chat-message { - padding: @grid-small-gutter-vertical @grid-small-gutter-horizontal; - margin: (@grid-small-gutter-vertical / 2) @grid-small-gutter-horizontal; - - border: solid 1px @content-border-color; - border-radius: 8px; - - background: @content-background-color; - color: inherit; - font-size: var(--dtp-chat-font-size); - - &.system-message { - background: #e8e8e8; - color: #1a1a1a; - - &[data-message-type="info"] { - background: #068be4; - color: white; - } - &[data-message-type="warning"] { - background: #e4c306; - color: white; - } - &[data-message-type="error"] { - background: #ff00131a; - color: white; - } - } - - .chat-username { - font-weight: bold; - font-size: var(--dtp-chat-font-size); - line-height: 1; - color: var(--dtp-chat-username-color); - } - - img.chat-author-image { - width: auto; - height: 40px; - border-radius: 4px; - } - - .chat-content { - line-height: 1.2em; - font-size: var(--dtp-chat-font-size); - color: inherit; - overflow-wrap: break-word; - - p:last-child { - margin-bottom: 0; - } - } - - .chat-timestamp { - color: var(--dtp-chat-timestamp-color); - } - - .chat-sticker { - display: inline-block; - margin-top: 4px; - margin-right: 8px; - color: inherit; - - video { - width: auto; - height: 100px; - } - } - - .chat-user-menu { - - button.chat-menu-button { - padding: 0; - margin: 0; - background: transparent; - outline: none; - border: none; - line-height: 1; - } - } - } } .chat-message-menu {