Spaces:
Sleeping
Sleeping
| {% extends "base.html" %} | |
| {% block title %}Dashboard - Telegram to YouTube Uploader{% endblock %} | |
| {% block content %} | |
| <h2>Dashboard</h2> | |
| <!-- Configuration Section --> | |
| <div class="card mb-4"> | |
| <div class="card-header"> | |
| <h5>Configuration</h5> | |
| </div> | |
| <div class="card-body"> | |
| <form id="configForm"> | |
| <div class="row"> | |
| <div class="col-md-6 mb-3"> | |
| <label for="telegram_channel_username" class="form-label">Telegram Channel Username *</label> | |
| <input type="text" class="form-control" id="telegram_channel_username" name="telegram_channel_username" value="{{ config.TELEGRAM_CHANNEL_USERNAME or '' }}" placeholder="@your_channel"> | |
| <div class="form-text">The username of the Telegram channel to monitor (e.g., @mychannel).</div> | |
| </div> | |
| <div class="col-md-6 mb-3"> | |
| <label for="youtube_title_prefix" class="form-label">YouTube Title Prefix</label> | |
| <input type="text" class="form-control" id="youtube_title_prefix" name="youtube_title_prefix" value="{{ config.YOUTUBE_TITLE_PREFIX or '[TG2YT] ' }}"> | |
| <div class="form-text">Text to prepend to the Telegram video caption for the YouTube title.</div> | |
| </div> | |
| </div> | |
| <div class="mb-3"> | |
| <label for="youtube_description_template" class="form-label">YouTube Description Template</label> | |
| <textarea class="form-control" id="youtube_description_template" name="youtube_description_template" rows="2">{{ config.YOUTUBE_DESCRIPTION_TEMPLATE or 'Video automatically uploaded from Telegram.' }}</textarea> | |
| <div class="form-text">Base description for uploaded videos. Original Telegram post link and caption will be appended.</div> | |
| </div> | |
| <div class="row"> | |
| <div class="col-md-6 mb-3"> | |
| <label for="youtube_tags" class="form-label">YouTube Tags</label> | |
| <input type="text" class="form-control" id="youtube_tags" name="youtube_tags" value="{{ config.YOUTUBE_TAGS or 'telegram,upload,automation' }}"> | |
| <div class="form-text">Comma-separated list of tags for YouTube videos.</div> | |
| </div> | |
| <div class="col-md-3 mb-3"> | |
| <label for="youtube_category_id" class="form-label">YouTube Category ID</label> | |
| <input type="text" class="form-control" id="youtube_category_id" name="youtube_category_id" value="{{ config.YOUTUBE_CATEGORY_ID or '22' }}"> | |
| <div class="form-text">Numeric ID for the YouTube video category (22 = People & Blogs).</div> | |
| </div> | |
| <div class="col-md-3 mb-3"> | |
| <label for="youtube_privacy_status" class="form-label">YouTube Privacy Status</label> | |
| <select class="form-select" id="youtube_privacy_status" name="youtube_privacy_status"> | |
| <option value="private" {% if (config.YOUTUBE_PRIVACY_STATUS or 'private') == 'private' %}selected{% endif %}>Private</option> | |
| <option value="public" {% if (config.YOUTUBE_PRIVACY_STATUS or 'private') == 'public' %}selected{% endif %}>Public</option> | |
| <option value="unlisted" {% if (config.YOUTUBE_PRIVACY_STATUS or 'private') == 'unlisted' %}selected{% endif %}>Unlisted</option> | |
| </select> | |
| <div class="form-text">Initial privacy setting for uploaded videos.</div> | |
| </div> | |
| </div> | |
| <button type="submit" class="btn btn-primary" id="saveConfigBtn">Save Configuration</button> | |
| <div id="configSaveStatus" class="mt-2"></div> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Existing Status and Control Sections --> | |
| <div class="row"> | |
| <div class="col-md-6"> | |
| <div class="card status-card"> | |
| <div class="card-header"> | |
| <h5>Authentication Status</h5> | |
| </div> | |
| <div class="card-body"> | |
| <p><strong>YouTube:</strong> | |
| {% if status.youtube_authenticated %} | |
| <span class="badge bg-success">Authenticated</span> | |
| <a href="{{ url_for('youtube_auth') }}" class="btn btn-sm btn-outline-primary">Re-authenticate</a> | |
| {% else %} | |
| <span class="badge bg-warning text-dark">Not Authenticated</span> | |
| <a href="{{ url_for('youtube_auth') }}" class="btn btn-sm btn-primary">Authenticate</a> | |
| {% endif %} | |
| </p> | |
| <p><strong>Telegram:</strong> | |
| {% if status.telegram_authenticated %} | |
| <span class="badge bg-success">Connected</span> | |
| <a href="{{ url_for('telegram_auth') }}" class="btn btn-sm btn-outline-primary">Re-authenticate</a> | |
| {% else %} | |
| <span class="badge bg-warning text-dark">Not Connected</span> | |
| <a href="{{ url_for('telegram_auth') }}" class="btn btn-sm btn-primary">Connect</a> | |
| {% endif %} | |
| </p> | |
| </div> | |
| </div> | |
| <div class="card status-card"> | |
| <div class="card-header"> | |
| <h5>Processing Control</h5> | |
| </div> | |
| <div class="card-body"> | |
| <form id="startForm"> | |
| <div class="mb-3"> | |
| <label for="limit" class="form-label">Batch Size (Videos per batch)</label> | |
| <input type="number" class="form-control" id="limit" name="limit" value="5" min="1" max="50"> | |
| </div> | |
| <button type="submit" class="btn btn-success" id="startBtn" {% if status.is_running %}disabled{% endif %}>Start Processing</button> | |
| <button type="button" class="btn btn-danger" id="stopBtn" {% if not status.is_running %}disabled{% endif %}>Stop Processing</button> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="col-md-6"> | |
| <div class="card status-card"> | |
| <div class="card-header"> | |
| <h5>Current Status</h5> | |
| </div> | |
| <div class="card-body"> | |
| <p><strong>Running:</strong> | |
| {% if status.is_running %} | |
| <span class="badge bg-success">Yes</span> | |
| {% else %} | |
| <span class="badge bg-secondary">No</span> | |
| {% endif %} | |
| </p> | |
| <p><strong>Current Batch:</strong> {{ status.current_batch }}</p> | |
| <p><strong>Processed in Session:</strong> {{ status.processed_count }}</p> | |
| <p><strong>Failed in Session:</strong> {{ status.failed_count }}</p> | |
| <p><strong>Total Processed (Ever):</strong> {{ status.total_processed }}</p> | |
| <div id="confirmationArea"> | |
| <!-- Confirmation prompt will be inserted here by JavaScript --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="row mt-4"> | |
| <div class="col-12"> | |
| <div class="card"> | |
| <div class="card-header d-flex justify-content-between align-items-center"> | |
| <h5>Processing Logs</h5> | |
| <button class="btn btn-sm btn-outline-secondary" id="clearLogsBtn">Clear Logs</button> | |
| </div> | |
| <div class="card-body"> | |
| <div class="log-container" id="logContainer"> | |
| <!-- Logs will be populated here by JavaScript --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {% endblock %} | |
| {% block scripts %} | |
| <script> | |
| // --- Configuration Saving Logic --- | |
| document.getElementById('configForm').addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| const saveBtn = document.getElementById('saveConfigBtn'); | |
| const statusDiv = document.getElementById('configSaveStatus'); | |
| const originalBtnText = saveBtn.textContent; | |
| saveBtn.disabled = true; | |
| saveBtn.textContent = 'Saving...'; | |
| statusDiv.innerHTML = '<div class="spinner-border spinner-border-sm" role="status"><span class="visually-hidden">Saving...</span></div>'; | |
| const formData = new FormData(this); | |
| fetch('/save_config', { | |
| method: 'POST', | |
| body: formData | |
| }) | |
| .then(response => { | |
| if (!response.ok) { | |
| return response.json().then(err => { throw new Error(err.message || 'Network response was not ok'); }); | |
| } | |
| return response.json(); | |
| }) | |
| .then(data => { | |
| if (data.success) { | |
| statusDiv.innerHTML = '<div class="alert alert-success alert-dismissible fade show" role="alert">Configuration saved successfully!<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>'; | |
| // Optionally, re-fetch status to update any dependent UI elements if needed | |
| // updateStatus(); // If you add config status checks to the main status endpoint | |
| } else { | |
| statusDiv.innerHTML = `<div class="alert alert-danger alert-dismissible fade show" role="alert">Error saving configuration: ${data.message}<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>`; | |
| } | |
| }) | |
| .catch(error => { | |
| console.error('Error:', error); | |
| statusDiv.innerHTML = `<div class="alert alert-danger alert-dismissible fade show" role="alert">Error saving configuration: ${error.message}<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>`; | |
| }) | |
| .finally(() => { | |
| saveBtn.disabled = false; | |
| saveBtn.textContent = originalBtnText; | |
| // Auto-hide success message after a few seconds | |
| const successAlert = statusDiv.querySelector('.alert-success'); | |
| if (successAlert) { | |
| setTimeout(() => { | |
| if (successAlert.parentNode === statusDiv) { // Check if still present | |
| successAlert.remove(); | |
| } | |
| }, 3000); | |
| } | |
| }); | |
| }); | |
| // --- Existing Status and Control Logic --- | |
| let isWaitingForConfirmation = false; | |
| function updateStatus() { | |
| fetch('/status') | |
| .then(response => response.json()) | |
| .then(data => { | |
| // Update status indicators | |
| document.getElementById('startBtn').disabled = data.is_running; | |
| document.getElementById('stopBtn').disabled = !data.is_running; | |
| // Update log container | |
| const logContainer = document.getElementById('logContainer'); | |
| logContainer.innerHTML = ''; | |
| data.logs.forEach(log => { | |
| const logEntry = document.createElement('div'); | |
| logEntry.textContent = `[${log.timestamp}] ${log.level}: ${log.message}`; | |
| logEntry.classList.add('mb-1'); | |
| if (log.level === 'ERROR') logEntry.classList.add('text-danger'); | |
| else if (log.level === 'WARNING') logEntry.classList.add('text-warning'); | |
| logContainer.appendChild(logEntry); | |
| }); | |
| logContainer.scrollTop = logContainer.scrollHeight; // Auto-scroll to bottom | |
| // Handle confirmation prompt | |
| const confirmationArea = document.getElementById('confirmationArea'); | |
| if (data.waiting_for_confirmation && !isWaitingForConfirmation) { | |
| isWaitingForConfirmation = true; | |
| confirmationArea.innerHTML = ` | |
| <div class="alert alert-info confirmation-prompt" role="alert"> | |
| <strong>Confirmation Needed:</strong> ${data.confirmation_message} | |
| <div class="mt-2"> | |
| <button class="btn btn-sm btn-success confirm-btn" data-action="continue">Continue</button> | |
| <button class="btn btn-sm btn-secondary confirm-btn" data-action="stop">Stop</button> | |
| </div> | |
| </div> | |
| `; | |
| } else if (!data.waiting_for_confirmation && isWaitingForConfirmation) { | |
| isWaitingForConfirmation = false; | |
| confirmationArea.innerHTML = ''; // Clear prompt | |
| } | |
| }) | |
| .catch(error => console.error('Error fetching status:', error)); | |
| } | |
| // Initial update | |
| updateStatus(); | |
| // Update status every 2 seconds | |
| const statusInterval = setInterval(updateStatus, 2000); | |
| // Event Listeners for Processing Control | |
| document.getElementById('startForm').addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| const formData = new FormData(this); | |
| fetch('/start_processing', { | |
| method: 'POST', | |
| body: formData | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.success) { | |
| // updateStatus(); // Will be updated by interval | |
| // Provide immediate feedback | |
| const startBtn = document.getElementById('startBtn'); | |
| startBtn.disabled = true; | |
| } else { | |
| alert(data.message); | |
| } | |
| }) | |
| .catch(error => console.error('Error starting processing:', error)); | |
| }); | |
| document.getElementById('stopBtn').addEventListener('click', function() { | |
| fetch('/stop_processing', { | |
| method: 'POST' | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.success) { | |
| // updateStatus(); // Will be updated by interval | |
| // Provide immediate feedback | |
| const stopBtn = document.getElementById('stopBtn'); | |
| stopBtn.disabled = true; | |
| } else { | |
| alert(data.message); | |
| } | |
| }) | |
| .catch(error => console.error('Error stopping processing:', error)); | |
| }); | |
| document.getElementById('clearLogsBtn').addEventListener('click', function() { | |
| fetch('/clear_logs', { | |
| method: 'POST' | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.success) { | |
| const logContainer = document.getElementById('logContainer'); | |
| logContainer.innerHTML = ''; | |
| } | |
| }) | |
| .catch(error => console.error('Error clearing logs:', error)); | |
| }); | |
| // Confirmation buttons (using event delegation) | |
| document.addEventListener('click', function(e) { | |
| if (e.target.classList.contains('confirm-btn')) { | |
| const action = e.target.getAttribute('data-action'); | |
| fetch('/batch_confirmation', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/x-www-form-urlencoded', | |
| }, | |
| body: `action=${action}` | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.success) { | |
| // updateStatus(); // Will be updated by interval | |
| // Optionally, clear the prompt immediately | |
| document.getElementById('confirmationArea').innerHTML = ''; | |
| isWaitingForConfirmation = false; | |
| } else { | |
| alert(data.message); | |
| } | |
| }) | |
| .catch(error => console.error('Error sending confirmation:', error)); | |
| } | |
| }); | |
| // Clear interval on page unload (optional, good practice) | |
| window.addEventListener('beforeunload', () => { | |
| clearInterval(statusInterval); | |
| }); | |
| </script> | |
| {% endblock %} |