feat(ui): bump thumbnail sizes 1.5x, make Settings fluid, add subscription_count stat
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ from quart import Blueprint, jsonify, request
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from ..extensions import get_session
|
||||
from ..models import AppSetting, ImageRecord, ImportBatch, ImportSettings, ImportTask, Tag
|
||||
from ..models import AppSetting, Artist, ImageRecord, ImportBatch, ImportSettings, ImportTask, Tag
|
||||
|
||||
settings_bp = Blueprint("settings", __name__, url_prefix="/api")
|
||||
|
||||
@@ -118,6 +118,9 @@ async def system_stats():
|
||||
storage_bytes = (
|
||||
(await session.execute(select(func.coalesce(func.sum(ImageRecord.size_bytes), 0)))).scalar_one()
|
||||
)
|
||||
subscription_count = (await session.execute(
|
||||
select(func.count(Artist.id)).where(Artist.is_subscription.is_(True))
|
||||
)).scalar_one()
|
||||
|
||||
# Task counts grouped by status
|
||||
status_rows = (
|
||||
@@ -163,6 +166,7 @@ async def system_stats():
|
||||
"total_images": total_images,
|
||||
"total_tags": total_tags,
|
||||
"storage_bytes": storage_bytes,
|
||||
"subscription_count": int(subscription_count),
|
||||
"tasks": {
|
||||
"pending": status_counts.get("pending", 0),
|
||||
"queued": status_counts.get("queued", 0),
|
||||
|
||||
@@ -86,7 +86,7 @@ function imagesForGroup(group) {
|
||||
}
|
||||
.fc-gallery-grid__items {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
.fc-gallery-grid__sentinel {
|
||||
@@ -101,7 +101,7 @@ function imagesForGroup(group) {
|
||||
}
|
||||
.fc-gallery-grid__skeleton {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
.fc-gallery-grid__skeleton-item {
|
||||
@@ -124,7 +124,7 @@ function imagesForGroup(group) {
|
||||
@media (max-width: 600px) {
|
||||
.fc-gallery-grid__items,
|
||||
.fc-gallery-grid__skeleton {
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<v-row dense>
|
||||
<v-col v-for="card in cards" :key="card.label" cols="12" sm="6" md="4" lg="2">
|
||||
<v-col v-for="card in cards" :key="card.label" cols="12" sm="6" md="4" lg="3" xl="2">
|
||||
<v-card class="fc-stat">
|
||||
<v-card-text>
|
||||
<div class="fc-stat__label text-caption">{{ card.label }}</div>
|
||||
@@ -33,15 +33,17 @@ const cards = computed(() => {
|
||||
{ label: 'Total images', value: '—' },
|
||||
{ label: 'Total tags', value: '—' },
|
||||
{ label: 'Storage used', value: '—' },
|
||||
{ label: 'Subscriptions', value: '—' },
|
||||
{ label: 'Pending tasks', value: '—' },
|
||||
{ label: 'Failed tasks', value: '—' }
|
||||
]
|
||||
return [
|
||||
{ label: 'Total images', value: s.total_images.toLocaleString() },
|
||||
{ label: 'Total tags', value: s.total_tags.toLocaleString() },
|
||||
{ label: 'Storage used', value: formatBytes(s.storage_bytes) },
|
||||
{ label: 'Pending', value: (s.tasks.pending + s.tasks.queued).toLocaleString() },
|
||||
{ label: 'Failed', value: s.tasks.failed.toLocaleString() }
|
||||
{ label: 'Total images', value: s.total_images.toLocaleString() },
|
||||
{ label: 'Total tags', value: s.total_tags.toLocaleString() },
|
||||
{ label: 'Storage used', value: formatBytes(s.storage_bytes) },
|
||||
{ label: 'Subscriptions', value: (s.subscription_count ?? 0).toLocaleString() },
|
||||
{ label: 'Pending', value: (s.tasks.pending + s.tasks.queued).toLocaleString() },
|
||||
{ label: 'Failed', value: s.tasks.failed.toLocaleString() }
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -29,10 +29,11 @@ export function distributeIntoColumns(items, columnCount) {
|
||||
}
|
||||
|
||||
// Reactive column count from container width. Breakpoints mirror the
|
||||
// gallery grid intent: ~260px target column width, min 1 column.
|
||||
// gallery grid intent: ~390px target column width, min 1 column. Bumped
|
||||
// from 260px on 2026-05-23 per dogfood UX feedback (thumbs were too small).
|
||||
export function columnCountForWidth(width) {
|
||||
if (!width || width < 0) return 1
|
||||
return Math.max(1, Math.floor(width / 260))
|
||||
return Math.max(1, Math.floor(width / 390))
|
||||
}
|
||||
|
||||
export function usePolyMasonry(containerRef) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-container class="py-6">
|
||||
<v-container fluid class="py-6">
|
||||
<v-tabs v-model="tab" color="accent" class="mb-4">
|
||||
<v-tab value="overview">Overview</v-tab>
|
||||
<v-tab value="import">Import</v-tab>
|
||||
|
||||
@@ -76,7 +76,7 @@ async def test_system_stats_shape(client):
|
||||
resp = await client.get("/api/system/stats")
|
||||
assert resp.status_code == 200
|
||||
body = await resp.get_json()
|
||||
for key in ("total_images", "total_tags", "storage_bytes", "tasks", "active_batch"):
|
||||
for key in ("total_images", "total_tags", "storage_bytes", "subscription_count", "tasks", "active_batch"):
|
||||
assert key in body
|
||||
for status in ("pending", "queued", "processing", "complete", "skipped", "failed"):
|
||||
assert status in body["tasks"]
|
||||
|
||||
Reference in New Issue
Block a user