feat(anime): rename NFO Diagnostics to Anime Settings + add edit endpoints
Replaces the read-only 'NFO Diagnostics' page with a full per-anime
Settings page reached from the right-click context menu on series cards.
Users can now view and edit key, name, folder, tmdb_id, tvdb_id and site
for each anime; changes are persisted to the DB and optionally written
back to the NFO file or used to regenerate it.
Backend
- Rename NfoDiagnosticsResponse -> NfoSettingsResponse,
NfoSeriesDiagnostics -> NfoSeriesSettings
- Rename get_nfo_diagnostics -> get_nfo_settings,
repair_nfo -> repair_nfo_settings
- Fix nfo.py bug: repair was calling non-existent
update_series_nfo_status(); now uses update_nfo_status() and an
explicit AnimeSeriesService.update(nfo_path=...)
- New endpoints on /api/anime/{key}:
GET /settings -> AnimeSettingsResponse
PUT /settings -> AnimeSettingsResponse
(body: name/folder/tmdb_id/tvdb_id/site,
options: apply_to_nfo, rename_disk)
POST /regenerate-nfo -> AnimeSettingsRegenerateNfoResponse
- New Pydantic models: AnimeSettingsResponse,
AnimeSettingsUpdateRequest, AnimeSettingsRegenerateNfoResponse
- /anime/settings page route; /settings/nfo now 301-redirects to it
Frontend
- New AniWorld.AnimeSettingsManager JS module (single-page form,
no tabs) with public API init/loadSeries/saveSettings/regenerateNfo/
validateField/populateForm/showSaveSuccess/showError
- New anime-settings.html template + anime-settings.css
- Right-click menu: data-action 'nfo-diagnostics' replaced by
'anime-settings' (label 'Anime Settings'), navigates to
/anime/settings?key=...
- Library 'Open NFO Diagnostics' link renamed to 'Open Anime Settings'
Bug fix
- context-menu click handler was calling hide() BEFORE building the
navigation URL, which cleared currentSeriesKey to null and produced
/anime/settings?key=null. Captures the key into a local const first.
Regression-locked by tests/frontend/unit/context_menu.test.js.
Tests
- 21 new pytest tests in tests/api/test_anime_settings_endpoints.py
(GET/PUT/regenerate-nfo, auth, validation, nfo-repair bug regression)
- tests/api/test_nfo_endpoints.py trimmed to 6 focused tests
- 31 new Vitest unit tests for AnimeSettingsManager
- 5 new Vitest unit tests for ContextMenu (incl. source-invariant
regression guard for the hide()-before-key bug)
- 5 new Playwright E2E tests covering right-click, direct nav,
legacy /settings/nfo redirect, and context-menu labels
- New vitest.config.js (environment: happy-dom)
Docs
- Docs/API.md: new section 'Anime Settings Endpoints'
- Docs/CHANGELOG.md: documents the rename and the context-menu bug fix
Verified
- pytest: 27/27 (21 new + 6 trimmed nfo)
- vitest: 36/36 (31 anime-settings + 5 context-menu)
- playwright e2e: 5/5
This commit is contained in:
226
src/server/web/static/css/pages/anime-settings.css
Normal file
226
src/server/web/static/css/pages/anime-settings.css
Normal file
@@ -0,0 +1,226 @@
|
||||
/* ============================================================
|
||||
Anime Settings Page
|
||||
------------------------------------------------------------
|
||||
Layout and styling for /anime/settings (renamed from
|
||||
/settings/nfo — formerly "NFO Diagnostics").
|
||||
============================================================ */
|
||||
|
||||
.anime-settings-main {
|
||||
padding: 1.5rem;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.settings-header-card {
|
||||
background: var(--color-card-bg, #1f2937);
|
||||
color: var(--color-text, #f3f4f6);
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1.25rem;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.settings-header-card h2 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.status-badges {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.6rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
background: var(--color-badge-bg, #374151);
|
||||
color: var(--color-badge-text, #f9fafb);
|
||||
}
|
||||
|
||||
.status-badge.status-complete {
|
||||
background: #10b981;
|
||||
color: #ffffff;
|
||||
}
|
||||
.status-badge.status-incomplete {
|
||||
background: #f59e0b;
|
||||
color: #ffffff;
|
||||
}
|
||||
.status-badge.status-failed {
|
||||
background: #ef4444;
|
||||
color: #ffffff;
|
||||
}
|
||||
.status-badge.status-pending {
|
||||
background: #6366f1;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.settings-section-card {
|
||||
background: var(--color-card-bg, #1f2937);
|
||||
border: 1px solid var(--color-border, #374151);
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.settings-section-card h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 1.1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.settings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.settings-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.settings-field.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.settings-field label {
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-muted, #9ca3af);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.settings-field input.input-field {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid var(--color-border, #4b5563);
|
||||
border-radius: 6px;
|
||||
background: var(--color-input-bg, #111827);
|
||||
color: var(--color-text, #f9fafb);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.settings-field input.input-field:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-accent, #3b82f6);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25);
|
||||
}
|
||||
|
||||
.settings-field .config-hint {
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-text-muted, #9ca3af);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.settings-field .config-hint.hint-error {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.value-mono {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
background: var(--color-code-bg, #111827);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
word-break: break-all;
|
||||
display: inline-block;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 0.95rem;
|
||||
color: var(--color-text, #f3f4f6);
|
||||
}
|
||||
|
||||
.settings-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.settings-toggles {
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid var(--color-border, #374151);
|
||||
}
|
||||
|
||||
.nfo-content {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: var(--color-code-bg, #111827);
|
||||
border: 1px solid var(--color-border, #4b5563);
|
||||
border-radius: 6px;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 0.8rem;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
color: var(--color-text, #e5e7eb);
|
||||
}
|
||||
|
||||
.error-box {
|
||||
background: var(--color-card-bg, #1f2937);
|
||||
border: 1px solid #ef4444;
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
color: var(--color-text, #f3f4f6);
|
||||
}
|
||||
|
||||
.error-box i {
|
||||
font-size: 2rem;
|
||||
color: #ef4444;
|
||||
margin-bottom: 0.5rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.error-box h2 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.error-box p {
|
||||
color: var(--color-text-muted, #9ca3af);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
text-align: center;
|
||||
padding: 3rem 1rem;
|
||||
color: var(--color-text-muted, #9ca3af);
|
||||
}
|
||||
|
||||
.loading-spinner i {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
display: block;
|
||||
color: var(--color-accent, #3b82f6);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.settings-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.anime-settings-main {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user