docker updates.
This commit is contained in:
1 parent
ff23474a54
commit
c6ec8f6ad9
6 files changed
+156
-61
No files matched your search
@@ -0,0 +1,46 @@
|
||||
name: Publish Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.lzstealth.com
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Extract image metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: git.lzstealth.com/LZStealth/pulse-signage
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=ref,event=tag
|
||||
type=sha
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -68,9 +68,19 @@ The app reads its settings from environment variables.
|
||||
The recommended way to run the project is with Docker Compose.
|
||||
|
||||
```bash
|
||||
docker-compose up --build
|
||||
npm run docker:up
|
||||
```
|
||||
|
||||
The main Compose file now uses the published Docker image and still reads values from the repository `.env` file. `DB_HOST` comes from that file for the app containers, so you can point the web UI and player at an external database host or change it to `mysql` if you want to use the bundled MySQL service.
|
||||
|
||||
If you only want to test the image build from the current checkout without using Compose, run:
|
||||
|
||||
```bash
|
||||
npm run docker:build
|
||||
```
|
||||
|
||||
To publish the Docker image to the Gitea container registry, push to `main` or create a `v*` tag. The workflow in [.gitea/workflows/docker-publish.yml](.gitea/workflows/docker-publish.yml) builds `git.lzstealth.com/LZStealth/pulse-signage` and pushes `latest`, `sha`, and tagged releases.
|
||||
|
||||
This starts:
|
||||
|
||||
- the web admin app on port `3000`
|
||||
@@ -79,6 +89,8 @@ This starts:
|
||||
|
||||
The web app and player app share the same uploaded media volume, so files uploaded in the admin UI are available to the player.
|
||||
|
||||
If you want to stop the stack later, run `npm run docker:down`. To follow logs, run `npm run docker:logs`.
|
||||
|
||||
## Important Notes
|
||||
|
||||
- The web app must be able to reach the player through `PLAYER_INTERNAL_BASE_URL`.
|
||||
|
||||
+14
-9
@@ -1,12 +1,10 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
webui:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: ${PULSE_SIGNAGE_IMAGE:-git.lzstealth.com/LZStealth/pulse-signage:latest}
|
||||
container_name: signage-webui
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
@@ -20,6 +18,12 @@ services:
|
||||
PLAYER_INTERNAL_BASE_URL: ${PLAYER_INTERNAL_BASE_URL:-http://player:3001}
|
||||
PLAYER_PUBLIC_BASE_URL: ${PLAYER_PUBLIC_BASE_URL:-http://localhost:3001}
|
||||
UPLOAD_DIR: ${UPLOAD_DIR:-/app/uploads}
|
||||
SESSION_MAX_AGE_DAYS: ${SESSION_MAX_AGE_DAYS:-14}
|
||||
DASHBOARD_REFRESH_INTERVAL_MS: ${DASHBOARD_REFRESH_INTERVAL_MS:-2000}
|
||||
DEFAULT_ADMIN_USERNAME: ${DEFAULT_ADMIN_USERNAME:-admin}
|
||||
DEFAULT_ADMIN_NAME: ${DEFAULT_ADMIN_NAME:-Admin}
|
||||
DEFAULT_ADMIN_PASSWORD: ${DEFAULT_ADMIN_PASSWORD:-admin}
|
||||
PASSWORD_HASH_ITERATIONS: ${PASSWORD_HASH_ITERATIONS:-310000}
|
||||
volumes:
|
||||
- uploads:/app/uploads
|
||||
command: ["npm", "run", "start:webui"]
|
||||
@@ -30,11 +34,11 @@ services:
|
||||
- signage-net
|
||||
|
||||
player:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: ${PULSE_SIGNAGE_IMAGE:-git.lzstealth.com/LZStealth/pulse-signage:latest}
|
||||
container_name: signage-player
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "3001:3001"
|
||||
environment:
|
||||
@@ -59,6 +63,8 @@ services:
|
||||
image: mysql:8.4
|
||||
container_name: signage-mysql
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "3306:3306"
|
||||
environment:
|
||||
@@ -67,7 +73,6 @@ services:
|
||||
MYSQL_PASSWORD: ${DB_PASSWORD:-signage_password}
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root_password}
|
||||
command:
|
||||
- --default-authentication-plugin=mysql_native_password
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
volumes:
|
||||
|
||||
+5
-1
@@ -9,7 +9,11 @@
|
||||
"start:webui": "node -r dotenv/config src/webui.js",
|
||||
"start:player": "node -r dotenv/config src/player.js",
|
||||
"dev:webui": "nodemon -r dotenv/config src/webui.js",
|
||||
"dev:player": "nodemon -r dotenv/config src/player.js"
|
||||
"dev:player": "nodemon -r dotenv/config src/player.js",
|
||||
"docker:build": "docker build -t pulse-signage:test .",
|
||||
"docker:up": "docker-compose up -d",
|
||||
"docker:down": "docker-compose down",
|
||||
"docker:logs": "docker-compose logs -f --tail=100"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^17.4.2",
|
||||
|
||||
@@ -14,6 +14,23 @@ function createPool() {
|
||||
});
|
||||
}
|
||||
|
||||
async function addColumnIfMissing(pool, tableName, columnName, columnDefinition) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT COUNT(*) AS column_count
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = ?
|
||||
AND column_name = ?`,
|
||||
[tableName, columnName]
|
||||
);
|
||||
|
||||
if (rows.length && Number(rows[0].column_count) > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await pool.query(`ALTER TABLE \`${tableName}\` ADD COLUMN \`${columnName}\` ${columnDefinition}`);
|
||||
}
|
||||
|
||||
async function ensureSchema(pool) {
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS canvas_sizes (
|
||||
@@ -26,9 +43,9 @@ async function ensureSchema(pool) {
|
||||
UNIQUE KEY uq_canvas_sizes_dimensions (width, height)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
await pool.query('ALTER TABLE canvas_sizes ADD COLUMN IF NOT EXISTS modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER created_at');
|
||||
await pool.query('ALTER TABLE canvas_sizes ADD COLUMN IF NOT EXISTS created_by INT NULL AFTER created_at');
|
||||
await pool.query('ALTER TABLE canvas_sizes ADD COLUMN IF NOT EXISTS modified_by INT NULL AFTER modified_at');
|
||||
await addColumnIfMissing(pool, 'canvas_sizes', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
||||
await addColumnIfMissing(pool, 'canvas_sizes', 'created_by', 'INT NULL');
|
||||
await addColumnIfMissing(pool, 'canvas_sizes', 'modified_by', 'INT NULL');
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS playlists (
|
||||
@@ -39,10 +56,10 @@ async function ensureSchema(pool) {
|
||||
modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
await pool.query('ALTER TABLE playlists ADD COLUMN IF NOT EXISTS fade_between_slides TINYINT(1) NOT NULL DEFAULT 0 AFTER name');
|
||||
await pool.query('ALTER TABLE playlists ADD COLUMN IF NOT EXISTS modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER created_at');
|
||||
await pool.query('ALTER TABLE playlists ADD COLUMN IF NOT EXISTS created_by INT NULL AFTER created_at');
|
||||
await pool.query('ALTER TABLE playlists ADD COLUMN IF NOT EXISTS modified_by INT NULL AFTER modified_at');
|
||||
await addColumnIfMissing(pool, 'playlists', 'fade_between_slides', 'TINYINT(1) NOT NULL DEFAULT 0');
|
||||
await addColumnIfMissing(pool, 'playlists', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
||||
await addColumnIfMissing(pool, 'playlists', 'created_by', 'INT NULL');
|
||||
await addColumnIfMissing(pool, 'playlists', 'modified_by', 'INT NULL');
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS slide_templates (
|
||||
@@ -55,11 +72,11 @@ async function ensureSchema(pool) {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
|
||||
await pool.query('ALTER TABLE slide_templates ADD COLUMN IF NOT EXISTS canvas_size_id INT NULL AFTER name');
|
||||
await pool.query('ALTER TABLE slide_templates ADD COLUMN IF NOT EXISTS background_image_path VARCHAR(512) NULL');
|
||||
await pool.query('ALTER TABLE slide_templates ADD COLUMN IF NOT EXISTS modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER created_at');
|
||||
await pool.query('ALTER TABLE slide_templates ADD COLUMN IF NOT EXISTS created_by INT NULL AFTER created_at');
|
||||
await pool.query('ALTER TABLE slide_templates ADD COLUMN IF NOT EXISTS modified_by INT NULL AFTER modified_at');
|
||||
await addColumnIfMissing(pool, 'slide_templates', 'canvas_size_id', 'INT NULL');
|
||||
await addColumnIfMissing(pool, 'slide_templates', 'background_image_path', 'VARCHAR(512) NULL');
|
||||
await addColumnIfMissing(pool, 'slide_templates', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
||||
await addColumnIfMissing(pool, 'slide_templates', 'created_by', 'INT NULL');
|
||||
await addColumnIfMissing(pool, 'slide_templates', 'modified_by', 'INT NULL');
|
||||
|
||||
await pool.query(`
|
||||
INSERT IGNORE INTO canvas_sizes (name, width, height) VALUES
|
||||
@@ -103,10 +120,10 @@ async function ensureSchema(pool) {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
|
||||
await pool.query('ALTER TABLE slide_template_regions ADD COLUMN IF NOT EXISTS font_family VARCHAR(100) NULL AFTER label');
|
||||
await pool.query('ALTER TABLE slide_template_regions ADD COLUMN IF NOT EXISTS modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER created_at');
|
||||
await pool.query('ALTER TABLE slide_template_regions ADD COLUMN IF NOT EXISTS created_by INT NULL AFTER created_at');
|
||||
await pool.query('ALTER TABLE slide_template_regions ADD COLUMN IF NOT EXISTS modified_by INT NULL AFTER modified_at');
|
||||
await addColumnIfMissing(pool, 'slide_template_regions', 'font_family', 'VARCHAR(100) NULL');
|
||||
await addColumnIfMissing(pool, 'slide_template_regions', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
||||
await addColumnIfMissing(pool, 'slide_template_regions', 'created_by', 'INT NULL');
|
||||
await addColumnIfMissing(pool, 'slide_template_regions', 'modified_by', 'INT NULL');
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS slides (
|
||||
@@ -122,14 +139,14 @@ async function ensureSchema(pool) {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
|
||||
await pool.query('ALTER TABLE slides ADD COLUMN IF NOT EXISTS body TEXT NULL AFTER title');
|
||||
await pool.query('ALTER TABLE slides ADD COLUMN IF NOT EXISTS template_id INT NULL AFTER body');
|
||||
await pool.query('ALTER TABLE slides ADD COLUMN IF NOT EXISTS content_json JSON NULL AFTER template_id');
|
||||
await pool.query('ALTER TABLE slides ADD COLUMN IF NOT EXISTS media_path VARCHAR(512) NULL AFTER content_json');
|
||||
await pool.query('ALTER TABLE slides ADD COLUMN IF NOT EXISTS media_type VARCHAR(100) NULL AFTER media_path');
|
||||
await pool.query('ALTER TABLE slides ADD COLUMN IF NOT EXISTS modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER created_at');
|
||||
await pool.query('ALTER TABLE slides ADD COLUMN IF NOT EXISTS created_by INT NULL AFTER created_at');
|
||||
await pool.query('ALTER TABLE slides ADD COLUMN IF NOT EXISTS modified_by INT NULL AFTER modified_at');
|
||||
await addColumnIfMissing(pool, 'slides', 'body', 'TEXT NULL');
|
||||
await addColumnIfMissing(pool, 'slides', 'template_id', 'INT NULL');
|
||||
await addColumnIfMissing(pool, 'slides', 'content_json', 'JSON NULL');
|
||||
await addColumnIfMissing(pool, 'slides', 'media_path', 'VARCHAR(512) NULL');
|
||||
await addColumnIfMissing(pool, 'slides', 'media_type', 'VARCHAR(100) NULL');
|
||||
await addColumnIfMissing(pool, 'slides', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
||||
await addColumnIfMissing(pool, 'slides', 'created_by', 'INT NULL');
|
||||
await addColumnIfMissing(pool, 'slides', 'modified_by', 'INT NULL');
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS playlist_slides (
|
||||
@@ -151,16 +168,16 @@ async function ensureSchema(pool) {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
|
||||
await pool.query('ALTER TABLE playlist_slides ADD COLUMN IF NOT EXISTS duration_seconds INT NOT NULL DEFAULT 10 AFTER position');
|
||||
await pool.query("ALTER TABLE playlist_slides ADD COLUMN IF NOT EXISTS schedule_mode VARCHAR(20) NOT NULL DEFAULT 'always' AFTER duration_seconds");
|
||||
await pool.query('ALTER TABLE playlist_slides ADD COLUMN IF NOT EXISTS schedule_start_datetime DATETIME NULL AFTER schedule_mode');
|
||||
await pool.query('ALTER TABLE playlist_slides ADD COLUMN IF NOT EXISTS schedule_end_datetime DATETIME NULL AFTER schedule_start_datetime');
|
||||
await pool.query('ALTER TABLE playlist_slides ADD COLUMN IF NOT EXISTS schedule_start_time TIME NULL AFTER schedule_end_datetime');
|
||||
await pool.query('ALTER TABLE playlist_slides ADD COLUMN IF NOT EXISTS schedule_end_time TIME NULL AFTER schedule_start_time');
|
||||
await pool.query('ALTER TABLE playlist_slides ADD COLUMN IF NOT EXISTS schedule_days_json JSON NULL AFTER schedule_end_time');
|
||||
await pool.query('ALTER TABLE playlist_slides ADD COLUMN IF NOT EXISTS modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER created_at');
|
||||
await pool.query('ALTER TABLE playlist_slides ADD COLUMN IF NOT EXISTS created_by INT NULL AFTER created_at');
|
||||
await pool.query('ALTER TABLE playlist_slides ADD COLUMN IF NOT EXISTS modified_by INT NULL AFTER modified_at');
|
||||
await addColumnIfMissing(pool, 'playlist_slides', 'duration_seconds', 'INT NOT NULL DEFAULT 10');
|
||||
await addColumnIfMissing(pool, 'playlist_slides', 'schedule_mode', "VARCHAR(20) NOT NULL DEFAULT 'always'");
|
||||
await addColumnIfMissing(pool, 'playlist_slides', 'schedule_start_datetime', 'DATETIME NULL');
|
||||
await addColumnIfMissing(pool, 'playlist_slides', 'schedule_end_datetime', 'DATETIME NULL');
|
||||
await addColumnIfMissing(pool, 'playlist_slides', 'schedule_start_time', 'TIME NULL');
|
||||
await addColumnIfMissing(pool, 'playlist_slides', 'schedule_end_time', 'TIME NULL');
|
||||
await addColumnIfMissing(pool, 'playlist_slides', 'schedule_days_json', 'JSON NULL');
|
||||
await addColumnIfMissing(pool, 'playlist_slides', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
||||
await addColumnIfMissing(pool, 'playlist_slides', 'created_by', 'INT NULL');
|
||||
await addColumnIfMissing(pool, 'playlist_slides', 'modified_by', 'INT NULL');
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS screens (
|
||||
@@ -174,10 +191,10 @@ async function ensureSchema(pool) {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
|
||||
await pool.query('ALTER TABLE screens ADD COLUMN IF NOT EXISTS playlist_id INT NULL AFTER slug');
|
||||
await pool.query('ALTER TABLE screens ADD COLUMN IF NOT EXISTS modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER created_at');
|
||||
await pool.query('ALTER TABLE screens ADD COLUMN IF NOT EXISTS created_by INT NULL AFTER created_at');
|
||||
await pool.query('ALTER TABLE screens ADD COLUMN IF NOT EXISTS modified_by INT NULL AFTER modified_at');
|
||||
await addColumnIfMissing(pool, 'screens', 'playlist_id', 'INT NULL');
|
||||
await addColumnIfMissing(pool, 'screens', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
||||
await addColumnIfMissing(pool, 'screens', 'created_by', 'INT NULL');
|
||||
await addColumnIfMissing(pool, 'screens', 'modified_by', 'INT NULL');
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
@@ -191,13 +208,13 @@ async function ensureSchema(pool) {
|
||||
modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
await pool.query('ALTER TABLE users ADD COLUMN IF NOT EXISTS name VARCHAR(255) NULL AFTER id');
|
||||
await pool.query('ALTER TABLE users ADD COLUMN IF NOT EXISTS password_hash CHAR(64) NOT NULL AFTER username');
|
||||
await pool.query('ALTER TABLE users ADD COLUMN IF NOT EXISTS password_salt VARCHAR(64) NOT NULL AFTER password_hash');
|
||||
await pool.query('ALTER TABLE users ADD COLUMN IF NOT EXISTS password_iterations INT NOT NULL AFTER password_salt');
|
||||
await pool.query('ALTER TABLE users ADD COLUMN IF NOT EXISTS modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER created_at');
|
||||
await pool.query('ALTER TABLE users ADD COLUMN IF NOT EXISTS created_by INT NULL AFTER created_at');
|
||||
await pool.query('ALTER TABLE users ADD COLUMN IF NOT EXISTS modified_by INT NULL AFTER modified_at');
|
||||
await addColumnIfMissing(pool, 'users', 'name', 'VARCHAR(255) NULL');
|
||||
await addColumnIfMissing(pool, 'users', 'password_hash', 'CHAR(64) NOT NULL');
|
||||
await addColumnIfMissing(pool, 'users', 'password_salt', 'VARCHAR(64) NOT NULL');
|
||||
await addColumnIfMissing(pool, 'users', 'password_iterations', 'INT NOT NULL');
|
||||
await addColumnIfMissing(pool, 'users', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
||||
await addColumnIfMissing(pool, 'users', 'created_by', 'INT NULL');
|
||||
await addColumnIfMissing(pool, 'users', 'modified_by', 'INT NULL');
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS auth_sessions (
|
||||
@@ -209,8 +226,8 @@ async function ensureSchema(pool) {
|
||||
CONSTRAINT fk_auth_sessions_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
await pool.query('ALTER TABLE auth_sessions ADD COLUMN IF NOT EXISTS created_by INT NULL AFTER created_at');
|
||||
await pool.query('ALTER TABLE auth_sessions ADD COLUMN IF NOT EXISTS modified_by INT NULL AFTER last_used_at');
|
||||
await addColumnIfMissing(pool, 'auth_sessions', 'created_by', 'INT NULL');
|
||||
await addColumnIfMissing(pool, 'auth_sessions', 'modified_by', 'INT NULL');
|
||||
|
||||
const [userCountRows] = await pool.query('SELECT COUNT(*) AS user_count FROM users');
|
||||
if (!userCountRows.length || Number(userCountRows[0].user_count) === 0) {
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
<script>
|
||||
const slug = {{SLUG_JSON}};
|
||||
const initialData = {{INITIAL_DATA_JSON}};
|
||||
function parseTemplateJson(templateValue, fallbackValue) {
|
||||
if (typeof templateValue !== 'string' || /^\s*\{\{.*\}\}\s*$/.test(templateValue)) {
|
||||
return fallbackValue;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(templateValue);
|
||||
} catch (_error) {
|
||||
return fallbackValue;
|
||||
}
|
||||
}
|
||||
|
||||
const slug = parseTemplateJson('{{SLUG_JSON}}', '');
|
||||
const initialData = parseTemplateJson('{{INITIAL_DATA_JSON}}', {});
|
||||
const app = document.getElementById('app');
|
||||
let slides = Array.isArray(initialData && initialData.slides) ? initialData.slides.map(normalizeSlide) : [];
|
||||
let currentPlaylistSignature = '';
|
||||
@@ -31,7 +42,7 @@
|
||||
let slideExpiresAt = null;
|
||||
const slideFadeDurationMs = 560;
|
||||
const commandSocketPath = '/ws/screens/' + encodeURIComponent(slug);
|
||||
const commandClientStorageKey = 'digital-signage-player-client-id:' + slug;
|
||||
const commandClientStorageKey = 'pulse-signage-player-client-id:' + slug;
|
||||
|
||||
// Escape text before inserting it into HTML.
|
||||
function escapeHtml(value) {
|
||||
|
||||
Reference in new issue
Block a user