feat(server,contracts,web): dashboard library counts and engine runs triggeredBy/multi-status filters
- GET /dashboard: automation.library {entities, movies, series, seasons,
episodes, assets} and automation.slotSatisfiedRate; home media overview
shows the counts instead of placeholders.
- GET /engine/runs: `status` accepts comma-separated values; new
`triggeredBy` filter (comma-separated) evaluated server-side by scanning
batches and paging with the last inspected row's cursor (scan cap 2000).
- Task stream: source filter and the "active" tab (running,pending) are now
one paginated server query.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
615917bb45
commit
363f85b1a1
13 files changed
+165
-250
No files matched your search
@@ -445,6 +445,12 @@ describe.skipIf(!hasPg)("acquisition-organize 纵切(真实 PG + mock qB + 临
|
||||
expect(dash.body.data.recentOutput.length).toBeGreaterThan(0);
|
||||
expect(dash.body.data.cache.volumes[0]).toMatchObject({ hardThresholdPercent: 90, resumePercent: 85 });
|
||||
expect(dash.body.data.needsAttention.total).toBeGreaterThan(0);
|
||||
const auto = dash.body.data.automation;
|
||||
expect(auto.library.entities).toBeGreaterThanOrEqual(1);
|
||||
expect(auto.library.movies).toBeGreaterThanOrEqual(1);
|
||||
expect(auto.library.assets).toBeGreaterThanOrEqual(1);
|
||||
const slotTotal = Object.values(auto.slotCounts as Record<string, number>).reduce((a, b) => a + b, 0);
|
||||
expect(auto.slotSatisfiedRate).toBe(slotTotal ? auto.slotCounts.satisfied / slotTotal : null);
|
||||
const libList = await h.api("GET", "/library?q=沙丘");
|
||||
expect(libList.body.data.items[0]).toMatchObject({ entity: { id: movieId }, assetCount: 1 });
|
||||
const det = await h.api("GET", `/library/${movieId}`);
|
||||
|
||||
@@ -63,6 +63,26 @@ export function registerDashboardRoutes(app: ModuleApp, d: AoDeps) {
|
||||
attention: 0,
|
||||
};
|
||||
for (const r of slotRows) slotCounts[r.status] = r.n;
|
||||
const slotTotal = Object.values(slotCounts).reduce((a, b) => a + b, 0);
|
||||
const library = (
|
||||
await sql<{
|
||||
entities: number;
|
||||
movies: number;
|
||||
series: number;
|
||||
seasons: number;
|
||||
episodes: number;
|
||||
assets: number;
|
||||
}>`
|
||||
select
|
||||
(select count(*) from media_entity where status <> 'merged')::int as entities,
|
||||
(select count(*) from media_entity where status <> 'merged' and kind = 'movie')::int as movies,
|
||||
(select count(*) from media_entity where status <> 'merged' and kind in ('series','variety'))::int as series,
|
||||
(select count(*) from content_unit where kind = 'season' and status <> 'retired')::int as seasons,
|
||||
(select count(*) from content_unit where kind = 'episode' and status <> 'retired')::int as episodes,
|
||||
(select count(*) from media_asset where status = 'active' and kind in ('video','bluray_dir'))::int as assets`.execute(
|
||||
db,
|
||||
)
|
||||
).rows[0]!;
|
||||
|
||||
const dlRows = await db
|
||||
.selectFrom("download_task")
|
||||
@@ -203,6 +223,8 @@ export function registerDashboardRoutes(app: ModuleApp, d: AoDeps) {
|
||||
},
|
||||
activeSubscriptions: today.subs,
|
||||
slotCounts,
|
||||
slotSatisfiedRate: slotTotal ? slotCounts.satisfied / slotTotal : null,
|
||||
library,
|
||||
},
|
||||
currentWork: {
|
||||
downloading: count("downloading"),
|
||||
|
||||
Reference in new issue
Block a user