From a47f25bfecb19f98c75a3d91c5797bdc62bf3178 Mon Sep 17 00:00:00 2001 From: rob Date: Thu, 11 Apr 2024 11:40:27 -0400 Subject: [PATCH] I wrote code. --- app/controllers/auth.js | 18 +- app/controllers/home.js | 1 + app/models/action-audit.js | 19 +++ app/models/chat-filter.js | 15 ++ app/services/action-audit.js | 30 ++++ app/services/display-engine.js | 183 +++++++++++++++++++++ app/services/text.js | 13 ++ app/services/user.js | 6 +- app/views/components/navbar.pug | 8 + app/views/home.pug | 16 +- app/views/layout/main.pug | 13 +- client/css/dtp-dark.less | 8 + client/css/dtp-light.less | 8 + client/css/dtp-site.less | 3 + client/css/main.less | 15 -- client/css/site/image.less | 10 ++ client/css/site/main.less | 8 + client/css/site/stage.less | 62 +++++++ client/css/site/uikit-theme.dtp-dark.less | 117 +++++++++++++ client/css/site/uikit-theme.dtp-light.less | 35 ++++ client/css/site/variables.less | 2 + webpack.config.js | 9 +- 22 files changed, 570 insertions(+), 29 deletions(-) create mode 100644 app/models/action-audit.js create mode 100644 app/models/chat-filter.js create mode 100644 app/services/action-audit.js create mode 100644 app/services/display-engine.js create mode 100644 client/css/dtp-dark.less create mode 100644 client/css/dtp-light.less create mode 100644 client/css/dtp-site.less delete mode 100644 client/css/main.less create mode 100644 client/css/site/image.less create mode 100644 client/css/site/main.less create mode 100644 client/css/site/stage.less create mode 100644 client/css/site/uikit-theme.dtp-dark.less create mode 100644 client/css/site/uikit-theme.dtp-light.less create mode 100644 client/css/site/variables.less diff --git a/app/controllers/auth.js b/app/controllers/auth.js index 66f897b..ea4e446 100644 --- a/app/controllers/auth.js +++ b/app/controllers/auth.js @@ -116,7 +116,10 @@ export default class AuthController extends SiteController { } async postOtpEnable (req, res, next) { - const { otpAuth: otpAuthService } = this.dtp.services; + const { + actionAudit: actionAuditService, + otpAuth: otpAuthService, + } = this.dtp.services; const service = req.body['otp-service']; const secret = req.body['otp-secret']; @@ -127,6 +130,7 @@ export default class AuthController extends SiteController { this.log.info('enabling OTP protections', { service, secret, token }); res.locals.otpAccount = await otpAuthService.createOtpAccount(req, service, secret, token); res.locals.otpRedirectURL = otpRedirectURL; + await actionAuditService.auditRequest(req, 'Enabled 2FA'); res.render('otp/new-account'); } catch (error) { this.log.error('failed to enable OTP protections', { @@ -137,7 +141,10 @@ export default class AuthController extends SiteController { } async postOtpAuthenticate (req, res, next) { - const { otpAuth: otpAuthService } = this.dtp.services; + const { + actionAudit: actionAuditService, + otpAuth: otpAuthService, + } = this.dtp.services; if (!req.user) { return res.status(403).json({ @@ -161,6 +168,7 @@ export default class AuthController extends SiteController { } try { await otpAuthService.startOtpSession(req, service, passcode); + await actionAuditService.auditRequest(req, '2FA credentials provided successfully'); return res.redirect(req.body['otp-redirect']); } catch (error) { this.log.error('failed to verify one-time password for 2FA', { @@ -241,6 +249,7 @@ export default class AuthController extends SiteController { } async getSocketToken (req, res, next) { + const { actionAudit: actionAuditService } = this.dtp.services; try { const token = await ConnectToken.create({ created: new Date(), @@ -250,6 +259,7 @@ export default class AuthController extends SiteController { consumer: req.user._id, token: uuidv4(), }); + await actionAuditService.auditRequest(req, 'Received socket connect token'); res.status(200).json({ success: true, token: token.token @@ -319,9 +329,13 @@ export default class AuthController extends SiteController { } async getLogout (req, res, next) { + const { actionAudit: actionAuditService } = this.dtp.services; + if (!req.user) { return next(new SiteError(403, 'You are not signed in')); } + + await actionAuditService.auditRequest(req, 'Logged out'); req.logout((err) => { if (err) { this.log.error('failed to destroy browser session', { err }); diff --git a/app/controllers/home.js b/app/controllers/home.js index 0cf9f9c..451f907 100644 --- a/app/controllers/home.js +++ b/app/controllers/home.js @@ -36,6 +36,7 @@ export default class HomeController extends SiteController { if (!req.user) { return res.redirect('/welcome'); } + res.locals.currentView = 'home'; res.locals.pageDescription = 'DTP Chat Home'; res.render('home'); } diff --git a/app/models/action-audit.js b/app/models/action-audit.js new file mode 100644 index 0000000..58c31be --- /dev/null +++ b/app/models/action-audit.js @@ -0,0 +1,19 @@ +// action-audit.js +// Copyright (C) 2024 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +import mongoose from 'mongoose'; +const Schema = mongoose.Schema; + +const ActionAuditSchema = new Schema({ + created: { type: Date, default: Date.now, required: true, index: -1, expires: '90d' }, + user: { type: Schema.ObjectId, required: true, index: 1, ref: 'User' }, + ip: { type: String }, + targetType: { type: String }, + target: { type: Schema.ObjectId, index: 1, refPath: 'targetType' }, + note: { type: String }, +}); + +export default mongoose.model('ActionAudit', ActionAuditSchema); \ No newline at end of file diff --git a/app/models/chat-filter.js b/app/models/chat-filter.js new file mode 100644 index 0000000..f5215d4 --- /dev/null +++ b/app/models/chat-filter.js @@ -0,0 +1,15 @@ +// chat-filter.js +// Copyright (C) 2022,2023 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +import mongoose from 'mongoose'; +const Schema = mongoose.Schema; + +const ChatFilterSchema = new Schema({ + created: { type: Date, required: true, default: Date.now, index: 1, expires: '300d' }, + filter: { type: String, required: true, unique: true, lowercase: true, index: 1 }, +}); + +export default mongoose.model('ChatFilter', ChatFilterSchema); \ No newline at end of file diff --git a/app/services/action-audit.js b/app/services/action-audit.js new file mode 100644 index 0000000..07b9209 --- /dev/null +++ b/app/services/action-audit.js @@ -0,0 +1,30 @@ +// action-audit.js +// Copyright (C) 2024 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +import mongoose from 'mongoose'; +const ActionAudit = mongoose.model('ActionAudit'); + +import { SiteService } from '../../lib/site-lib.js'; + +export default class ActionAuditService extends SiteService { + + static get slug () { return 'actionAudit'; } + static get name ( ) { return 'ActionAuditService'; } + + constructor (dtp) { + super(dtp, ActionAuditService); + } + + async auditRequest (req, note) { + const NOW = new Date(); + const audit = new ActionAudit(); + audit.created = NOW; + audit.user = req.user; + audit.ip = req.ip; + audit.note = note; + await audit.save(); + } +} \ No newline at end of file diff --git a/app/services/display-engine.js b/app/services/display-engine.js new file mode 100644 index 0000000..4e26a3d --- /dev/null +++ b/app/services/display-engine.js @@ -0,0 +1,183 @@ +// display-engine.js +// Copyright (C) 2022,2023 DTP Technologies, LLC +// All Rights Reserved + +'use strict'; + +import path from 'node:path'; + +import pug from 'pug'; +import { v4 as uuidv4 } from 'uuid'; + +import { SiteService, SiteError } from '../../lib/site-lib.js'; + +class DisplayList { + + constructor (service, name) { + this.name = name; + this.id = uuidv4(); + this.commands = [ ]; + } + + showNotification (message, status, pos, timeout) { + this.commands.push({ + action: 'showNotification', + params: { message, status, pos, timeout }, + }); + } + + showModal (html) { + this.commands.push({ + action: 'showModal', + params: { html }, + }); + } + + playNotificationSound (action) { + this.commands.push({ + action: 'playNotificationSound', + params: { action }, + }); + } + + playSound (soundId) { + this.commands.push({ + action: 'playSound', + params: { soundId }, + }); + } + + addElement (selector, where, html) { + this.commands.push({ + selector, action: 'addElement', + params: { where, html }, + }); + } + + removeChildren (selector) { + this.commands.push({ + selector, action: 'removeChildren', + params: { }, + }); + } + + setTextContent (selector, text) { + this.commands.push({ + selector, action: 'setTextContent', + params: { text }, + }); + } + + replaceElement (selector, html) { + this.commands.push({ + selector, action: 'replaceElement', + params: { html }, + }); + } + + removeElement (selector) { + this.commands.push({ + selector, action: 'removeElement', + params: { }, + }); + } + + setAttribute (selector, name, value) { + this.commands.push({ + selector, action: 'setAttribute', + params: { name, value }, + }); + } + + removeAttribute (selector, name) { + this.commands.push({ + selector, action: 'removeAttribute', + params: { name }, + }); + } + + toggleAttribute (selector, name, force) { + this.commands.push({ + selector, action: 'toggleAttribute', + params: { name, force }, + }); + } + + addClass (selector, add) { + this.commands.push({ + selector, action: 'addClass', + params: { add }, + }); + } + + removeClass (selector, remove) { + this.commands.push({ + selector, action: 'removeClass', + params: { remove }, + }); + } + + replaceClass (selector, remove, add) { + this.commands.push({ + selector, action: 'replaceClass', + params: { remove, add }, + }); + } + + setValue (selector, value) { + this.commands.push({ + selector, action: 'setValue', + params: { value }, + }); + } + + navigateTo (href) { + this.commands.push({ + action: 'navigateTo', + params: { href }, + }); + } + + navigateBack ( ) { + this.commands.push({ + action: 'navigateBack', + params: { }, + }); + } + + reloadView ( ) { + this.commands.push({ + action: 'reloadView', + params: { }, + }); + } +} + +export default class DisplayEngineService extends SiteService { + + static get name ( ) { return 'DisplayEngineService'; } + static get slug ( ) { return 'displayEngine'; } + + constructor (dtp) { + super(dtp, DisplayEngineService); + this.templates = { }; + } + + loadTemplate (name, pugScript) { + const scriptFile = path.join(this.dtp.config.root, 'app', 'views', pugScript); + this.templates[name] = pug.compileFile(scriptFile); + } + + executeTemplate (name, data) { + if (!this.templates[name]) { + this.log.error('view engine template undefined', { name }); + throw new SiteError(500, 'Unknown display engine template'); + } + data = Object.assign(this.dtp.app.locals, data); + return this.templates[name](data); + } + + createDisplayList (name = 'default') { + return new DisplayList(this, name); + } +} \ No newline at end of file diff --git a/app/services/text.js b/app/services/text.js index 04742be..612f569 100644 --- a/app/services/text.js +++ b/app/services/text.js @@ -4,6 +4,9 @@ 'use strict'; +import mongoose from 'mongoose'; +const ChatFilter = mongoose.model('ChatFilter'); + import striptags from 'striptags'; import unzalgo from 'unzalgo'; import shoetest from 'shoetest'; @@ -21,6 +24,10 @@ export default class TextService extends SiteService { super(dtp, TextService); } + async start ( ) { + await this.loadChatFilters(); + } + /** * Basic text cleaning function to remove Zalgo and tags. * @param {String} text The text to be cleaned @@ -68,4 +75,10 @@ export default class TextService extends SiteService { */ return this.clean(text); } + + async loadChatFilters ( ) { + this.chatFilters = await ChatFilter.find().lean(); + this.chatFilters = this.chatFilters.map((filter) => filter.filter); + this.log.debug('loading chat filters', { count: this.chatFilters.length }); + } } \ No newline at end of file diff --git a/app/services/user.js b/app/services/user.js index 9258ea7..4def525 100644 --- a/app/services/user.js +++ b/app/services/user.js @@ -392,6 +392,8 @@ export default class UserService extends SiteService { } async login (req, res, next, options) { + const { actionAudit: actionAuditService } = this.dtp.services; + options = Object.assign({ loginUrl: '/welcome/login', }, options); @@ -408,6 +410,7 @@ export default class UserService extends SiteService { } return res.redirect(options.loginUrl); } + this.log.alert('user login', { user: { _id: user._id, @@ -415,10 +418,11 @@ export default class UserService extends SiteService { ip: req.ip, } }); - req.login(user, (error) => { + req.login(user, async (error) => { if (error) { return next(error); } + await actionAuditService.auditRequest(req, 'User login'); if (options.onLoginSuccess) { return options.onLoginSuccess(req, res, next); } diff --git a/app/views/components/navbar.pug b/app/views/components/navbar.pug index ddb066d..d872b19 100644 --- a/app/views/components/navbar.pug +++ b/app/views/components/navbar.pug @@ -51,3 +51,11 @@ nav(style="background: #000000;").uk-navbar-container.uk-light span.nav-item-icon i.fas.fa-user-lock span Admin + + li.uk-nav-divider + + li + a(href=`/auth/logout`) + span.nav-item-icon + i.fas.fa-right-from-bracket + span Sign Out \ No newline at end of file diff --git a/app/views/home.pug b/app/views/home.pug index 21f8415..34400ae 100644 --- a/app/views/home.pug +++ b/app/views/home.pug @@ -1,7 +1,15 @@ extends layout/main block view-content - section.uk-section.uk-section-default.uk-section-small - .uk-container - h1= site.name - div= site.description \ No newline at end of file + .dtp-chat-stage + .chat-sidebar + .chat-stage-header Active Members + .sidebar-panel This is a sidebar content panel. It should word-wrap correctly and will be used to display usernames in the room with status. + + .chat-stage-header Idle Members + .sidebar-panel This is a sidebar content panel. It should word-wrap correctly and will be used to display usernames in the room with status. + + .chat-container + .chat-stage-header Chat Room and Host names go here. + .chat-content-panel This is the chat content panel. It should word-wrap and scroll correctly, and will be where individual chat messages will render as they arrive and are sent. + .chat-input-panel This is the chat input panel. It will be where text is entered and sent, and contain some menu items, icons, and buttons. \ No newline at end of file diff --git a/app/views/layout/main.pug b/app/views/layout/main.pug index 8486f1b..79166dc 100644 --- a/app/views/layout/main.pug +++ b/app/views/layout/main.pug @@ -19,7 +19,7 @@ html(lang='en', data-obs-widget= obsWidget) block vendorcss - link(rel='stylesheet', href=`/dist/chat-client.css?v=${pkg.version}`) + link(rel='stylesheet', href=`/dist/chat-light.css?v=${pkg.version}`) link(rel='stylesheet', href=`/pretty-checkbox/pretty-checkbox.min.css?v=${pkg.version}`) block viewcss @@ -56,7 +56,16 @@ html(lang='en', data-obs-widget= obsWidget) } } - body.dtp(class= 'dtp-dark', data-dtp-env= process.env.NODE_ENV, data-dtp-domain= site.domainKey, data-current-view= currentView, data-is-popout= isPopOutView, data-obs-widget= obsWidget, data-embed-widget= embedWidget) + body.dtp( + class= 'dtp-dark', + data-dtp-env= process.env.NODE_ENV, + data-dtp-domain= site.domainKey, + data-current-view= currentView, + data-is-popout= isPopOutView, + data-obs-widget= obsWidget, + data-embed-widget= embedWidget, + ) + block view-navbar include ../components/navbar diff --git a/client/css/dtp-dark.less b/client/css/dtp-dark.less new file mode 100644 index 0000000..2f2f632 --- /dev/null +++ b/client/css/dtp-dark.less @@ -0,0 +1,8 @@ +@import "site/variables.less"; + +@import "uikit/src/less/uikit.less"; +@import "node_modules/uikit/src/less/uikit.theme.less"; + +@import "site/uikit-theme.dtp-dark.less"; + +@import "dtp-site.less"; \ No newline at end of file diff --git a/client/css/dtp-light.less b/client/css/dtp-light.less new file mode 100644 index 0000000..1ad5089 --- /dev/null +++ b/client/css/dtp-light.less @@ -0,0 +1,8 @@ +@import "site/variables.less"; + +@import "uikit/src/less/uikit.less"; +@import "node_modules/uikit/src/less/uikit.theme.less"; + +@import "site/uikit-theme.dtp-light.less"; + +@import "dtp-site.less"; \ No newline at end of file diff --git a/client/css/dtp-site.less b/client/css/dtp-site.less new file mode 100644 index 0000000..aa6126a --- /dev/null +++ b/client/css/dtp-site.less @@ -0,0 +1,3 @@ +@import "site/main.less"; +@import "site/stage.less"; +@import "site/image.less"; \ No newline at end of file diff --git a/client/css/main.less b/client/css/main.less deleted file mode 100644 index d93c7f3..0000000 --- a/client/css/main.less +++ /dev/null @@ -1,15 +0,0 @@ -@import "uikit/src/less/uikit.less"; - -html, body { - font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; -} - -img.navbar-logo { - width: auto; - height: 48px; -} -img.profile-navbar { - width: auto; - height: 48px; - border-radius: 5px; -} \ No newline at end of file diff --git a/client/css/site/image.less b/client/css/site/image.less new file mode 100644 index 0000000..1e0488f --- /dev/null +++ b/client/css/site/image.less @@ -0,0 +1,10 @@ +img.navbar-logo { + width: auto; + height: 48px; +} + +img.profile-navbar { + width: auto; + height: 48px; + border-radius: 5px; +} \ No newline at end of file diff --git a/client/css/site/main.less b/client/css/site/main.less new file mode 100644 index 0000000..3f6eac7 --- /dev/null +++ b/client/css/site/main.less @@ -0,0 +1,8 @@ +html, body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + + &[data-current-view="home"] { + position: fixed; + top: 0; right: 0; bottom: 0; left: 0; + } +} \ No newline at end of file diff --git a/client/css/site/stage.less b/client/css/site/stage.less new file mode 100644 index 0000000..9ef57b1 --- /dev/null +++ b/client/css/site/stage.less @@ -0,0 +1,62 @@ +@stage-panel-padding: 4px 10px; +@stage-border-color: #4a4a4a; + +.dtp-chat-stage { + position: absolute; + top: @site-navbar-height; right: 0; bottom: 0; left: 0; + + width: 100%; + height: 100%; + + display: flex; + + .chat-stage-header { + flex-grow: 0; + flex-shrink: 0; + + padding: @stage-panel-padding; + + font-weight: bold; + + background-color: #2a2a2a; + color: #e8e8e8; + } + + .chat-sidebar { + box-sizing: border-box; + width: 240px; + flex-shrink: 0; + flex-grow: 0; + + background-color: #1a1a1a; + color: #e8e8e8; + + border-right: solid 1px @stage-border-color; + + .sidebar-panel { + padding: @stage-panel-padding; + margin-bottom: 10px; + color: inherit; + } + } + + .chat-container { + display: flex; + flex-grow: 1; + flex-direction: column; + + background-color: #e8e8e8; + color: #1a1a1a; + + .chat-content-panel { + padding: @stage-panel-padding; + flex-grow: 1; + } + .chat-input-panel { + height: 200px; + padding: @stage-panel-padding; + background-color: #1a1a1a; + color: #e8e8e8; + } + } +} \ No newline at end of file diff --git a/client/css/site/uikit-theme.dtp-dark.less b/client/css/site/uikit-theme.dtp-dark.less new file mode 100644 index 0000000..d58894e --- /dev/null +++ b/client/css/site/uikit-theme.dtp-dark.less @@ -0,0 +1,117 @@ +// +// Colors +// + +@page-background-color: #000000; +@content-background-color: #2a2a2a; +@content-border-color: #4a4a4a; +@content-container-color: #2a2a2a; + +@site-brand-color: #ff0013; +@button-label-color: #e8e8e8; +@button-label-hover: #2a2a2a; +@social-link-color: #e8e8e8; +@checkout-button-text-color: #e8e8e8; + +@emoji-react-button-active-color: #adc7a0; + +@scrollbar-border-color: @content-border-color; +@scrollbar-thumb-color: #ff001380; + +@global-background: #1a1a1a; +@global-muted-background: #3a3a3a; +@global-primary-background: #1e87f0; +@global-secondary-background: #222; + +@global-success-background: #32d296; +@global-warning-background: #faa05a; +@global-danger-background: #f0506e; + +@global-color: #c8c8c8; +@global-emphasis-color: #ffffff; +@global-muted-color: #9a9a9a; + +@global-link-color: #e00000; +@global-link-hover-color: #ff0000; + +@global-border: #4a4a4a; +@global-inverse-color: #e8e8e8; + +// +// Inverse +// + +@inverse-global-color-mode: light; + +@inverse-global-color: fade(@global-inverse-color, 70%); +@inverse-global-emphasis-color: @global-inverse-color; +@inverse-global-muted-color: #a8a8a8; +@inverse-global-inverse-color: #1a1a1a; + +@inverse-global-primary-background: @global-inverse-color; +@inverse-global-muted-background: fade(@global-inverse-color, 10%); + +@inverse-global-border: fade(@global-inverse-color, 20%); + +// +// Button +// + +@button-default-color: #000000; +@button-primary-color: #000000; +@button-secondary-color: #000000; + +@button-text-color: #000000; +@button-text-hover-color: #000000; +@button-text-disabled-color: #000000; + +button.uk-button.uk-button-default, +a.uk-button.uk-button-default { + color: @global-color; +} + +// +// Component: Navbar +// + +@navbar-background: #1a1a1a; +@navbar-nav-item-height: @site-navbar-height; + +// +// Off-Canvas +// + +@offcanvas-bar-background: #1a1a1a; + +// +// Navbar +// + +@navbar-dropdown-background: #2a2a2a; +@navbar-dropdown-border: #3a3a3a; + +.uk-navbar-dropdown { + border: solid 2px @navbar-dropdown-border; + border-radius: 4px; + + .uk-nav-divider { + border-color: @navbar-dropdown-border; + } +} + +// +// Form +// + +// @internal-form-select-image: "/uikit/images/backgrounds/form-select.svg"; +// @internal-form-datalist-image: "/uikit/images/backgrounds/form-datalist.svg"; +// @internal-form-radio-image: "/uikit/images/backgrounds/form-radio.svg"; +// @internal-form-checkbox-image: "/uikit/images/backgrounds/form-checkbox.svg"; +// @internal-form-checkbox-indeterminate-image: "/uikit/images/backgrounds/form-checkbox-indeterminate.svg"; + +/* Disabled */ +.uk-input:disabled, +.uk-select:disabled, +.uk-textarea:disabled { + color: @inverse-global-muted-color; +} \ No newline at end of file diff --git a/client/css/site/uikit-theme.dtp-light.less b/client/css/site/uikit-theme.dtp-light.less new file mode 100644 index 0000000..95fb141 --- /dev/null +++ b/client/css/site/uikit-theme.dtp-light.less @@ -0,0 +1,35 @@ +// +// Colors +// + +@page-background-color: #e8e8e8; +@content-background-color: #ffffff; +@content-border-color: #a8a8a8; +@content-container-color: #c8c8c8; + +@site-brand-color: #ff0013; +@button-label-color: #2a2a2a; +@button-label-hover: #ffffff; +@social-link-color: #2a2a2a; +@checkout-button-text-color: #2a2a2a; + +@emoji-react-button-active-color: #adc7a0; + +@scrollbar-border-color: @content-border-color; +@scrollbar-thumb-color: #ff001380; + +// +// Component: Navbar +// + +@navbar-nav-item-height: @site-navbar-height; + +// +// Form +// + +// @internal-form-select-image: "/uikit/images/backgrounds/form-select.svg"; +// @internal-form-datalist-image: "/uikit/images/backgrounds/form-datalist.svg"; +// @internal-form-radio-image: "/uikit/images/backgrounds/form-radio.svg"; +// @internal-form-checkbox-image: "/uikit/images/backgrounds/form-checkbox.svg"; +// @internal-form-checkbox-indeterminate-image: "/uikit/images/backgrounds/form-checkbox-indeterminate.svg"; \ No newline at end of file diff --git a/client/css/site/variables.less b/client/css/site/variables.less new file mode 100644 index 0000000..62ad349 --- /dev/null +++ b/client/css/site/variables.less @@ -0,0 +1,2 @@ +@brand-color-gab: #00d178; +@site-navbar-height: 64px; \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index b0abd2b..a2bd9d5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -25,7 +25,7 @@ if (webpackMode === 'development') { ws: true, }, host: 'dev.chat.digitaltelepresence.com', - open: 'external', + open: 'local', port: 3333, cors: true, ui: { @@ -49,10 +49,8 @@ if (webpackMode === 'development') { export default { entry: { 'app': './client/js/index.js', - 'chat-client': [ - // './client/js/chat-client.js', - './client/css/main.less', - ], + 'chat-light': './client/css/dtp-light.less', + 'chat-dark': './client/css/dtp-dark.less', }, devtool: 'source-map', mode: webpackMode, @@ -100,6 +98,7 @@ export default { sourceMap: true, lessOptions: { strictMath: false, + paths: [path.resolve(__dirname, "node_modules")], }, }, },