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:
2026-07-13 16:46:19 +03:00
parent ae3b82862d
commit 2af04e1db3
24 changed files with 2571 additions and 248 deletions
@@ -314,11 +314,18 @@
</td>
<td><span class="drag-handle">⋮⋮</span></td>
<td>{{ loop.index }}</td>
<td>{{ content.filename }}</td>
<td>
{% if content.content_type == 'weblink' %}
<a href="{{ content.url }}" target="_blank" rel="noopener noreferrer">{{ content.url }}</a>
{% else %}
{{ content.filename }}
{% endif %}
</td>
<td>
{% if content.content_type == 'image' %}📷 Image
{% elif content.content_type == 'video' %}🎥 Video
{% elif content.content_type == 'pdf' %}📄 PDF
{% elif content.content_type == 'weblink' %}🔗 Web Link
{% else %}📁 Other{% endif %}
</td>
<td>
@@ -401,6 +408,22 @@
<div class="card">
<h2 style="margin-bottom: 20px;"> Add Content</h2>
<div style="margin-bottom: 24px; padding-bottom: 24px; border-bottom: 1px solid #e0e0e0;">
<h3 style="margin-bottom: 12px; font-size: 16px;">🔗 Add Web Link</h3>
<form method="POST"
action="{{ url_for('content.add_weblink_to_playlist', playlist_id=playlist.id) }}">
<input type="url" name="url" required
placeholder="https://example.com/dashboard"
style="width: 100%; padding: 8px; margin-bottom: 8px; box-sizing: border-box;">
<div style="display: flex; gap: 8px; align-items: center;">
<label style="font-size: 13px; color: #666;">Duration (s):</label>
<input type="number" name="duration" value="30" min="1"
style="width: 80px; padding: 6px;">
<button type="submit" class="btn btn-primary btn-sm">+ Add Link</button>
</div>
</form>
</div>
{% if available_content %}
<div class="available-content">
{% for content in available_content %}