Add app auth, dashboard, scheduler, video management, and new scrapers
- JWT-based app authentication with user roles, folder/route access control - Dashboard with storage stats, health checks, and recent activity - Auto-download/scrape scheduler (12h interval) with per-user and per-job configs - Video upload, tagging, HLS transcoding, and detail pages - New scrapers: LeakGallery, Mega (megajs), yt-dlp - FlareSolverr integration for Cloudflare-protected sites - Gallery: advanced filtering (date, size, search), sort modes, equal-mix shuffle - Forum sites management with stored cookies/auth - GridWall/GridCell components for responsive media grid - Media API with folder-access permissions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { getDownloadHistory, getActiveDownloads, getUser, getScrapeJobs } from '../api'
|
||||
import { getDownloadHistory, getActiveDownloadDetails, getUser, getScrapeJobs } from '../api'
|
||||
import Spinner from '../components/Spinner'
|
||||
|
||||
export default function Downloads() {
|
||||
@@ -10,6 +10,8 @@ export default function Downloads() {
|
||||
const [error, setError] = useState(null)
|
||||
const pollRef = useRef(null)
|
||||
const [usernames, setUsernames] = useState({})
|
||||
const prevCompleted = useRef({})
|
||||
const [speeds, setSpeeds] = useState({})
|
||||
|
||||
useEffect(() => {
|
||||
loadAll()
|
||||
@@ -26,7 +28,7 @@ export default function Downloads() {
|
||||
|
||||
const [histData, activeData, scrapeData] = await Promise.all([
|
||||
getDownloadHistory(),
|
||||
getActiveDownloads(),
|
||||
getActiveDownloadDetails(),
|
||||
getScrapeJobs(),
|
||||
])
|
||||
|
||||
@@ -47,11 +49,24 @@ export default function Downloads() {
|
||||
const startPolling = () => {
|
||||
pollRef.current = setInterval(async () => {
|
||||
const [activeData, scrapeData] = await Promise.all([
|
||||
getActiveDownloads(),
|
||||
getActiveDownloadDetails(),
|
||||
getScrapeJobs(),
|
||||
])
|
||||
if (!activeData.error) {
|
||||
const list = Array.isArray(activeData) ? activeData : []
|
||||
// Calculate download speed (files/sec) based on completed delta
|
||||
const newSpeeds = {}
|
||||
for (const dl of list) {
|
||||
const uid = dl.user_id
|
||||
const prev = prevCompleted.current[uid] || 0
|
||||
const delta = (dl.completed || 0) - prev
|
||||
prevCompleted.current[uid] = dl.completed || 0
|
||||
if (delta > 0) {
|
||||
newSpeeds[uid] = (delta / 2).toFixed(1) // 2s poll interval
|
||||
}
|
||||
}
|
||||
setSpeeds((prev) => ({ ...prev, ...newSpeeds }))
|
||||
|
||||
setActive((prev) => {
|
||||
if (prev.length > 0 && list.length < prev.length) {
|
||||
getDownloadHistory().then((h) => {
|
||||
@@ -141,6 +156,11 @@ export default function Downloads() {
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
{dl.completed || 0} / {dl.total || '?'} files
|
||||
{speeds[uid] && (
|
||||
<span className="text-gray-400 ml-2">
|
||||
({speeds[uid]} files/s)
|
||||
</span>
|
||||
)}
|
||||
{dl.errors > 0 && (
|
||||
<span className="text-red-400 ml-2">
|
||||
({dl.errors} error{dl.errors !== 1 ? 's' : ''})
|
||||
@@ -154,12 +174,26 @@ export default function Downloads() {
|
||||
</div>
|
||||
|
||||
{/* Progress Bar */}
|
||||
<div className="w-full bg-[#1a1a1a] rounded-full h-1.5">
|
||||
<div className="w-full bg-[#1a1a1a] rounded-full h-1.5 mb-2">
|
||||
<div
|
||||
className="bg-[#0095f6] h-1.5 rounded-full transition-all duration-500"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Recent File Log */}
|
||||
{dl.recentFiles && dl.recentFiles.length > 0 && (
|
||||
<div className="space-y-0.5">
|
||||
{dl.recentFiles.map((f, fi) => (
|
||||
<div key={fi} className="flex items-center gap-2 text-xs">
|
||||
<span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${
|
||||
f.status === 'ok' ? 'bg-green-400' : 'bg-red-400'
|
||||
}`} />
|
||||
<span className="text-gray-500 truncate">{f.filename?.slice(0, 50)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user