Release 2.7.4
This commit is contained in:
@@ -2,6 +2,16 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 2.7.4 - 2026-08-15
|
||||
|
||||
### Added
|
||||
|
||||
- A new About page.
|
||||
|
||||
### Changed
|
||||
|
||||
- Refreshed the vendored AdminLTE assets to 4.3.1.
|
||||
|
||||
## 2.7.3 - 2026-08-15
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pulse-signage-player",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"private": false,
|
||||
"description": "Pulse Signage player application bundle",
|
||||
"main": "src/common.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pulse-signage-web",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"private": false,
|
||||
"description": "Pulse Signage web and bridge application bundle",
|
||||
"main": "src/common.js",
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pulse-signage",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pulse-signage",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"dependencies": {
|
||||
"@sparticuz/chromium": "^137.0.0",
|
||||
"animate.css": "^4.1.1",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pulse-signage",
|
||||
"version": "2.7.3",
|
||||
"version": "2.7.4",
|
||||
"private": false,
|
||||
"description": "Pulse Signage application with MySQL and media storage",
|
||||
"repository": {
|
||||
|
||||
@@ -9,6 +9,7 @@ function routePath(...segments) {
|
||||
module.exports = {
|
||||
renderLoginPage: require(routePath('auth', 'login')),
|
||||
renderAccountPage: require(routePath('account', 'password')),
|
||||
renderAboutPage: require(routePath('settings', 'about', 'index')),
|
||||
renderUsersPage: require(routePath('settings', 'users', 'list')),
|
||||
renderUsersAddPage: require(routePath('settings', 'users', 'add')),
|
||||
renderUsersEditPage: require(routePath('settings', 'users', 'edit')),
|
||||
|
||||
+2
-2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -15,6 +15,7 @@ const registerRssFeedRoutes = require('./data-sources/rss-feeds/routes');
|
||||
const registerTimetableRoutes = require('./data-sources/timetables/routes');
|
||||
const registerSettingsRoutes = require('./settings/background-tasks');
|
||||
const registerFontRoutes = require('./settings/fonts');
|
||||
const registerAboutRoutes = require('./settings/about/routes');
|
||||
const registerInternalSyncRoutes = require('./internal/sync');
|
||||
const registerScreensRoutes = require('./signage/screens/routes');
|
||||
const { PERMISSIONS, normalizePermissionKeys } = require('#src/rbac');
|
||||
@@ -24,6 +25,7 @@ function registerRoutes(app, deps) {
|
||||
registerAuthAndAccountRoutes(app, deps);
|
||||
registerSignageRoutes(app, deps);
|
||||
registerSettingsAndContentRoutes(app, deps);
|
||||
registerAboutRoutes(app, deps);
|
||||
registerInternalSyncRoutes(app, {
|
||||
backgroundTaskQueue: deps.backgroundTaskQueue,
|
||||
uploadSyncService: deps.uploadSyncService,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Settings page renderer for the About page.
|
||||
|
||||
const { renderView } = require('../../../view');
|
||||
|
||||
function buildLibraryList() {
|
||||
return [
|
||||
{ name: 'AdminLTE', version: '4.3.1', usage: 'Admin dashboard layout and components', license: 'MIT', url: 'https://adminlte.io/' },
|
||||
{ name: 'Animate.css', version: '4.1.1', usage: 'Reusable animation classes', license: 'MIT', url: 'https://animate.style/' },
|
||||
{ name: 'Bootstrap Icons', version: '1.11.3', usage: 'Icon set used throughout the UI', license: 'MIT', url: 'https://icons.getbootstrap.com/' },
|
||||
{ name: 'Cropper.js', version: '1.6.2', usage: 'Image cropping in the slide editor', license: 'MIT', url: 'https://github.com/fengyuanchen/cropperjs' },
|
||||
{ name: 'Express', version: '4.21.2', usage: 'Web server and routing', license: 'MIT', url: 'https://expressjs.com/' },
|
||||
{ name: 'Handlebars', version: '4.7.8', usage: 'Server-side view rendering', license: 'MIT', url: 'https://handlebarsjs.com/' },
|
||||
{ name: 'hls.js', version: '1.5.15', usage: 'HTTP Live Streaming playback', license: 'Apache-2.0', url: 'https://github.com/video-dev/hls.js' },
|
||||
{ name: 'Multer', version: '2.2.0', usage: 'Multipart file uploads', license: 'MIT', url: 'https://github.com/expressjs/multer' },
|
||||
{ name: 'MySQL2', version: '3.14.3', usage: 'Database access layer', license: 'MIT', url: 'https://github.com/sidorares/node-mysql2' },
|
||||
{ name: 'Sharp', version: '0.35.3', usage: 'Image processing and transforms', license: 'Apache-2.0', url: 'https://sharp.pixelplumbing.com/' },
|
||||
{ name: 'TinyMCE', version: '7.7.0', usage: 'Rich text editing for content fields', license: 'GPL-2.0-or-later', url: 'https://www.tiny.cloud/' },
|
||||
{ name: 'ws', version: '8.21.0', usage: 'WebSocket transport', license: 'MIT', url: 'https://github.com/websockets/ws' }
|
||||
];
|
||||
}
|
||||
|
||||
module.exports = function renderAboutPage(currentUser) {
|
||||
return renderView('settings/about/index', {
|
||||
title: 'About',
|
||||
active: 'about',
|
||||
currentUser: currentUser || null,
|
||||
repositoryUrl: 'https://git.lzstealth.com/LZStealth/pulse-signage.git',
|
||||
libraries: buildLibraryList(),
|
||||
scripts: []
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
// Settings route registration for the About page.
|
||||
|
||||
module.exports = function registerAboutRoutes(app, deps) {
|
||||
const pages = deps.pages;
|
||||
|
||||
if (!pages) {
|
||||
throw new Error('registerAboutRoutes requires pages.');
|
||||
}
|
||||
|
||||
app.get('/settings/about', function (req, res) {
|
||||
res.send(pages.renderAboutPage(req.currentUser));
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2>About Pulse Signage</h2>
|
||||
<p>Version details and the main open-source libraries bundled with the app.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-xl-4">
|
||||
<div class="card card-outline card-primary h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
|
||||
<div>
|
||||
<div class="text-uppercase text-muted small fw-semibold">Application</div>
|
||||
<div class="fs-3 fw-semibold">Pulse Signage</div>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="text-muted small text-uppercase">Version</div>
|
||||
<div class="fs-4 fw-semibold">v{{appVersion}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-muted mb-3">Pulse is a self-hosted signage platform for managing screens, playlists, slides, and data sources from one web interface.</p>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-primary" href="/dashboard"><i class="bi bi-speedometer2 me-1" aria-hidden="true"></i>Open dashboard</a>
|
||||
<a class="btn btn-outline-secondary" href="{{repositoryUrl}}" target="_blank" rel="noreferrer"><i class="bi bi-github me-1" aria-hidden="true"></i>Repository</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xl-8">
|
||||
<div class="card card-outline card-primary h-100 overflow-hidden">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Third-party libraries</h3>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Library</th>
|
||||
<th>Version</th>
|
||||
<th>Use</th>
|
||||
<th>License</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each libraries}}
|
||||
<tr>
|
||||
<td class="fw-semibold">{{name}}</td>
|
||||
<td>{{version}}</td>
|
||||
<td>{{usage}}</td>
|
||||
<td>
|
||||
{{#if url}}
|
||||
<a href="{{url}}" target="_blank" rel="noreferrer">{{license}}</a>
|
||||
{{else}}
|
||||
{{license}}
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -326,8 +326,9 @@
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="sidebar-version mt-auto pt-2 text-secondary small">
|
||||
<div class="sidebar-version__inner border-top pt-2">
|
||||
v{{appVersion}}
|
||||
<div class="sidebar-version__inner border-top pt-2 d-flex align-items-center justify-content-between gap-3">
|
||||
<span>v{{appVersion}}</span>
|
||||
<a href="/settings/about" class="text-secondary text-decoration-none text-nowrap">Pulse Signage</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
require('../src/common');
|
||||
|
||||
const pages = require('../src/web/pages');
|
||||
const registerAboutRoutes = require('../src/web/routes/settings/about/routes');
|
||||
|
||||
test('about page renders the footer link beside the version and lists bundled libraries', () => {
|
||||
const html = pages.renderAboutPage({ id: 1 });
|
||||
|
||||
const orderedNames = ['AdminLTE', 'Animate.css', 'Bootstrap Icons', 'Cropper.js', 'Express', 'Handlebars', 'hls.js', 'Multer', 'MySQL2', 'Sharp', 'TinyMCE', 'ws'];
|
||||
let previousIndex = -1;
|
||||
|
||||
orderedNames.forEach(function (name) {
|
||||
const currentIndex = html.indexOf(name);
|
||||
assert.ok(currentIndex >= 0, `missing ${name}`);
|
||||
assert.ok(currentIndex > previousIndex, `${name} is out of order`);
|
||||
previousIndex = currentIndex;
|
||||
});
|
||||
|
||||
assert.match(html, /v2\.7\.4/);
|
||||
assert.match(html, /About Pulse/);
|
||||
assert.match(html, /href="\/settings\/about"/);
|
||||
assert.match(html, /Cropper\.js/);
|
||||
assert.match(html, /TinyMCE/);
|
||||
assert.match(html, /7\.7\.0/);
|
||||
assert.match(html, /card card-outline card-primary h-100 overflow-hidden/);
|
||||
assert.doesNotMatch(html, /rounded-3 overflow-hidden border/);
|
||||
});
|
||||
|
||||
test('about route sends the about page', async () => {
|
||||
const handlers = {};
|
||||
const app = {
|
||||
get(path, ...routeHandlers) {
|
||||
handlers[path] = routeHandlers;
|
||||
}
|
||||
};
|
||||
|
||||
registerAboutRoutes(app, {
|
||||
pages: {
|
||||
renderAboutPage(currentUser) {
|
||||
return currentUser && currentUser.id === 7 ? 'about-page' : 'unexpected';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const response = {
|
||||
body: null,
|
||||
send(value) {
|
||||
this.body = value;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
await handlers['/settings/about'][0]({ currentUser: { id: 7 } }, response, function () {});
|
||||
|
||||
assert.equal(response.body, 'about-page');
|
||||
});
|
||||
Reference in New Issue
Block a user