diff --git a/docs/features.md b/docs/features.md index 4017a10..53e01c1 100644 --- a/docs/features.md +++ b/docs/features.md @@ -8,12 +8,24 @@ ## Configuration Management -- **Setup Page**: Initial configuration interface for server setup and basic settings -- **Config Page**: View and modify application configuration settings -- **NFO Settings**: Configure TMDB API key and NFO auto-creation options -- **Media Download Settings**: Configure automatic poster, logo, and fanart downloads -- **Scheduler Configuration**: Configure automated rescan schedules -- **Backup Management**: Create, restore, and manage configuration backups +- **Enhanced Setup Page**: Comprehensive initial configuration interface with all settings in one place: + - General Settings: Application name and data directory configuration + - Security Settings: Master password setup with strength indicator + - Anime Directory: Primary directory path for anime storage + - Scheduler Settings: Enable/disable scheduler and configure check interval (in minutes) + - Logging Settings: Configure log level, file path, file size limits, and backup count + - Backup Settings: Enable automatic backups with configurable path and retention period + - NFO Settings: TMDB API key, auto-creation options, and media file download preferences +- **Enhanced Settings/Config Modal**: Comprehensive configuration interface accessible from main page: + - General Settings: Edit application name and data directory + - Anime Directory: Modify anime storage location with browse functionality + - Scheduler Configuration: Enable/disable and configure check interval for automated operations + - Logging Configuration: Full control over logging level, file rotation, and backup count + - Backup Configuration: Configure automatic backup settings including path and retention + - NFO Settings: Complete control over TMDB integration and media file downloads + - Configuration Validation: Validate configuration for errors before saving + - Backup Management: Create, restore, and manage configuration backups + - Export/Import: Export configuration for backup or transfer to another instance ## User Interface diff --git a/docs/instructions.md b/docs/instructions.md index aaa032a..8de8c38 100644 --- a/docs/instructions.md +++ b/docs/instructions.md @@ -8,32 +8,32 @@ The goal is to create a FastAPI-based web application that provides a modern int ## Architecture Principles -- **Single Responsibility**: Each file/class has one clear purpose -- **Dependency Injection**: Use FastAPI's dependency system -- **Clean Separation**: Web layer calls core logic, never the reverse -- **File Size Limit**: Maximum 500 lines per file -- **Type Hints**: Use comprehensive type annotations -- **Error Handling**: Proper exception handling and logging +- **Single Responsibility**: Each file/class has one clear purpose +- **Dependency Injection**: Use FastAPI's dependency system +- **Clean Separation**: Web layer calls core logic, never the reverse +- **File Size Limit**: Maximum 500 lines per file +- **Type Hints**: Use comprehensive type annotations +- **Error Handling**: Proper exception handling and logging ## Additional Implementation Guidelines ### Code Style and Standards -- **Type Hints**: Use comprehensive type annotations throughout all modules -- **Docstrings**: Follow PEP 257 for function and class documentation -- **Error Handling**: Implement custom exception classes with meaningful messages -- **Logging**: Use structured logging with appropriate log levels -- **Security**: Validate all inputs and sanitize outputs -- **Performance**: Use async/await patterns for I/O operations +- **Type Hints**: Use comprehensive type annotations throughout all modules +- **Docstrings**: Follow PEP 257 for function and class documentation +- **Error Handling**: Implement custom exception classes with meaningful messages +- **Logging**: Use structured logging with appropriate log levels +- **Security**: Validate all inputs and sanitize outputs +- **Performance**: Use async/await patterns for I/O operations ## 📞 Escalation If you encounter: -- Architecture issues requiring design decisions -- Tests that conflict with documented requirements -- Breaking changes needed -- Unclear requirements or expectations +- Architecture issues requiring design decisions +- Tests that conflict with documented requirements +- Breaking changes needed +- Unclear requirements or expectations **Document the issue and escalate rather than guessing.** @@ -92,45 +92,77 @@ conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app --host 127.0. For each task completed: -- [ ] Implementation follows coding standards -- [ ] Unit tests written and passing -- [ ] Integration tests passing -- [ ] Documentation updated -- [ ] Error handling implemented -- [ ] Logging added -- [ ] Security considerations addressed -- [ ] Performance validated -- [ ] Code reviewed -- [ ] Task marked as complete in instructions.md -- [ ] Infrastructure.md updated and other docs -- [ ] Changes committed to git; keep your messages in git short and clear -- [ ] Take the next task +- [ ] Implementation follows coding standards +- [ ] Unit tests written and passing +- [ ] Integration tests passing +- [ ] Documentation updated +- [ ] Error handling implemented +- [ ] Logging added +- [ ] Security considerations addressed +- [ ] Performance validated +- [ ] Code reviewed +- [ ] Task marked as complete in instructions.md +- [ ] Infrastructure.md updated and other docs +- [ ] Changes committed to git; keep your messages in git short and clear +- [ ] Take the next task --- ## TODO List: - +### ✅ Feature: Enhanced Setup and Settings Pages (COMPLETED) -## Recently Fixed Issues +1. **Setup Page Configuration** ✅ + - [x] Update setup page to allow configuration of the following settings: + - `name`: Application name (default: "Aniworld") + - `data_dir`: Data directory path (default: "data") + - `scheduler`: + - `enabled`: Enable/disable scheduler (default: true) + - `interval_minutes`: Scheduler interval in minutes (default: 60) + - `logging`: + - `level`: Log level (default: "INFO") + - `file`: Log file path (default: null) + - `max_bytes`: Max log file size in bytes (default: null) + - `backup_count`: Number of backup log files (default: 3) + - `backup`: + - `enabled`: Enable/disable backups (default: false) + - `path`: Backup directory path (default: "data/backups") + - `keep_days`: Days to keep backups (default: 30) + - `nfo`: + - `tmdb_api_key`: TMDB API key (default: null) + - `auto_create`: Auto-create NFO files (default: true) + - `update_on_scan`: Update NFO on scan (default: true) + - `download_poster`: Download poster images (default: true) + - `download_logo`: Download logo images (default: true) + - `download_fanart`: Download fanart images (default: true) + - `image_size`: Image size preference (default: "original") + - [x] Implement validation for all configuration fields + - [x] Add form UI with appropriate input types and validation feedback + - [x] Save configuration to config.json on setup completion -### 1. NFO API Endpoint Mismatch (Fixed: 2026-01-16) +2. **Settings Page Enhancement** ✅ + - [x] Display all configuration settings in settings page + - [x] Make all settings editable: + - General: `name`, `data_dir` + - Scheduler: `enabled`, `interval_minutes` + - Logging: `level`, `file`, `max_bytes`, `backup_count` + - Backup: `enabled`, `path`, `keep_days` + - NFO: `tmdb_api_key`, `auto_create`, `update_on_scan`, `download_poster`, `download_logo`, `download_fanart`, `image_size` + - Other: `master_password_hash` (allow password change), `anime_directory` + - [x] Implement save functionality with validation + - [x] Add success/error notifications for settings updates + - [x] Settings changes are applied immediately via API (some may require restart) + - [x] Add configuration section headers for better organization + - [x] Update JavaScript modules to handle all new configuration fields -**Issue**: Frontend JavaScript was calling incorrect NFO API endpoints causing 404 errors. - -**Root Cause**: -- Frontend was using `/api/nfo/series/{key}` pattern -- Backend API uses `/api/nfo/{serie_id}/create`, `/api/nfo/{serie_id}/update`, `/api/nfo/{serie_id}/content` - -**Changes Made**: -- Updated [nfo-manager.js](../src/server/web/static/js/index/nfo-manager.js): - - Fixed `createNFO()`: Now calls `POST /api/nfo/{key}/create` with proper request body - - Fixed `refreshNFO()`: Now calls `PUT /api/nfo/{key}/update` - - Fixed `viewNFO()`: Now calls `GET /api/nfo/{key}/content` - - Updated response handling to check for actual API response fields (e.g., `message`, `content`) - - Removed non-existent `getStatistics()` function - - Fixed `getSeriesWithoutNFO()` to use correct endpoint and response structure - -**Status**: ✅ Fixed and tested +**Implementation Notes:** +- The setup page ([setup.html](../src/server/web/templates/setup.html)) now includes all configuration sections with proper validation +- The SetupRequest model ([auth.py](../src/server/models/auth.py)) has been extended with all configuration fields +- The setup API endpoint ([api/auth.py](../src/server/api/auth.py)) now saves all configuration values +- The config modal in [index.html](../src/server/web/templates/index.html) displays all settings with organized sections +- JavaScript modules ([main-config.js](../src/server/web/static/js/index/main-config.js), [scheduler-config.js](../src/server/web/static/js/index/scheduler-config.js), [logging-config.js](../src/server/web/static/js/index/logging-config.js), [nfo-config.js](../src/server/web/static/js/index/nfo-config.js)) have been updated to use the unified config API +- All configuration is saved through the `/api/config` endpoint using PUT requests +- Configuration validation is performed both client-side and server-side --- + diff --git a/src/server/api/auth.py b/src/server/api/auth.py index 45fd11d..34d8953 100644 --- a/src/server/api/auth.py +++ b/src/server/api/auth.py @@ -29,8 +29,8 @@ optional_bearer = HTTPBearer(auto_error=False) async def setup_auth(req: SetupRequest): """Initial setup endpoint to configure the master password. - This endpoint also initializes the configuration with default values - and saves the anime directory and master password hash. + This endpoint also initializes the configuration with all provided values + and saves them to config.json. """ if auth_service.is_configured(): raise HTTPException( @@ -44,26 +44,77 @@ async def setup_auth(req: SetupRequest): req.master_password ) - # Initialize or update config with master password hash - # and anime directory + # Initialize or update config with all provided values config_service = get_config_service() try: config = config_service.load_config() except Exception: # If config doesn't exist, create default + from src.server.models.config import ( + SchedulerConfig, + LoggingConfig, + BackupConfig, + NFOConfig, + ) config = AppConfig() + # Update basic settings + if req.name: + config.name = req.name + if req.data_dir: + config.data_dir = req.data_dir + + # Update scheduler configuration + if req.scheduler_enabled is not None: + config.scheduler.enabled = req.scheduler_enabled + if req.scheduler_interval_minutes is not None: + config.scheduler.interval_minutes = req.scheduler_interval_minutes + + # Update logging configuration + if req.logging_level: + config.logging.level = req.logging_level.upper() + if req.logging_file is not None: + config.logging.file = req.logging_file + if req.logging_max_bytes is not None: + config.logging.max_bytes = req.logging_max_bytes + if req.logging_backup_count is not None: + config.logging.backup_count = req.logging_backup_count + + # Update backup configuration + if req.backup_enabled is not None: + config.backup.enabled = req.backup_enabled + if req.backup_path: + config.backup.path = req.backup_path + if req.backup_keep_days is not None: + config.backup.keep_days = req.backup_keep_days + + # Update NFO configuration + if req.nfo_tmdb_api_key is not None: + config.nfo.tmdb_api_key = req.nfo_tmdb_api_key + if req.nfo_auto_create is not None: + config.nfo.auto_create = req.nfo_auto_create + if req.nfo_update_on_scan is not None: + config.nfo.update_on_scan = req.nfo_update_on_scan + if req.nfo_download_poster is not None: + config.nfo.download_poster = req.nfo_download_poster + if req.nfo_download_logo is not None: + config.nfo.download_logo = req.nfo_download_logo + if req.nfo_download_fanart is not None: + config.nfo.download_fanart = req.nfo_download_fanart + if req.nfo_image_size: + config.nfo.image_size = req.nfo_image_size.lower() + # Store master password hash in config's other field config.other['master_password_hash'] = password_hash # Store anime directory in config's other field if provided anime_directory = None - if hasattr(req, 'anime_directory') and req.anime_directory: + if req.anime_directory: anime_directory = req.anime_directory.strip() if anime_directory: config.other['anime_directory'] = anime_directory - # Save the config with the password hash and anime directory + # Save the config with all updates config_service.save_config(config, create_backup=False) # Sync series from data files to database if anime directory is set diff --git a/src/server/models/auth.py b/src/server/models/auth.py index 6d8676c..a786d2e 100644 --- a/src/server/models/auth.py +++ b/src/server/models/auth.py @@ -36,14 +36,106 @@ class LoginResponse(BaseModel): class SetupRequest(BaseModel): - """Request to initialize the master password during first-time setup.""" + """Request to initialize the master password during first-time setup. + + This request includes all configuration fields needed to set up the application. + """ + # Required fields master_password: str = Field( ..., min_length=8, description="New master password" ) anime_directory: Optional[str] = Field( None, description="Optional anime directory path" ) + + # Application settings + name: Optional[str] = Field( + default="Aniworld", description="Application name" + ) + data_dir: Optional[str] = Field( + default="data", description="Data directory path" + ) + + # Scheduler configuration + scheduler_enabled: Optional[bool] = Field( + default=True, description="Enable/disable scheduler" + ) + scheduler_interval_minutes: Optional[int] = Field( + default=60, ge=1, description="Scheduler interval in minutes" + ) + + # Logging configuration + logging_level: Optional[str] = Field( + default="INFO", description="Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)" + ) + logging_file: Optional[str] = Field( + default=None, description="Log file path" + ) + logging_max_bytes: Optional[int] = Field( + default=None, ge=0, description="Max log file size in bytes" + ) + logging_backup_count: Optional[int] = Field( + default=3, ge=0, description="Number of backup log files" + ) + + # Backup configuration + backup_enabled: Optional[bool] = Field( + default=False, description="Enable/disable backups" + ) + backup_path: Optional[str] = Field( + default="data/backups", description="Backup directory path" + ) + backup_keep_days: Optional[int] = Field( + default=30, ge=0, description="Days to keep backups" + ) + + # NFO configuration + nfo_tmdb_api_key: Optional[str] = Field( + default=None, description="TMDB API key" + ) + nfo_auto_create: Optional[bool] = Field( + default=True, description="Auto-create NFO files" + ) + nfo_update_on_scan: Optional[bool] = Field( + default=True, description="Update NFO on scan" + ) + nfo_download_poster: Optional[bool] = Field( + default=True, description="Download poster images" + ) + nfo_download_logo: Optional[bool] = Field( + default=True, description="Download logo images" + ) + nfo_download_fanart: Optional[bool] = Field( + default=True, description="Download fanart images" + ) + nfo_image_size: Optional[str] = Field( + default="original", description="Image size preference (original or w500)" + ) + + @field_validator("logging_level") + @classmethod + def validate_logging_level(cls, v: Optional[str]) -> Optional[str]: + """Validate logging level.""" + if v is None: + return v + allowed = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"} + lvl = v.upper() + if lvl not in allowed: + raise ValueError(f"Invalid logging level: {v}. Must be one of {allowed}") + return lvl + + @field_validator("nfo_image_size") + @classmethod + def validate_image_size(cls, v: Optional[str]) -> Optional[str]: + """Validate image size.""" + if v is None: + return v + allowed = {"original", "w500"} + size = v.lower() + if size not in allowed: + raise ValueError(f"Invalid image size: {v}. Must be 'original' or 'w500'") + return size class AuthStatus(BaseModel): diff --git a/src/server/web/static/js/index/config-manager.js b/src/server/web/static/js/index/config-manager.js index 1b53c90..4295d50 100644 --- a/src/server/web/static/js/index/config-manager.js +++ b/src/server/web/static/js/index/config-manager.js @@ -56,6 +56,9 @@ AniWorld.ConfigManager = (function() { // NFO configuration bindNFOEvents(); + // Backup configuration + bindBackupEvents(); + // Status panel const closeStatus = document.getElementById('close-status'); if (closeStatus) { @@ -118,8 +121,16 @@ AniWorld.ConfigManager = (function() { } } - /** - * Bind NFO config events + /** * Bind backup config events + */ + function bindBackupEvents() { + const saveBackup = document.getElementById('save-backup-config'); + if (saveBackup) { + saveBackup.addEventListener('click', AniWorld.MainConfig.saveBackupConfig); + } + } + + /** * Bind NFO config events */ function bindNFOEvents() { const saveNFO = document.getElementById('save-nfo-config'); diff --git a/src/server/web/static/js/index/logging-config.js b/src/server/web/static/js/index/logging-config.js index 085d1f0..dbde63b 100644 --- a/src/server/web/static/js/index/logging-config.js +++ b/src/server/web/static/js/index/logging-config.js @@ -113,28 +113,25 @@ AniWorld.LoggingConfig = (function() { */ async function save() { try { - const config = { - log_level: document.getElementById('log-level').value, - enable_console_logging: document.getElementById('enable-console-logging').checked, - enable_console_progress: document.getElementById('enable-console-progress').checked, - enable_fail2ban_logging: document.getElementById('enable-fail2ban-logging').checked + // Get current config + const configResponse = await AniWorld.ApiClient.get(AniWorld.Constants.API.CONFIG); + if (!configResponse) return; + const config = await configResponse.json(); + + // Update logging settings + config.logging = { + level: document.getElementById('log-level').value.toUpperCase(), + file: document.getElementById('log-file').value.trim() || null, + max_bytes: document.getElementById('log-max-bytes').value ? parseInt(document.getElementById('log-max-bytes').value) : null, + backup_count: parseInt(document.getElementById('log-backup-count').value) || 3 }; - const response = await AniWorld.ApiClient.request(API.LOGGING_CONFIG, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(config) - }); - + // Save updated config + const response = await AniWorld.ApiClient.put(AniWorld.Constants.API.CONFIG, config); if (!response) return; - const data = await response.json(); - if (data.success) { - AniWorld.UI.showToast('Logging configuration saved successfully', 'success'); - await load(); - } else { - AniWorld.UI.showToast('Failed to save logging config: ' + data.error, 'error'); - } + AniWorld.UI.showToast('Logging configuration saved successfully', 'success'); + await load(); } catch (error) { console.error('Error saving logging config:', error); AniWorld.UI.showToast('Failed to save logging configuration', 'error'); diff --git a/src/server/web/static/js/index/main-config.js b/src/server/web/static/js/index/main-config.js index b8c1a60..a539d1f 100644 --- a/src/server/web/static/js/index/main-config.js +++ b/src/server/web/static/js/index/main-config.js @@ -20,31 +20,55 @@ AniWorld.MainConfig = (function() { async function save() { try { const animeDirectory = document.getElementById('anime-directory-input').value.trim(); + const appName = document.getElementById('app-name-input').value.trim(); + const dataDir = document.getElementById('data-dir-input').value.trim(); if (!animeDirectory) { AniWorld.UI.showToast('Please enter an anime directory path', 'error'); return; } - const response = await AniWorld.ApiClient.post(API.CONFIG_DIRECTORY, { - directory: animeDirectory - }); + // Get current config + const currentConfig = await loadCurrentConfig(); + if (!currentConfig) { + AniWorld.UI.showToast('Failed to load current configuration', 'error'); + return; + } + + // Update fields + if (appName) currentConfig.name = appName; + if (dataDir) currentConfig.data_dir = dataDir; + if (!currentConfig.other) currentConfig.other = {}; + currentConfig.other.anime_directory = animeDirectory; + + // Save updated config + const response = await AniWorld.ApiClient.put(AniWorld.Constants.API.CONFIG, currentConfig); if (!response) return; const data = await response.json(); - if (data.success) { - AniWorld.UI.showToast('Main configuration saved successfully', 'success'); - await refreshStatus(); - } else { - AniWorld.UI.showToast('Failed to save configuration: ' + data.error, 'error'); - } + AniWorld.UI.showToast('Main configuration saved successfully', 'success'); + await refreshStatus(); } catch (error) { console.error('Error saving main config:', error); AniWorld.UI.showToast('Failed to save main configuration', 'error'); } } + /** + * Load current configuration from API + */ + async function loadCurrentConfig() { + try { + const response = await AniWorld.ApiClient.get(AniWorld.Constants.API.CONFIG); + if (!response) return null; + return await response.json(); + } catch (error) { + console.error('Error loading config:', error); + return null; + } + } + /** * Reset main configuration */ @@ -109,12 +133,45 @@ AniWorld.MainConfig = (function() { */ async function refreshStatus() { try { - const response = await AniWorld.ApiClient.get(API.ANIME_STATUS); - if (!response) return; - const data = await response.json(); + // Load full configuration + const config = await loadCurrentConfig(); + if (!config) return; - document.getElementById('anime-directory-input').value = data.directory || ''; - document.getElementById('series-count-input').value = data.series_count || '0'; + // Populate general settings + document.getElementById('app-name-input').value = config.name || 'Aniworld'; + document.getElementById('data-dir-input').value = config.data_dir || 'data'; + document.getElementById('anime-directory-input').value = config.other?.anime_directory || ''; + + // Populate scheduler settings + document.getElementById('scheduled-rescan-enabled').checked = config.scheduler?.enabled || false; + document.getElementById('scheduled-rescan-interval').value = config.scheduler?.interval_minutes || 60; + + // Populate logging settings + document.getElementById('log-level').value = config.logging?.level || 'INFO'; + document.getElementById('log-file').value = config.logging?.file || ''; + document.getElementById('log-max-bytes').value = config.logging?.max_bytes || ''; + document.getElementById('log-backup-count').value = config.logging?.backup_count || 3; + + // Populate backup settings + document.getElementById('backup-enabled').checked = config.backup?.enabled || false; + document.getElementById('backup-path').value = config.backup?.path || 'data/backups'; + document.getElementById('backup-keep-days').value = config.backup?.keep_days || 30; + + // Populate NFO settings + document.getElementById('tmdb-api-key').value = config.nfo?.tmdb_api_key || ''; + document.getElementById('nfo-auto-create').checked = config.nfo?.auto_create || false; + document.getElementById('nfo-update-on-scan').checked = config.nfo?.update_on_scan || false; + document.getElementById('nfo-download-poster').checked = config.nfo?.download_poster !== false; + document.getElementById('nfo-download-logo').checked = config.nfo?.download_logo !== false; + document.getElementById('nfo-download-fanart').checked = config.nfo?.download_fanart !== false; + document.getElementById('nfo-image-size').value = config.nfo?.image_size || 'original'; + + // Get series count from status endpoint + const statusResponse = await AniWorld.ApiClient.get(API.ANIME_STATUS); + if (statusResponse) { + const statusData = await statusResponse.json(); + document.getElementById('series-count-input').value = statusData.series_count || '0'; + } } catch (error) { console.error('Error refreshing status:', error); } @@ -278,6 +335,36 @@ AniWorld.MainConfig = (function() { } } + /** + * Save backup configuration + */ + async function saveBackupConfig() { + try { + const config = await loadCurrentConfig(); + if (!config) { + AniWorld.UI.showToast('Failed to load current configuration', 'error'); + return; + } + + // Update backup settings + config.backup = { + enabled: document.getElementById('backup-enabled').checked, + path: document.getElementById('backup-path').value.trim(), + keep_days: parseInt(document.getElementById('backup-keep-days').value) || 30 + }; + + // Save updated config + const response = await AniWorld.ApiClient.put(AniWorld.Constants.API.CONFIG, config); + + if (!response) return; + + AniWorld.UI.showToast('Backup configuration saved successfully', 'success'); + } catch (error) { + console.error('Error saving backup config:', error); + AniWorld.UI.showToast('Failed to save backup configuration', 'error'); + } + } + // Public API return { save: save, @@ -289,6 +376,7 @@ AniWorld.MainConfig = (function() { viewBackups: viewBackups, exportConfig: exportConfig, validateConfig: validateConfig, - resetAllConfig: resetAllConfig + resetAllConfig: resetAllConfig, + saveBackupConfig: saveBackupConfig }; })(); diff --git a/src/server/web/static/js/index/nfo-config.js b/src/server/web/static/js/index/nfo-config.js index 112212e..1551429 100644 --- a/src/server/web/static/js/index/nfo-config.js +++ b/src/server/web/static/js/index/nfo-config.js @@ -92,8 +92,16 @@ AniWorld.NFOConfig = (function() { return; } - const nfoConfig = { - tmdb_api_key: tmdbKey ? tmdbKey.value.trim() : null, + // Get current config + const configResponse = await AniWorld.ApiClient.get(AniWorld.Constants.API.CONFIG); + if (!configResponse) { + throw new Error('Failed to load current configuration'); + } + const config = await configResponse.json(); + + // Update NFO settings + config.nfo = { + tmdb_api_key: tmdbKey ? tmdbKey.value.trim() || null : null, auto_create: autoCreate ? autoCreate.checked : false, update_on_scan: updateOnScan ? updateOnScan.checked : false, download_poster: downloadPoster ? downloadPoster.checked : true, @@ -103,13 +111,7 @@ AniWorld.NFOConfig = (function() { }; // Save configuration - const response = await AniWorld.ApiClient.request( - API.CONFIG, - { - method: 'PUT', - body: JSON.stringify({ nfo: nfoConfig }) - } - ); + const response = await AniWorld.ApiClient.put(AniWorld.Constants.API.CONFIG, config); if (response) { AniWorld.UI.showToast('NFO configuration saved successfully', 'success'); diff --git a/src/server/web/static/js/index/scheduler-config.js b/src/server/web/static/js/index/scheduler-config.js index baf4da0..3ec3e1d 100644 --- a/src/server/web/static/js/index/scheduler-config.js +++ b/src/server/web/static/js/index/scheduler-config.js @@ -55,24 +55,25 @@ AniWorld.SchedulerConfig = (function() { async function save() { try { const enabled = document.getElementById('scheduled-rescan-enabled').checked; - const time = document.getElementById('scheduled-rescan-time').value; - const autoDownload = document.getElementById('auto-download-after-rescan').checked; + const interval = parseInt(document.getElementById('scheduled-rescan-interval').value) || 60; - const response = await AniWorld.ApiClient.post(API.SCHEDULER_CONFIG, { + // Get current config + const configResponse = await AniWorld.ApiClient.get(AniWorld.Constants.API.CONFIG); + if (!configResponse) return; + const config = await configResponse.json(); + + // Update scheduler settings + config.scheduler = { enabled: enabled, - time: time, - auto_download_after_rescan: autoDownload - }); + interval_minutes: interval + }; + // Save updated config + const response = await AniWorld.ApiClient.put(AniWorld.Constants.API.CONFIG, config); if (!response) return; - const data = await response.json(); - if (data.success) { - AniWorld.UI.showToast('Scheduler configuration saved successfully', 'success'); - await load(); - } else { - AniWorld.UI.showToast('Failed to save config: ' + data.error, 'error'); - } + AniWorld.UI.showToast('Scheduler configuration saved successfully', 'success'); + await load(); } catch (error) { console.error('Error saving scheduler config:', error); AniWorld.UI.showToast('Failed to save scheduler configuration', 'error'); diff --git a/src/server/web/templates/index.html b/src/server/web/templates/index.html index 23d5c67..fd91f78 100644 --- a/src/server/web/templates/index.html +++ b/src/server/web/templates/index.html @@ -186,6 +186,23 @@