Add complete frontend interface with Jinja2 templates
- Created base.html template with navigation and flash messages - Created dashboard.html for video listing sorted by date - Created channels.html for channel management - Created add_channel.html with subscription form - Created watch.html with HTML5 video player - Created static/style.css with YouTube-inspired dark theme - Updated main.py with frontend routes: - / (index): Dashboard with all videos - /channels: Channel management page - /add-channel: Add new channel form (GET/POST) - /watch/<video_id>: Video player page - Added new API endpoints: - /api/videos/refresh/<channel_id>: Refresh channel videos - /api/video/stream/<video_id>: Stream/download video files - Enhanced /api/download/<video_id> with status checks - Updated CLAUDE.md with comprehensive frontend documentation Features: - Video grid with thumbnails and download status badges - Inline download buttons for pending videos - Channel subscription and refresh functionality - HTML5 video player for downloaded videos - Auto-refresh during video downloads - Responsive design for mobile/desktop - Flash message system for user feedback - Dark theme with hover effects and animations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
75
templates/channels.html
Normal file
75
templates/channels.html
Normal file
@@ -0,0 +1,75 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Channels - YottoB{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="channels-page">
|
||||
<div class="page-header">
|
||||
<h2>Subscribed Channels</h2>
|
||||
<a href="/add-channel" class="btn btn-primary">Add New Channel</a>
|
||||
</div>
|
||||
|
||||
{% if channels %}
|
||||
<div class="channels-list">
|
||||
{% for channel in channels %}
|
||||
<div class="channel-card">
|
||||
<div class="channel-info">
|
||||
<h3 class="channel-title">{{ channel.title }}</h3>
|
||||
<p class="channel-url">
|
||||
<a href="{{ channel.rss_url }}" target="_blank">{{ channel.rss_url }}</a>
|
||||
</p>
|
||||
<div class="channel-meta">
|
||||
<span class="video-count">{{ channel.video_entries|length }} videos</span>
|
||||
<span class="last-updated">
|
||||
Last updated: {{ channel.last_fetched_at.strftime('%b %d, %Y %I:%M %p') if channel.last_fetched_at else 'Never' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="channel-actions">
|
||||
<button class="btn btn-secondary" onclick="refreshChannel({{ channel.id }})">
|
||||
Refresh Videos
|
||||
</button>
|
||||
<a href="/?channel={{ channel.id }}" class="btn btn-link">View Videos</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<h3>No channels subscribed</h3>
|
||||
<p>Add your first YouTube channel to start downloading videos</p>
|
||||
<a href="/add-channel" class="btn btn-primary">Add Channel</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
function refreshChannel(channelId) {
|
||||
const button = event.target;
|
||||
button.disabled = true;
|
||||
button.textContent = 'Refreshing...';
|
||||
|
||||
fetch(`/api/videos/refresh/${channelId}`, {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
alert(`Refreshed! Found ${data.new_videos} new videos`);
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Failed to refresh: ' + data.message);
|
||||
button.disabled = false;
|
||||
button.textContent = 'Refresh Videos';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Error: ' + error);
|
||||
button.disabled = false;
|
||||
button.textContent = 'Refresh Videos';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user