diff --git a/.claude/settings.local.json b/.claude/settings.local.json index d41d16e..7e8e1bd 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -17,7 +17,8 @@ "WebFetch(domain:raw.githubusercontent.com)", "Bash(python -m py_compile:*)", "Bash(powershell:*)", - "Bash(python:*)" + "Bash(python:*)", + "Bash(xargs basename:*)" ] } } diff --git a/backend/app/api/downloads.py b/backend/app/api/downloads.py index 7d8fafe..fac7728 100644 --- a/backend/app/api/downloads.py +++ b/backend/app/api/downloads.py @@ -29,8 +29,10 @@ async def list_downloads(): async with current_app.db_engine.connect() as conn: session = AsyncSession(bind=conn) - # Build query - query = select(Download) + # Build query - eager load source and subscription for labeling + query = select(Download).options( + selectinload(Download.source).selectinload(Source.subscription) + ) count_query = select(func.count()).select_from(Download) filters = [] @@ -58,8 +60,20 @@ async def list_downloads(): result = await session.execute(query) downloads = result.scalars().all() + # Include source info with each download + items = [] + for d in downloads: + item = d.to_dict() + if d.source: + item["subscription_name"] = d.source.subscription.name if d.source.subscription else None + item["platform"] = d.source.platform + else: + item["subscription_name"] = None + item["platform"] = None + items.append(item) + return jsonify({ - "items": [d.to_dict() for d in downloads], + "items": items, "total": total, "page": page, "per_page": per_page, @@ -147,8 +161,11 @@ async def recent_activity(): session = AsyncSession(bind=conn) # Get failed downloads OR completed with files (noteworthy activity) + # Eager load source and subscription for labeling from sqlalchemy import or_ - query = select(Download).where( + query = select(Download).options( + selectinload(Download.source).selectinload(Source.subscription) + ).where( and_( Download.created_at >= cutoff, or_( @@ -164,8 +181,20 @@ async def recent_activity(): result = await session.execute(query) downloads = result.scalars().all() + # Include source info with each download + items = [] + for d in downloads: + item = d.to_dict() + if d.source: + item["subscription_name"] = d.source.subscription.name if d.source.subscription else None + item["platform"] = d.source.platform + else: + item["subscription_name"] = None + item["platform"] = None + items.append(item) + return jsonify({ - "items": [d.to_dict() for d in downloads], + "items": items, "count": len(downloads), }) diff --git a/extension/manifest.json b/extension/manifest.json index 72b27f4..5c32255 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "GallerySubscriber", - "version": "1.1.0", + "version": "1.1.1", "description": "Export cookies from supported platforms to GallerySubscriber for automated downloads", "browser_specific_settings": { diff --git a/extension/web-ext-artifacts/gallerysubscriber-1.1.1.zip b/extension/web-ext-artifacts/gallerysubscriber-1.1.1.zip new file mode 100644 index 0000000..25fa33f Binary files /dev/null and b/extension/web-ext-artifacts/gallerysubscriber-1.1.1.zip differ diff --git a/frontend/src/views/Dashboard.vue b/frontend/src/views/Dashboard.vue index eaa2913..dcd6036 100644 --- a/frontend/src/views/Dashboard.vue +++ b/frontend/src/views/Dashboard.vue @@ -173,7 +173,7 @@ - {{ truncate(dl.url, 30) }} + {{ getDownloadLabel(dl) }} @@ -211,7 +211,7 @@ - {{ truncate(download.url, 40) }} + {{ getDownloadLabel(download) }} {{ formatRelativeTime(download.created_at) }} @@ -255,20 +255,18 @@ - Name - Platform - Error Count + Source + Last Error Last Check Actions - {{ source.subscription_name || source.subscription?.name || 'Unknown' }} - {{ source.platform }} + {{ getSourceLabel(source) }} - {{ source.error_count }} errors + {{ source.last_error_type || 'failed' }} {{ formatDate(source.last_check) }} @@ -318,7 +316,7 @@ const credentialsStore = useCredentialsStore() const notifications = useNotificationStore() // Supported platforms for credential status -const supportedPlatforms = ['patreon', 'subscribestar', 'hentaifoundry', 'discord'] +const supportedPlatforms = ['patreon', 'subscribestar', 'hentaifoundry', 'discord', 'pixiv', 'deviantart'] // State const checkingAll = ref(false) @@ -335,9 +333,27 @@ let refreshInterval = null // Computed const stats = computed(() => downloadsStore.stats) const recentActivity = computed(() => downloadsStore.recentActivity) -const sourcesWithErrors = computed(() => - sourcesStore.sources.filter(s => s.error_count > 0).slice(0, 5) -) + +// Sources with recent failures - derived from recent activity +// Only shows sources where a recent download failed (not cumulative error_count) +const sourcesWithErrors = computed(() => { + const failedDownloads = recentActivity.value.filter(d => d.status === 'failed') + // Group by source_id and keep unique sources with their most recent failure + const sourceMap = new Map() + for (const dl of failedDownloads) { + if (dl.source_id && !sourceMap.has(dl.source_id)) { + sourceMap.set(dl.source_id, { + id: dl.source_id, + subscription_name: dl.subscription_name, + platform: dl.platform, + last_error_type: dl.error_type, + last_check: dl.created_at, + }) + } + } + return Array.from(sourceMap.values()).slice(0, 5) +}) + const failedCount = computed(() => stats.value?.failed || 0) const activeDownloadsCount = computed(() => (stats.value?.queued || 0) + (stats.value?.running || 0) @@ -565,10 +581,25 @@ function truncate(str, length) { return str.substring(0, length) + '...' } +function getDownloadLabel(download) { + if (download.subscription_name && download.platform) { + return `${download.subscription_name}:${download.platform}` + } + // Fallback to truncated URL if source info not available + return truncate(download.url, 35) +} + +function getSourceLabel(source) { + const name = source.subscription_name || source.subscription?.name || 'Unknown' + return `${name}:${source.platform}` +} + async function retrySource(source) { try { await sourcesStore.triggerCheck(source.id) - notifications.success(`Queued check for ${source.subscription_name || source.platform}`) + notifications.success(`Queued check for ${getSourceLabel(source)}`) + // Refresh recent activity after retrying + await downloadsStore.fetchRecentActivity({ limit: 10 }) } catch (error) { notifications.error(`Failed to queue check: ${error.message}`) } diff --git a/summary.md b/summary.md index e241f5a..99937c0 100644 --- a/summary.md +++ b/summary.md @@ -1,6 +1,6 @@ # GallerySubscriber - Project Summary -**Updated:** 2026-02-04 (Fixed Pixiv authentication - requires OAuth refresh token) +**Updated:** 2026-02-04 (Pixiv OAuth in extension, improved failed/naming display) ## Overview