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
248 lines
12 KiB
HTML
248 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Anime Settings - AniWorld Manager</title>
|
|
<link rel="stylesheet" href="/static/css/styles.css?v={{ static_version('css/styles.css') }}">
|
|
<link rel="stylesheet" href="/static/css/pages/anime-settings.css?v={{ static_version('css/pages/anime-settings.css') }}">
|
|
<link rel="stylesheet" href="/static/css/ux_features.css?v={{ static_version('css/ux_features.css') }}">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
</head>
|
|
<body>
|
|
<div class="app-container">
|
|
<!-- Header -->
|
|
<header class="header">
|
|
<div class="header-content">
|
|
<div class="header-title">
|
|
<i class="fas fa-gear"></i>
|
|
<h1>Anime Settings</h1>
|
|
</div>
|
|
<div class="header-actions">
|
|
<a href="/" class="btn btn-secondary">
|
|
<i class="fas fa-arrow-left"></i>
|
|
<span>Back to Library</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="main-content anime-settings-main">
|
|
<!-- Series key selector (when no key in URL) -->
|
|
<section id="no-key-section" class="settings-section hidden">
|
|
<h2>Select a Series</h2>
|
|
<p class="config-hint">
|
|
No series selected. Right-click any series card on the
|
|
library page and choose <strong>Anime Settings</strong>,
|
|
or use the dropdown below.
|
|
</p>
|
|
<div class="config-item">
|
|
<label for="series-select">Series:</label>
|
|
<select id="series-select" class="input-field">
|
|
<option value="">Loading series...</option>
|
|
</select>
|
|
<button id="load-series-btn" class="btn btn-primary">
|
|
<i class="fas fa-folder-open"></i>
|
|
<span>Open Settings</span>
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Loading state -->
|
|
<section id="loading-section" class="settings-section">
|
|
<div class="loading-spinner">
|
|
<i class="fas fa-spinner fa-spin"></i>
|
|
<p>Loading series settings...</p>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Error state -->
|
|
<section id="error-section" class="settings-section hidden">
|
|
<div class="error-box">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<h2>Could not load settings</h2>
|
|
<p id="error-message">Unknown error.</p>
|
|
<button id="retry-btn" class="btn btn-primary">
|
|
<i class="fas fa-rotate"></i>
|
|
<span>Retry</span>
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Main settings view -->
|
|
<section id="settings-section" class="settings-section hidden">
|
|
<!-- Header card with name + status badges -->
|
|
<div class="settings-header-card">
|
|
<h2 id="series-display-name">Loading...</h2>
|
|
<div class="status-badges">
|
|
<span id="badge-loading-status" class="status-badge"></span>
|
|
<span id="badge-has-nfo" class="status-badge"></span>
|
|
<span id="badge-episode-counts" class="status-badge"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Overview (read-only) -->
|
|
<div class="settings-section-card">
|
|
<h3>
|
|
<i class="fas fa-info-circle"></i>
|
|
Overview
|
|
</h3>
|
|
<div class="settings-grid">
|
|
<div class="settings-field">
|
|
<label>Key (provider ID)</label>
|
|
<code id="overview-key" class="value-mono">—</code>
|
|
<small class="config-hint">
|
|
Provider-assigned URL-safe identifier. Read-only.
|
|
</small>
|
|
</div>
|
|
<div class="settings-field">
|
|
<label>Year</label>
|
|
<span id="overview-year" class="value">—</span>
|
|
</div>
|
|
<div class="settings-field">
|
|
<label>Loading Status</label>
|
|
<span id="overview-loading-status" class="value">—</span>
|
|
</div>
|
|
<div class="settings-field">
|
|
<label>Episode Count</label>
|
|
<span id="overview-episode-count" class="value">—</span>
|
|
</div>
|
|
<div class="settings-field">
|
|
<label>Missing Episodes</label>
|
|
<span id="overview-missing-count" class="value">—</span>
|
|
</div>
|
|
<div class="settings-field">
|
|
<label>NFO Created</label>
|
|
<span id="overview-nfo-created" class="value">—</span>
|
|
</div>
|
|
<div class="settings-field">
|
|
<label>NFO Updated</label>
|
|
<span id="overview-nfo-updated" class="value">—</span>
|
|
</div>
|
|
<div class="settings-field full-width">
|
|
<label>NFO Path</label>
|
|
<code id="overview-nfo-path" class="value-mono">—</code>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Editable fields -->
|
|
<div class="settings-section-card">
|
|
<h3>
|
|
<i class="fas fa-pen-to-square"></i>
|
|
Editable Fields
|
|
</h3>
|
|
<p class="config-hint">
|
|
Changes are saved to the database. Use the action
|
|
buttons below to also rename the on-disk folder or
|
|
regenerate tvshow.nfo.
|
|
</p>
|
|
|
|
<div class="settings-grid">
|
|
<div class="settings-field">
|
|
<label for="field-name">Name</label>
|
|
<input type="text" id="field-name" class="input-field"
|
|
data-field="name" maxlength="500">
|
|
<small id="hint-name" class="config-hint"></small>
|
|
</div>
|
|
|
|
<div class="settings-field">
|
|
<label for="field-folder">Folder</label>
|
|
<input type="text" id="field-folder" class="input-field"
|
|
data-field="folder" maxlength="1000">
|
|
<small id="hint-folder" class="config-hint"></small>
|
|
</div>
|
|
|
|
<div class="settings-field">
|
|
<label for="field-tmdb-id">TMDB ID</label>
|
|
<input type="number" id="field-tmdb-id" class="input-field"
|
|
data-field="tmdb_id" min="1" max="9999999999" step="1">
|
|
<small id="hint-tmdb-id" class="config-hint">
|
|
Positive integer up to 10 digits.
|
|
</small>
|
|
</div>
|
|
|
|
<div class="settings-field">
|
|
<label for="field-tvdb-id">TVDB ID</label>
|
|
<input type="number" id="field-tvdb-id" class="input-field"
|
|
data-field="tvdb_id" min="1" max="9999999999" step="1">
|
|
<small id="hint-tvdb-id" class="config-hint">
|
|
Optional. Positive integer up to 10 digits.
|
|
</small>
|
|
</div>
|
|
|
|
<div class="settings-field full-width">
|
|
<label for="field-site">Site URL</label>
|
|
<input type="text" id="field-site" class="input-field"
|
|
data-field="site" maxlength="500">
|
|
<small id="hint-site" class="config-hint">
|
|
Provider URL (e.g. https://aniworld.to/anime/stream/...)
|
|
</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-actions">
|
|
<button id="save-db-btn" class="btn btn-primary">
|
|
<i class="fas fa-save"></i>
|
|
<span>Save to DB</span>
|
|
</button>
|
|
<button id="save-db-nfo-btn" class="btn btn-success">
|
|
<i class="fas fa-save"></i>
|
|
<i class="fas fa-file-lines"></i>
|
|
<span>Save & Regenerate NFO</span>
|
|
</button>
|
|
<button id="reset-btn" class="btn btn-secondary">
|
|
<i class="fas fa-undo"></i>
|
|
<span>Reset</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="settings-toggles">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="rename-disk-toggle">
|
|
<span class="checkbox-custom"></span>
|
|
<span>Also rename the folder on disk when folder changes</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- NFO actions -->
|
|
<div class="settings-section-card">
|
|
<h3>
|
|
<i class="fas fa-file-lines"></i>
|
|
NFO Actions
|
|
</h3>
|
|
<p class="config-hint">
|
|
tvshow.nfo is consumed by Plex / Jellyfin / Emby /
|
|
Kodi. Use the buttons below to regenerate or view
|
|
its contents.
|
|
</p>
|
|
<div class="settings-actions">
|
|
<button id="regenerate-nfo-btn" class="btn btn-primary">
|
|
<i class="fas fa-rotate"></i>
|
|
<span>Regenerate tvshow.nfo</span>
|
|
</button>
|
|
<button id="view-nfo-btn" class="btn btn-secondary">
|
|
<i class="fas fa-eye"></i>
|
|
<span>View NFO XML</span>
|
|
</button>
|
|
</div>
|
|
<pre id="nfo-content" class="nfo-content hidden"></pre>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<!-- Toast notifications -->
|
|
<div id="toast-container" class="toast-container"></div>
|
|
</div>
|
|
|
|
<!-- Shared modules -->
|
|
<script src="/static/js/shared/constants.js?v={{ static_version('js/shared/constants.js') }}"></script>
|
|
<script src="/static/js/shared/auth.js?v={{ static_version('js/shared/auth.js') }}"></script>
|
|
<script src="/static/js/shared/api-client.js?v={{ static_version('js/shared/api-client.js') }}"></script>
|
|
<script src="/static/js/shared/ui-utils.js?v={{ static_version('js/shared/ui-utils.js') }}"></script>
|
|
|
|
<!-- Page script -->
|
|
<script src="/static/js/pages/anime-settings.js?v={{ static_version('js/pages/anime-settings.js') }}"></script>
|
|
</body>
|
|
</html> |