Upload
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html lang="da">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}TuxiNet StreamHub{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('main.static', filename='style.css') }}">
|
||||
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<div class="brand">🎛️ TuxiNet StreamHub</div>
|
||||
{% if current_user %}
|
||||
<nav>
|
||||
<a href="{{ url_for('main.dashboard') }}">Dashboard</a>
|
||||
<a href="{{ url_for('main.logout') }}">Log ud</a>
|
||||
</nav>
|
||||
{% endif %}
|
||||
</header>
|
||||
<main class="container">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="flash {{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,27 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<div class="page-head">
|
||||
<h1>Dashboard</h1>
|
||||
{% if current_user.role == 'admin' %}
|
||||
<a class="button" href="{{ url_for('main.stream_new') }}">+ Opret stream</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="grid cards">
|
||||
{% for s in streams %}
|
||||
<div class="card">
|
||||
<div class="status-row">
|
||||
<h2>{{ s.name }}</h2>
|
||||
<span class="badge {{ 'live' if s.is_live else 'off' }}">{{ 'LIVE' if s.is_live else 'OFFLINE' }}</span>
|
||||
</div>
|
||||
<p><strong>Slug:</strong> {{ s.slug }}</p>
|
||||
<p><strong>Publish:</strong> {{ 'Åben' if s.active else 'Lukket' }}</p>
|
||||
<div class="actions">
|
||||
<a class="button secondary" href="{{ url_for('main.stream_detail', stream_id=s.id) }}">Detaljer</a>
|
||||
<a class="button secondary" href="{{ url_for('main.streamer_panel', slug=s.slug) }}">Streamer panel</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card"><p>Ingen streams endnu.</p></div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,12 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Login{% endblock %}
|
||||
{% block content %}
|
||||
<div class="card narrow">
|
||||
<h1>Login</h1>
|
||||
<form method="post" class="form-grid">
|
||||
<label>Brugernavn<input type="text" name="username" required></label>
|
||||
<label>Kodeord<input type="password" name="password" required></label>
|
||||
<button type="submit">Log ind</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,61 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<div class="page-head">
|
||||
<h1>{{ stream.name }}</h1>
|
||||
<div class="actions">
|
||||
<a class="button secondary" href="{{ url_for('main.streamer_panel', slug=stream.slug) }}">Åbn streamer-panel</a>
|
||||
<a class="button secondary" href="{{ url_for('main.stream_edit', stream_id=stream.id) }}">Rediger</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid two">
|
||||
<div class="card">
|
||||
<h2>Preview</h2>
|
||||
<video id="preview" class="video" controls autoplay muted playsinline></video>
|
||||
<p class="small">HLS: <code>{{ public_hls }}</code></p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Status</h2>
|
||||
<p><strong>LIVE:</strong> {{ 'Ja' if stream.is_live else 'Nej' }}</p>
|
||||
<p><strong>Publish:</strong> {{ 'Åben' if stream.active else 'Lukket' }}</p>
|
||||
<p><strong>Video:</strong> {{ stats.video_codec or '-' }} {{ stats.width or '-' }}x{{ stats.height or '-' }} / {{ stats.fps or '-' }} FPS</p>
|
||||
<p><strong>Lyd:</strong> {{ stats.audio_codec or '-' }} / {{ stats.audio_sample_rate or '-' }} Hz / {% if stats.audio_channels == 1 %}<span class="warn">MONO ⚠️</span>{% elif stats.audio_channels %}{{ stats.audio_channels }} ch{% else %}-{% endif %}</p>
|
||||
<p><strong>Bitrate:</strong> {% if stats.bitrate %}{{ (stats.bitrate / 1000)|round(1) }} kbps{% else %}-{% endif %}</p>
|
||||
<p><strong>RTMP ingest:</strong><br><code>{{ ingest }}</code></p>
|
||||
<form method="post" action="{{ url_for('main.toggle_stream', stream_id=stream.id) }}">
|
||||
<button type="submit">{{ 'Stop publish' if stream.active else 'Start publish' }}</button>
|
||||
</form>
|
||||
<form method="post" action="{{ url_for('main.rotate_token', stream_id=stream.id) }}">
|
||||
<button type="submit" class="secondary">Rotér token</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid two">
|
||||
<div class="card">
|
||||
<h2>Chat</h2>
|
||||
{% if stream.chat_url %}
|
||||
<iframe class="chatframe" src="{{ stream.chat_url }}"></iframe>
|
||||
{% else %}
|
||||
<p>Ingen chat sat op.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Hændelseslog</h2>
|
||||
<div class="loglist">
|
||||
{% for log in logs %}
|
||||
<div class="log {{ log.level }}">{{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }} — {{ log.message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const video = document.getElementById('preview');
|
||||
const src = {{ public_hls|tojson }};
|
||||
if (Hls.isSupported()) {
|
||||
const hls = new Hls({ lowLatencyMode: true, backBufferLength: 90 });
|
||||
hls.loadSource(src);
|
||||
hls.attachMedia(video);
|
||||
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
video.src = src;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,24 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<h1>{{ 'Rediger stream' if stream else 'Ny stream' }}</h1>
|
||||
<form method="post" class="form-grid">
|
||||
<label>Navn<input type="text" name="name" value="{{ stream.name if stream else '' }}" required></label>
|
||||
<label>Slug<input type="text" name="slug" value="{{ stream.slug if stream else '' }}" required></label>
|
||||
<label>Chat type
|
||||
<select name="chat_type">
|
||||
{% set chat_type = stream.chat_type if stream else 'none' %}
|
||||
<option value="none" {{ 'selected' if chat_type == 'none' else '' }}>Ingen</option>
|
||||
<option value="twitch" {{ 'selected' if chat_type == 'twitch' else '' }}>Twitch</option>
|
||||
<option value="youtube" {{ 'selected' if chat_type == 'youtube' else '' }}>YouTube</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>Chat URL / embed URL<input type="text" name="chat_url" value="{{ stream.chat_url if stream else '' }}"></label>
|
||||
<label class="full">Noter<textarea name="notes" rows="5">{{ stream.notes if stream else '' }}</textarea></label>
|
||||
{% if stream %}
|
||||
<label class="check"><input type="checkbox" name="active" value="1" {{ 'checked' if stream.active else '' }}> Tillad publish</label>
|
||||
{% endif %}
|
||||
<button type="submit">Gem</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,44 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<div class="page-head">
|
||||
<h1>Streamer-panel — {{ stream.name }}</h1>
|
||||
<span class="badge {{ 'live' if stream.is_live else 'off' }}">{{ 'LIVE' if stream.is_live else 'OFFLINE' }}</span>
|
||||
</div>
|
||||
<div class="grid streamer-layout">
|
||||
<div class="card">
|
||||
<h2>Server preview</h2>
|
||||
<video id="preview" class="video" controls autoplay muted playsinline></video>
|
||||
<div class="stats-grid">
|
||||
<div><strong>Video</strong><br>{{ stats.video_codec or '-' }}</div>
|
||||
<div><strong>Opløsning</strong><br>{{ stats.width or '-' }}x{{ stats.height or '-' }}</div>
|
||||
<div><strong>FPS</strong><br>{{ stats.fps or '-' }}</div>
|
||||
<div><strong>Bitrate</strong><br>{% if stats.bitrate %}{{ (stats.bitrate/1000)|round(1) }} kbps{% else %}-{% endif %}</div>
|
||||
<div><strong>Lyd</strong><br>{{ stats.audio_codec or '-' }}</div>
|
||||
<div><strong>Kanaler</strong><br>{% if stats.audio_channels == 1 %}<span class="warn">MONO ⚠️</span>{% elif stats.audio_channels %}{{ stats.audio_channels }} ch{% else %}-{% endif %}</div>
|
||||
</div>
|
||||
<div class="codebox">
|
||||
<div><strong>RTMP URL</strong><br><code>{{ ingest }}</code></div>
|
||||
<div><strong>HLS preview</strong><br><code>{{ public_hls }}</code></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Chat</h2>
|
||||
{% if stream.chat_url %}
|
||||
<iframe class="chatframe tall" src="{{ stream.chat_url }}"></iframe>
|
||||
{% else %}
|
||||
<p>Ingen chat sat op.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const video = document.getElementById('preview');
|
||||
const src = {{ public_hls|tojson }};
|
||||
if (Hls.isSupported()) {
|
||||
const hls = new Hls({ lowLatencyMode: true, backBufferLength: 90 });
|
||||
hls.loadSource(src);
|
||||
hls.attachMedia(video);
|
||||
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
video.src = src;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user