Actually grab status from server

This commit is contained in:
Zerebos
2026-05-21 02:33:06 -04:00
parent bd79169f71
commit 745b6993de
3 changed files with 59 additions and 18 deletions
+17
View File
@@ -78,6 +78,23 @@ export const LIBRARY_UPDATE_STATUS = `
} }
`; `;
export const GET_LIBRARY_UPDATE_PANEL_STATUS = `
query GetLibraryUpdatePanelStatus {
libraryUpdateStatus {
jobsInfo {
isRunning
finishedJobs
totalJobs
skippedMangasCount
skippedCategoriesCount
}
}
lastUpdateTimestamp {
timestamp
}
}
`;
export const GET_RESTORE_STATUS = ` export const GET_RESTORE_STATUS = `
query GetRestoreStatus($id: String!) { query GetRestoreStatus($id: String!) {
restoreStatus(id: $id) { mangaProgress state totalManga } restoreStatus(id: $id) { mangaProgress state totalManga }
+1
View File
@@ -11,6 +11,7 @@
| `GET_DOWNLOADED_CHAPTERS_PAGES` | — | Page counts for all downloaded chapters — used for storage stats | | `GET_DOWNLOADED_CHAPTERS_PAGES` | — | Page counts for all downloaded chapters — used for storage stats |
| `GET_DOWNLOADS_PATH` | — | `downloadsPath` and `localSourcePath` from settings | | `GET_DOWNLOADS_PATH` | — | `downloadsPath` and `localSourcePath` from settings |
| `LIBRARY_UPDATE_STATUS` | — | Current library update job — `jobsInfo` progress and `mangaUpdates` list with new chapters | | `LIBRARY_UPDATE_STATUS` | — | Current library update job — `jobsInfo` progress and `mangaUpdates` list with new chapters |
| `GET_LIBRARY_UPDATE_PANEL_STATUS` | — | Library updater status + server `lastUpdateTimestamp` for UI status displays |
| `GET_RESTORE_STATUS` | `id: String!` | Backup restore job status by job ID — `mangaProgress`, `state`, `totalManga` | | `GET_RESTORE_STATUS` | `id: String!` | Backup restore job status by job ID — `mangaProgress`, `state`, `totalManga` |
| `VALIDATE_BACKUP` | `backup: Upload!` | Validate a backup file before restore — returns missing sources and trackers | | `VALIDATE_BACKUP` | `backup: Upload!` | Validate a backup file before restore — returns missing sources and trackers |
| `MANGAS_BY_GENRE` | `filter: MangaFilterInput`, `first: Int`, `offset: Int` | Paginated manga filtered by genre, ordered by `IN_LIBRARY_AT DESC` | | `MANGAS_BY_GENRE` | `filter: MangaFilterInput`, `first: Int`, `offset: Int` | Paginated manga filtered by genre, ordered by `IN_LIBRARY_AT DESC` |
@@ -2,7 +2,7 @@
import { onMount, onDestroy } from "svelte"; import { onMount, onDestroy } from "svelte";
import { BookOpen, CircleNotch } from "phosphor-svelte"; import { BookOpen, CircleNotch } from "phosphor-svelte";
import { gql } from "@api/client"; import { gql } from "@api/client";
import { GET_RECENTLY_UPDATED, GET_CHAPTERS } from "@api/queries"; import { GET_RECENTLY_UPDATED, GET_CHAPTERS, GET_LIBRARY_UPDATE_PANEL_STATUS } from "@api/queries";
import { cache, CACHE_GROUPS, CACHE_KEYS } from "@core/cache"; import { cache, CACHE_GROUPS, CACHE_KEYS } from "@core/cache";
import { store, openReader, setActiveManga, addToast } from "@store/state.svelte"; import { store, openReader, setActiveManga, addToast } from "@store/state.svelte";
import { dayLabel } from "@core/util"; import { dayLabel } from "@core/util";
@@ -29,6 +29,8 @@
let updates = $state<RecentUpdate[]>([]); let updates = $state<RecentUpdate[]>([]);
let error = $state<string | null>(null); let error = $state<string | null>(null);
let openingId = $state<number | null>(null); let openingId = $state<number | null>(null);
let updaterRunning = $state(false);
let lastUpdatedTs = $state<number | null>(null);
let ctrl: AbortController | null = null; let ctrl: AbortController | null = null;
const RECENT_UPDATES_TTL_MS = 60 * 1_000; const RECENT_UPDATES_TTL_MS = 60 * 1_000;
@@ -57,13 +59,9 @@
return Object.entries(grouped).map(([label, items]) => ({ label, items })) as UpdateGroup[]; return Object.entries(grouped).map(([label, items]) => ({ label, items })) as UpdateGroup[];
}); });
const lastCheckedTs = $derived( const lastUpdatedLabel = $derived(
updates.length > 0 ? fetchedAtMs(updates[0]) : null lastUpdatedTs
); ? new Date(lastUpdatedTs).toLocaleString("en-US", {
const lastCheckedLabel = $derived(
lastCheckedTs
? new Date(lastCheckedTs).toLocaleString("en-US", {
month: "short", month: "short",
day: "numeric", day: "numeric",
year: "numeric", year: "numeric",
@@ -73,6 +71,17 @@
: null : null
); );
function parseServerTimestamp(value: unknown): number | null {
if (typeof value === "number") return Number.isFinite(value) ? value : null;
if (typeof value === "string") {
const numeric = Number(value);
if (Number.isFinite(numeric)) return numeric;
const parsed = new Date(value).getTime();
return Number.isFinite(parsed) ? parsed : null;
}
return null;
}
function mangaStub(item: RecentUpdate): Manga { function mangaStub(item: RecentUpdate): Manga {
return { return {
id: item.manga?.id ?? item.mangaId, id: item.manga?.id ?? item.mangaId,
@@ -99,21 +108,35 @@
const key = CACHE_KEYS.RECENT_UPDATES; const key = CACHE_KEYS.RECENT_UPDATES;
if (force) cache.clear(key); if (force) cache.clear(key);
const res = await cache.get<{ chapters: { nodes: RecentUpdate[] } }>( const [updatesRes, statusRes] = await Promise.all([
key, cache.get<{ chapters: { nodes: RecentUpdate[] } }>(
() => gql<{ chapters: { nodes: RecentUpdate[] } }>(GET_RECENTLY_UPDATED, {}, nextCtrl.signal), key,
RECENT_UPDATES_TTL_MS, () => gql<{ chapters: { nodes: RecentUpdate[] } }>(GET_RECENTLY_UPDATED, {}, nextCtrl.signal),
CACHE_GROUPS.LIBRARY, RECENT_UPDATES_TTL_MS,
); CACHE_GROUPS.LIBRARY,
),
gql<{
libraryUpdateStatus: {
jobsInfo: { isRunning: boolean };
};
lastUpdateTimestamp: { timestamp: string | number | null } | null;
}>(GET_LIBRARY_UPDATE_PANEL_STATUS, {}, nextCtrl.signal).catch(() => null),
]);
updaterRunning = statusRes?.libraryUpdateStatus.jobsInfo.isRunning ?? false;
lastUpdatedTs = parseServerTimestamp(statusRes?.lastUpdateTimestamp?.timestamp ?? null);
if (nextCtrl.signal.aborted) return; if (nextCtrl.signal.aborted) return;
updates = res.chapters.nodes updates = updatesRes.chapters.nodes
.filter(item => item.manga?.inLibrary) .filter(item => item.manga?.inLibrary)
.sort((a, b) => fetchedAtMs(b) - fetchedAtMs(a)); .sort((a, b) => fetchedAtMs(b) - fetchedAtMs(a));
} catch (e: any) { } catch (e: any) {
if (nextCtrl.signal.aborted) return; if (nextCtrl.signal.aborted) return;
error = e?.message ?? "Failed to load updates"; error = e?.message ?? "Failed to load updates";
updates = []; updates = [];
updaterRunning = false;
lastUpdatedTs = null;
} finally { } finally {
if (!nextCtrl.signal.aborted) loading = false; if (!nextCtrl.signal.aborted) loading = false;
} }
@@ -150,11 +173,11 @@
<div class="status-bar"> <div class="status-bar">
<div class="status-dot" class:active={loading}></div> <div class="status-dot" class:active={loading}></div>
<span class="status-text"> <span class="status-text">
{#if loading}Checking for updates…{:else if error}Update check failed{:else}Up to date{/if} {#if loading}Checking for updates…{:else if error}Update check failed{:else if updaterRunning}Library update in progress{:else}Up to date{/if}
</span> </span>
<div class="status-right"> <div class="status-right">
{#if !loading && lastCheckedLabel} {#if !loading && lastUpdatedLabel}
<span class="status-detail">Last checked: {lastCheckedLabel}</span> <span class="status-detail">Last updated: {lastUpdatedLabel}</span>
<div class="bar-sep"></div> <div class="bar-sep"></div>
{/if} {/if}
{#if !loading && updates.length > 0} {#if !loading && updates.length > 0}