asteroid/template/admin.ctml

288 lines
12 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<title data-text="title">Asteroid Radio - Admin Dashboard</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/asteroid/static/asteroid.css">
<script src="/asteroid/static/js/auth-ui.js"></script>
<script src="/asteroid/static/js/admin.js"></script>
</head>
<body>
<div class="container">
<h1>🎛️ ADMIN DASHBOARD</h1>
<div class="nav">
<a href="/asteroid">Home</a>
<a href="/asteroid/player">Player</a>
<a href="/asteroid/profile">Profile</a>
<a href="/asteroid/admin/users">👥 Users</a>
<a href="/asteroid/logout" class="btn-logout">Logout</a>
</div>
<!-- System Status -->
<div class="admin-section">
<h2>System Status</h2>
<div class="admin-grid">
<div class="status-card">
<h3>Server Status</h3>
<p class="status-good" data-text="server-status">🟢 Running</p>
</div>
<div class="status-card">
<h3>Database Status</h3>
<p class="status-good" data-text="database-status">🟢 Connected</p>
</div>
<div class="status-card">
<h3>Liquidsoap Status</h3>
<p class="status-error" data-text="liquidsoap-status">🔴 Not Running</p>
</div>
<div class="status-card">
<h3>Icecast Status</h3>
<p class="status-error" data-text="icecast-status">🔴 Not Running</p>
<button id="icecast-restart" class="btn btn-danger btn-sm" style="margin-top: 8px;">🔄 Restart</button>
</div>
</div>
</div>
<!-- Listener Statistics -->
<div class="admin-section">
<h2>📊 Current Listeners</h2>
<table class="listener-stats-table" style="table-layout: fixed; width: 100%;">
<colgroup>
<col style="width: 25%;">
<col style="width: 25%;">
<col style="width: 25%;">
<col style="width: 25%;">
</colgroup>
<thead>
<tr>
<th>🎵 MP3</th>
<th>🎧 AAC</th>
<th>📱 Low</th>
<th>🎲 Shuffle</th>
<th>📈 Total</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;"><span class="stat-number" id="listeners-mp3">0</span></td>
<td style="text-align: center;"><span class="stat-number" id="listeners-aac">0</span></td>
<td style="text-align: center;"><span class="stat-number" id="listeners-low">0</span></td>
<td style="text-align: center;"><span class="stat-number" id="listeners-shuffle">0</span></td>
<td style="text-align: center;"><span class="stat-number" id="listeners-total">0</span></td>
</tr>
<tr class="stat-peak-row">
<td style="text-align: center;">Peak: <span id="peak-mp3">0</span></td>
<td style="text-align: center;">Peak: <span id="peak-aac">0</span></td>
<td style="text-align: center;">Peak: <span id="peak-low">0</span></td>
<td style="text-align: center;">Peak: <span id="peak-shuffle">0</span></td>
<td style="text-align: center;">Updated: <span id="stats-updated">--</span></td>
</tr>
</tbody>
</table>
<div class="admin-controls" style="margin-top: 10px;">
<button id="refresh-stats" class="btn btn-secondary" onclick="refreshListenerStats()">🔄 Refresh</button>
<span id="stats-status" style="margin-left: 15px;"></span>
</div>
<!-- Geo Stats -->
<h3 style="margin-top: 20px;">🌍 Listener Locations (Last 7 Days)</h3>
<div id="geo-stats-container">
<table class="listener-stats-table" id="geo-stats-table">
<thead>
<tr>
<th>Country</th>
<th>Listeners</th>
<th>Minutes</th>
</tr>
</thead>
<tbody id="geo-stats-body">
<tr><td colspan="3" style="color: #888;">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Music Library Management -->
<div class="admin-section">
<h2>Music Library Management</h2>
<!-- Music Library Info -->
<div class="upload-section">
<h3>Music Library</h3>
<div class="upload-info">
<p>The music library is mounted from your local filesystem into the Liquidsoap container.</p>
<p><strong>To add music:</strong></p>
<ol>
<li>Add files to your music library directory (set via <code>MUSIC_LIBRARY</code> env var)</li>
<li>Click "Scan Library" to index new tracks into the database</li>
</ol>
<p><em>Supported formats: MP3, FLAC, OGG, WAV, OPUS</em></p>
</div>
</div>
<div class="admin-controls">
<button id="scan-library" class="btn btn-primary">🔍 Scan Library</button>
<button id="refresh-tracks" class="btn btn-secondary">🔄 Refresh Track List</button>
<span id="scan-status" style="margin-left: 15px; font-weight: bold;"></span>
</div>
<div class="track-stats">
<p>Total Tracks: <span id="track-count" data-text="track-count">0</span></p>
</div>
</div>
<!-- Track Management -->
<div class="admin-section">
<h2>Track Management</h2>
<div class="track-controls">
<input type="text" id="track-search" placeholder="Search tracks..." class="search-input">
<select id="sort-tracks" class="sort-select">
<option value="title">Sort by Title</option>
<option value="artist">Sort by Artist</option>
<option value="album">Sort by Album</option>
</select>
<select id="tracks-per-page" class="sort-select" onchange="changeTracksPerPage()">
<option value="10">10 per page</option>
<option value="20" selected>20 per page</option>
<option value="50">50 per page</option>
<option value="100">100 per page</option>
</select>
</div>
<div id="tracks-container" class="tracks-list">
<div class="loading">Loading tracks...</div>
</div>
<!-- Pagination Controls -->
<div id="pagination-controls" style="display: none; margin-top: 20px; text-align: center;">
<button onclick="goToPage(1)" class="btn btn-secondary">« First</button>
<button onclick="previousPage()" class="btn btn-secondary"> Prev</button>
<span id="page-info" style="margin: 0 15px; font-weight: bold;">Page 1 of 1</span>
<button onclick="nextPage()" class="btn btn-secondary">Next </button>
<button onclick="goToLastPage()" class="btn btn-secondary">Last »</button>
</div>
</div>
<!-- Stream Queue Management -->
<div class="admin-section">
<h2>🎵 Stream Queue Management</h2>
<p>Manage the live stream playback queue. Liquidsoap watches <code>stream-queue.m3u</code> and reloads automatically.</p>
<!-- Playlist Selection -->
<div class="playlist-controls" style="margin-bottom: 20px; padding: 15px; background: #2a2a2a; border-radius: 8px;">
<h3 style="margin-top: 0;">📋 Load Playlist</h3>
<div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
<select id="playlist-select" class="sort-select" style="min-width: 250px;">
<option value="">-- Select a playlist --</option>
</select>
<button id="load-playlist-btn" class="btn btn-success">📂 Load Selected</button>
<button id="refresh-playlists-btn" class="btn btn-secondary">🔄 Refresh List</button>
</div>
<p style="margin: 10px 0 0 0; font-size: 0.9em; color: #888;">
Loading a playlist will copy it to <code>stream-queue.m3u</code> and Liquidsoap will start playing it.
</p>
</div>
<!-- Queue Controls -->
<div class="queue-controls" style="margin-bottom: 15px;">
<button id="refresh-queue" class="btn btn-secondary">🔄 Refresh Queue</button>
<button id="save-queue-btn" class="btn btn-primary">💾 Save Queue</button>
<button id="clear-queue-btn" class="btn btn-warning">🗑️ Clear Queue</button>
<button id="add-random-tracks" class="btn btn-info">🎲 Add 10 Random</button>
</div>
<!-- Save As -->
<div style="margin-bottom: 15px; display: flex; gap: 10px; align-items: center;">
<input type="text" id="save-as-name" placeholder="New playlist name..." class="search-input" style="max-width: 250px;">
<button id="save-as-btn" class="btn btn-success">💾 Save As New Playlist</button>
</div>
<!-- Queue Status -->
<div id="queue-status" style="margin-bottom: 15px; padding: 10px; background: #1a1a1a; border-radius: 4px;">
<span id="queue-count">0</span> tracks in queue
</div>
<!-- Queue Contents -->
<div id="stream-queue-container" class="queue-list" style="max-height: 400px; overflow-y: auto;">
<div class="loading">Loading queue...</div>
</div>
<div class="queue-actions" style="margin-top: 20px;">
<h3>Add Tracks to Queue</h3>
<input type="text" id="queue-track-search" placeholder="Search tracks to add..." class="search-input">
<div id="queue-track-results" class="track-results"></div>
</div>
</div>
<!-- Liquidsoap Stream Control -->
<div class="admin-section">
<h2>📡 Stream Control (Liquidsoap)</h2>
<p>Control the live audio stream. Commands are sent directly to Liquidsoap.</p>
<!-- Status Display -->
<div id="liquidsoap-status" style="margin-bottom: 20px; padding: 15px; background: #1a1a1a; border-radius: 8px;">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div>
<strong>Uptime:</strong> <span id="ls-uptime">--</span>
</div>
<div>
<strong>Remaining:</strong> <span id="ls-remaining">--</span>
</div>
</div>
<div style="margin-top: 10px;">
<strong>Now Playing:</strong> <span id="ls-metadata">--</span>
</div>
</div>
<!-- Control Buttons -->
<div class="queue-controls" style="margin-bottom: 15px;">
<button id="ls-refresh-status" class="btn btn-secondary">🔄 Refresh Status</button>
<button id="ls-skip" class="btn btn-warning">⏭️ Skip Track</button>
<button id="ls-reload" class="btn btn-info">📂 Reload Playlist</button>
<button id="ls-restart" class="btn btn-danger">🔄 Restart Container</button>
</div>
<p style="font-size: 0.9em; color: #888;">
<strong>Skip Track:</strong> Immediately skip to the next track in the playlist.<br>
<strong>Reload Playlist:</strong> Force Liquidsoap to re-read stream-queue.m3u.<br>
<strong>Restart Container:</strong> Restart the Liquidsoap Docker container (causes brief stream interruption).
</p>
</div>
<!-- User Management -->
<div class="admin-section">
<div class="card">
<h3>👥 User Management</h3>
<p>Manage user accounts, roles, and permissions.</p>
<div class="controls">
<a href="/asteroid/admin/users" class="btn btn-primary">👥 Manage Users</a>
</div>
<!-- Admin Password Reset Form -->
<div class="form-section" style="margin-top: 20px;">
<h4>🔒 Reset User Password</h4>
<form id="admin-reset-password-form" onsubmit="return resetUserPassword(event)">
<div class="form-group">
<label for="reset-username">Username:</label>
<input type="text" id="reset-username" name="username" required>
</div>
<div class="form-group">
<label for="reset-new-password">New Password:</label>
<input type="password" id="reset-new-password" name="new-password" required minlength="8">
</div>
<div class="form-group">
<label for="reset-confirm-password">Confirm Password:</label>
<input type="password" id="reset-confirm-password" name="confirm-password" required minlength="8">
</div>
<div id="reset-password-message" class="message"></div>
<button type="submit" class="btn btn-primary">Reset Password</button>
</form>
</div>
</div>
</div>
</div>
<!-- Listener stats, geo stats, and password reset now handled by ParenScript in admin.lisp -->
</body>
</html>