feat: add weblink playlists, SSH player deployment, Caddy HTTPS, build player page
- Content model: add url column + is_weblink() for web page content items - Player model: add deployment tracking fields (status, timestamps, message) - Content blueprint: add add_weblink and add_weblink_to_playlist routes; weblinks auto-deleted when removed from playlist - Players blueprint: add SSH deploy mode in add_player; weblink-aware playlist - API blueprint: weblink URL served directly in playlist response; add /api/deploy/test-ssh and /api/deploy/player endpoints - Admin blueprint: add build-player page (clone from Gitea, write base config); replace nginx status card with Caddy status on HTTPS config page - caddy_manager: rewritten to generate proper HTTPS/internal-CA Caddyfiles and reload Caddy via admin API (/load) for live config updates - ssh_deploy, background_tasks, player_build: new utils for SSH deployment - background_tasks: push Flask app context into background thread so DB updates after deployment complete correctly - ssh_deploy: robust install script detection with passwordless sudo injection (uses SSH credentials, cleaned up after install) - Dockerfile: add git, sshpass, openssh-client, rsync - docker-compose: switch nginx to Caddy on ports 80/443; add port 5000 for dev - Templates: add_player deploy mode UI, weblink form in playlist/upload pages, build_player admin page, Caddy status on HTTPS config page - Migrations: add_url_to_content, add_deployment_fields_to_player - app.py: call db.create_all() on startup for schema bootstrap - config.py: add PLAYER_CODE_DIR and PLAYER_REPO_URL settings
This commit is contained in:
@@ -95,6 +95,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Build Player Files Card (Admin Only) -->
|
||||
<div class="card management-card" style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);">
|
||||
<h2>🚀 Build Player Files</h2>
|
||||
<p>Clone/refresh the player code from Gitea and configure the server address for deployment</p>
|
||||
<div class="card-actions">
|
||||
<a href="{{ url_for('admin.build_player') }}" class="btn btn-primary">
|
||||
Build Player Files
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logo Customization Card (Admin Only) -->
|
||||
<div class="card management-card" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);">
|
||||
<h2>🎨 Logo Customization</h2>
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Build Player Files - DigiServer v2{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="page-header">
|
||||
<a href="{{ url_for('admin.admin_panel') }}" class="back-link">← Back to Admin Panel</a>
|
||||
<h1>🧰 Build Player Files for Deployment</h1>
|
||||
<p style="color: #6c757d; margin-top: 6px;">
|
||||
Pull the latest player source from a repository onto this server and bake the
|
||||
configuration it needs to talk to this server. SSH deployments ship exactly
|
||||
this staged copy, so the version you build here is what players run.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Current staged code status -->
|
||||
<div class="card status-card">
|
||||
<h2>Current Staged Player Code</h2>
|
||||
{% if code_status.available %}
|
||||
<p><span class="badge badge-success">✅ Ready</span></p>
|
||||
<ul style="line-height: 1.8;">
|
||||
<li><strong>Version (git):</strong> <code>{{ code_status.version }}</code></li>
|
||||
<li><strong>Size:</strong> {{ code_status.size }}</li>
|
||||
{% if code_status.updated_str %}
|
||||
<li><strong>Last updated:</strong> {{ code_status.updated_str }}</li>
|
||||
{% endif %}
|
||||
<li><strong>Path:</strong> <code>{{ code_status.path }}</code></li>
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>
|
||||
<span class="badge badge-warning">⚠️ Not staged</span>
|
||||
{{ code_status.reason }}
|
||||
</p>
|
||||
<p style="color: #6c757d;">Path: <code>{{ code_status.path }}</code></p>
|
||||
{% endif %}
|
||||
{% if settings.built_at %}
|
||||
<p style="color: #6c757d; margin-top: 8px;">
|
||||
Last build saved: {{ settings.built_at }}
|
||||
{% if settings.built_by %}by {{ settings.built_by }}{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ url_for('admin.build_player_action') }}">
|
||||
<!-- Repository -->
|
||||
<div class="card">
|
||||
<h2>1. Player Files Repository</h2>
|
||||
<div class="form-group">
|
||||
<label for="repo_url">Git Repository URL</label>
|
||||
<input type="text" id="repo_url" name="repo_url" class="form-control"
|
||||
value="{{ settings.repo_url }}"
|
||||
placeholder="https://gitea.example.com/org/Kiwy-Signage.git">
|
||||
<small style="color: #6c757d;">Cloned/refreshed into the staged directory on this server.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="branch">Branch</label>
|
||||
<input type="text" id="branch" name="branch" class="form-control"
|
||||
value="{{ settings.branch }}" placeholder="main">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Server configuration -->
|
||||
<div class="card">
|
||||
<h2>2. Player → Server Configuration</h2>
|
||||
<p style="color: #6c757d;">
|
||||
The player contacts the DigiServer API directly at
|
||||
<code>{scheme}://{server}:{port}/api/...</code> (no <code>/digiserver</code> path).
|
||||
Enter the address players can reach this server on.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="server_ip">Server IP / Domain</label>
|
||||
<input type="text" id="server_ip" name="server_ip" class="form-control"
|
||||
value="{{ settings.server_ip }}" placeholder="signage.example.com or 192.168.0.50">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="port">Port</label>
|
||||
<input type="number" id="port" name="port" class="form-control"
|
||||
value="{{ settings.port }}" min="1" max="65535">
|
||||
<small style="color: #6c757d;">Use 443 for HTTPS, 80 for plain HTTP, or your custom port (e.g. 8080).</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||||
<input type="checkbox" name="use_https" {% if settings.use_https %}checked{% endif %}>
|
||||
Use HTTPS
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||||
<input type="checkbox" name="verify_ssl" {% if settings.verify_ssl %}checked{% endif %}>
|
||||
Verify SSL certificate
|
||||
</label>
|
||||
<small style="color: #6c757d;">Leave off for self-signed certificates.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="orientation">Orientation</label>
|
||||
<select id="orientation" name="orientation" class="form-control">
|
||||
<option value="Landscape" {% if settings.orientation == 'Landscape' %}selected{% endif %}>Landscape</option>
|
||||
<option value="Portrait" {% if settings.orientation == 'Portrait' %}selected{% endif %}>Portrait</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="max_resolution">Max Resolution</label>
|
||||
<input type="text" id="max_resolution" name="max_resolution" class="form-control"
|
||||
value="{{ settings.max_resolution }}" placeholder="1920x1080">
|
||||
</div>
|
||||
<p style="color: #6c757d; font-size: 13px;">
|
||||
ℹ️ The per-player <strong>screen name</strong> and <strong>quick-connect code</strong>
|
||||
are filled in automatically for each device during SSH deployment.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="card">
|
||||
<h2>3. Build</h2>
|
||||
<div class="card-actions" style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<button type="submit" name="action" value="build_and_config" class="btn btn-primary">
|
||||
⬇️ Build files & write config
|
||||
</button>
|
||||
<button type="submit" name="action" value="build_files" class="btn btn-secondary">
|
||||
Build files only
|
||||
</button>
|
||||
<button type="submit" name="action" value="save_config" class="btn btn-secondary">
|
||||
Write config only
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -160,93 +160,35 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Nginx Status Card -->
|
||||
<!-- Caddy Reverse Proxy Status Card -->
|
||||
<div class="card nginx-status-card">
|
||||
<h2>🔧 Nginx Reverse Proxy Status</h2>
|
||||
{% if nginx_status.available %}
|
||||
<div class="nginx-status-content">
|
||||
<div class="status-item">
|
||||
<strong>Status:</strong>
|
||||
<span class="badge badge-success">✅ Nginx Configured</span>
|
||||
</div>
|
||||
|
||||
<div class="status-item">
|
||||
<strong>Configuration Path:</strong>
|
||||
<code>{{ nginx_status.path }}</code>
|
||||
</div>
|
||||
|
||||
{% if nginx_status.ssl_enabled %}
|
||||
<div class="status-item">
|
||||
<strong>SSL/TLS:</strong>
|
||||
<span class="badge badge-success">🔒 Enabled</span>
|
||||
</div>
|
||||
<h2>🔧 Caddy Reverse Proxy Status</h2>
|
||||
<div class="nginx-status-content">
|
||||
<div class="status-item">
|
||||
<strong>Reverse Proxy:</strong>
|
||||
<span class="badge badge-success">✅ Caddy</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<strong>SSL/TLS:</strong>
|
||||
{% if config and config.https_enabled %}
|
||||
<span class="badge badge-success">🔒 Enabled — auto-managed by Caddy</span>
|
||||
{% else %}
|
||||
<div class="status-item">
|
||||
<strong>SSL/TLS:</strong>
|
||||
<span class="badge badge-warning">⚠️ Not Configured</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.http_ports %}
|
||||
<div class="status-item">
|
||||
<strong>HTTP Ports:</strong>
|
||||
<code>{{ nginx_status.http_ports|join(', ') }}</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.https_ports %}
|
||||
<div class="status-item">
|
||||
<strong>HTTPS Ports:</strong>
|
||||
<code>{{ nginx_status.https_ports|join(', ') }}</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.server_names %}
|
||||
<div class="status-item">
|
||||
<strong>Server Names:</strong>
|
||||
{% for name in nginx_status.server_names %}
|
||||
<code>{{ name }}</code>{% if not loop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.upstream_servers %}
|
||||
<div class="status-item">
|
||||
<strong>Upstream Servers:</strong>
|
||||
{% for server in nginx_status.upstream_servers %}
|
||||
<code>{{ server }}</code>{% if not loop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.ssl_protocols %}
|
||||
<div class="status-item">
|
||||
<strong>SSL Protocols:</strong>
|
||||
<code>{{ nginx_status.ssl_protocols|join(', ') }}</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.client_max_body_size %}
|
||||
<div class="status-item">
|
||||
<strong>Max Body Size:</strong>
|
||||
<code>{{ nginx_status.client_max_body_size }}</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.gzip_enabled %}
|
||||
<div class="status-item">
|
||||
<strong>Gzip Compression:</strong>
|
||||
<span class="badge badge-success">✅ Enabled</span>
|
||||
</div>
|
||||
<span class="badge badge-warning">⚠️ HTTP only — enable HTTPS above</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="status-disabled">
|
||||
<p>⚠️ <strong>Nginx configuration not accessible</strong></p>
|
||||
<p>Error: {{ nginx_status.error|default('Unknown error') }}</p>
|
||||
<p style="font-size: 12px; color: #666;">Path checked: {{ nginx_status.path }}</p>
|
||||
<div class="status-item">
|
||||
<strong>Certificate Provider:</strong>
|
||||
<code>Let's Encrypt (automatic)</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="status-item">
|
||||
<strong>Admin API:</strong>
|
||||
<code>caddy:2019</code>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<strong>Upstream:</strong>
|
||||
<code>digiserver-app:5000</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Information Section -->
|
||||
|
||||
Reference in New Issue
Block a user