Tidy release picker profile toggle and repair display text
This commit is contained in:
@@ -3401,7 +3401,7 @@ async def action_search(request_id: str, user: Dict[str, str] = Depends(get_curr
|
|||||||
if len(results) > len(releases):
|
if len(results) > len(releases):
|
||||||
message += ' Duplicate results are combined; up to 200 ranked releases are shown.'
|
message += ' Duplicate results are combined; up to 200 ranked releases are shown.'
|
||||||
if total_missing:
|
if total_missing:
|
||||||
message += f' Searched episodes {offset + 1}?{min(offset + 20, total_missing)} of {total_missing} missing monitored episodes.'
|
message += f' Searched {min(20, max(0, total_missing - offset))} of {total_missing} missing monitored episodes.'
|
||||||
await asyncio.to_thread(save_action, request_id, 'search_releases', 'Search and choose a download', 'ok', message)
|
await asyncio.to_thread(save_action, request_id, 'search_releases', 'Search and choose a download', 'ok', message)
|
||||||
return {'status': 'ok', 'collector': collector, 'qualityFiltered': False, 'message': message,
|
return {'status': 'ok', 'collector': collector, 'qualityFiltered': False, 'message': message,
|
||||||
'outcome': 'matches' if approved else 'attention', 'rejectionReasons': rejection_reasons,
|
'outcome': 'matches' if approved else 'attention', 'rejectionReasons': rejection_reasons,
|
||||||
|
|||||||
@@ -4912,3 +4912,52 @@ textarea:focus {
|
|||||||
max-height: calc(100vh - 84px);
|
max-height: calc(100vh - 84px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Release picker control: independent of the user-management page stylesheet. */
|
||||||
|
.request-release-profile-toggle {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border: 1px solid var(--ops-border, #393941);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.025);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.request-release-profile-toggle > span { display: grid; gap: 5px; min-width: 0; }
|
||||||
|
.request-release-profile-toggle strong { font-size: 0.85rem; line-height: 1.4; }
|
||||||
|
.request-release-profile-toggle small { color: var(--ops-muted); font-size: 0.75rem; line-height: 1.5; }
|
||||||
|
.request-release-profile-toggle input[type="checkbox"] {
|
||||||
|
appearance: none;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 38px;
|
||||||
|
min-width: 38px;
|
||||||
|
height: 22px;
|
||||||
|
min-height: 22px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid #62616d !important;
|
||||||
|
border-radius: 999px !important;
|
||||||
|
background: #45444e !important;
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
.request-release-profile-toggle input[type="checkbox"]::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
left: 3px;
|
||||||
|
top: 3px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #eeedf5;
|
||||||
|
transition: transform 0.15s;
|
||||||
|
}
|
||||||
|
.request-release-profile-toggle input[type="checkbox"]:checked { background: #c7bdff !important; border-color: #c7bdff !important; }
|
||||||
|
.request-release-profile-toggle input[type="checkbox"]:checked::before { transform: translateX(16px); background: #181529; }
|
||||||
|
.request-release-profile-toggle input[type="checkbox"]:focus-visible { outline: 2px solid #c7bdff; outline-offset: 4px; }
|
||||||
|
.request-release-profile-toggle input[type="checkbox"]:disabled { cursor: wait; opacity: 0.5; }
|
||||||
|
|||||||
@@ -968,7 +968,7 @@ export default function RequestTimelinePage() {
|
|||||||
<div>
|
<div>
|
||||||
<span className="section-kicker">Results from {releaseCollector ?? (snapshot.request_type === 'tv' ? 'Sonarr' : 'Radarr')}</span>
|
<span className="section-kicker">Results from {releaseCollector ?? (snapshot.request_type === 'tv' ? 'Sonarr' : 'Radarr')}</span>
|
||||||
<h2 id="release-picker-title">Choose an available download</h2>
|
<h2 id="release-picker-title">Choose an available download</h2>
|
||||||
<p>Search results include the collector?s rejection reasons. The assigned profile stays unchanged.</p>
|
<p>Review available releases and any rejection reasons. The assigned profile stays unchanged.</p>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" className="ghost-button" onClick={closeReleasePicker} disabled={busyAction?.startsWith('grab:')}>Close</button>
|
<button type="button" className="ghost-button" onClick={closeReleasePicker} disabled={busyAction?.startsWith('grab:')}>Close</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -1005,9 +1005,9 @@ export default function RequestTimelinePage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{canIgnoreProfileLimits && <label className="feature-access-row">
|
{canIgnoreProfileLimits && <label className="request-release-profile-toggle">
|
||||||
|
<span><strong>Ignore profile limits</strong><small>Allow releases outside this title's profile. You'll confirm each download.</small></span>
|
||||||
<input type="checkbox" checked={ignoreProfileLimits} disabled={Boolean(busyAction)} onChange={event => setIgnoreProfileLimits(event.target.checked)} />
|
<input type="checkbox" checked={ignoreProfileLimits} disabled={Boolean(busyAction)} onChange={event => setIgnoreProfileLimits(event.target.checked)} />
|
||||||
<span><strong>Ignore profile limits</strong><small>Enable selection outside quality, size, language and custom-format limits for this search. Each download requires confirmation. Other rejection reasons remain blocked.</small></span>
|
|
||||||
</label>}
|
</label>}
|
||||||
{nextSearchOffset !== null && <button type="button" className="ghost-button" disabled={Boolean(busyAction)} onClick={() => void runAction({ id: 'search_releases', label: 'Search next missing episodes', risk: 'low', requires_confirmation: false }, nextSearchOffset)}>Search next missing episodes</button>}
|
{nextSearchOffset !== null && <button type="button" className="ghost-button" disabled={Boolean(busyAction)} onClick={() => void runAction({ id: 'search_releases', label: 'Search next missing episodes', risk: 'low', requires_confirmation: false }, nextSearchOffset)}>Search next missing episodes</button>}
|
||||||
{releaseOptions.length > 0 && (
|
{releaseOptions.length > 0 && (
|
||||||
@@ -1029,7 +1029,7 @@ export default function RequestTimelinePage() {
|
|||||||
{release.indexer ?? 'Unknown indexer'} · {release.seeders ?? 0} seeders · {formatBytes(release.size)}
|
{release.indexer ?? 'Unknown indexer'} · {release.seeders ?? 0} seeders · {formatBytes(release.size)}
|
||||||
{typeof release.customFormatScore === 'number' ? ` · Score ${release.customFormatScore}` : ''}
|
{typeof release.customFormatScore === 'number' ? ` · Score ${release.customFormatScore}` : ''}
|
||||||
</span>
|
</span>
|
||||||
{!!release.rejections?.length && <small className="release-rejection-reasons">{release.rejections.join(' ? ')}</small>}
|
{!!release.rejections?.length && <small className="release-rejection-reasons">{release.rejections.join('; ')}</small>}
|
||||||
{isBestPick && <small>This is the highest-ranked release approved by {releaseCollector ?? 'the collector'}.</small>}
|
{isBestPick && <small>This is the highest-ranked release approved by {releaseCollector ?? 'the collector'}.</small>}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user