Checkpoint peering identity and opt-in discovery
This commit is contained in:
1 parent
eba9b50e30
commit
47de081721
44 files changed
+1557
-55
No files matched your search
+23
-2
@@ -224,16 +224,24 @@
|
||||
const jobRow = (item, movable = false) => {
|
||||
const running = activeEntries.some(entry => entry.current.id === item.id);
|
||||
const availability = item.availability;
|
||||
const expected = availability?.expectedLastChapter;
|
||||
const sparse = /^\d+$/.test(String(expected || '')) && Number(availability?.numberedChapters) < Number(expected);
|
||||
const partialCoverage = availability?.coverage === 'partial' || sparse;
|
||||
const outcome = item.status === 'complete'
|
||||
? `${item.publishedCount ?? 0} chapter${item.publishedCount === 1 ? '' : 's'} published · finished ${when(item.completed)}`
|
||||
: item.status === 'ready' ? 'Awaiting edition confirmation' : item.status.replaceAll('_', ' ');
|
||||
return h('div', { class: 'scraper-item' + (running ? ' active' : ''), 'data-source': item.source },
|
||||
h('div', { class: 'grow' }, h('b', {}, item.title),
|
||||
item.status === 'complete' || availability ? h('div', { class: 'coverage-indicators' },
|
||||
item.status === 'complete' ? h('span', { class: 'coverage-badge coverage-complete' }, 'Hosted selection complete') : null,
|
||||
availability ? h('span', { class: 'coverage-badge ' + (partialCoverage ? 'coverage-partial' : 'coverage-unknown') },
|
||||
partialCoverage ? 'Series coverage partial' : 'Series coverage unverified') : null,
|
||||
item.status === 'complete' && !availability ? h('span', { class: 'coverage-badge coverage-unknown' }, 'Series coverage unverified') : null) : null,
|
||||
h('div', { class: 'muted small' }, `${item.source} · ${item.kind === 'discovery' ? 'Checking for updates' : item.scopeLabel || 'all missing chapters'} · ${outcome}`
|
||||
+ (item.existingPolicy === 'add-source' ? ' · alternate source' : '')),
|
||||
item.error ? h('div', { class: 'small error' }, item.error) : null,
|
||||
item.sourceTitle ? h('div', { class: 'small' }, `Source edition: ${item.sourceTitle}`) : null,
|
||||
availability ? h('div', { class: 'muted small' }, `${availability.selectedChapters} hosted selections${availability.specialReleases ? ` · ${availability.specialReleases} unnumbered releases` : ''}${availability.expectedLastChapter ? ` · metadata last chapter: ${availability.expectedLastChapter}` : ''} · ${availability.coverage === 'partial' ? 'Partial source coverage' : 'Whole-series completeness not verified'}`) : null),
|
||||
availability ? h('div', { class: 'muted small' }, `${availability.selectedChapters} hosted selections${availability.specialReleases ? ` · ${availability.specialReleases} unnumbered releases` : ''}${availability.expectedLastChapter ? ` · metadata last chapter: ${availability.expectedLastChapter}` : ''}`) : null),
|
||||
movable ? h('button', { class: 'btn small', disabled: running || pending[0]?.id === item.id, title: 'Move up', onclick: () => move(item, -1) }, '↑') : null,
|
||||
movable ? h('button', { class: 'btn small', disabled: running || pending.at(-1)?.id === item.id, title: 'Move down', onclick: () => move(item, 1) }, '↓') : null,
|
||||
item.status === 'error' ? h('button', { class: 'btn small primary', onclick: () => command({ command: 'control', action: 'retry', id: item.id }) }, 'Retry') : null,
|
||||
@@ -354,7 +362,20 @@
|
||||
return h('section', { class: 'scraper-source-card' }, h('h3', {}, adapter.label),
|
||||
h('p', { class: 'muted small' }, `${running ? 'Active' : state.sourcePaused?.[adapter.id] ? 'Paused by source protection' : 'Idle'} · ${waiting} waiting · ${problems} need attention`),
|
||||
sourceRateEditors.find(editor => editor.querySelector('b')?.textContent === adapter.label));
|
||||
}), h('p', { class: 'muted small' }, 'Each source runs sequentially with its own limits. New limits apply when that source starts or resumes a manga.'));
|
||||
}), h('p', { class: 'muted small' }, 'Each source runs sequentially with its own limits. New limits apply when that source starts or resumes a manga.'),
|
||||
h('h2', {}, 'Available-source log'),
|
||||
h('p', { class: 'muted small' }, 'A queued URL is only a lead. Catalog found, adapter compatibility and download rights are separate facts. This log does not approve or start a download.'),
|
||||
h('div', { class: 'scraper-queue' }, (state.sourceLedger || []).filter(entry => !sourceFilter || entry.source === sourceFilter).map(entry =>
|
||||
h('div', { class: 'scraper-item' }, h('div', { class: 'grow' },
|
||||
h('b', {}, entry.title || entry.sourceTitle || entry.seriesId || 'Unknown title'),
|
||||
h('div', { class: 'muted small' }, `${entry.source} · catalog ${entry.catalog} · adapter ${entry.adapter} · rights ${entry.rights}`),
|
||||
h('div', { class: 'muted small' }, `${entry.hostedCount == null ? 'Chapter count unknown' : `${entry.hostedCount} selected chapters`} · series coverage ${entry.coverage || 'unknown'} · checked ${when(entry.lastObservedAt)}`),
|
||||
entry.sourceTitle && entry.sourceTitle !== entry.title ? h('div', { class: 'small' }, `Source edition: ${entry.sourceTitle}`) : null,
|
||||
entry.reason ? h('div', { class: 'small' }, `Availability: ${entry.reason}`) : null,
|
||||
entry.lastFailure ? h('div', { class: 'small error' }, `${entry.lastFailure.kind}: ${entry.lastFailure.message}`) : null),
|
||||
entry.url && /^https?:\/\//.test(entry.url) ? h('a', { class: 'btn small', href: entry.url, target: '_blank', rel: 'noopener noreferrer' }, 'Source ↗') : null))),
|
||||
!(state.sourceLedger || []).some(entry => !sourceFilter || entry.source === sourceFilter)
|
||||
? h('p', { class: 'muted small' }, 'No source observations yet for this filter.') : null);
|
||||
card.append(activityPanel, queuePanel, updatesPanel, sourcesPanel);
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user