pahvalentines/admin.html
2026-02-03 21:37:49 +05:30

305 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.datatables.net/2.3.6/css/dataTables.dataTables.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/responsive/3.0.7/css/responsive.dataTables.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/3.0.2/css/buttons.dataTables.min.css">
<link href="https://cdn.datatables.net/columncontrol/1.2.0/css/columnControl.dataTables.min.css" rel="stylesheet">
<style>
body { background-color: #f8f9fa; }
.user-photo {
width: 45px;
height: 45px;
object-fit: cover;
border-radius: 10%;
border: 1px solid #ddd;
}
/* Style adjustments for DataTables 2.0 + Bootstrap */
.dt-container { margin-top: 20px; padding: 5px; }
</style>
</head>
<body>
<nav class="navbar navbar-dark bg-dark shadow-sm mb-4">
<div class="container">
<span class="navbar-brand">Admin Dashboard</span>
</div>
</nav>
<div class="container mb-5">
<div class="card shadow-sm">
<div class="card-body">
<div class="row g-3 mb-3">
<!-- Left Column: Total Records -->
<div class="col-md-6">
<div class="alert alert-light mb-0 h-100 d-flex align-items-center">
<span>
Total Records:&nbsp;
<span id="total-records-count" class="badge bg-dark rounded-pill">0</span>
</span>
</div>
</div>
<!-- Right Column: AI Credits -->
<div class="col-md-6">
<div class="alert alert-light mb-0 h-100 d-flex align-items-center">
<span>
SonautoAI Credits Remaining:&nbsp;
<span id="credits-remaining" class="badge bg-primary rounded-pill">Loading...</span>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="card shadow-sm dt-container">
<div class="card-dt">
<table id="usersTable" class="table table-striped w-100">
<thead>
<tr>
<th>Session</th>
<th>Created</th>
<th>Cookie ID</th>
<th>Owner Name</th>
<th>Pet Name</th>
<th>Photo</th>
<th>Pet Type</th>
<th>Vibe</th>
<th>Retries</th>
<th>Sent to LLM</th>
<th>Task ID</th>
<th>Recv from LLM</th>
<th>LLM Response</th>
<th>Full Response</th>
<th>Song Path</th>
<th>LLM Status</th>
<th>Lyrics</th>
<th>Vid Start</th>
<th>Vid End</th>
<th>Video Path</th>
<th>Entry Status</th>
</tr>
</thead>
<tbody></tbody> <!-- Empty: DataTables will fill this -->
</table>
</div>
</div>
</div>
<div class="modal fade" id="photoModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalUserName">User Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="row">
<div class="col-md-12">
<div class="d-flex align-items-center mb-3">
<img src="" id="modalImage" class="user-photo me-3" style="width: 80px; height: 80px;">
<div>
<h4 id="modalUserName" class="mb-0"></h4>
<small id="modalSession" class="text-muted"></small>
</div>
</div>
</div>
<div class="col-md-6">
<h6>Pet Details</h6>
<table class="table table-sm border">
<tr><td>Pet:</td><td id="modalPetName" class="fw-bold"></td></tr>
<tr><td>Type:</td><td id="modalPetType"></td></tr>
<tr><td>Vibe:</td><td id="modalVibe"></td></tr>
</table>
</div>
<div class="col-md-6">
<h6>Assets</h6>
<div id="modalMediaContainer">
<!-- We will inject an <audio> or <video> tag here via JS -->
</div>
</div>
<div class="col-md-12 mt-3">
<h6>Lyrics</h6>
<pre id="modalLyrics" class="bg-dark text-white p-3 rounded small" style="max-height: 150px; overflow-y: auto;"></pre>
</div>
</div>
</div>
</div>
</div>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.datatables.net/2.3.6/js/dataTables.min.js"></script>
<script src="https://cdn.datatables.net/responsive/3.0.7/js/dataTables.responsive.min.js"></script>
<script src="https://cdn.datatables.net/buttons/3.0.2/js/dataTables.buttons.min.js"></script>
<script src="https://cdn.datatables.net/buttons/3.0.2/js/buttons.colVis.min.js"></script>
<script src="https://cdn.datatables.net/columncontrol/1.2.0/js/dataTables.columnControl.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
$(document).ready(function() {
const API_BASE_URL = '';
const API_ENDPOINT = `/back/api/admin/data`;
// Helper to convert container paths to web URLs
function fixStoragePath(path) {
if (!path) return '';
return path.replace(/^\/app\/storage\//, '/storage/');
}
// Fetch and display Sonauto credits
function loadCredits() {
fetch('/back/api/admin/queue-status')
.then(response => response.json())
.then(data => {
$('#credits-remaining').text((data.sonauto_credits || 0).toLocaleString());
})
.catch(err => {
console.error('Failed to load credits:', err);
$('#credits-remaining').text('Error');
});
}
loadCredits();
// Initialize DataTable
const table = $('#usersTable').DataTable({
serverSide: true,
processing: true,
responsive: {
details: {
type: 'column',
target: 'tr' // Clicking the row expands it
}
},
// Define which columns are visible by default
columnDefs: [
{ responsivePriority: 1, targets: [3, 4, 12, 5] }, // Owner, Pet, Status, Photo
{ responsivePriority: 2, targets: [1, 20] }, // CreatedAt, EntryStatus
{ targets: [2, 6, 8, 9, 10, 11, 14, 17, 18], visible: false } // Hide technical IDs by default
],
ajax: {
url: API_ENDPOINT,
dataSrc: function (json) {
// Your FastAPI endpoint returns AdminResponse directly
// Structure: { draw, recordsTotal, recordsFiltered, data }
// console.log('DEBUG: API Response:', json);
// Update the total records badge at the top
$('#total-records-count').text((json.recordsTotal || 0).toLocaleString());
// DataTables expects these fields at root level (already there!)
// No need to move anything - just return the data array
return json.data || [];
}
},
layout: {
topStart: {
buttons: [
{
extend: 'colvis',
text: 'Select Columns',
className: 'btn btn-sm btn-outline-secondary'
}
]
},
topEnd: 'search',
bottomStart: 'info',
bottomEnd: 'paging'
},
columns: [
{ data: 'session_id', title: 'Session', defaultContent: 'N/A' },
{ data: 'created_at', title: 'Created', defaultContent: 'N/A' },
{ data: 'cookie_id', title: 'Cookie ID', defaultContent: 'N/A' },
{ data: 'owner_name', title: 'Owner', defaultContent: 'N/A' },
{ data: 'pet_name', title: 'Pet Name', defaultContent: 'N/A' },
{
data: 'photo_path',
title: 'Photo',
defaultContent: '',
render: (data) => `<img src="${fixStoragePath(data)}" class="user-photo img-preview" style="cursor:pointer">`
},
{ data: 'pet_type', title: 'Type', defaultContent: 'N/A' },
{ data: 'music_vibe', title: 'Vibe', defaultContent: 'N/A' },
{ data: 'retry_count', title: 'Retries', defaultContent: 'N/A' },
{ data: 'sent_to_LLM', title: 'Sent to LLM', defaultContent: 'N/A' },
{ data: 'LLM_task_id', title: 'Task ID', defaultContent: 'N/A' },
{ data: 'received_from_LLM', title: 'LLM Recv', defaultContent: 'N/A' },
{
data: 'LLM_response',
title: 'LLM Response',
defaultContent: 'N/A',
render: (data) => data ? (data.substring(0, 30) + '...') : 'Pending'
},
{ data: 'LLM_full_response', defaultContent: 'N/A', visible: false },
{ data: 'generated_song_path', title: 'Song', defaultContent: '', render: d => d ? '🎵' : '-' },
{ data: 'LLM_status', title: 'LLM Status', defaultContent: 'N/A' },
{ data: 'lyrics', title: 'Lyrics', defaultContent: '', visible: false },
{ data: 'video_creation_start', title: 'Vid Start', defaultContent: '', visible: false },
{ data: 'video_creation_end', title: 'Vid End', defaultContent: '', visible: false },
{ data: 'generated_video_path', title: 'Video', defaultContent: '', render: d => d ? '🎥' : '-' },
{
data: 'entry_status',
title: 'Status',
defaultContent: '',
render: (data) => {
return `<span>${data}</span>`;
}
}
]
});
// Use event delegation (so it works even after searching/paging)
$('#usersTable').on('click', '.img-preview', function() {
// 1. Get the data for the specific row clicked
// We use .closest('tr') to find the table row containing the clicked image
const rowData = table.row($(this).closest('tr')).data();
// 2. Populate the Modal fields
$('#modalUserName').text(rowData.owner_name + ' - Profile');
$('#modalImage').attr('src', fixStoragePath(rowData.photo_path));
$('#modalPetName').text(rowData.pet_name || 'N/A');
$('#modalPetType').text(rowData.pet_type || 'N/A');
$('#modalVibe').text(rowData.music_vibe || 'N/A');
$('#modalSession').text(rowData.session_id);
// 3. Handle Lyrics
if (rowData.lyrics) {
$('#modalLyrics').text(rowData.lyrics);
} else {
$('#modalLyrics').text('No lyrics available yet...');
}
// 4. Handle Media (Song or Video)
const mediaContainer = $('#modalMediaContainer');
mediaContainer.empty(); // Clear previous content
if (rowData.generated_video_path) {
mediaContainer.html(`
<video controls class="w-100" style="max-height: 300px;">
<source src="${fixStoragePath(rowData.generated_video_path)}" type="video/mp4">
Your browser does not support video.
</video>
`);
} else if (rowData.generated_song_path) {
mediaContainer.html(`
<audio controls class="w-100">
<source src="${fixStoragePath(rowData.generated_song_path)}" type="audio/mpeg">
Your browser does not support audio.
</audio>
`);
} else {
mediaContainer.html('<p class="text-muted">No media available yet...</p>');
}
// 5. Show the Modal
new bootstrap.Modal('#photoModal').show();
});
});
</script>
</body>
</html>