qBitrr2 5.5.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. qBitrr/__init__.py +14 -0
  2. qBitrr/arss.py +7100 -0
  3. qBitrr/auto_update.py +382 -0
  4. qBitrr/bundled_data.py +7 -0
  5. qBitrr/config.py +192 -0
  6. qBitrr/config_version.py +144 -0
  7. qBitrr/db_lock.py +400 -0
  8. qBitrr/db_recovery.py +202 -0
  9. qBitrr/env_config.py +73 -0
  10. qBitrr/errors.py +41 -0
  11. qBitrr/ffprobe.py +105 -0
  12. qBitrr/gen_config.py +1331 -0
  13. qBitrr/home_path.py +23 -0
  14. qBitrr/logger.py +235 -0
  15. qBitrr/main.py +790 -0
  16. qBitrr/search_activity_store.py +92 -0
  17. qBitrr/static/assets/ArrView.js +2 -0
  18. qBitrr/static/assets/ArrView.js.map +1 -0
  19. qBitrr/static/assets/ConfigView.js +4 -0
  20. qBitrr/static/assets/ConfigView.js.map +1 -0
  21. qBitrr/static/assets/LogsView.js +2 -0
  22. qBitrr/static/assets/LogsView.js.map +1 -0
  23. qBitrr/static/assets/ProcessesView.js +2 -0
  24. qBitrr/static/assets/ProcessesView.js.map +1 -0
  25. qBitrr/static/assets/app.css +1 -0
  26. qBitrr/static/assets/app.js +11 -0
  27. qBitrr/static/assets/app.js.map +1 -0
  28. qBitrr/static/assets/build.svg +3 -0
  29. qBitrr/static/assets/check-mark.svg +5 -0
  30. qBitrr/static/assets/close.svg +4 -0
  31. qBitrr/static/assets/download.svg +5 -0
  32. qBitrr/static/assets/gear.svg +5 -0
  33. qBitrr/static/assets/live-streaming.svg +8 -0
  34. qBitrr/static/assets/log.svg +3 -0
  35. qBitrr/static/assets/logo.svg +48 -0
  36. qBitrr/static/assets/plus.svg +4 -0
  37. qBitrr/static/assets/process.svg +15 -0
  38. qBitrr/static/assets/react-select.esm.js +7 -0
  39. qBitrr/static/assets/react-select.esm.js.map +1 -0
  40. qBitrr/static/assets/refresh-arrow.svg +3 -0
  41. qBitrr/static/assets/table.js +5 -0
  42. qBitrr/static/assets/table.js.map +1 -0
  43. qBitrr/static/assets/trash.svg +8 -0
  44. qBitrr/static/assets/up-arrow.svg +3 -0
  45. qBitrr/static/assets/useInterval.js +2 -0
  46. qBitrr/static/assets/useInterval.js.map +1 -0
  47. qBitrr/static/assets/vendor.js +2 -0
  48. qBitrr/static/assets/vendor.js.map +1 -0
  49. qBitrr/static/assets/visibility.svg +9 -0
  50. qBitrr/static/index.html +33 -0
  51. qBitrr/static/logov2-clean.svg +48 -0
  52. qBitrr/static/manifest.json +23 -0
  53. qBitrr/static/sw.js +87 -0
  54. qBitrr/static/vite.svg +1 -0
  55. qBitrr/tables.py +143 -0
  56. qBitrr/utils.py +274 -0
  57. qBitrr/versioning.py +136 -0
  58. qBitrr/webui.py +3114 -0
  59. qbitrr2-5.5.5.dist-info/METADATA +1191 -0
  60. qbitrr2-5.5.5.dist-info/RECORD +64 -0
  61. qbitrr2-5.5.5.dist-info/WHEEL +5 -0
  62. qbitrr2-5.5.5.dist-info/entry_points.txt +2 -0
  63. qbitrr2-5.5.5.dist-info/licenses/LICENSE +21 -0
  64. qbitrr2-5.5.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,4 @@
1
+ import{u as ce,n as de,o as ue,j as t,I as j,p as z,C as J,R as me}from"./app.js";import{r as b}from"./table.js";import{S as he}from"./react-select.esm.js";import"./vendor.js";const w={"Settings.ConsoleLevel":"Level of logging; choose between CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, TRACE.","Settings.Logging":"Enable writing log output to files.","Settings.CompletedDownloadFolder":"Folder where completed downloads are stored. Replace backslashes with forward slashes.","Settings.FreeSpace":"Desired free space threshold (use K, M, G, T suffix). Set to -1 to disable the free space guard.","Settings.FreeSpaceFolder":"Path used when checking free space. Replace backslashes with forward slashes.","Settings.AutoPauseResume":"Automatically pause and resume torrents in response to the free space guard.","Settings.NoInternetSleepTimer":"Delay, in seconds, before retrying when no internet connectivity is detected.","Settings.LoopSleepTimer":"Delay, in seconds, between processing passes when monitoring torrents.","Settings.SearchLoopDelay":"Delay, in seconds, between media search requests.","Settings.FailedCategory":"Category that marks torrents as failed.","Settings.RecheckCategory":"Category that triggers recheck handling.","Settings.Tagless":"Enable tagless operation when categories are not used.","Settings.IgnoreTorrentsYoungerThan":"Ignore torrents younger than this many seconds when evaluating failures.","Settings.PingURLS":"Hostnames used to test for internet connectivity. They are pinged frequently.","Settings.FFprobeAutoUpdate":"Download and update the bundled ffprobe binary automatically.","Settings.AutoUpdateEnabled":"Enable the background worker that periodically checks for qBitrr updates.","Settings.AutoUpdateCron":"Cron expression describing when to check for updates (default weekly Sunday at 03:00).","WebUI.Host":"Interface address for the built-in WebUI. 0.0.0.0 binds on all interfaces.","WebUI.Port":"Port number for the built-in WebUI.","WebUI.Token":"Optional bearer token required by the WebUI/API. Leave empty to disable authentication.","WebUI.LiveArr":"Enable live updates for Arr views.","WebUI.GroupSonarr":"Group Sonarr views by series and seasons in collapsible sections.","WebUI.Theme":"Choose the visual theme for the WebUI (light or dark).","qBit.Disabled":"Disable qBitrr's direct qBittorrent integration (headless mode for search-only setups).","qBit.Host":"qBittorrent WebUI host or IP address.","qBit.Port":"qBittorrent WebUI port.","qBit.UserName":"qBittorrent WebUI username.","qBit.Password":"qBittorrent WebUI password. Remove this if authentication is bypassed for the host.","ARR.Managed":"Toggle whether this Servarr instance is actively managed by qBitrr.","ARR.URI":"Servarr URL, including protocol and port if needed (for example http://localhost:8989).","ARR.APIKey":"Servarr API key from Settings > General > Security.","ARR.Category":"qBittorrent category applied by the Servarr instance to its downloads.","ARR.ReSearch":"Re-run searches for failed torrents that qBitrr removes.","ARR.importMode":"Preferred import mode (Move, Copy, or Auto) when Servarr grabs completed files.","ARR.RssSyncTimer":"Interval, in minutes, between RSS sync requests (0 disables the task).","ARR.RefreshDownloadsTimer":"Interval, in minutes, between queue refresh requests (0 disables the task).","ARR.ArrErrorCodesToBlocklist":"List of Servarr error messages that should trigger blocklisting and cleanup.","EntrySearch.SearchMissing":"Search for missing media items.","EntrySearch.AlsoSearchSpecials":"Include season 0 specials in missing searches.","EntrySearch.Unmonitored":"Include unmonitored series or episodes in searches.","EntrySearch.SearchLimit":"Maximum number of concurrent search tasks (Servarr enforces its own limits).","EntrySearch.SearchByYear":"Order searches by the year the episode or movie first aired.","EntrySearch.SearchInReverse":"Reverse search order (search oldest to newest instead of newest to oldest).","EntrySearch.SearchRequestsEvery":"Delay, in seconds, between submitting individual search requests.","EntrySearch.DoUpgradeSearch":"Search for improved releases even if a file already exists.","EntrySearch.QualityUnmetSearch":"Search again when the quality requirements were not met.","EntrySearch.CustomFormatUnmetSearch":"Search again when the minimum custom format score was not met.","EntrySearch.ForceMinimumCustomFormat":"Automatically remove torrents that do not meet the minimum custom format score.","EntrySearch.SearchAgainOnSearchCompletion":"Restart the search loop when the configured year range is exhausted.","EntrySearch.UseTempForMissing":"Switch to temporary profiles when searching for missing media.","EntrySearch.KeepTempProfile":"Do not revert to the main profile after using the temp profile.","EntrySearch.MainQualityProfile":"Primary quality profile names, in the same order as the temporary profiles.","EntrySearch.TempQualityProfile":"Temporary quality profile names, paired with the primary profiles.","EntrySearch.SearchBySeries":"Search by entire series instead of individual episodes when applicable.","EntrySearch.PrioritizeTodaysReleases":"Prioritise items released today (similar to RSS prioritisation).","EntrySearch.Ombi.SearchOmbiRequests":"Pull pending Ombi requests when SearchMissing is enabled.","EntrySearch.Ombi.OmbiURI":"Ombi server URL.","EntrySearch.Ombi.OmbiAPIKey":"Ombi API key.","EntrySearch.Ombi.ApprovedOnly":"Only process Ombi requests that are approved.","EntrySearch.Ombi.Is4K":"Treat this Ombi configuration as 4K specific.","EntrySearch.Overseerr.SearchOverseerrRequests":"Pull Overseerr requests when SearchMissing is enabled.","EntrySearch.Overseerr.OverseerrURI":"Overseerr server URL.","EntrySearch.Overseerr.OverseerrAPIKey":"Overseerr API key.","EntrySearch.Overseerr.ApprovedOnly":"Only process Overseerr requests that are approved.","EntrySearch.Overseerr.Is4K":"Treat this Overseerr configuration as 4K specific.","Torrent.CaseSensitiveMatches":"When enabled, regex matches will respect case; otherwise they are case-insensitive.","Torrent.FolderExclusionRegex":"Regex patterns that exclude folders outright (full-name match).","Torrent.FileNameExclusionRegex":"Regex patterns that exclude individual files based on the file name.","Torrent.FileExtensionAllowlist":"Allowed file extensions (or regex) for downloads; leave empty to allow all.","Torrent.AutoDelete":"Automatically delete files that are not recognised as media.","Torrent.IgnoreTorrentsYoungerThan":"Ignore torrents younger than this many seconds for failure handling.","Torrent.MaximumETA":"Maximum allowed remaining ETA in seconds; values above this are considered stalled.","Torrent.MaximumDeletablePercentage":"Upper bound for completion percentage when deciding to delete a torrent.","Torrent.DoNotRemoveSlow":"Ignore slow torrents when pruning.","Torrent.StalledDelay":"Minutes to allow stalled torrents before taking action (-1 disables, 0 is infinite).","Torrent.ReSearchStalled":"Re-run searches for stalled torrents before or after removal depending on configuration.","Torrent.RemoveDeadTrackers":"Remove trackers flagged as dead.","Torrent.RemoveTrackerWithMessage":"Tracker status messages that should trigger tracker removal when RemoveDeadTrackers is enabled.","Torrent.SeedingMode.DownloadRateLimitPerTorrent":"Per-torrent download rate limit in bytes per second (-1 disables the limit).","Torrent.SeedingMode.UploadRateLimitPerTorrent":"Per-torrent upload rate limit in bytes per second (-1 disables the limit).","Torrent.SeedingMode.MaxUploadRatio":"Maximum allowed upload ratio (-1 disables the limit).","Torrent.SeedingMode.MaxSeedingTime":"Maximum seeding duration in seconds (-1 disables the limit).","Torrent.SeedingMode.RemoveTorrent":"Removal policy: -1 do not remove, 1 remove on ratio, 2 remove on time, 3 remove on ratio or time, 4 remove on ratio and time."};function Y(e){const r=e.join(".");if(w[r])return w[r];if(e.length>1){const a=["ARR",...e.slice(1)].join(".");if(w[a])return w[a];const l=["EntrySearch",...e.slice(2)].join(".");if(e[1]==="EntrySearch"&&w[l])return w[l];const s=["Torrent",...e.slice(2)].join(".");if(e[1]==="Torrent"&&w[s])return w[s]}const n=e[e.length-1];return w[n]}const pe="/static/assets/visibility.svg",X="/static/assets/plus.svg",G="/static/assets/check-mark.svg",Z="/static/assets/trash.svg",H=/(rad|son|anim)arr/i,be=()=>{const e=document.documentElement.getAttribute("data-theme")==="dark";return{control:r=>({...r,background:e?"#0f131a":"#ffffff",color:e?"#eaeef2":"#1d1d1f",borderColor:e?"#2a2f36":"#d2d2d7",minHeight:"38px",boxShadow:"none","&:hover":{borderColor:e?"#3a4149":"#b8b8bd"}}),menu:r=>({...r,background:e?"#0f131a":"#ffffff",borderColor:e?"#2a2f36":"#d2d2d7",border:`1px solid ${e?"#2a2f36":"#d2d2d7"}`}),option:(r,n)=>({...r,background:n.isFocused?e?"rgba(122, 162, 247, 0.15)":"rgba(0, 113, 227, 0.1)":e?"#0f131a":"#ffffff",color:e?"#eaeef2":"#1d1d1f","&:active":{background:e?"rgba(122, 162, 247, 0.25)":"rgba(0, 113, 227, 0.2)"}}),singleValue:r=>({...r,color:e?"#eaeef2":"#1d1d1f"}),input:r=>({...r,color:e?"#eaeef2":"#1d1d1f"}),placeholder:r=>({...r,color:e?"#9aa3ac":"#6e6e73"}),menuList:r=>({...r,padding:"4px"})}},O=e=>String(e).split(",").map(r=>r.trim()).filter(Boolean),U=e=>Array.isArray(e)?e.join(", "):String(e??""),fe=["Move","Copy","Auto"],ye=/(.+?[.!?])(\s|$)/;function ee(e){if(!e)return;const r=e.trim();if(!r)return;const n=r.match(ye),a=n?n[1]:r;return a.length>160?`${a.slice(0,157)}…`:a}const re=[{label:"Console Level",path:["Settings","ConsoleLevel"],type:"select",options:["CRITICAL","ERROR","WARNING","NOTICE","INFO","DEBUG","TRACE"],required:!0},{label:"Logging",path:["Settings","Logging"],type:"checkbox"},{label:"Completed Download Folder",path:["Settings","CompletedDownloadFolder"],type:"text",required:!0,validate:e=>{const r=String(e??"").trim();if(!r||r.toUpperCase()==="CHANGE_ME")return"Completed Download Folder must be set to a valid path."}},{label:"Free Space",path:["Settings","FreeSpace"],type:"text",required:!0,validate:e=>{const r=String(e??"").trim();if(!r)return"Free Space must be provided.";if(r!=="-1"&&!/^-?\d+(\.\d+)?[KMGTP]?$/i.test(r))return"Free Space must be -1 or a number optionally suffixed with K, M, G, T, or P."}},{label:"Free Space Folder",path:["Settings","FreeSpaceFolder"],type:"text",validate:(e,r)=>{const n=k(r.root,["Settings","FreeSpace"]);if(!(String(n??"").trim()!=="-1"))return;const l=String(e??"").trim();if(!l||l.toUpperCase()==="CHANGE_ME")return"Free Space Folder is required when Free Space monitoring is enabled."}},{label:"Auto Pause/Resume",path:["Settings","AutoPauseResume"],type:"checkbox"},{label:"No Internet Sleep (s)",path:["Settings","NoInternetSleepTimer"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<0)return"No Internet Sleep must be a non-negative number."}},{label:"Loop Sleep (s)",path:["Settings","LoopSleepTimer"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<0)return"Loop Sleep must be a non-negative number."}},{label:"Search Loop Delay (s)",path:["Settings","SearchLoopDelay"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<0)return"Search Loop Delay must be a non-negative number."}},{label:"Failed Category",path:["Settings","FailedCategory"],type:"text"},{label:"Recheck Category",path:["Settings","RecheckCategory"],type:"text"},{label:"Tagless",path:["Settings","Tagless"],type:"checkbox"},{label:"Ignore Torrents Younger Than",path:["Settings","IgnoreTorrentsYoungerThan"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<0)return"Ignore Torrents Younger Than must be a non-negative number."}},{label:"Ping URLs",path:["Settings","PingURLS"],type:"text",parse:O,format:U,placeholder:"one.one.one.one, dns.google.com"},{label:"FFprobe Auto Update",path:["Settings","FFprobeAutoUpdate"],type:"checkbox"},{label:"Auto Update Enabled",path:["Settings","AutoUpdateEnabled"],type:"checkbox"},{label:"Auto Update Cron",path:["Settings","AutoUpdateCron"],type:"text",placeholder:"0 3 * * 0",required:!0,validate:e=>{const n=String(e??"").trim().split(/\s+/).filter(Boolean);if(n.length<5||n.length>6)return"Auto Update Cron must contain 5 or 6 space-separated fields."}}],te=[{label:"WebUI Host",path:["WebUI","Host"],type:"text",required:!0,validate:e=>{if(!String(e??"").trim())return"WebUI Host is required."}},{label:"WebUI Port",path:["WebUI","Port"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isInteger(r)||r<1||r>65535)return"WebUI Port must be between 1 and 65535."}},{label:"WebUI Token",path:["WebUI","Token"],type:"password",secure:!0,fullWidth:!0},{label:"Live Arr",path:["WebUI","LiveArr"],type:"checkbox"},{label:"Group Sonarr by Series",path:["WebUI","GroupSonarr"],type:"checkbox"},{label:"Theme",path:["WebUI","Theme"],type:"select",options:["Light","Dark"]}],ne=[{label:"Disabled",path:["qBit","Disabled"],type:"checkbox"},{label:"Host",path:["qBit","Host"],type:"text",required:!0,validate:e=>{if(!String(e??"").trim())return"qBit Host is required."}},{label:"Port",path:["qBit","Port"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isInteger(r)||r<1||r>65535)return"qBit Port must be between 1 and 65535."}},{label:"UserName",path:["qBit","UserName"],type:"text"},{label:"Password",path:["qBit","Password"],type:"password"}],ge=[{label:"Display Name",type:"text",placeholder:"Sonarr-TV",sectionName:!0},{label:"Managed",path:["Managed"],type:"checkbox"},{label:"URI",path:["URI"],type:"text",placeholder:"http://host:port",required:!0,validate:(e,r)=>{const n=String(e??"").trim();if(k(r.section??{},["Managed"])&&(!n||n.toUpperCase()==="CHANGE_ME"))return"URI must be set to a valid URL when the instance is managed."}},{label:"API Key",path:["APIKey"],type:"password",secure:!0,required:!0,validate:(e,r)=>{const n=String(e??"").trim();if(k(r.section??{},["Managed"])&&(!n||n.toUpperCase()==="CHANGE_ME"))return"API Key must be provided when the instance is managed."}},{label:"Category",path:["Category"],type:"text",required:!0,validate:e=>{if(!String(e??"").trim())return"Category is required."}},{label:"Re-search",path:["ReSearch"],type:"checkbox"},{label:"Import Mode",path:["importMode"],type:"select",options:fe,required:!0},{label:"RSS Sync Timer (min)",path:["RssSyncTimer"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<0)return"RSS Sync Timer must be a non-negative number."}},{label:"Refresh Downloads Timer (min)",path:["RefreshDownloadsTimer"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<0)return"Refresh Downloads Timer must be a non-negative number."}},{label:"Arr Error Codes To Blocklist",path:["ArrErrorCodesToBlocklist"],type:"text",parse:O,format:U}],Se=[{label:"Search Missing",path:["EntrySearch","SearchMissing"],type:"checkbox"},{label:"Also Search Specials",path:["EntrySearch","AlsoSearchSpecials"],type:"checkbox"},{label:"Unmonitored",path:["EntrySearch","Unmonitored"],type:"checkbox"},{label:"Do Upgrade Search",path:["EntrySearch","DoUpgradeSearch"],type:"checkbox"},{label:"Quality Unmet Search",path:["EntrySearch","QualityUnmetSearch"],type:"checkbox"},{label:"Custom Format Unmet Search",path:["EntrySearch","CustomFormatUnmetSearch"],type:"checkbox"},{label:"Force Minimum Custom Format",path:["EntrySearch","ForceMinimumCustomFormat"],type:"checkbox"},{label:"Search Limit",path:["EntrySearch","SearchLimit"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<1)return"Search Limit must be at least 1."}},{label:"Search By Year",path:["EntrySearch","SearchByYear"],type:"checkbox"},{label:"Search In Reverse",path:["EntrySearch","SearchInReverse"],type:"checkbox"},{label:"Search Requests Every (s)",path:["EntrySearch","SearchRequestsEvery"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<1)return"Search Requests Every must be at least 1 second."}},{label:"Search Again On Completion",path:["EntrySearch","SearchAgainOnSearchCompletion"],type:"checkbox"},{label:"Use Temp Profile For Missing",path:["EntrySearch","UseTempForMissing"],type:"checkbox"},{label:"Keep Temp Profile",path:["EntrySearch","KeepTempProfile"],type:"checkbox"},{label:"Main Quality Profile",path:["EntrySearch","MainQualityProfile"],type:"text",parse:O,format:U},{label:"Temp Quality Profile",path:["EntrySearch","TempQualityProfile"],type:"text",parse:O,format:U},{label:"Search By Series",path:["EntrySearch","SearchBySeries"],type:"select",options:["smart","true","false"],description:"smart = auto (series search for multiple episodes, episode search for single), true = always series search, false = always episode search",format:e=>typeof e=="boolean"?e?"true":"false":String(e||"smart"),parse:e=>{const r=String(e);return r==="true"||r==="false"?r:"smart"}},{label:"Prioritize Today's Releases",path:["EntrySearch","PrioritizeTodaysReleases"],type:"checkbox"}],xe=[{label:"Search Ombi Requests",path:["EntrySearch","Ombi","SearchOmbiRequests"],type:"checkbox"},{label:"Ombi URI",path:["EntrySearch","Ombi","OmbiURI"],type:"text",placeholder:"http://host:port"},{label:"Ombi API Key",path:["EntrySearch","Ombi","OmbiAPIKey"],type:"password"},{label:"Approved Only",path:["EntrySearch","Ombi","ApprovedOnly"],type:"checkbox"},{label:"Is 4K Instance",path:["EntrySearch","Ombi","Is4K"],type:"checkbox"}],ve=[{label:"Search Overseerr Requests",path:["EntrySearch","Overseerr","SearchOverseerrRequests"],type:"checkbox"},{label:"Overseerr URI",path:["EntrySearch","Overseerr","OverseerrURI"],type:"text",placeholder:"http://host:port"},{label:"Overseerr API Key",path:["EntrySearch","Overseerr","OverseerrAPIKey"],type:"password"},{label:"Approved Only",path:["EntrySearch","Overseerr","ApprovedOnly"],type:"checkbox"},{label:"Is 4K Instance",path:["EntrySearch","Overseerr","Is4K"],type:"checkbox"}],Re=[{label:"Case Sensitive Matches",path:["Torrent","CaseSensitiveMatches"],type:"checkbox"},{label:"Folder Exclusion Regex",path:["Torrent","FolderExclusionRegex"],type:"text",parse:O,format:U},{label:"File Name Exclusion Regex",path:["Torrent","FileNameExclusionRegex"],type:"text",parse:O,format:U},{label:"File Extension Allowlist",path:["Torrent","FileExtensionAllowlist"],type:"text",parse:O,format:U},{label:"Auto Delete",path:["Torrent","AutoDelete"],type:"checkbox"},{label:"Ignore Torrents Younger Than (s)",path:["Torrent","IgnoreTorrentsYoungerThan"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<0)return"Ignore Torrents Younger Than must be a non-negative number."}},{label:"Maximum ETA (s)",path:["Torrent","MaximumETA"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<-1)return"Maximum ETA must be -1 or a non-negative number."}},{label:"Maximum Deletable Percentage",path:["Torrent","MaximumDeletablePercentage"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<0||r>100)return"Maximum Deletable Percentage must be between 0 and 100."}},{label:"Do Not Remove Slow",path:["Torrent","DoNotRemoveSlow"],type:"checkbox"},{label:"Stalled Delay (min)",path:["Torrent","StalledDelay"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<0)return"Stalled Delay must be a non-negative number."}},{label:"Re-search Stalled",path:["Torrent","ReSearchStalled"],type:"checkbox"},{label:"Remove Dead Trackers",path:["Torrent","RemoveDeadTrackers"],type:"checkbox"},{label:"Remove Tracker Messages",path:["Torrent","RemoveTrackerWithMessage"],type:"text",parse:O,format:U}],Te=[{label:"Download Rate Limit Per Torrent",path:["Torrent","SeedingMode","DownloadRateLimitPerTorrent"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<-1)return"Download Rate Limit must be -1 or greater."}},{label:"Upload Rate Limit Per Torrent",path:["Torrent","SeedingMode","UploadRateLimitPerTorrent"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<-1)return"Upload Rate Limit must be -1 or greater."}},{label:"Max Upload Ratio",path:["Torrent","SeedingMode","MaxUploadRatio"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<-1)return"Max Upload Ratio must be -1 or greater."}},{label:"Max Seeding Time (s)",path:["Torrent","SeedingMode","MaxSeedingTime"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<-1)return"Max Seeding Time must be -1 or greater."}},{label:"Remove Torrent (policy)",path:["Torrent","SeedingMode","RemoveTorrent"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r))return"Remove Torrent policy must be a number.";if(r!==-1&&![1,2,3,4].includes(r))return"Remove Torrent policy must be -1, 1, 2, 3, or 4."}}],Ee=[{label:"Name",path:["Name"],type:"text",required:!0},{label:"URI",path:["URI"],type:"text",required:!0},{label:"Priority",path:["Priority"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<0)return"Priority must be a non-negative number."}},{label:"Maximum ETA",path:["MaximumETA"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<-1)return"Maximum ETA must be -1 or a non-negative number."}},{label:"Download Rate Limit",path:["DownloadRateLimit"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<-1)return"Download Rate Limit must be -1 or greater."}},{label:"Upload Rate Limit",path:["UploadRateLimit"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<-1)return"Upload Rate Limit must be -1 or greater."}},{label:"Max Upload Ratio",path:["MaxUploadRatio"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<-1)return"Max Upload Ratio must be -1 or greater."}},{label:"Max Seeding Time",path:["MaxSeedingTime"],type:"number",validate:e=>{const r=typeof e=="number"?e:Number(e);if(!Number.isFinite(r)||r<-1)return"Max Seeding Time must be -1 or greater."}},{label:"Add Tracker If Missing",path:["AddTrackerIfMissing"],type:"checkbox"},{label:"Remove If Exists",path:["RemoveIfExists"],type:"checkbox"},{label:"Super Seed Mode",path:["SuperSeedMode"],type:"checkbox"},{label:"Add Tags",path:["AddTags"],type:"text",parse:O,format:U}];function se(e){const n=e.toLowerCase().includes("sonarr"),a=[...ge],l=Se.filter(y=>{if(!y.path)return!0;const g=y.path.join(".");return!(!n&&(g==="EntrySearch.AlsoSearchSpecials"||g==="EntrySearch.SearchBySeries"||g==="EntrySearch.PrioritizeTodaysReleases"))}),s=[...xe],c=[...ve],p=[...Re],R=[...Te],i=[...Ee];return{generalFields:a,entryFields:l,entryOmbiFields:s,entryOverseerrFields:c,torrentFields:p,seedingFields:R,trackerFields:i}}function Q(e){return e==null||typeof e=="string"&&e.trim()===""||Array.isArray(e)&&e.length===0}function Ne(e,r){const n=e.label,a=e.required??(e.type==="number"||e.type==="select");switch(e.type){case"text":case"password":return a&&Q(r)?`${n} is required.`:void 0;case"number":{if(r==null||r==="")return a?`${n} is required.`:void 0;const l=typeof r=="number"?r:Number(r);return Number.isFinite(l)?void 0:`${n} must be a valid number.`}case"checkbox":return r==null?a?`${n} is required.`:void 0:typeof r!="boolean"?`${n} must be true or false.`:void 0;case"select":return Q(r)?`${n} is required.`:typeof r!="string"?`${n} must be selected.`:e.options&&!e.options.includes(r)?`${n} must be one of ${e.options.join(", ")}.`:void 0;default:return}}function C(e,r,n,a,l){if(n)for(const s of r){if(s.sectionName)continue;const c=s.path??[],p=c.length?k(n,c):void 0,R=[...a,...c],i=Ne(s,p);if(i){e.push({path:R,message:i});continue}if(s.validate){const y=s.validate(p,l);y&&e.push({path:R,message:y})}}}function je(e){if(!e)return[];const r=[],n={root:e};C(r,re,e,[],n),C(r,te,e,[],n),C(r,ne,e,[],n);for(const[a,l]of Object.entries(e)){if(!H.test(a)||!l||typeof l!="object")continue;const s=l,c={root:e,section:s,sectionKey:a},p=se(a);C(r,p.generalFields,s,[a],c),C(r,p.entryFields,s,[a],c),C(r,p.entryOmbiFields,s,[a],c),C(r,p.entryOverseerrFields,s,[a],c),C(r,p.torrentFields,s,[a],c),C(r,p.seedingFields,s,[a],c)}return r}function q(e){return e?JSON.parse(JSON.stringify(e)):null}function k(e,r){if(!e)return;let n=e;for(const a of r){if(n==null||typeof n!="object")return;n=n[a]}return n}function Ae(e,r,n){let a=e;r.forEach((l,s)=>{s===r.length-1?a[l]=n:((typeof a[l]!="object"||a[l]===null)&&(a[l]={}),a=a[l])})}function _(e,r=[]){const n={};for(const[a,l]of Object.entries(e)){const s=[...r,a];l&&typeof l=="object"&&!Array.isArray(l)?Object.assign(n,_(l,s)):n[s.join(".")]=l}return n}function Ie(e){const r=e.toLowerCase(),n=r.includes("sonarr"),a=r.includes("radarr"),l=r.includes("4k"),s=a?["Not an upgrade for existing movie file(s)","Not a preferred word upgrade for existing movie file(s)","Unable to determine if file is a sample"]:["Not an upgrade for existing episode file(s)","Not a preferred word upgrade for existing episode file(s)","Unable to determine if file is a sample"],c={SearchMissing:!0,Unmonitored:!1,SearchLimit:5,SearchByYear:!0,SearchInReverse:!1,SearchRequestsEvery:300,DoUpgradeSearch:!1,QualityUnmetSearch:!1,CustomFormatUnmetSearch:!1,ForceMinimumCustomFormat:!1,SearchAgainOnSearchCompletion:!0,UseTempForMissing:!1,KeepTempProfile:!1,MainQualityProfile:[],TempQualityProfile:[]};return n&&(c.AlsoSearchSpecials=!1,c.SearchBySeries="smart",c.PrioritizeTodaysReleases=!0),c.Ombi={SearchOmbiRequests:!1,OmbiURI:"CHANGE_ME",OmbiAPIKey:"CHANGE_ME",ApprovedOnly:!0,Is4K:l},c.Overseerr={SearchOverseerrRequests:!1,OverseerrURI:"CHANGE_ME",OverseerrAPIKey:"CHANGE_ME",ApprovedOnly:!0,Is4K:l},{Managed:!0,URI:"CHANGE_ME",APIKey:"CHANGE_ME",Category:e,ReSearch:!0,importMode:"Auto",RssSyncTimer:5,RefreshDownloadsTimer:5,ArrErrorCodesToBlocklist:s,EntrySearch:c,Torrent:{CaseSensitiveMatches:!1,FolderExclusionRegex:["\\bextras?\\b","\\bfeaturettes?\\b","\\bsamples?\\b","\\bscreens?\\b","\\bnc(ed|op)?(\\\\d+)?\\b"],FileNameExclusionRegex:["\\bncop\\\\d+?\\b","\\bnced\\\\d+?\\b","\\bsample\\b","brarbg.com\\b","\\btrailer\\b","music video","comandotorrents.com"],FileExtensionAllowlist:[".mp4",".mkv",".sub",".ass",".srt",".!qB",".parts"],AutoDelete:!1,IgnoreTorrentsYoungerThan:600,MaximumETA:604800,MaximumDeletablePercentage:.99,DoNotRemoveSlow:!0,StalledDelay:15,ReSearchStalled:!1,RemoveDeadTrackers:!1,RemoveTrackerWithMessage:["skipping tracker announce (unreachable)","No such host is known","unsupported URL protocol","info hash is not authorized with this tracker"],SeedingMode:{DownloadRateLimitPerTorrent:-1,UploadRateLimitPerTorrent:-1,MaxUploadRatio:-1,MaxSeedingTime:-1,RemoveTorrent:-1}}}}function Pe(e){const{onDirtyChange:r}=e??{},{push:n}=ce(),[a,l]=b.useState(null),[s,c]=b.useState(null),[p,R]=b.useState(!1),[i,y]=b.useState(!1),g=b.useCallback(async()=>{R(!0);try{const o=await de();l(o),c(q(o))}catch(o){n(o instanceof Error?o.message:"Failed to load configuration","error")}finally{R(!1)}},[n]);b.useEffect(()=>{g()},[g]);const x=b.useCallback((o,m,d)=>{if(!s)return;const u=q(s)??{},h=m.parse?.(d)??(m.type==="number"?Number(d)||0:m.type==="checkbox"?!!d:d);Ae(u,o,h),c(u)},[s]),E=b.useMemo(()=>s?Object.entries(s).filter(([o,m])=>H.test(o)&&m&&typeof m=="object"):[],[s]),A=b.useMemo(()=>{const o=[],m=[...E].sort((f,N)=>f[0].localeCompare(N[0],void 0,{numeric:!0,sensitivity:"base"})),d=[],u=[],h=[];for(const f of m){const[N]=f,W=N.toLowerCase();W.startsWith("radarr")?d.push(f):W.startsWith("sonarr")?u.push(f):h.push(f)}return d.length&&o.push({label:"Radarr Instances",type:"radarr",items:d}),u.length&&o.push({label:"Sonarr Instances",type:"sonarr",items:u}),h.length&&o.push({label:"Other Instances",type:"other",items:h}),o},[E]),[S,v]=b.useState(null),[B,L]=b.useState(!1),[M,T]=b.useState(!1),[I,D]=b.useState(!1),[P,V]=b.useState(!1);b.useEffect(()=>{if(!s||!a){V(!1);return}const o=_(a),m=_(s);let d=!1;for(const[u,h]of Object.entries(m)){const f=o[u];if(Array.isArray(h)||Array.isArray(f)?JSON.stringify(h??[])!==JSON.stringify(f??[]):h!==f){d=!0;break}}if(!d){for(const u of Object.keys(o))if(!(u in m)){d=!0;break}}V(d)},[s,a]),b.useEffect(()=>{r?.(P)},[P,r]),b.useEffect(()=>{if(!P)return;const o=m=>{m.preventDefault(),m.returnValue=""};return window.addEventListener("beforeunload",o),()=>{window.removeEventListener("beforeunload",o)}},[P]),b.useEffect(()=>()=>{r?.(!1)},[r]),b.useEffect(()=>{if(!!!(S||B||M||I))return;const m=h=>{h.key==="Escape"&&(v(null),L(!1),D(!1))};window.addEventListener("keydown",m);const{style:d}=document.body,u=d.overflow;return d.overflow="hidden",()=>{window.removeEventListener("keydown",m),d.overflow=u}},[S,B,M,I]),b.useEffect(()=>{S&&(E.some(([o])=>o===S)||v(null))},[S,E]);const ae=b.useCallback(o=>{if(!s)return;const m=o.charAt(0).toUpperCase()+o.slice(1);let d=1,u=`${m}-${d}`;for(;s[u];)d+=1,u=`${m}-${d}`;const h=q(s)??{},f=Ie(o);f&&typeof f=="object"&&(f.Name=u),h[u]=f,c(h)},[s]),ie=b.useCallback(o=>{if(!s)return;const m=o.toLowerCase();if(!m.startsWith("radarr")&&!m.startsWith("sonarr")||!window.confirm(`Delete ${o}? This action cannot be undone.`))return;const u=q(s)??{};o in u&&(delete u[o],c(u),S===o&&v(null),n(`${o} removed`,"success"))},[s,S,n]),oe=b.useCallback((o,m)=>{if(!s)return;const d=m.trim();if(!d||d===o)return;if(s[d]){n(`An instance named "${d}" already exists`,"error");return}const u=q(s)??{},h=u[o];delete u[o],u[d]=h,h&&typeof h=="object"&&(h.Name=d),c(u),S===o&&v(d)},[s,n,S]),le=b.useCallback(async()=>{if(s){y(!0);try{const o=je(s);if(o.length){const h=o.map(N=>`${N.path.join(".")}: ${N.message}`).join(`
2
+ `),f=o.length===1?h:`Please resolve the following issues:
3
+ ${h}`;n(f,"error"),y(!1);return}const m=_(a??{}),d=_(s),u={};for(const[h,f]of Object.entries(d)){const N=m[h];(Array.isArray(f)||Array.isArray(N)?JSON.stringify(f??[])!==JSON.stringify(N??[]):f!==N)&&(u[h]=f)}for(const h of Object.keys(m))h in d||(u[h]=null);if(Object.keys(u).length===0){n("No changes detected","info"),y(!1);return}await ue({changes:u}),n("Configuration saved","success"),await g()}catch(o){n(o instanceof Error?o.message:"Failed to update configuration","error")}finally{y(!1)}}},[s,a,g,n]);return p||!s?t.jsxs("section",{className:"card",children:[t.jsx("div",{className:"card-header",children:"Config"}),t.jsx("div",{className:"card-body",children:t.jsxs("div",{className:"loading",children:[t.jsx("span",{className:"spinner"})," Loading configuration…"]})})]}):t.jsxs(t.Fragment,{children:[t.jsxs("section",{className:"card",children:[t.jsx("div",{className:"card-header",children:"Config"}),t.jsxs("div",{className:"card-body config-layout",children:[t.jsx("section",{className:"config-arr-group",children:t.jsxs("details",{className:"config-arr-group__details",open:!0,children:[t.jsx("summary",{children:t.jsx("span",{children:"Core Configuration"})}),t.jsxs("div",{className:"config-grid",children:[t.jsx(K,{title:"Settings",description:"Core application configuration",onConfigure:()=>L(!0)}),t.jsx(K,{title:"Web Settings",description:"Web UI configuration",onConfigure:()=>T(!0)}),t.jsx(K,{title:"qBit",description:"qBittorrent connection details",onConfigure:()=>D(!0)})]})]})}),A.length?t.jsx("div",{className:"config-arr-groups",children:A.map(o=>t.jsx("section",{className:"config-arr-group",children:t.jsxs("details",{className:"config-arr-group__details",open:!0,children:[t.jsxs("summary",{children:[t.jsx("span",{children:o.label}),t.jsx("span",{className:"config-arr-group__count",children:o.items.length}),(o.type==="radarr"||o.type==="sonarr")&&t.jsxs("button",{className:"btn small",type:"button",onClick:()=>ae(o.type),children:[t.jsx(j,{src:X}),"Add Instance"]})]}),t.jsx("div",{className:"config-arr-grid",children:o.items.map(([m,d])=>{const u=k(d,["URI"]),h=k(d,["Category"]),f=k(d,["Managed"]),N=o.type==="radarr"||o.type==="sonarr";return t.jsxs("div",{className:"card config-card config-arr-card",children:[t.jsx("div",{className:"card-header",children:m}),t.jsxs("div",{className:"card-body",children:[t.jsxs("dl",{className:"config-arr-summary",children:[t.jsxs("div",{className:"config-arr-summary__item",children:[t.jsx("dt",{children:"Managed"}),t.jsx("dd",{children:f?"Enabled":"Disabled"})]}),t.jsxs("div",{className:"config-arr-summary__item",children:[t.jsx("dt",{children:"Category"}),t.jsx("dd",{children:h?String(h):"-"})]}),t.jsxs("div",{className:"config-arr-summary__item",children:[t.jsx("dt",{children:"URI"}),t.jsx("dd",{className:"config-arr-summary__uri",children:u?String(u):"-"})]})]}),t.jsxs("div",{className:"config-arr-actions",children:[N?t.jsxs("button",{className:"btn danger",type:"button",onClick:()=>ie(m),children:[t.jsx(j,{src:Z}),"Delete"]}):null,t.jsxs("button",{className:"btn primary",type:"button",onClick:()=>v(m),children:[t.jsx(j,{src:z}),"Configure"]})]})]})]},m)})})]})},o.type))}):null,t.jsx("div",{className:"config-footer",children:t.jsxs("button",{className:"btn primary",onClick:()=>void le(),disabled:i,children:[t.jsx(j,{src:G}),"Save + Live Reload"]})})]})]}),S&&s?t.jsx(Fe,{keyName:S,state:s[S]??null,onChange:x,onRename:oe,onClose:()=>v(null)}):null,B?t.jsx($,{title:"Settings",fields:re,state:s,basePath:[],onChange:x,onClose:()=>L(!1)}):null,M?t.jsx($,{title:"Web Settings",fields:te,state:s,basePath:[],onChange:x,onClose:()=>T(!1)}):null,I?t.jsx($,{title:"qBit",fields:ne,state:s,basePath:[],onChange:x,onClose:()=>D(!1)}):null]})}function K({title:e,description:r,onConfigure:n}){return t.jsxs("div",{className:"card config-card",children:[t.jsx("div",{className:"card-header",children:e}),t.jsxs("div",{className:"card-body config-summary-card",children:[t.jsx("p",{children:r}),t.jsx("div",{className:"config-arr-actions",children:t.jsxs("button",{className:"btn primary",type:"button",onClick:n,children:[t.jsx(j,{src:z}),"Configure"]})})]})]})}function F({title:e,fields:r,state:n,basePath:a,onChange:l,onRenameSection:s,defaultOpen:c=!1}){const p=a[0]??"";if(e==="Trackers"){const i=k(n,["Torrent","Trackers"])??[],y=()=>{const x=[...i,{Url:"",RemoveIfExists:!1,SuperSeedMode:!1,AddTags:[]}];l([...a,"Torrent","Trackers"],{},x)},g=x=>{const E=[...i];E.splice(x,1),l([...a,"Torrent","Trackers"],{},E)};return t.jsxs("details",{className:"config-section",open:c,children:[t.jsx("summary",{children:e}),t.jsxs("div",{className:"config-section__body",children:[t.jsx("div",{className:"tracker-grid",children:i.map((x,E)=>t.jsx(ke,{fields:r,state:x,basePath:[...a,"Torrent","Trackers",String(E)],onChange:l,onDelete:()=>g(E)},E))}),t.jsx("div",{className:"config-actions",children:t.jsxs("button",{className:"btn",type:"button",onClick:y,children:[t.jsx(j,{src:X}),"Add Tracker"]})})]})]})}const R=r.map(i=>{if(i.sectionName){if(!p)return null;const T=Y([p]);return t.jsx(we,{label:i.label,tooltip:T,currentName:p,placeholder:i.placeholder,onRename:I=>s?.(p,I)},`${p}.__name`)}const y=i.path??[],g=[...a,...y],x=g.join("."),E=i.path?k(n,i.path):void 0,A=i.format?.(E)??(i.type==="checkbox"?!!E:String(E??"")),S=Y(g),v=i.description??ee(S)??(i.type==="checkbox"?`Enable or disable ${i.label}.`:`Set the ${i.label} value.`),L=a.length>0&&H.test(a[0]??"")&&(i.path?.[i.path.length-1]??"")==="APIKey",M=i.fullWidth?"field field--full-width":"field";if(i.secure)return t.jsx(Ce,{label:i.label,tooltip:S,description:v,value:String(E??""),placeholder:i.placeholder,canRefresh:!L,onChange:T=>l(g,i,T)},x);if(i.type==="checkbox")return t.jsxs("div",{className:"checkbox-field",children:[t.jsxs("label",{title:S,children:[t.jsx("input",{type:"checkbox",checked:!!A,onChange:T=>l(g,i,T.target.checked)}),i.label]}),v&&t.jsx("div",{className:"field-description",children:v})]},x);if(i.type==="select"){const T=i.label==="Theme"&&g.join(".")==="WebUI.Theme";return t.jsxs("div",{className:M,children:[t.jsx("label",{title:S,children:i.label}),t.jsx(he,{options:(i.options??[]).map(I=>({value:I,label:I})),value:A?{value:A,label:A}:null,onChange:I=>{const D=I?.value||"";if(l(g,i,D),T&&typeof D=="string"&&D){const P=D.toLowerCase();document.documentElement.setAttribute("data-theme",P),localStorage.setItem("theme",P)}},styles:be()}),v&&t.jsx("div",{className:"field-description",children:v}),T&&t.jsx("div",{className:"field-hint",children:"Theme changes apply immediately"})]},x)}return i.type==="number"?t.jsxs("div",{className:M,children:[t.jsx("label",{title:S,children:i.label}),t.jsx("input",{type:"number",value:Number(A)||0,onChange:T=>l(g,i,String(T.target.value)),placeholder:i.placeholder}),v&&t.jsx("div",{className:"field-description",children:v})]},x):i.type==="password"?t.jsxs("div",{className:M,children:[t.jsx("label",{title:S,children:i.label}),t.jsx("input",{type:"password",value:String(A),onChange:T=>l(g,i,T.target.value),placeholder:i.placeholder}),v&&t.jsx("div",{className:"field-description",children:v})]},x):t.jsxs("div",{className:M,children:[t.jsx("label",{title:S,children:i.label}),t.jsx("input",{type:"text",value:String(A),onChange:T=>l(g,i,T.target.value),placeholder:i.placeholder}),v&&t.jsx("div",{className:"field-description",children:v})]},x)});return e?t.jsxs("details",{className:"config-section",open:c,children:[t.jsx("summary",{children:e}),t.jsx("div",{className:"config-section__body field-grid",children:R})]}):t.jsx("div",{className:"field-grid",children:R})}function ke({fields:e,state:r,basePath:n,onChange:a,onDelete:l}){const s=k(r,["Name"])||"New Tracker";return t.jsxs("details",{className:"card tracker-card",open:!0,children:[t.jsxs("summary",{className:"card-header",children:[t.jsx("span",{children:s}),t.jsx("button",{className:"btn danger ghost",type:"button",onClick:l,children:t.jsx(j,{src:Z})})]}),t.jsx("div",{className:"card-body",children:t.jsx(F,{title:null,fields:e,state:r,basePath:n,onChange:a})})]})}function we({label:e,currentName:r,placeholder:n,tooltip:a,onRename:l}){const[s,c]=b.useState(r),p=ee(a)??`Rename the ${r} instance.`;b.useEffect(()=>{c(r)},[r]);const R=()=>{const i=s.trim();if(!i){c(r);return}i!==r&&l(i)};return t.jsxs("div",{className:"field",children:[t.jsxs("label",{className:"field-label",children:[t.jsx("span",{children:e}),a?t.jsx("span",{className:"help-icon",title:a,"aria-label":a,children:"?"}):null]}),p?t.jsx("p",{className:"field-description",children:p}):null,t.jsx("input",{type:"text",value:s,placeholder:n,onChange:i=>c(i.target.value),onBlur:R,onKeyDown:i=>{i.key==="Enter"?(i.preventDefault(),R()):i.key==="Escape"&&(i.preventDefault(),c(r))}})]})}function Ce({label:e,value:r,placeholder:n,tooltip:a,description:l,canRefresh:s=!0,onChange:c}){const[p,R]=b.useState(!1),i=()=>{let y="";typeof crypto<"u"&&typeof crypto.randomUUID=="function"?y=crypto.randomUUID().replace(/-/g,""):y=Array.from({length:32},()=>Math.floor(Math.random()*16).toString(16)).join(""),c(y)};return t.jsxs("div",{className:"field secure-field",children:[t.jsx("label",{title:a,children:e}),t.jsxs("div",{className:"secure-field__input-group",children:[t.jsx("input",{type:p?"text":"password",value:r,placeholder:n,onChange:y=>c(y.target.value)}),t.jsx("button",{type:"button",className:"btn ghost",onClick:()=>R(!p),children:t.jsx(j,{src:pe})}),s&&t.jsx("button",{type:"button",className:"btn ghost",onClick:i,children:t.jsx(j,{src:me})})]}),l&&t.jsx("div",{className:"field-description",children:l})]})}function Fe({keyName:e,state:r,onChange:n,onRename:a,onClose:l}){const{generalFields:s,entryFields:c,entryOmbiFields:p,entryOverseerrFields:R,torrentFields:i,seedingFields:y,trackerFields:g}=se(e);return t.jsx("div",{className:"modal-backdrop",role:"presentation",onClick:l,children:t.jsxs("div",{className:"modal",role:"dialog","aria-modal":"true","aria-labelledby":"arr-instance-modal-title",onClick:x=>x.stopPropagation(),children:[t.jsxs("div",{className:"modal-header",children:[t.jsxs("h2",{id:"arr-instance-modal-title",children:["Configure ",t.jsx("code",{children:e})]}),t.jsxs("button",{className:"btn ghost",type:"button",onClick:l,children:[t.jsx(j,{src:J}),"Close"]})]}),t.jsxs("div",{className:"modal-body",children:[t.jsx(F,{title:null,fields:s,state:r,basePath:[e],onChange:n,onRenameSection:a,defaultOpen:!0}),t.jsx(F,{title:"Entry Search",fields:c,state:r,basePath:[e],onChange:n,defaultOpen:!0}),t.jsx(F,{title:"Ombi Integration",fields:p,state:r,basePath:[e],onChange:n}),t.jsx(F,{title:"Overseerr Integration",fields:R,state:r,basePath:[e],onChange:n}),t.jsx(F,{title:"Torrent Handling",fields:i,state:r,basePath:[e],onChange:n}),t.jsx(F,{title:"Seeding",fields:y,state:r,basePath:[e],onChange:n}),t.jsx(F,{title:"Trackers",fields:g,state:r,basePath:[e],onChange:n})]}),t.jsx("div",{className:"modal-footer",children:t.jsxs("button",{className:"btn primary",type:"button",onClick:l,children:[t.jsx(j,{src:G}),"Done"]})})]})})}function $({title:e,fields:r,state:n,basePath:a,onChange:l,onClose:s}){return n?t.jsx("div",{className:"modal-backdrop",role:"presentation",onClick:s,children:t.jsxs("div",{className:"modal",role:"dialog","aria-modal":"true","aria-labelledby":`${e}-modal-title`,onClick:c=>c.stopPropagation(),children:[t.jsxs("div",{className:"modal-header",children:[t.jsx("h2",{id:`${e}-modal-title`,children:e}),t.jsxs("button",{className:"btn ghost",type:"button",onClick:s,children:[t.jsx(j,{src:J}),"Close"]})]}),t.jsx("div",{className:"modal-body",children:t.jsx(F,{title:null,fields:r,state:n,basePath:a,onChange:l,defaultOpen:!0})}),t.jsx("div",{className:"modal-footer",children:t.jsxs("button",{className:"btn primary",type:"button",onClick:s,children:[t.jsx(j,{src:G}),"Done"]})})]})}):null}export{Pe as ConfigView};
4
+ //# sourceMappingURL=ConfigView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigView.js","sources":["../../../webui/src/config/tooltips.ts","../../../webui/src/icons/visibility.svg","../../../webui/src/icons/plus.svg","../../../webui/src/icons/check-mark.svg","../../../webui/src/icons/trash.svg","../../../webui/src/pages/ConfigView.tsx"],"sourcesContent":["export const FIELD_TOOLTIPS: Record<string, string> = {\n \"Settings.ConsoleLevel\":\n \"Level of logging; choose between CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, TRACE.\",\n \"Settings.Logging\": \"Enable writing log output to files.\",\n \"Settings.CompletedDownloadFolder\":\n \"Folder where completed downloads are stored. Replace backslashes with forward slashes.\",\n \"Settings.FreeSpace\":\n \"Desired free space threshold (use K, M, G, T suffix). Set to -1 to disable the free space guard.\",\n \"Settings.FreeSpaceFolder\":\n \"Path used when checking free space. Replace backslashes with forward slashes.\",\n \"Settings.AutoPauseResume\":\n \"Automatically pause and resume torrents in response to the free space guard.\",\n \"Settings.NoInternetSleepTimer\":\n \"Delay, in seconds, before retrying when no internet connectivity is detected.\",\n \"Settings.LoopSleepTimer\":\n \"Delay, in seconds, between processing passes when monitoring torrents.\",\n \"Settings.SearchLoopDelay\":\n \"Delay, in seconds, between media search requests.\",\n \"Settings.FailedCategory\": \"Category that marks torrents as failed.\",\n \"Settings.RecheckCategory\": \"Category that triggers recheck handling.\",\n \"Settings.Tagless\": \"Enable tagless operation when categories are not used.\",\n \"Settings.IgnoreTorrentsYoungerThan\":\n \"Ignore torrents younger than this many seconds when evaluating failures.\",\n \"Settings.PingURLS\":\n \"Hostnames used to test for internet connectivity. They are pinged frequently.\",\n \"Settings.FFprobeAutoUpdate\":\n \"Download and update the bundled ffprobe binary automatically.\",\n \"Settings.AutoUpdateEnabled\":\n \"Enable the background worker that periodically checks for qBitrr updates.\",\n \"Settings.AutoUpdateCron\":\n \"Cron expression describing when to check for updates (default weekly Sunday at 03:00).\",\n \"WebUI.Host\":\n \"Interface address for the built-in WebUI. 0.0.0.0 binds on all interfaces.\",\n \"WebUI.Port\": \"Port number for the built-in WebUI.\",\n \"WebUI.Token\":\n \"Optional bearer token required by the WebUI/API. Leave empty to disable authentication.\",\n \"WebUI.LiveArr\": \"Enable live updates for Arr views.\",\n \"WebUI.GroupSonarr\": \"Group Sonarr views by series and seasons in collapsible sections.\",\n \"WebUI.Theme\": \"Choose the visual theme for the WebUI (light or dark).\",\n\n \"qBit.Disabled\":\n \"Disable qBitrr's direct qBittorrent integration (headless mode for search-only setups).\",\n \"qBit.Host\": \"qBittorrent WebUI host or IP address.\",\n \"qBit.Port\": \"qBittorrent WebUI port.\",\n \"qBit.UserName\": \"qBittorrent WebUI username.\",\n \"qBit.Password\":\n \"qBittorrent WebUI password. Remove this if authentication is bypassed for the host.\",\n\n \"ARR.Managed\": \"Toggle whether this Servarr instance is actively managed by qBitrr.\",\n \"ARR.URI\":\n \"Servarr URL, including protocol and port if needed (for example http://localhost:8989).\",\n \"ARR.APIKey\": \"Servarr API key from Settings > General > Security.\",\n \"ARR.Category\":\n \"qBittorrent category applied by the Servarr instance to its downloads.\",\n \"ARR.ReSearch\": \"Re-run searches for failed torrents that qBitrr removes.\",\n \"ARR.importMode\":\n \"Preferred import mode (Move, Copy, or Auto) when Servarr grabs completed files.\",\n \"ARR.RssSyncTimer\":\n \"Interval, in minutes, between RSS sync requests (0 disables the task).\",\n \"ARR.RefreshDownloadsTimer\":\n \"Interval, in minutes, between queue refresh requests (0 disables the task).\",\n \"ARR.ArrErrorCodesToBlocklist\":\n \"List of Servarr error messages that should trigger blocklisting and cleanup.\",\n\n \"EntrySearch.SearchMissing\": \"Search for missing media items.\",\n \"EntrySearch.AlsoSearchSpecials\": \"Include season 0 specials in missing searches.\",\n \"EntrySearch.Unmonitored\": \"Include unmonitored series or episodes in searches.\",\n \"EntrySearch.SearchLimit\":\n \"Maximum number of concurrent search tasks (Servarr enforces its own limits).\",\n \"EntrySearch.SearchByYear\":\n \"Order searches by the year the episode or movie first aired.\",\n \"EntrySearch.SearchInReverse\":\n \"Reverse search order (search oldest to newest instead of newest to oldest).\",\n \"EntrySearch.SearchRequestsEvery\":\n \"Delay, in seconds, between submitting individual search requests.\",\n \"EntrySearch.DoUpgradeSearch\":\n \"Search for improved releases even if a file already exists.\",\n \"EntrySearch.QualityUnmetSearch\":\n \"Search again when the quality requirements were not met.\",\n \"EntrySearch.CustomFormatUnmetSearch\":\n \"Search again when the minimum custom format score was not met.\",\n \"EntrySearch.ForceMinimumCustomFormat\":\n \"Automatically remove torrents that do not meet the minimum custom format score.\",\n \"EntrySearch.SearchAgainOnSearchCompletion\":\n \"Restart the search loop when the configured year range is exhausted.\",\n \"EntrySearch.UseTempForMissing\":\n \"Switch to temporary profiles when searching for missing media.\",\n \"EntrySearch.KeepTempProfile\": \"Do not revert to the main profile after using the temp profile.\",\n \"EntrySearch.MainQualityProfile\":\n \"Primary quality profile names, in the same order as the temporary profiles.\",\n \"EntrySearch.TempQualityProfile\":\n \"Temporary quality profile names, paired with the primary profiles.\",\n \"EntrySearch.SearchBySeries\":\n \"Search by entire series instead of individual episodes when applicable.\",\n \"EntrySearch.PrioritizeTodaysReleases\":\n \"Prioritise items released today (similar to RSS prioritisation).\",\n\n \"EntrySearch.Ombi.SearchOmbiRequests\":\n \"Pull pending Ombi requests when SearchMissing is enabled.\",\n \"EntrySearch.Ombi.OmbiURI\": \"Ombi server URL.\",\n \"EntrySearch.Ombi.OmbiAPIKey\": \"Ombi API key.\",\n \"EntrySearch.Ombi.ApprovedOnly\": \"Only process Ombi requests that are approved.\",\n \"EntrySearch.Ombi.Is4K\": \"Treat this Ombi configuration as 4K specific.\",\n\n \"EntrySearch.Overseerr.SearchOverseerrRequests\":\n \"Pull Overseerr requests when SearchMissing is enabled.\",\n \"EntrySearch.Overseerr.OverseerrURI\": \"Overseerr server URL.\",\n \"EntrySearch.Overseerr.OverseerrAPIKey\": \"Overseerr API key.\",\n \"EntrySearch.Overseerr.ApprovedOnly\": \"Only process Overseerr requests that are approved.\",\n \"EntrySearch.Overseerr.Is4K\": \"Treat this Overseerr configuration as 4K specific.\",\n\n \"Torrent.CaseSensitiveMatches\":\n \"When enabled, regex matches will respect case; otherwise they are case-insensitive.\",\n \"Torrent.FolderExclusionRegex\":\n \"Regex patterns that exclude folders outright (full-name match).\",\n \"Torrent.FileNameExclusionRegex\":\n \"Regex patterns that exclude individual files based on the file name.\",\n \"Torrent.FileExtensionAllowlist\":\n \"Allowed file extensions (or regex) for downloads; leave empty to allow all.\",\n \"Torrent.AutoDelete\": \"Automatically delete files that are not recognised as media.\",\n \"Torrent.IgnoreTorrentsYoungerThan\":\n \"Ignore torrents younger than this many seconds for failure handling.\",\n \"Torrent.MaximumETA\":\n \"Maximum allowed remaining ETA in seconds; values above this are considered stalled.\",\n \"Torrent.MaximumDeletablePercentage\":\n \"Upper bound for completion percentage when deciding to delete a torrent.\",\n \"Torrent.DoNotRemoveSlow\": \"Ignore slow torrents when pruning.\",\n \"Torrent.StalledDelay\":\n \"Minutes to allow stalled torrents before taking action (-1 disables, 0 is infinite).\",\n \"Torrent.ReSearchStalled\":\n \"Re-run searches for stalled torrents before or after removal depending on configuration.\",\n \"Torrent.RemoveDeadTrackers\": \"Remove trackers flagged as dead.\",\n \"Torrent.RemoveTrackerWithMessage\":\n \"Tracker status messages that should trigger tracker removal when RemoveDeadTrackers is enabled.\",\n\n \"Torrent.SeedingMode.DownloadRateLimitPerTorrent\":\n \"Per-torrent download rate limit in bytes per second (-1 disables the limit).\",\n \"Torrent.SeedingMode.UploadRateLimitPerTorrent\":\n \"Per-torrent upload rate limit in bytes per second (-1 disables the limit).\",\n \"Torrent.SeedingMode.MaxUploadRatio\":\n \"Maximum allowed upload ratio (-1 disables the limit).\",\n \"Torrent.SeedingMode.MaxSeedingTime\":\n \"Maximum seeding duration in seconds (-1 disables the limit).\",\n \"Torrent.SeedingMode.RemoveTorrent\":\n \"Removal policy: -1 do not remove, 1 remove on ratio, 2 remove on time, 3 remove on ratio or time, 4 remove on ratio and time.\",\n};\n\nexport function getTooltip(path: string[]): string | undefined {\n const joined = path.join(\".\");\n if (FIELD_TOOLTIPS[joined]) return FIELD_TOOLTIPS[joined];\n if (path.length > 1) {\n const withArrPrefix = [\"ARR\", ...path.slice(1)].join(\".\");\n if (FIELD_TOOLTIPS[withArrPrefix]) return FIELD_TOOLTIPS[withArrPrefix];\n const entrySearchPrefix = [\"EntrySearch\", ...path.slice(2)].join(\".\");\n if (path[1] === \"EntrySearch\" && FIELD_TOOLTIPS[entrySearchPrefix]) {\n return FIELD_TOOLTIPS[entrySearchPrefix];\n }\n const torrentPrefix = [\"Torrent\", ...path.slice(2)].join(\".\");\n if (path[1] === \"Torrent\" && FIELD_TOOLTIPS[torrentPrefix]) {\n return FIELD_TOOLTIPS[torrentPrefix];\n }\n }\n const leaf = path[path.length - 1];\n return FIELD_TOOLTIPS[leaf];\n}\n","export default \"__VITE_ASSET__CdYVzc2P__\"","export default \"__VITE_ASSET__DD$$IqcQ__\"","export default \"__VITE_ASSET__DxHRzqTL__\"","export default \"__VITE_ASSET__By16xPBV__\"","import { useCallback, useEffect, useMemo, useState, type JSX } from \"react\";\nimport { getConfig, updateConfig } from \"../api/client\";\nimport type { ConfigDocument } from \"../api/types\";\nimport { useToast } from \"../context/ToastContext\";\nimport { getTooltip } from \"../config/tooltips\";\nimport { IconImage } from \"../components/IconImage\";\nimport Select from \"react-select\";\nimport ConfigureIcon from \"../icons/gear.svg\";\n\nimport RefreshIcon from \"../icons/refresh-arrow.svg\";\nimport VisibilityIcon from \"../icons/visibility.svg\";\nimport AddIcon from \"../icons/plus.svg\";\nimport SaveIcon from \"../icons/check-mark.svg\";\nimport DeleteIcon from \"../icons/trash.svg\";\nimport CloseIcon from \"../icons/close.svg\";\n\ntype FieldType = \"text\" | \"number\" | \"checkbox\" | \"password\" | \"select\";\n\ninterface ValidationContext {\n root: ConfigDocument;\n section?: ConfigDocument | null;\n sectionKey?: string;\n}\n\ntype FieldValidator = (value: unknown, context: ValidationContext) => string | undefined;\n\ninterface FieldDefinition {\n label: string;\n path?: string[];\n type: FieldType;\n options?: string[];\n placeholder?: string;\n description?: string;\n parse?: (value: string | boolean) => unknown;\n format?: (value: unknown) => string | boolean;\n sectionName?: boolean;\n secure?: boolean;\n required?: boolean;\n validate?: FieldValidator;\n fullWidth?: boolean;\n}\n\ninterface ValidationError {\n path: string[];\n message: string;\n}\n\nconst SERVARR_SECTION_REGEX = /(rad|son|anim)arr/i;\n\n// Helper function for react-select theme-aware styles\nconst getSelectStyles = () => {\n const isDark = document.documentElement.getAttribute('data-theme') === 'dark';\n return {\n control: (base: any) => ({\n ...base,\n background: isDark ? '#0f131a' : '#ffffff',\n color: isDark ? '#eaeef2' : '#1d1d1f',\n borderColor: isDark ? '#2a2f36' : '#d2d2d7',\n minHeight: '38px',\n boxShadow: 'none',\n '&:hover': {\n borderColor: isDark ? '#3a4149' : '#b8b8bd',\n }\n }),\n menu: (base: any) => ({\n ...base,\n background: isDark ? '#0f131a' : '#ffffff',\n borderColor: isDark ? '#2a2f36' : '#d2d2d7',\n border: `1px solid ${isDark ? '#2a2f36' : '#d2d2d7'}`,\n }),\n option: (base: any, state: any) => ({\n ...base,\n background: state.isFocused\n ? (isDark ? 'rgba(122, 162, 247, 0.15)' : 'rgba(0, 113, 227, 0.1)')\n : (isDark ? '#0f131a' : '#ffffff'),\n color: isDark ? '#eaeef2' : '#1d1d1f',\n '&:active': {\n background: isDark ? 'rgba(122, 162, 247, 0.25)' : 'rgba(0, 113, 227, 0.2)',\n }\n }),\n singleValue: (base: any) => ({\n ...base,\n color: isDark ? '#eaeef2' : '#1d1d1f',\n }),\n input: (base: any) => ({\n ...base,\n color: isDark ? '#eaeef2' : '#1d1d1f',\n }),\n placeholder: (base: any) => ({\n ...base,\n color: isDark ? '#9aa3ac' : '#6e6e73',\n }),\n menuList: (base: any) => ({\n ...base,\n padding: '4px',\n }),\n };\n};\n\nconst parseList = (value: string | boolean): string[] =>\n String(value)\n .split(\",\")\n .map((part) => part.trim())\n .filter(Boolean);\n\nconst formatList = (value: unknown): string =>\n Array.isArray(value) ? value.join(\", \") : String(value ?? \"\");\n\nconst IMPORT_MODE_OPTIONS = [\"Move\", \"Copy\", \"Auto\"];\n\n\n\n\n\n\n\nconst SENTENCE_END = /(.+?[.!?])(\\s|$)/;\n\nfunction extractTooltipSummary(tooltip?: string): string | undefined {\n if (!tooltip) return undefined;\n const trimmed = tooltip.trim();\n if (!trimmed) return undefined;\n const match = trimmed.match(SENTENCE_END);\n const sentence = match ? match[1] : trimmed;\n return sentence.length > 160 ? `${sentence.slice(0, 157)}…` : sentence;\n}\n\n\n\n\n\nconst SETTINGS_FIELDS: FieldDefinition[] = [\n {\n label: \"Console Level\",\n path: [\"Settings\", \"ConsoleLevel\"],\n type: \"select\",\n options: [\"CRITICAL\", \"ERROR\", \"WARNING\", \"NOTICE\", \"INFO\", \"DEBUG\", \"TRACE\"],\n required: true,\n },\n { label: \"Logging\", path: [\"Settings\", \"Logging\"], type: \"checkbox\" },\n {\n label: \"Completed Download Folder\",\n path: [\"Settings\", \"CompletedDownloadFolder\"],\n type: \"text\",\n required: true,\n validate: (value) => {\n const folder = String(value ?? \"\").trim();\n if (!folder || folder.toUpperCase() === \"CHANGE_ME\") {\n return \"Completed Download Folder must be set to a valid path.\";\n }\n return undefined;\n },\n },\n {\n label: \"Free Space\",\n path: [\"Settings\", \"FreeSpace\"],\n type: \"text\",\n required: true,\n validate: (value) => {\n const raw = String(value ?? \"\").trim();\n if (!raw) {\n return \"Free Space must be provided.\";\n }\n if (raw === \"-1\") {\n return undefined;\n }\n if (!/^-?\\d+(\\.\\d+)?[KMGTP]?$/i.test(raw)) {\n return \"Free Space must be -1 or a number optionally suffixed with K, M, G, T, or P.\";\n }\n return undefined;\n },\n },\n {\n label: \"Free Space Folder\",\n path: [\"Settings\", \"FreeSpaceFolder\"],\n type: \"text\",\n validate: (value, context) => {\n const freeSpace = getValue(context.root, [\"Settings\", \"FreeSpace\"]);\n const requiresFolder = String(freeSpace ?? \"\").trim() !== \"-1\";\n if (!requiresFolder) {\n return undefined;\n }\n const folder = String(value ?? \"\").trim();\n if (!folder || folder.toUpperCase() === \"CHANGE_ME\") {\n return \"Free Space Folder is required when Free Space monitoring is enabled.\";\n }\n return undefined;\n },\n },\n { label: \"Auto Pause/Resume\", path: [\"Settings\", \"AutoPauseResume\"], type: \"checkbox\" },\n {\n label: \"No Internet Sleep (s)\",\n path: [\"Settings\", \"NoInternetSleepTimer\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < 0) {\n return \"No Internet Sleep must be a non-negative number.\";\n }\n return undefined;\n },\n },\n {\n label: \"Loop Sleep (s)\",\n path: [\"Settings\", \"LoopSleepTimer\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < 0) {\n return \"Loop Sleep must be a non-negative number.\";\n }\n return undefined;\n },\n },\n {\n label: \"Search Loop Delay (s)\",\n path: [\"Settings\", \"SearchLoopDelay\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < 0) {\n return \"Search Loop Delay must be a non-negative number.\";\n }\n return undefined;\n },\n },\n { label: \"Failed Category\", path: [\"Settings\", \"FailedCategory\"], type: \"text\" },\n { label: \"Recheck Category\", path: [\"Settings\", \"RecheckCategory\"], type: \"text\" },\n { label: \"Tagless\", path: [\"Settings\", \"Tagless\"], type: \"checkbox\" },\n {\n label: \"Ignore Torrents Younger Than\",\n path: [\"Settings\", \"IgnoreTorrentsYoungerThan\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < 0) {\n return \"Ignore Torrents Younger Than must be a non-negative number.\";\n }\n return undefined;\n },\n },\n {\n label: \"Ping URLs\",\n path: [\"Settings\", \"PingURLS\"],\n type: \"text\",\n parse: parseList,\n format: formatList,\n placeholder: \"one.one.one.one, dns.google.com\",\n },\n {\n label: \"FFprobe Auto Update\",\n path: [\"Settings\", \"FFprobeAutoUpdate\"],\n type: \"checkbox\",\n },\n {\n label: \"Auto Update Enabled\",\n path: [\"Settings\", \"AutoUpdateEnabled\"],\n type: \"checkbox\",\n },\n {\n label: \"Auto Update Cron\",\n path: [\"Settings\", \"AutoUpdateCron\"],\n type: \"text\",\n placeholder: \"0 3 * * 0\",\n required: true,\n validate: (value) => {\n const cron = String(value ?? \"\").trim();\n const parts = cron.split(/\\s+/).filter(Boolean);\n if (parts.length < 5 || parts.length > 6) {\n return \"Auto Update Cron must contain 5 or 6 space-separated fields.\";\n }\n return undefined;\n },\n },\n\n];\n\nconst WEB_SETTINGS_FIELDS: FieldDefinition[] = [\n {\n label: \"WebUI Host\",\n path: [\"WebUI\", \"Host\"],\n type: \"text\",\n required: true,\n validate: (value) => {\n if (!String(value ?? \"\").trim()) {\n return \"WebUI Host is required.\";\n }\n return undefined;\n },\n },\n {\n label: \"WebUI Port\",\n path: [\"WebUI\", \"Port\"],\n type: \"number\",\n validate: (value) => {\n const port = typeof value === \"number\" ? value : Number(value);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n return \"WebUI Port must be between 1 and 65535.\";\n }\n return undefined;\n },\n },\n {\n label: \"WebUI Token\",\n path: [\"WebUI\", \"Token\"],\n type: \"password\",\n secure: true,\n fullWidth: true,\n },\n { label: \"Live Arr\", path: [\"WebUI\", \"LiveArr\"], type: \"checkbox\" },\n { label: \"Group Sonarr by Series\", path: [\"WebUI\", \"GroupSonarr\"], type: \"checkbox\" },\n { label: \"Theme\", path: [\"WebUI\", \"Theme\"], type: \"select\", options: [\"Light\", \"Dark\"] },\n];\n\nconst QBIT_FIELDS: FieldDefinition[] = [\n { label: \"Disabled\", path: [\"qBit\", \"Disabled\"], type: \"checkbox\" },\n {\n label: \"Host\",\n path: [\"qBit\", \"Host\"],\n type: \"text\",\n required: true,\n validate: (value) => {\n if (!String(value ?? \"\").trim()) {\n return \"qBit Host is required.\";\n }\n return undefined;\n },\n },\n {\n label: \"Port\",\n path: [\"qBit\", \"Port\"],\n type: \"number\",\n validate: (value) => {\n const port = typeof value === \"number\" ? value : Number(value);\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n return \"qBit Port must be between 1 and 65535.\";\n }\n return undefined;\n },\n },\n { label: \"UserName\", path: [\"qBit\", \"UserName\"], type: \"text\" },\n { label: \"Password\", path: [\"qBit\", \"Password\"], type: \"password\" },\n];\n\nconst ARR_GENERAL_FIELDS: FieldDefinition[] = [\n { label: \"Display Name\", type: \"text\", placeholder: \"Sonarr-TV\", sectionName: true },\n { label: \"Managed\", path: [\"Managed\"], type: \"checkbox\" },\n {\n label: \"URI\",\n path: [\"URI\"],\n type: \"text\",\n placeholder: \"http://host:port\",\n required: true,\n validate: (value, context) => {\n const uri = String(value ?? \"\").trim();\n const managed = Boolean(getValue(context.section ?? {}, [\"Managed\"]));\n if (!managed) {\n return undefined;\n }\n if (!uri || uri.toUpperCase() === \"CHANGE_ME\") {\n return \"URI must be set to a valid URL when the instance is managed.\";\n }\n return undefined;\n },\n },\n {\n label: \"API Key\",\n path: [\"APIKey\"],\n type: \"password\",\n secure: true,\n required: true,\n validate: (value, context) => {\n const apiKey = String(value ?? \"\").trim();\n const managed = Boolean(getValue(context.section ?? {}, [\"Managed\"]));\n if (!managed) {\n return undefined;\n }\n if (!apiKey || apiKey.toUpperCase() === \"CHANGE_ME\") {\n return \"API Key must be provided when the instance is managed.\";\n }\n return undefined;\n },\n },\n {\n label: \"Category\",\n path: [\"Category\"],\n type: \"text\",\n required: true,\n validate: (value) => {\n if (!String(value ?? \"\").trim()) {\n return \"Category is required.\";\n }\n return undefined;\n },\n },\n { label: \"Re-search\", path: [\"ReSearch\"], type: \"checkbox\" },\n {\n label: \"Import Mode\",\n path: [\"importMode\"],\n type: \"select\",\n options: IMPORT_MODE_OPTIONS,\n required: true,\n },\n {\n label: \"RSS Sync Timer (min)\",\n path: [\"RssSyncTimer\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < 0) {\n return \"RSS Sync Timer must be a non-negative number.\";\n }\n return undefined;\n },\n },\n {\n label: \"Refresh Downloads Timer (min)\",\n path: [\"RefreshDownloadsTimer\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < 0) {\n return \"Refresh Downloads Timer must be a non-negative number.\";\n }\n return undefined;\n },\n },\n {\n label: \"Arr Error Codes To Blocklist\",\n path: [\"ArrErrorCodesToBlocklist\"],\n type: \"text\",\n parse: parseList,\n format: formatList,\n },\n];\n\nconst ARR_ENTRY_SEARCH_FIELDS: FieldDefinition[] = [\n {\n label: \"Search Missing\",\n path: [\"EntrySearch\", \"SearchMissing\"],\n type: \"checkbox\",\n },\n {\n label: \"Also Search Specials\",\n path: [\"EntrySearch\", \"AlsoSearchSpecials\"],\n type: \"checkbox\",\n },\n {\n label: \"Unmonitored\",\n path: [\"EntrySearch\", \"Unmonitored\"],\n type: \"checkbox\",\n },\n {\n label: \"Do Upgrade Search\",\n path: [\"EntrySearch\", \"DoUpgradeSearch\"],\n type: \"checkbox\",\n },\n {\n label: \"Quality Unmet Search\",\n path: [\"EntrySearch\", \"QualityUnmetSearch\"],\n type: \"checkbox\",\n },\n {\n label: \"Custom Format Unmet Search\",\n path: [\"EntrySearch\", \"CustomFormatUnmetSearch\"],\n type: \"checkbox\",\n },\n {\n label: \"Force Minimum Custom Format\",\n path: [\"EntrySearch\", \"ForceMinimumCustomFormat\"],\n type: \"checkbox\",\n },\n {\n label: \"Search Limit\",\n path: [\"EntrySearch\", \"SearchLimit\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < 1) {\n return \"Search Limit must be at least 1.\";\n }\n return undefined;\n },\n },\n {\n label: \"Search By Year\",\n path: [\"EntrySearch\", \"SearchByYear\"],\n type: \"checkbox\",\n },\n {\n label: \"Search In Reverse\",\n path: [\"EntrySearch\", \"SearchInReverse\"],\n type: \"checkbox\",\n },\n {\n label: \"Search Requests Every (s)\",\n path: [\"EntrySearch\", \"SearchRequestsEvery\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < 1) {\n return \"Search Requests Every must be at least 1 second.\";\n }\n return undefined;\n },\n },\n {\n label: \"Search Again On Completion\",\n path: [\"EntrySearch\", \"SearchAgainOnSearchCompletion\"],\n type: \"checkbox\",\n },\n {\n label: \"Use Temp Profile For Missing\",\n path: [\"EntrySearch\", \"UseTempForMissing\"],\n type: \"checkbox\",\n },\n {\n label: \"Keep Temp Profile\",\n path: [\"EntrySearch\", \"KeepTempProfile\"],\n type: \"checkbox\",\n },\n {\n label: \"Main Quality Profile\",\n path: [\"EntrySearch\", \"MainQualityProfile\"],\n type: \"text\",\n parse: parseList,\n format: formatList,\n },\n {\n label: \"Temp Quality Profile\",\n path: [\"EntrySearch\", \"TempQualityProfile\"],\n type: \"text\",\n parse: parseList,\n format: formatList,\n },\n {\n label: \"Search By Series\",\n path: [\"EntrySearch\", \"SearchBySeries\"],\n type: \"select\",\n options: [\"smart\", \"true\", \"false\"],\n description: \"smart = auto (series search for multiple episodes, episode search for single), true = always series search, false = always episode search\",\n format: (value: unknown) => {\n // Convert boolean or string to string for display\n if (typeof value === \"boolean\") {\n return value ? \"true\" : \"false\";\n }\n return String(value || \"smart\");\n },\n parse: (value: string | boolean) => {\n // Keep as string for config - backend will handle parsing\n const str = String(value);\n if (str === \"true\" || str === \"false\") {\n return str;\n }\n return \"smart\";\n },\n },\n {\n label: \"Prioritize Today's Releases\",\n path: [\"EntrySearch\", \"PrioritizeTodaysReleases\"],\n type: \"checkbox\",\n },\n];\n\nconst ARR_ENTRY_SEARCH_OMBI_FIELDS: FieldDefinition[] = [\n {\n label: \"Search Ombi Requests\",\n path: [\"EntrySearch\", \"Ombi\", \"SearchOmbiRequests\"],\n type: \"checkbox\",\n },\n {\n label: \"Ombi URI\",\n path: [\"EntrySearch\", \"Ombi\", \"OmbiURI\"],\n type: \"text\",\n placeholder: \"http://host:port\",\n },\n {\n label: \"Ombi API Key\",\n path: [\"EntrySearch\", \"Ombi\", \"OmbiAPIKey\"],\n type: \"password\",\n },\n {\n label: \"Approved Only\",\n path: [\"EntrySearch\", \"Ombi\", \"ApprovedOnly\"],\n type: \"checkbox\",\n },\n {\n label: \"Is 4K Instance\",\n path: [\"EntrySearch\", \"Ombi\", \"Is4K\"],\n type: \"checkbox\",\n },\n];\n\nconst ARR_ENTRY_SEARCH_OVERSEERR_FIELDS: FieldDefinition[] = [\n {\n label: \"Search Overseerr Requests\",\n path: [\"EntrySearch\", \"Overseerr\", \"SearchOverseerrRequests\"],\n type: \"checkbox\",\n },\n {\n label: \"Overseerr URI\",\n path: [\"EntrySearch\", \"Overseerr\", \"OverseerrURI\"],\n type: \"text\",\n placeholder: \"http://host:port\",\n },\n {\n label: \"Overseerr API Key\",\n path: [\"EntrySearch\", \"Overseerr\", \"OverseerrAPIKey\"],\n type: \"password\",\n },\n {\n label: \"Approved Only\",\n path: [\"EntrySearch\", \"Overseerr\", \"ApprovedOnly\"],\n type: \"checkbox\",\n },\n {\n label: \"Is 4K Instance\",\n path: [\"EntrySearch\", \"Overseerr\", \"Is4K\"],\n type: \"checkbox\",\n },\n];\n\nconst ARR_TORRENT_FIELDS: FieldDefinition[] = [\n {\n label: \"Case Sensitive Matches\",\n path: [\"Torrent\", \"CaseSensitiveMatches\"],\n type: \"checkbox\",\n },\n {\n label: \"Folder Exclusion Regex\",\n path: [\"Torrent\", \"FolderExclusionRegex\"],\n type: \"text\",\n parse: parseList,\n format: formatList,\n },\n {\n label: \"File Name Exclusion Regex\",\n path: [\"Torrent\", \"FileNameExclusionRegex\"],\n type: \"text\",\n parse: parseList,\n format: formatList,\n },\n {\n label: \"File Extension Allowlist\",\n path: [\"Torrent\", \"FileExtensionAllowlist\"],\n type: \"text\",\n parse: parseList,\n format: formatList,\n },\n {\n label: \"Auto Delete\",\n path: [\"Torrent\", \"AutoDelete\"],\n type: \"checkbox\",\n },\n {\n label: \"Ignore Torrents Younger Than (s)\",\n path: [\"Torrent\", \"IgnoreTorrentsYoungerThan\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < 0) {\n return \"Ignore Torrents Younger Than must be a non-negative number.\";\n }\n return undefined;\n },\n },\n {\n label: \"Maximum ETA (s)\",\n path: [\"Torrent\", \"MaximumETA\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < -1) {\n return \"Maximum ETA must be -1 or a non-negative number.\";\n }\n return undefined;\n },\n },\n {\n label: \"Maximum Deletable Percentage\",\n path: [\"Torrent\", \"MaximumDeletablePercentage\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < 0 || num > 100) {\n return \"Maximum Deletable Percentage must be between 0 and 100.\";\n }\n return undefined;\n },\n },\n {\n label: \"Do Not Remove Slow\",\n path: [\"Torrent\", \"DoNotRemoveSlow\"],\n type: \"checkbox\",\n },\n {\n label: \"Stalled Delay (min)\",\n path: [\"Torrent\", \"StalledDelay\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < 0) {\n return \"Stalled Delay must be a non-negative number.\";\n }\n return undefined;\n },\n },\n {\n label: \"Re-search Stalled\",\n path: [\"Torrent\", \"ReSearchStalled\"],\n type: \"checkbox\",\n },\n {\n label: \"Remove Dead Trackers\",\n path: [\"Torrent\", \"RemoveDeadTrackers\"],\n type: \"checkbox\",\n },\n {\n label: \"Remove Tracker Messages\",\n path: [\"Torrent\", \"RemoveTrackerWithMessage\"],\n type: \"text\",\n parse: parseList,\n format: formatList,\n },\n];\n\nconst ARR_SEEDING_FIELDS: FieldDefinition[] = [\n {\n label: \"Download Rate Limit Per Torrent\",\n path: [\"Torrent\", \"SeedingMode\", \"DownloadRateLimitPerTorrent\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < -1) {\n return \"Download Rate Limit must be -1 or greater.\";\n }\n return undefined;\n },\n },\n {\n label: \"Upload Rate Limit Per Torrent\",\n path: [\"Torrent\", \"SeedingMode\", \"UploadRateLimitPerTorrent\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < -1) {\n return \"Upload Rate Limit must be -1 or greater.\";\n }\n return undefined;\n },\n },\n {\n label: \"Max Upload Ratio\",\n path: [\"Torrent\", \"SeedingMode\", \"MaxUploadRatio\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < -1) {\n return \"Max Upload Ratio must be -1 or greater.\";\n }\n return undefined;\n },\n },\n {\n label: \"Max Seeding Time (s)\",\n path: [\"Torrent\", \"SeedingMode\", \"MaxSeedingTime\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < -1) {\n return \"Max Seeding Time must be -1 or greater.\";\n }\n return undefined;\n },\n },\n {\n label: \"Remove Torrent (policy)\",\n path: [\"Torrent\", \"SeedingMode\", \"RemoveTorrent\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num)) {\n return \"Remove Torrent policy must be a number.\";\n }\n if (num === -1) {\n return undefined;\n }\n if (![1, 2, 3, 4].includes(num)) {\n return \"Remove Torrent policy must be -1, 1, 2, 3, or 4.\";\n }\n return undefined;\n },\n },\n];\n\nconst ARR_TRACKER_FIELDS: FieldDefinition[] = [\n { label: \"Name\", path: [\"Name\"], type: \"text\", required: true },\n { label: \"URI\", path: [\"URI\"], type: \"text\", required: true },\n {\n label: \"Priority\",\n path: [\"Priority\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < 0) {\n return \"Priority must be a non-negative number.\";\n }\n return undefined;\n },\n },\n {\n label: \"Maximum ETA\",\n path: [\"MaximumETA\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < -1) {\n return \"Maximum ETA must be -1 or a non-negative number.\";\n }\n return undefined;\n },\n },\n {\n label: \"Download Rate Limit\",\n path: [\"DownloadRateLimit\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < -1) {\n return \"Download Rate Limit must be -1 or greater.\";\n }\n return undefined;\n },\n },\n {\n label: \"Upload Rate Limit\",\n path: [\"UploadRateLimit\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < -1) {\n return \"Upload Rate Limit must be -1 or greater.\";\n }\n return undefined;\n },\n },\n {\n label: \"Max Upload Ratio\",\n path: [\"MaxUploadRatio\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < -1) {\n return \"Max Upload Ratio must be -1 or greater.\";\n }\n return undefined;\n },\n },\n {\n label: \"Max Seeding Time\",\n path: [\"MaxSeedingTime\"],\n type: \"number\",\n validate: (value) => {\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num) || num < -1) {\n return \"Max Seeding Time must be -1 or greater.\";\n }\n return undefined;\n },\n },\n {\n label: \"Add Tracker If Missing\",\n path: [\"AddTrackerIfMissing\"],\n type: \"checkbox\",\n },\n { label: \"Remove If Exists\", path: [\"RemoveIfExists\"], type: \"checkbox\" },\n { label: \"Super Seed Mode\", path: [\"SuperSeedMode\"], type: \"checkbox\" },\n {\n label: \"Add Tags\",\n path: [\"AddTags\"],\n type: \"text\",\n parse: parseList,\n format: formatList,\n },\n];\n\nfunction getArrFieldSets(arrKey: string) {\n const lower = arrKey.toLowerCase();\n const isSonarr = lower.includes(\"sonarr\");\n const generalFields = [...ARR_GENERAL_FIELDS];\n const entryFields = ARR_ENTRY_SEARCH_FIELDS.filter((field) => {\n if (!field.path) {\n return true;\n }\n const joined = field.path.join(\".\");\n if (!isSonarr) {\n if (\n joined === \"EntrySearch.AlsoSearchSpecials\" ||\n joined === \"EntrySearch.SearchBySeries\" ||\n joined === \"EntrySearch.PrioritizeTodaysReleases\"\n ) {\n return false;\n }\n }\n return true;\n });\n const entryOmbiFields = [...ARR_ENTRY_SEARCH_OMBI_FIELDS];\n const entryOverseerrFields = [...ARR_ENTRY_SEARCH_OVERSEERR_FIELDS];\n const torrentFields = [...ARR_TORRENT_FIELDS];\n const seedingFields = [...ARR_SEEDING_FIELDS];\n const trackerFields = [...ARR_TRACKER_FIELDS];\n return {\n generalFields,\n entryFields,\n entryOmbiFields,\n entryOverseerrFields,\n torrentFields,\n seedingFields,\n trackerFields,\n };\n}\n\nfunction isEmptyValue(value: unknown): boolean {\n return (\n value === null ||\n value === undefined ||\n (typeof value === \"string\" && value.trim() === \"\") ||\n (Array.isArray(value) && value.length === 0)\n );\n}\n\nfunction basicValidation(def: FieldDefinition, value: unknown): string | undefined {\n const label = def.label;\n const isRequired = def.required ?? (def.type === \"number\" || def.type === \"select\");\n switch (def.type) {\n case \"text\":\n case \"password\": {\n if (!isRequired) {\n return undefined;\n }\n if (isEmptyValue(value)) {\n return `${label} is required.`;\n }\n return undefined;\n }\n case \"number\": {\n if (value === null || value === undefined || value === \"\") {\n return isRequired ? `${label} is required.` : undefined;\n }\n const num = typeof value === \"number\" ? value : Number(value);\n if (!Number.isFinite(num)) {\n return `${label} must be a valid number.`;\n }\n return undefined;\n }\n case \"checkbox\": {\n if (value === null || value === undefined) {\n return isRequired ? `${label} is required.` : undefined;\n }\n if (typeof value !== \"boolean\") {\n return `${label} must be true or false.`;\n }\n return undefined;\n }\n case \"select\": {\n if (isEmptyValue(value)) {\n return `${label} is required.`;\n }\n if (typeof value !== \"string\") {\n return `${label} must be selected.`;\n }\n if (def.options && !def.options.includes(value)) {\n return `${label} must be one of ${def.options.join(\", \")}.`;\n }\n return undefined;\n }\n default:\n return undefined;\n }\n}\n\nfunction validateFieldGroup(\n errors: ValidationError[],\n fields: FieldDefinition[],\n state: ConfigDocument | null,\n basePath: string[],\n context: ValidationContext\n): void {\n if (!state) return;\n for (const field of fields) {\n if (field.sectionName) {\n continue;\n }\n const pathSegments = field.path ?? [];\n const value = pathSegments.length\n ? getValue(state as ConfigDocument, pathSegments)\n : undefined;\n const fullPath = [...basePath, ...pathSegments];\n const baseError = basicValidation(field, value);\n if (baseError) {\n errors.push({ path: fullPath, message: baseError });\n continue;\n }\n if (field.validate) {\n const customError = field.validate(value, context);\n if (customError) {\n errors.push({ path: fullPath, message: customError });\n }\n }\n }\n}\n\nfunction validateFormState(formState: ConfigDocument | null): ValidationError[] {\n if (!formState) return [];\n const errors: ValidationError[] = [];\n const rootContext: ValidationContext = { root: formState };\n validateFieldGroup(errors, SETTINGS_FIELDS, formState, [], rootContext);\n validateFieldGroup(errors, WEB_SETTINGS_FIELDS, formState, [], rootContext);\n validateFieldGroup(errors, QBIT_FIELDS, formState, [], rootContext);\n for (const [key, value] of Object.entries(formState)) {\n if (!SERVARR_SECTION_REGEX.test(key) || !value || typeof value !== \"object\") {\n continue;\n }\n const section = value as ConfigDocument;\n const sectionContext: ValidationContext = { root: formState, section, sectionKey: key };\n const fieldSets = getArrFieldSets(key);\n validateFieldGroup(errors, fieldSets.generalFields, section, [key], sectionContext);\n validateFieldGroup(errors, fieldSets.entryFields, section, [key], sectionContext);\n validateFieldGroup(errors, fieldSets.entryOmbiFields, section, [key], sectionContext);\n validateFieldGroup(errors, fieldSets.entryOverseerrFields, section, [key], sectionContext);\n validateFieldGroup(errors, fieldSets.torrentFields, section, [key], sectionContext);\n validateFieldGroup(errors, fieldSets.seedingFields, section, [key], sectionContext);\n }\n return errors;\n}\n\nfunction cloneConfig(config: ConfigDocument | null): ConfigDocument | null {\n return config ? JSON.parse(JSON.stringify(config)) : null;\n}\n\nfunction getValue(doc: ConfigDocument | null, path: string[]): unknown {\n if (!doc) return undefined;\n let cur: unknown = doc;\n for (const key of path) {\n if (cur == null || typeof cur !== \"object\") return undefined;\n cur = (cur as Record<string, unknown>)[key];\n }\n return cur;\n}\n\nfunction setValue(\n doc: ConfigDocument,\n path: string[],\n value: unknown\n): void {\n let cur: Record<string, unknown> = doc;\n path.forEach((key, idx) => {\n if (idx === path.length - 1) {\n cur[key] = value;\n } else {\n if (typeof cur[key] !== \"object\" || cur[key] === null) {\n cur[key] = {};\n }\n cur = cur[key] as Record<string, unknown>;\n }\n });\n}\n\nfunction flatten(doc: ConfigDocument, prefix: string[] = []): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(doc)) {\n const nextPath = [...prefix, key];\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n Object.assign(result, flatten(value as ConfigDocument, nextPath));\n } else {\n result[nextPath.join(\".\")] = value;\n }\n }\n return result;\n}\n\nfunction ensureArrDefaults(type: string): ConfigDocument {\n const lowerType = type.toLowerCase();\n const isSonarr = lowerType.includes(\"sonarr\");\n const isRadarr = lowerType.includes(\"radarr\");\n const is4k = lowerType.includes(\"4k\");\n const arrErrorCodes = isRadarr\n ? [\n \"Not an upgrade for existing movie file(s)\",\n \"Not a preferred word upgrade for existing movie file(s)\",\n \"Unable to determine if file is a sample\",\n ]\n : [\n \"Not an upgrade for existing episode file(s)\",\n \"Not a preferred word upgrade for existing episode file(s)\",\n \"Unable to determine if file is a sample\",\n ];\n\n const entrySearch: Record<string, unknown> = {\n SearchMissing: true,\n Unmonitored: false,\n SearchLimit: 5,\n SearchByYear: true,\n SearchInReverse: false,\n SearchRequestsEvery: 300,\n DoUpgradeSearch: false,\n QualityUnmetSearch: false,\n CustomFormatUnmetSearch: false,\n ForceMinimumCustomFormat: false,\n SearchAgainOnSearchCompletion: true,\n UseTempForMissing: false,\n KeepTempProfile: false,\n MainQualityProfile: [],\n TempQualityProfile: [],\n };\n\n if (isSonarr) {\n entrySearch.AlsoSearchSpecials = false;\n entrySearch.SearchBySeries = \"smart\";\n entrySearch.PrioritizeTodaysReleases = true;\n }\n\n entrySearch.Ombi = {\n SearchOmbiRequests: false,\n OmbiURI: \"CHANGE_ME\",\n OmbiAPIKey: \"CHANGE_ME\",\n ApprovedOnly: true,\n Is4K: is4k,\n };\n entrySearch.Overseerr = {\n SearchOverseerrRequests: false,\n OverseerrURI: \"CHANGE_ME\",\n OverseerrAPIKey: \"CHANGE_ME\",\n ApprovedOnly: true,\n Is4K: is4k,\n };\n\n const torrent: Record<string, unknown> = {\n CaseSensitiveMatches: false,\n FolderExclusionRegex: [\n \"\\\\bextras?\\\\b\",\n \"\\\\bfeaturettes?\\\\b\",\n \"\\\\bsamples?\\\\b\",\n \"\\\\bscreens?\\\\b\",\n \"\\\\bnc(ed|op)?(\\\\\\\\d+)?\\\\b\",\n ],\n FileNameExclusionRegex: [\n \"\\\\bncop\\\\\\\\d+?\\\\b\",\n \"\\\\bnced\\\\\\\\d+?\\\\b\",\n \"\\\\bsample\\\\b\",\n \"brarbg.com\\\\b\",\n \"\\\\btrailer\\\\b\",\n \"music video\",\n \"comandotorrents.com\",\n ],\n FileExtensionAllowlist: [\".mp4\", \".mkv\", \".sub\", \".ass\", \".srt\", \".!qB\", \".parts\"],\n AutoDelete: false,\n IgnoreTorrentsYoungerThan: 600,\n MaximumETA: 604800,\n MaximumDeletablePercentage: 0.99,\n DoNotRemoveSlow: true,\n StalledDelay: 15,\n ReSearchStalled: false,\n RemoveDeadTrackers: false,\n RemoveTrackerWithMessage: [\n \"skipping tracker announce (unreachable)\",\n \"No such host is known\",\n \"unsupported URL protocol\",\n \"info hash is not authorized with this tracker\",\n ],\n SeedingMode: {\n DownloadRateLimitPerTorrent: -1,\n UploadRateLimitPerTorrent: -1,\n MaxUploadRatio: -1,\n MaxSeedingTime: -1,\n RemoveTorrent: -1,\n },\n };\n\n return {\n Managed: true,\n URI: \"CHANGE_ME\",\n APIKey: \"CHANGE_ME\",\n Category: type,\n ReSearch: true,\n importMode: \"Auto\",\n RssSyncTimer: 5,\n RefreshDownloadsTimer: 5,\n ArrErrorCodesToBlocklist: arrErrorCodes,\n EntrySearch: entrySearch as ConfigDocument,\n Torrent: torrent as ConfigDocument,\n } as ConfigDocument;\n}\n\ninterface ConfigViewProps {\n onDirtyChange?: (dirty: boolean) => void;\n}\n\nexport function ConfigView(props?: ConfigViewProps): JSX.Element {\n const { onDirtyChange } = props ?? {};\n const { push } = useToast();\n const [originalConfig, setOriginalConfig] = useState<ConfigDocument | null>(\n null\n );\n const [formState, setFormState] = useState<ConfigDocument | null>(null);\n const [loading, setLoading] = useState(false);\n const [saving, setSaving] = useState(false);\n\n const loadConfig = useCallback(async () => {\n setLoading(true);\n try {\n const config = await getConfig();\n setOriginalConfig(config);\n setFormState(cloneConfig(config));\n } catch (error) {\n push(\n error instanceof Error\n ? error.message\n : \"Failed to load configuration\",\n \"error\"\n );\n } finally {\n setLoading(false);\n }\n }, [push]);\n\n useEffect(() => {\n void loadConfig();\n }, [loadConfig]);\n\n const handleFieldChange = useCallback(\n (path: string[], def: FieldDefinition, raw: unknown) => {\n if (!formState) return;\n const next = cloneConfig(formState) ?? {};\n const parsed =\n def.parse?.(raw as string | boolean) ??\n (def.type === \"number\"\n ? Number(raw) || 0\n : def.type === \"checkbox\"\n ? Boolean(raw)\n : raw);\n setValue(next, path, parsed);\n setFormState(next);\n },\n [formState]\n );\n\n const arrSections = useMemo(() => {\n if (!formState) return [] as Array<[string, ConfigDocument]>;\n return Object.entries(formState).filter(([key, value]) =>\n SERVARR_SECTION_REGEX.test(key) && value && typeof value === \"object\"\n ) as Array<[string, ConfigDocument]>;\n }, [formState]);\n const groupedArrSections = useMemo(() => {\n const groups: Array<{\n label: string;\n type: \"radarr\" | \"sonarr\" | \"other\";\n items: Array<[string, ConfigDocument]>;\n }> = [];\n const sorted = [...arrSections].sort((a, b) =>\n a[0].localeCompare(b[0], undefined, { numeric: true, sensitivity: \"base\" })\n );\n const radarr: Array<[string, ConfigDocument]> = [];\n const sonarr: Array<[string, ConfigDocument]> = [];\n const others: Array<[string, ConfigDocument]> = [];\n for (const entry of sorted) {\n const [key] = entry;\n const keyLower = key.toLowerCase();\n if (keyLower.startsWith(\"radarr\")) {\n radarr.push(entry);\n } else if (keyLower.startsWith(\"sonarr\")) {\n sonarr.push(entry);\n } else {\n others.push(entry);\n }\n }\n if (radarr.length) {\n groups.push({ label: \"Radarr Instances\", type: \"radarr\", items: radarr });\n }\n if (sonarr.length) {\n groups.push({ label: \"Sonarr Instances\", type: \"sonarr\", items: sonarr });\n }\n if (others.length) {\n groups.push({ label: \"Other Instances\", type: \"other\", items: others });\n }\n return groups;\n }, [arrSections]);\n const [activeArrKey, setActiveArrKey] = useState<string | null>(null);\n const [isSettingsOpen, setSettingsOpen] = useState(false);\n const [isWebSettingsOpen, setWebSettingsOpen] = useState(false);\n const [isQbitOpen, setQbitOpen] = useState(false);\n const [isDirty, setDirty] = useState(false);\n\n useEffect(() => {\n if (!formState || !originalConfig) {\n setDirty(false);\n return;\n }\n const flattenedOriginal = flatten(originalConfig);\n const flattenedCurrent = flatten(formState);\n\n let dirty = false;\n for (const [key, value] of Object.entries(flattenedCurrent)) {\n const originalValue = flattenedOriginal[key];\n const changed =\n Array.isArray(value) || Array.isArray(originalValue)\n ? JSON.stringify(value ?? []) !== JSON.stringify(originalValue ?? [])\n : value !== originalValue;\n if (changed) {\n dirty = true;\n break;\n }\n }\n if (!dirty) {\n for (const key of Object.keys(flattenedOriginal)) {\n if (!(key in flattenedCurrent)) {\n dirty = true;\n break;\n }\n }\n }\n setDirty(dirty);\n }, [formState, originalConfig]);\n\n useEffect(() => {\n onDirtyChange?.(isDirty);\n }, [isDirty, onDirtyChange]);\n\n useEffect(() => {\n if (!isDirty) return;\n const handleBeforeUnload = (event: BeforeUnloadEvent) => {\n event.preventDefault();\n event.returnValue = \"\";\n };\n window.addEventListener(\"beforeunload\", handleBeforeUnload);\n return () => {\n window.removeEventListener(\"beforeunload\", handleBeforeUnload);\n };\n }, [isDirty]);\n\n useEffect(() => {\n return () => {\n onDirtyChange?.(false);\n };\n }, [onDirtyChange]);\n\n useEffect(() => {\n const anyModalOpen = Boolean(activeArrKey || isSettingsOpen || isWebSettingsOpen || isQbitOpen);\n if (!anyModalOpen) return;\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n setActiveArrKey(null);\n setSettingsOpen(false);\n setQbitOpen(false);\n }\n };\n window.addEventListener(\"keydown\", handleKeyDown);\n const { style } = document.body;\n const originalOverflow = style.overflow;\n style.overflow = \"hidden\";\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n style.overflow = originalOverflow;\n };\n }, [activeArrKey, isSettingsOpen, isWebSettingsOpen, isQbitOpen]);\n\n useEffect(() => {\n if (!activeArrKey) return;\n if (!arrSections.some(([key]) => key === activeArrKey)) {\n setActiveArrKey(null);\n }\n }, [activeArrKey, arrSections]);\n\n const addArrInstance = useCallback(\n (type: \"radarr\" | \"sonarr\") => {\n if (!formState) return;\n const prefix = type.charAt(0).toUpperCase() + type.slice(1);\n let index = 1;\n let key = `${prefix}-${index}`;\n while (formState[key]) {\n index += 1;\n key = `${prefix}-${index}`;\n }\n const next = cloneConfig(formState) ?? {};\n const defaults = ensureArrDefaults(type);\n if (defaults && typeof defaults === \"object\") {\n (defaults as Record<string, unknown>).Name = key;\n }\n next[key] = defaults;\n setFormState(next);\n },\n [formState]\n );\n const deleteArrInstance = useCallback(\n (key: string) => {\n if (!formState) return;\n const keyLower = key.toLowerCase();\n if (!keyLower.startsWith(\"radarr\") && !keyLower.startsWith(\"sonarr\")) {\n return;\n }\n const confirmed = window.confirm(\n `Delete ${key}? This action cannot be undone.`\n );\n if (!confirmed) {\n return;\n }\n const next = cloneConfig(formState) ?? {};\n if (!(key in next)) {\n return;\n }\n delete next[key];\n setFormState(next);\n if (activeArrKey === key) {\n setActiveArrKey(null);\n }\n push(`${key} removed`, \"success\");\n },\n [formState, activeArrKey, push]\n );\n\n const handleRenameSection = useCallback(\n (oldName: string, rawNewName: string) => {\n if (!formState) return;\n const newName = rawNewName.trim();\n if (!newName || newName === oldName) {\n return;\n }\n if (formState[newName]) {\n push(`An instance named \"${newName}\" already exists`, \"error\");\n return;\n }\n const next = cloneConfig(formState) ?? {};\n const section = next[oldName];\n delete next[oldName];\n next[newName] = section;\n if (section && typeof section === \"object\") {\n (section as Record<string, unknown>).Name = newName;\n }\n setFormState(next);\n if (activeArrKey === oldName) {\n setActiveArrKey(newName);\n }\n },\n [formState, push, activeArrKey]\n );\n\n const handleSubmit = useCallback(async () => {\n if (!formState) return;\n setSaving(true);\n try {\n const validationErrors = validateFormState(formState);\n if (validationErrors.length) {\n const formatted = validationErrors\n .map((error) => `${error.path.join(\".\")}: ${error.message}`)\n .join(\"\\n\");\n const message =\n validationErrors.length === 1\n ? formatted\n : `Please resolve the following issues:\\n${formatted}`;\n push(message, \"error\");\n setSaving(false);\n return;\n }\n const flattenedOriginal = flatten(originalConfig ?? {});\n const flattenedCurrent = flatten(formState);\n const changes: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(flattenedCurrent)) {\n const originalValue = flattenedOriginal[key];\n const changed =\n Array.isArray(value) || Array.isArray(originalValue)\n ? JSON.stringify(value ?? []) !==\n JSON.stringify(originalValue ?? [])\n : value !== originalValue;\n if (changed) {\n changes[key] = value;\n }\n }\n for (const key of Object.keys(flattenedOriginal)) {\n if (!(key in flattenedCurrent)) {\n changes[key] = null;\n }\n }\n if (Object.keys(changes).length === 0) {\n push(\"No changes detected\", \"info\");\n setSaving(false);\n return;\n }\n await updateConfig({ changes });\n push(\"Configuration saved\", \"success\");\n await loadConfig();\n } catch (error) {\n push(\n error instanceof Error\n ? error.message\n : \"Failed to update configuration\",\n \"error\"\n );\n } finally {\n setSaving(false);\n }\n }, [formState, originalConfig, loadConfig, push]);\n\n if (loading || !formState) {\n return (\n <section className=\"card\">\n <div className=\"card-header\">Config</div>\n <div className=\"card-body\">\n <div className=\"loading\">\n <span className=\"spinner\" /> Loading configuration…\n </div>\n </div>\n </section>\n );\n }\n\n return (\n <>\n <section className=\"card\">\n <div className=\"card-header\">Config</div>\n <div className=\"card-body config-layout\">\n <section className=\"config-arr-group\">\n <details className=\"config-arr-group__details\" open>\n <summary>\n <span>Core Configuration</span>\n </summary>\n <div className=\"config-grid\">\n <ConfigSummaryCard\n title=\"Settings\"\n description=\"Core application configuration\"\n onConfigure={() => setSettingsOpen(true)}\n />\n <ConfigSummaryCard\n title=\"Web Settings\"\n description=\"Web UI configuration\"\n onConfigure={() => setWebSettingsOpen(true)}\n />\n <ConfigSummaryCard\n title=\"qBit\"\n description=\"qBittorrent connection details\"\n onConfigure={() => setQbitOpen(true)}\n />\n </div>\n </details>\n </section>\n {groupedArrSections.length ? (\n <div className=\"config-arr-groups\">\n {groupedArrSections.map((group) => (\n <section className=\"config-arr-group\" key={group.type}>\n <details className=\"config-arr-group__details\" open>\n <summary>\n <span>{group.label}</span>\n <span className=\"config-arr-group__count\">\n {group.items.length}\n </span>\n {(group.type === \"radarr\" || group.type === \"sonarr\") && (\n <button\n className=\"btn small\"\n type=\"button\"\n onClick={() => addArrInstance(group.type as \"radarr\" | \"sonarr\")}\n >\n <IconImage src={AddIcon} />\n Add Instance\n </button>\n )}\n </summary>\n <div className=\"config-arr-grid\">\n {group.items.map(([key, value]) => {\n const uri = getValue(value as ConfigDocument, [\"URI\"]);\n const category = getValue(value as ConfigDocument, [\"Category\"]);\n const managed = getValue(value as ConfigDocument, [\"Managed\"]);\n const canDelete = group.type === \"radarr\" || group.type === \"sonarr\";\n return (\n <div className=\"card config-card config-arr-card\" key={key}>\n <div className=\"card-header\">{key}</div>\n <div className=\"card-body\">\n <dl className=\"config-arr-summary\">\n <div className=\"config-arr-summary__item\">\n <dt>Managed</dt>\n <dd>{managed ? \"Enabled\" : \"Disabled\"}</dd>\n </div>\n <div className=\"config-arr-summary__item\">\n <dt>Category</dt>\n <dd>{category ? String(category) : \"-\"}</dd>\n </div>\n <div className=\"config-arr-summary__item\">\n <dt>URI</dt>\n <dd className=\"config-arr-summary__uri\">\n {uri ? String(uri) : \"-\"}\n </dd>\n </div>\n </dl>\n <div className=\"config-arr-actions\">\n {canDelete ? (\n <button\n className=\"btn danger\"\n type=\"button\"\n onClick={() => deleteArrInstance(key)}\n >\n <IconImage src={DeleteIcon} />\n Delete\n </button>\n ) : null}\n <button\n className=\"btn primary\"\n type=\"button\"\n onClick={() => setActiveArrKey(key)}\n >\n <IconImage src={ConfigureIcon} />\n Configure\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n </details>\n </section>\n ))}\n </div>\n ) : null}\n <div className=\"config-footer\">\n <button\n className=\"btn primary\"\n onClick={() => void handleSubmit()}\n disabled={saving}\n >\n <IconImage src={SaveIcon} />\n Save + Live Reload\n </button>\n </div>\n </div>\n </section>\n {activeArrKey && formState ? (\n <ArrInstanceModal\n keyName={activeArrKey}\n state={(formState[activeArrKey] as ConfigDocument) ?? null}\n onChange={handleFieldChange}\n onRename={handleRenameSection}\n onClose={() => setActiveArrKey(null)}\n />\n ) : null}\n {isSettingsOpen ? (\n <SimpleConfigModal\n title=\"Settings\"\n fields={SETTINGS_FIELDS}\n state={formState}\n basePath={[]}\n onChange={handleFieldChange}\n onClose={() => setSettingsOpen(false)}\n />\n ) : null}\n {isWebSettingsOpen ? (\n <SimpleConfigModal\n title=\"Web Settings\"\n fields={WEB_SETTINGS_FIELDS}\n state={formState}\n basePath={[]}\n onChange={handleFieldChange}\n onClose={() => setWebSettingsOpen(false)}\n />\n ) : null}\n {isQbitOpen ? (\n <SimpleConfigModal\n title=\"qBit\"\n fields={QBIT_FIELDS}\n state={formState}\n basePath={[]}\n onChange={handleFieldChange}\n onClose={() => setQbitOpen(false)}\n />\n ) : null}\n </>\n );\n}\n\ninterface ConfigSummaryCardProps {\n title: string;\n description: string;\n onConfigure: () => void;\n}\n\nfunction ConfigSummaryCard({\n title,\n description,\n onConfigure,\n}: ConfigSummaryCardProps): JSX.Element {\n return (\n <div className=\"card config-card\">\n <div className=\"card-header\">{title}</div>\n <div className=\"card-body config-summary-card\">\n <p>{description}</p>\n <div className=\"config-arr-actions\">\n <button className=\"btn primary\" type=\"button\" onClick={onConfigure}>\n <IconImage src={ConfigureIcon} />\n Configure\n </button>\n </div>\n </div>\n </div>\n );\n}\n\n\n\ninterface FieldGroupProps {\n title: string | null;\n fields: FieldDefinition[];\n state: ConfigDocument | ConfigDocument[keyof ConfigDocument] | null;\n basePath: string[];\n onChange: (path: string[], def: FieldDefinition, value: unknown) => void;\n onRenameSection?: (oldName: string, newName: string) => void;\n defaultOpen?: boolean;\n}\n\nfunction FieldGroup({\n title,\n fields,\n state,\n basePath,\n onChange,\n onRenameSection,\n defaultOpen = false,\n}: FieldGroupProps): JSX.Element {\n const sectionName = basePath[0] ?? \"\";\n\n if (title === \"Trackers\") {\n const trackers = (getValue(state as ConfigDocument, [\"Torrent\", \"Trackers\"]) ?? []) as ConfigDocument[];\n const handleAddTracker = () => {\n const nextTrackers = [\n ...trackers,\n {\n Url: \"\",\n RemoveIfExists: false,\n SuperSeedMode: false,\n AddTags: [],\n },\n ];\n onChange([...basePath, \"Torrent\", \"Trackers\"], {} as FieldDefinition, nextTrackers);\n };\n const handleDeleteTracker = (index: number) => {\n const nextTrackers = [...trackers];\n nextTrackers.splice(index, 1);\n onChange([...basePath, \"Torrent\", \"Trackers\"], {} as FieldDefinition, nextTrackers);\n };\n return (\n <details className=\"config-section\" open={defaultOpen}>\n <summary>{title}</summary>\n <div className=\"config-section__body\">\n <div className=\"tracker-grid\">\n {trackers.map((tracker, index) => (\n <TrackerCard\n key={index}\n fields={fields}\n state={tracker}\n basePath={[...basePath, \"Torrent\", \"Trackers\", String(index)]}\n onChange={onChange}\n onDelete={() => handleDeleteTracker(index)}\n />\n ))}\n </div>\n <div className=\"config-actions\">\n <button className=\"btn\" type=\"button\" onClick={handleAddTracker}>\n <IconImage src={AddIcon} />\n Add Tracker\n </button>\n </div>\n </div>\n </details>\n );\n }\n\n const renderedFields = fields.map((field) => {\n if (field.sectionName) {\n if (!sectionName) {\n return null;\n }\n const tooltip = getTooltip([sectionName]);\n return (\n <SectionNameField\n key={`${sectionName}.__name`}\n label={field.label}\n tooltip={tooltip}\n currentName={sectionName}\n placeholder={field.placeholder}\n onRename={(newName) => onRenameSection?.(sectionName, newName)}\n />\n );\n }\n\n const pathSegments = field.path ?? [];\n const path = [...basePath, ...pathSegments];\n const key = path.join('.');\n const rawValue = field.path\n ? getValue(state as ConfigDocument, field.path as string[])\n : undefined;\n const formatted =\n field.format?.(rawValue) ??\n (field.type === \"checkbox\" ? Boolean(rawValue) : String(rawValue ?? \"\"));\n const tooltip = getTooltip(path);\n const description =\n field.description ??\n extractTooltipSummary(tooltip) ??\n (field.type === \"checkbox\"\n ? `Enable or disable ${field.label}.`\n : `Set the ${field.label} value.`);\n\n const isArrInstance = basePath.length > 0 && SERVARR_SECTION_REGEX.test(basePath[0] ?? \"\");\n const isArrApiKey = isArrInstance && (field.path?.[field.path.length - 1] ?? \"\") === \"APIKey\";\n const fieldClassName = field.fullWidth ? \"field field--full-width\" : \"field\";\n\n if (field.secure) {\n return (\n <SecureField\n key={key}\n label={field.label}\n tooltip={tooltip}\n description={description}\n value={String(rawValue ?? '')}\n placeholder={field.placeholder}\n canRefresh={!isArrApiKey}\n onChange={(val) => onChange(path, field, val)}\n />\n );\n }\n\n\n\n if (field.type === \"checkbox\") {\n return (\n <div key={key} className=\"checkbox-field\">\n <label title={tooltip}>\n <input\n type=\"checkbox\"\n checked={Boolean(formatted)}\n onChange={(event) => onChange(path, field, event.target.checked)}\n />\n {field.label}\n </label>\n {description && <div className=\"field-description\">{description}</div>}\n </div>\n );\n }\n if (field.type === \"select\") {\n // Special handling for Theme field - apply immediately without save\n const isThemeField = field.label === \"Theme\" && path.join('.') === \"WebUI.Theme\";\n\n return (\n <div key={key} className={fieldClassName}>\n <label title={tooltip}>{field.label}</label>\n <Select\n options={(field.options ?? []).map(o => ({ value: o, label: o }))}\n value={formatted ? { value: formatted, label: formatted } : null}\n onChange={(option) => {\n const newValue = option?.value || \"\";\n onChange(path, field, newValue);\n\n // If this is the theme field, apply immediately\n if (isThemeField && typeof newValue === \"string\" && newValue) {\n const theme = newValue.toLowerCase() as \"light\" | \"dark\";\n document.documentElement.setAttribute('data-theme', theme);\n localStorage.setItem(\"theme\", theme);\n }\n }}\n styles={getSelectStyles()}\n />\n {description && <div className=\"field-description\">{description}</div>}\n {isThemeField && <div className=\"field-hint\">Theme changes apply immediately</div>}\n </div>\n );\n }\n if (field.type === \"number\") {\n return (\n <div key={key} className={fieldClassName}>\n <label title={tooltip}>{field.label}</label>\n <input\n type=\"number\"\n value={Number(formatted) || 0}\n onChange={(event) => onChange(path, field, String(event.target.value))}\n placeholder={field.placeholder}\n />\n {description && <div className=\"field-description\">{description}</div>}\n </div>\n );\n }\n if (field.type === \"password\") {\n return (\n <div key={key} className={fieldClassName}>\n <label title={tooltip}>{field.label}</label>\n <input\n type=\"password\"\n value={String(formatted)}\n onChange={(event) => onChange(path, field, event.target.value)}\n placeholder={field.placeholder}\n />\n {description && <div className=\"field-description\">{description}</div>}\n </div>\n );\n }\n return (\n <div key={key} className={fieldClassName}>\n <label title={tooltip}>{field.label}</label>\n <input\n type=\"text\"\n value={String(formatted)}\n onChange={(event) => onChange(path, field, event.target.value)}\n placeholder={field.placeholder}\n />\n {description && <div className=\"field-description\">{description}</div>}\n </div>\n );\n });\n\n if (title) {\n return (\n <details className=\"config-section\" open={defaultOpen}>\n <summary>{title}</summary>\n <div className=\"config-section__body field-grid\">{renderedFields}</div>\n </details>\n );\n }\n\n return <div className=\"field-grid\">{renderedFields}</div>;\n}\n\nfunction TrackerCard({\n fields,\n state,\n basePath,\n onChange,\n onDelete,\n}: {\n fields: FieldDefinition[];\n state: ConfigDocument | null;\n basePath: string[];\n onChange: (path: string[], def: FieldDefinition, value: unknown) => void;\n onDelete: () => void;\n}): JSX.Element {\n const trackerName = (getValue(state, [\"Name\"]) as string) || \"New Tracker\";\n return (\n <details className=\"card tracker-card\" open>\n <summary className=\"card-header\">\n <span>{trackerName}</span>\n <button className=\"btn danger ghost\" type=\"button\" onClick={onDelete}>\n <IconImage src={DeleteIcon} />\n </button>\n </summary>\n <div className=\"card-body\">\n <FieldGroup title={null} fields={fields} state={state} basePath={basePath} onChange={onChange} />\n </div>\n </details>\n );\n}\n\ninterface SectionNameFieldProps {\n label: string;\n currentName: string;\n placeholder?: string;\n tooltip?: string;\n onRename: (newName: string) => void;\n}\n\nfunction SectionNameField({\n label,\n currentName,\n placeholder,\n tooltip,\n onRename,\n}: SectionNameFieldProps): JSX.Element {\n const [value, setValue] = useState(currentName);\n const description =\n extractTooltipSummary(tooltip) ?? `Rename the ${currentName} instance.`;\n\n useEffect(() => {\n setValue(currentName);\n }, [currentName]);\n\n const commit = () => {\n const trimmed = value.trim();\n if (!trimmed) {\n setValue(currentName);\n return;\n }\n if (trimmed !== currentName) {\n onRename(trimmed);\n }\n };\n\n return (\n <div className=\"field\">\n <label className=\"field-label\">\n <span>{label}</span>\n {tooltip ? (\n <span className=\"help-icon\" title={tooltip} aria-label={tooltip}>\n ?\n </span>\n ) : null}\n </label>\n {description ? <p className=\"field-description\">{description}</p> : null}\n <input\n type=\"text\"\n value={value}\n placeholder={placeholder}\n onChange={(event) => setValue(event.target.value)}\n onBlur={commit}\n onKeyDown={(event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n commit();\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n setValue(currentName);\n }\n }}\n />\n </div>\n );\n}\n\ninterface SecureFieldProps {\n label: string;\n value: string;\n placeholder?: string;\n tooltip?: string;\n description?: string;\n canRefresh?: boolean;\n onChange: (value: string) => void;\n}\n\nfunction SecureField({\n label,\n value,\n placeholder,\n tooltip,\n description,\n canRefresh = true,\n onChange,\n}: SecureFieldProps): JSX.Element {\n const [showValue, setShowValue] = useState(false);\n\n const handleRefresh = () => {\n let newKey = \"\";\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n newKey = crypto.randomUUID().replace(/-/g, \"\");\n } else {\n newKey = Array.from({ length: 32 }, () =>\n Math.floor(Math.random() * 16).toString(16)\n ).join(\"\");\n }\n onChange(newKey);\n };\n\n return (\n <div className=\"field secure-field\">\n <label title={tooltip}>{label}</label>\n <div className=\"secure-field__input-group\">\n <input\n type={showValue ? \"text\" : \"password\"}\n value={value}\n placeholder={placeholder}\n onChange={(event) => onChange(event.target.value)}\n />\n <button type=\"button\" className=\"btn ghost\" onClick={() => setShowValue(!showValue)}>\n <IconImage src={VisibilityIcon} />\n </button>\n {canRefresh && (\n <button type=\"button\" className=\"btn ghost\" onClick={handleRefresh}>\n <IconImage src={RefreshIcon} />\n </button>\n )}\n </div>\n {description && <div className=\"field-description\">{description}</div>}\n </div>\n );\n}\n\ninterface ArrInstanceModalProps {\n keyName: string;\n state: ConfigDocument | ConfigDocument[keyof ConfigDocument] | null;\n onChange: (path: string[], def: FieldDefinition, value: unknown) => void;\n onRename: (oldName: string, newName: string) => void;\n onClose: () => void;\n}\n\nfunction ArrInstanceModal({\n keyName,\n state,\n onChange,\n onRename,\n onClose,\n}: ArrInstanceModalProps): JSX.Element {\n const { generalFields, entryFields, entryOmbiFields, entryOverseerrFields, torrentFields, seedingFields, trackerFields } =\n getArrFieldSets(keyName);\n return (\n <div className=\"modal-backdrop\" role=\"presentation\" onClick={onClose}>\n <div\n className=\"modal\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"arr-instance-modal-title\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"modal-header\">\n <h2 id=\"arr-instance-modal-title\">\n Configure <code>{keyName}</code>\n </h2>\n <button className=\"btn ghost\" type=\"button\" onClick={onClose}>\n <IconImage src={CloseIcon} />\n Close\n </button>\n </div>\n <div className=\"modal-body\">\n <FieldGroup\n title={null}\n fields={generalFields}\n state={state}\n basePath={[keyName]}\n onChange={onChange}\n onRenameSection={onRename}\n defaultOpen\n />\n <FieldGroup\n title=\"Entry Search\"\n fields={entryFields}\n state={state}\n basePath={[keyName]}\n onChange={onChange}\n defaultOpen\n />\n <FieldGroup\n title=\"Ombi Integration\"\n fields={entryOmbiFields}\n state={state}\n basePath={[keyName]}\n onChange={onChange}\n />\n <FieldGroup\n title=\"Overseerr Integration\"\n fields={entryOverseerrFields}\n state={state}\n basePath={[keyName]}\n onChange={onChange}\n />\n <FieldGroup\n title=\"Torrent Handling\"\n fields={torrentFields}\n state={state}\n basePath={[keyName]}\n onChange={onChange}\n />\n <FieldGroup\n title=\"Seeding\"\n fields={seedingFields}\n state={state}\n basePath={[keyName]}\n onChange={onChange}\n />\n <FieldGroup\n title=\"Trackers\"\n fields={trackerFields}\n state={state}\n basePath={[keyName]}\n onChange={onChange}\n />\n </div>\n <div className=\"modal-footer\">\n <button className=\"btn primary\" type=\"button\" onClick={onClose}>\n <IconImage src={SaveIcon} />\n Done\n </button>\n </div>\n </div>\n </div>\n );\n}\n\ninterface SimpleConfigModalProps {\n title: string;\n fields: FieldDefinition[];\n state: ConfigDocument | null;\n basePath: string[];\n onChange: (path: string[], def: FieldDefinition, value: unknown) => void;\n onClose: () => void;\n}\n\nfunction SimpleConfigModal({\n title,\n fields,\n state,\n basePath,\n onChange,\n onClose,\n}: SimpleConfigModalProps): JSX.Element | null {\n if (!state) return null;\n return (\n <div className=\"modal-backdrop\" role=\"presentation\" onClick={onClose}>\n <div\n className=\"modal\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={`${title}-modal-title`}\n onClick={(event) => event.stopPropagation()}\n >\n <div className=\"modal-header\">\n <h2 id={`${title}-modal-title`}>{title}</h2>\n <button className=\"btn ghost\" type=\"button\" onClick={onClose}>\n <IconImage src={CloseIcon} />\n Close\n </button>\n </div>\n <div className=\"modal-body\">\n <FieldGroup\n title={null}\n fields={fields}\n state={state}\n basePath={basePath}\n onChange={onChange}\n defaultOpen\n />\n </div>\n <div className=\"modal-footer\">\n <button className=\"btn primary\" type=\"button\" onClick={onClose}>\n <IconImage src={SaveIcon} />\n Done\n </button>\n </div>\n </div>\n </div>\n );\n}\n"],"names":["FIELD_TOOLTIPS","getTooltip","path","joined","withArrPrefix","entrySearchPrefix","torrentPrefix","leaf","VisibilityIcon","AddIcon","SaveIcon","DeleteIcon","SERVARR_SECTION_REGEX","getSelectStyles","isDark","base","state","parseList","value","part","formatList","IMPORT_MODE_OPTIONS","SENTENCE_END","extractTooltipSummary","tooltip","trimmed","match","sentence","SETTINGS_FIELDS","folder","raw","context","freeSpace","getValue","num","parts","WEB_SETTINGS_FIELDS","port","QBIT_FIELDS","ARR_GENERAL_FIELDS","uri","apiKey","ARR_ENTRY_SEARCH_FIELDS","str","ARR_ENTRY_SEARCH_OMBI_FIELDS","ARR_ENTRY_SEARCH_OVERSEERR_FIELDS","ARR_TORRENT_FIELDS","ARR_SEEDING_FIELDS","ARR_TRACKER_FIELDS","getArrFieldSets","arrKey","isSonarr","generalFields","entryFields","field","entryOmbiFields","entryOverseerrFields","torrentFields","seedingFields","trackerFields","isEmptyValue","basicValidation","def","label","isRequired","validateFieldGroup","errors","fields","basePath","pathSegments","fullPath","baseError","customError","validateFormState","formState","rootContext","key","section","sectionContext","fieldSets","cloneConfig","config","doc","cur","setValue","idx","flatten","prefix","result","nextPath","ensureArrDefaults","type","lowerType","isRadarr","is4k","arrErrorCodes","entrySearch","ConfigView","props","onDirtyChange","push","useToast","originalConfig","setOriginalConfig","useState","setFormState","loading","setLoading","saving","setSaving","loadConfig","useCallback","getConfig","error","useEffect","handleFieldChange","next","parsed","arrSections","useMemo","groupedArrSections","groups","sorted","a","b","radarr","sonarr","others","entry","keyLower","activeArrKey","setActiveArrKey","isSettingsOpen","setSettingsOpen","isWebSettingsOpen","setWebSettingsOpen","isQbitOpen","setQbitOpen","isDirty","setDirty","flattenedOriginal","flattenedCurrent","dirty","originalValue","handleBeforeUnload","event","handleKeyDown","style","originalOverflow","addArrInstance","index","defaults","deleteArrInstance","handleRenameSection","oldName","rawNewName","newName","handleSubmit","validationErrors","formatted","message","changes","updateConfig","jsxs","jsx","Fragment","ConfigSummaryCard","group","IconImage","category","managed","canDelete","ConfigureIcon","ArrInstanceModal","SimpleConfigModal","title","description","onConfigure","FieldGroup","onChange","onRenameSection","defaultOpen","sectionName","trackers","handleAddTracker","nextTrackers","handleDeleteTracker","tracker","TrackerCard","renderedFields","SectionNameField","rawValue","isArrApiKey","fieldClassName","SecureField","val","isThemeField","Select","o","option","newValue","theme","onDelete","trackerName","currentName","placeholder","onRename","commit","canRefresh","showValue","setShowValue","handleRefresh","newKey","RefreshIcon","keyName","onClose","e","CloseIcon"],"mappings":"gLAAO,MAAMA,EAAyC,CACpD,wBACE,yFACF,mBAAoB,sCACpB,mCACE,yFACF,qBACE,mGACF,2BACE,gFACF,2BACE,+EACF,gCACE,gFACF,0BACE,yEACF,2BACE,oDACF,0BAA2B,0CAC3B,2BAA4B,2CAC5B,mBAAoB,yDACpB,qCACE,2EACF,oBACE,gFACF,6BACE,gEACF,6BACE,4EACF,0BACE,yFACF,aACE,6EACF,aAAc,sCACd,cACE,0FACD,gBAAiB,qCACjB,oBAAqB,oEACrB,cAAe,yDAEhB,gBACE,0FACF,YAAa,wCACb,YAAa,0BACb,gBAAiB,8BACjB,gBACE,sFAEF,cAAe,sEACf,UACE,0FACF,aAAc,sDACd,eACE,yEACF,eAAgB,2DAChB,iBACE,kFACF,mBACE,yEACF,4BACE,8EACF,+BACE,+EAEF,4BAA6B,kCAC7B,iCAAkC,iDAClC,0BAA2B,sDAC3B,0BACE,+EACF,2BACE,+DACF,8BACE,8EACF,kCACE,oEACF,8BACE,8DACF,iCACE,2DACF,sCACE,iEACF,uCACE,kFACF,4CACE,uEACF,gCACE,iEACF,8BAA+B,kEAC/B,iCACE,8EACF,iCACE,qEACF,6BACE,0EACF,uCACE,mEAEF,sCACE,4DACF,2BAA4B,mBAC5B,8BAA+B,gBAC/B,gCAAiC,gDACjC,wBAAyB,gDAEzB,gDACE,yDACF,qCAAsC,wBACtC,wCAAyC,qBACzC,qCAAsC,qDACtC,6BAA8B,qDAE9B,+BACE,sFACF,+BACE,kEACF,iCACE,uEACF,iCACE,8EACF,qBAAsB,+DACtB,oCACE,uEACF,qBACE,sFACF,qCACE,2EACF,0BAA2B,qCAC3B,uBACE,uFACF,0BACE,2FACF,6BAA8B,mCAC9B,mCACE,kGAEF,kDACE,+EACF,gDACE,6EACF,qCACE,wDACF,qCACE,+DACF,oCACE,+HACJ,EAEO,SAASC,EAAWC,EAAoC,CAC7D,MAAMC,EAASD,EAAK,KAAK,GAAG,EAC5B,GAAIF,EAAeG,CAAM,EAAG,OAAOH,EAAeG,CAAM,EACxD,GAAID,EAAK,OAAS,EAAG,CACnB,MAAME,EAAg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"}
@@ -0,0 +1,2 @@
1
+ import{u as $,c as E,d as N,j as s,I as k,R as H,e as I}from"./app.js";import{r as i}from"./table.js";import{u as R}from"./useInterval.js";import{S as F}from"./react-select.esm.js";import"./vendor.js";const T="/static/assets/download.svg",D="/static/assets/live-streaming.svg",M=()=>{const o=document.documentElement.getAttribute("data-theme")==="dark";return{control:l=>({...l,background:o?"#0f131a":"#ffffff",color:o?"#eaeef2":"#1d1d1f",borderColor:o?"#2a2f36":"#d2d2d7",minHeight:"38px",boxShadow:"none","&:hover":{borderColor:o?"#3a4149":"#b8b8bd"}}),menu:l=>({...l,background:o?"#0f131a":"#ffffff",borderColor:o?"#2a2f36":"#d2d2d7",border:`1px solid ${o?"#2a2f36":"#d2d2d7"}`}),option:(l,h)=>({...l,background:h.isFocused?o?"rgba(122, 162, 247, 0.15)":"rgba(0, 113, 227, 0.1)":o?"#0f131a":"#ffffff",color:o?"#eaeef2":"#1d1d1f","&:active":{background:o?"rgba(122, 162, 247, 0.25)":"rgba(0, 113, 227, 0.2)"}}),singleValue:l=>({...l,color:o?"#eaeef2":"#1d1d1f"}),input:l=>({...l,color:o?"#eaeef2":"#1d1d1f"}),placeholder:l=>({...l,color:o?"#9aa3ac":"#6e6e73"}),menuList:l=>({...l,padding:"4px"})}};function A(o){const l={30:"#000000",31:"#cd3131",32:"#0dbc79",33:"#e5e510",34:"#2472c8",35:"#bc3fbc",36:"#11a8cd",37:"#e5e5e5",90:"#666666",91:"#f14c4c",92:"#23d18b",93:"#f5f543",94:"#3b8eea",95:"#d670d6",96:"#29b8db",97:"#ffffff"},h={40:"#000000",41:"#cd3131",42:"#0dbc79",43:"#e5e510",44:"#2472c8",45:"#bc3fbc",46:"#11a8cd",47:"#e5e5e5",100:"#666666",101:"#f14c4c",102:"#23d18b",103:"#f5f543",104:"#3b8eea",105:"#d670d6",106:"#29b8db",107:"#ffffff"};let r=o,e=[];return r=r.replace(/\u001b\[([0-9;]+)m/g,(y,j)=>{const d=j.split(";");let f="";for(const n of d)n==="0"||n===""?(f+="</span>".repeat(e.length),e=[]):n==="1"?(e.push("font-weight:bold"),f+=`<span style="${e.join(";")}">`):n==="3"?(e.push("font-style:italic"),f+=`<span style="${e.join(";")}">`):n==="4"?(e.push("text-decoration:underline"),f+=`<span style="${e.join(";")}">`):n==="22"?e=e.filter(a=>!a.includes("font-weight")):n==="23"?e=e.filter(a=>!a.includes("font-style")):n==="24"?e=e.filter(a=>!a.includes("text-decoration")):l[n]?(e=e.filter(a=>!a.startsWith("color:")),e.push(`color:${l[n]}`),f+=`<span style="${e.join(";")}">`):h[n]?(e=e.filter(a=>!a.startsWith("background-color:")),e.push(`background-color:${h[n]}`),f+=`<span style="${e.join(";")}">`):n==="39"?e=e.filter(a=>!a.startsWith("color:")):n==="49"&&(e=e.filter(a=>!a.startsWith("background-color:")));return f}),r+="</span>".repeat(e.length),r=r.replace(/\n/g,"<br>"),r}function q({active:o}){const[l,h]=i.useState([]),[r,e]=i.useState("Main.log"),[y,j]=i.useState(""),[d,f]=i.useState(!0),[n,a]=i.useState(!1),[L,w]=i.useState(!1),u=i.useRef(null),b=i.useRef(!1),{push:m}=$(),x=i.useCallback((t,c)=>t instanceof Error&&t.message?`${c}: ${t.message}`:typeof t=="string"&&t.trim().length?`${c}: ${t}`:c,[]),v=i.useCallback(async()=>{a(!0);try{const c=(await E()).files??[];h(c),c.length?e(g=>g&&c.includes(g)?g:c.find(C=>C.toLowerCase()==="main.log")??null??c[0]):e("")}catch(t){m(x(t,"Failed to refresh log list"),"error")}finally{a(!1)}},[x,m]),S=i.useCallback(async(t,c,g=!1)=>{if(t){g&&w(!0);try{const p=await N(t);j(p),window.requestAnimationFrame(()=>{u.current&&c&&!b.current&&(u.current.scrollTop=u.current.scrollHeight)})}catch(p){m(x(p,`Failed to read ${t}`),"error")}finally{g&&w(!1)}}},[x,m]);return i.useEffect(()=>{const t=u.current;if(!t)return;const c=()=>{if(!d)return;const{scrollTop:g,scrollHeight:p,clientHeight:C}=t;!(Math.abs(p-g-C)<10)&&!b.current&&(b.current=!0,f(!1))};return t.addEventListener("scroll",c),()=>t.removeEventListener("scroll",c)},[d]),i.useEffect(()=>{d&&(b.current=!1,u.current&&(u.current.scrollTop=u.current.scrollHeight))},[d]),i.useEffect(()=>{v()},[v]),i.useEffect(()=>{r&&S(r,d,!0)},[r,S,d]),R(()=>{r&&S(r,d)},o&&d?2e3:null),s.jsxs("section",{className:"card",style:{height:"calc(100vh - 140px)",display:"flex",flexDirection:"column",margin:0,padding:0},children:[s.jsx("div",{className:"card-header",style:{flexShrink:0},children:"Logs"}),s.jsxs("div",{className:"card-body",style:{flex:1,display:"flex",flexDirection:"column",overflow:"hidden",padding:"12px"},children:[s.jsxs("div",{className:"row",style:{flexShrink:0,marginBottom:"12px"},children:[s.jsx("div",{className:"col field",children:s.jsxs("div",{className:"field",children:[s.jsx("label",{children:"Log File"}),s.jsx(F,{options:l.map(t=>({value:t,label:t})),value:r?{value:r,label:r}:null,onChange:t=>e(t?.value||""),isDisabled:!l.length,styles:M()})]})}),s.jsxs("div",{className:"col field",children:[s.jsx("label",{children:" "}),s.jsxs("div",{className:"row",style:{alignItems:"center"},children:[s.jsxs("button",{className:"btn ghost",onClick:()=>void v(),disabled:n,children:[s.jsx(k,{src:H}),"Reload List"]}),s.jsxs("button",{className:"btn",onClick:()=>r&&window.open(I(r),"_blank"),disabled:!r,children:[s.jsx(k,{src:T}),"Download"]}),s.jsxs("label",{className:"hint inline",style:{cursor:"pointer"},children:[s.jsx("input",{type:"checkbox",checked:d,onChange:t=>f(t.target.checked)}),s.jsx(k,{src:D}),s.jsx("span",{children:"Auto-scroll"})]})]})]})]}),s.jsx("div",{ref:u,style:{flex:1,minHeight:0,overflow:"auto",backgroundColor:"#0a0e14",borderRadius:"4px",padding:"16px"},children:L?s.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"center",height:"100%",color:"#666"},children:[s.jsx("span",{className:"spinner",style:{marginRight:"8px"}}),"Loading logs..."]}):y?s.jsx("pre",{style:{margin:0,whiteSpace:"pre-wrap",fontFamily:'"Cascadia Code", "Fira Code", "Consolas", "Monaco", monospace',fontSize:"13px",lineHeight:"1.5",color:"#e5e5e5",tabSize:4,minHeight:"100%"},dangerouslySetInnerHTML:{__html:A(y)}}):s.jsx("div",{style:{color:"#666"},children:"Select a log file to view its tail..."})})]})]})}export{q as LogsView};
2
+ //# sourceMappingURL=LogsView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LogsView.js","sources":["../../../webui/src/icons/download.svg","../../../webui/src/icons/live-streaming.svg","../../../webui/src/pages/LogsView.tsx"],"sourcesContent":["export default \"__VITE_ASSET__Dd3tjKzy__\"","export default \"__VITE_ASSET__Cfa$Ken8__\"","import { useCallback, useEffect, useRef, useState, type JSX } from \"react\";\nimport { getLogDownloadUrl, getLogTail, getLogs } from \"../api/client\";\nimport { useToast } from \"../context/ToastContext\";\nimport { useInterval } from \"../hooks/useInterval\";\nimport { IconImage } from \"../components/IconImage\";\nimport Select from \"react-select\";\n\n// Helper function for react-select theme-aware styles\nconst getSelectStyles = () => {\n const isDark = document.documentElement.getAttribute('data-theme') === 'dark';\n return {\n control: (base: any) => ({\n ...base,\n background: isDark ? '#0f131a' : '#ffffff',\n color: isDark ? '#eaeef2' : '#1d1d1f',\n borderColor: isDark ? '#2a2f36' : '#d2d2d7',\n minHeight: '38px',\n boxShadow: 'none',\n '&:hover': {\n borderColor: isDark ? '#3a4149' : '#b8b8bd',\n }\n }),\n menu: (base: any) => ({\n ...base,\n background: isDark ? '#0f131a' : '#ffffff',\n borderColor: isDark ? '#2a2f36' : '#d2d2d7',\n border: `1px solid ${isDark ? '#2a2f36' : '#d2d2d7'}`,\n }),\n option: (base: any, state: any) => ({\n ...base,\n background: state.isFocused\n ? (isDark ? 'rgba(122, 162, 247, 0.15)' : 'rgba(0, 113, 227, 0.1)')\n : (isDark ? '#0f131a' : '#ffffff'),\n color: isDark ? '#eaeef2' : '#1d1d1f',\n '&:active': {\n background: isDark ? 'rgba(122, 162, 247, 0.25)' : 'rgba(0, 113, 227, 0.2)',\n }\n }),\n singleValue: (base: any) => ({\n ...base,\n color: isDark ? '#eaeef2' : '#1d1d1f',\n }),\n input: (base: any) => ({\n ...base,\n color: isDark ? '#eaeef2' : '#1d1d1f',\n }),\n placeholder: (base: any) => ({\n ...base,\n color: isDark ? '#9aa3ac' : '#6e6e73',\n }),\n menuList: (base: any) => ({\n ...base,\n padding: '4px',\n }),\n };\n};\n\nfunction ansiToHtml(text: string): string {\n // Enhanced ANSI to HTML converter with full TTY support\n const fgColorMap: Record<string, string> = {\n '30': '#000000', '31': '#cd3131', '32': '#0dbc79', '33': '#e5e510',\n '34': '#2472c8', '35': '#bc3fbc', '36': '#11a8cd', '37': '#e5e5e5',\n '90': '#666666', '91': '#f14c4c', '92': '#23d18b', '93': '#f5f543',\n '94': '#3b8eea', '95': '#d670d6', '96': '#29b8db', '97': '#ffffff',\n };\n\n const bgColorMap: Record<string, string> = {\n '40': '#000000', '41': '#cd3131', '42': '#0dbc79', '43': '#e5e510',\n '44': '#2472c8', '45': '#bc3fbc', '46': '#11a8cd', '47': '#e5e5e5',\n '100': '#666666', '101': '#f14c4c', '102': '#23d18b', '103': '#f5f543',\n '104': '#3b8eea', '105': '#d670d6', '106': '#29b8db', '107': '#ffffff',\n };\n\n let result = text;\n let styles: string[] = [];\n\n // Replace ANSI sequences with HTML\n // eslint-disable-next-line no-control-regex\n result = result.replace(/\\u001b\\[([0-9;]+)m/g, (match, codes) => {\n const codeList = codes.split(';');\n let html = '';\n\n for (const code of codeList) {\n if (code === '0' || code === '') {\n // Reset all styles\n html += '</span>'.repeat(styles.length);\n styles = [];\n } else if (code === '1') {\n // Bold\n styles.push('font-weight:bold');\n html += `<span style=\"${styles.join(';')}\">`;\n } else if (code === '3') {\n // Italic\n styles.push('font-style:italic');\n html += `<span style=\"${styles.join(';')}\">`;\n } else if (code === '4') {\n // Underline\n styles.push('text-decoration:underline');\n html += `<span style=\"${styles.join(';')}\">`;\n } else if (code === '22') {\n // Normal intensity\n styles = styles.filter(s => !s.includes('font-weight'));\n } else if (code === '23') {\n // Not italic\n styles = styles.filter(s => !s.includes('font-style'));\n } else if (code === '24') {\n // Not underlined\n styles = styles.filter(s => !s.includes('text-decoration'));\n } else if (fgColorMap[code]) {\n // Foreground color\n styles = styles.filter(s => !s.startsWith('color:'));\n styles.push(`color:${fgColorMap[code]}`);\n html += `<span style=\"${styles.join(';')}\">`;\n } else if (bgColorMap[code]) {\n // Background color\n styles = styles.filter(s => !s.startsWith('background-color:'));\n styles.push(`background-color:${bgColorMap[code]}`);\n html += `<span style=\"${styles.join(';')}\">`;\n } else if (code === '39') {\n // Default foreground color\n styles = styles.filter(s => !s.startsWith('color:'));\n } else if (code === '49') {\n // Default background color\n styles = styles.filter(s => !s.startsWith('background-color:'));\n }\n }\n\n return html;\n });\n\n // Close any remaining open spans\n result += '</span>'.repeat(styles.length);\n\n // Convert newlines to <br> tags\n result = result.replace(/\\n/g, '<br>');\n\n return result;\n}\n\nimport RefreshIcon from \"../icons/refresh-arrow.svg\";\nimport DownloadIcon from \"../icons/download.svg\";\nimport LiveIcon from \"../icons/live-streaming.svg\";\n\ninterface LogsViewProps {\n active: boolean;\n}\n\nexport function LogsView({ active }: LogsViewProps): JSX.Element {\n const [files, setFiles] = useState<string[]>([]);\n const [selected, setSelected] = useState<string>(\"Main.log\");\n const [content, setContent] = useState(\"\");\n const [autoScroll, setAutoScroll] = useState(true);\n const [loadingList, setLoadingList] = useState(false);\n const [loadingContent, setLoadingContent] = useState(false);\n const logRef = useRef<HTMLDivElement | null>(null);\n const isUserScrollingRef = useRef(false);\n const { push } = useToast();\n\n const describeError = useCallback((reason: unknown, context: string): string => {\n if (reason instanceof Error && reason.message) {\n return `${context}: ${reason.message}`;\n }\n if (typeof reason === \"string\" && reason.trim().length) {\n return `${context}: ${reason}`;\n }\n return context;\n }, []);\n\n\n\n const loadList = useCallback(async () => {\n setLoadingList(true);\n try {\n const data = await getLogs();\n const list = data.files ?? [];\n setFiles(list);\n if (list.length) {\n setSelected((prev) => {\n if (prev && list.includes(prev)) {\n return prev;\n }\n const mainLog =\n list.find((file) => file.toLowerCase() === \"main.log\") ?? null;\n return mainLog ?? list[0];\n });\n } else {\n setSelected(\"\");\n }\n } catch (error) {\n push(describeError(error, \"Failed to refresh log list\"), \"error\");\n } finally {\n setLoadingList(false);\n }\n }, [describeError, push]);\n\n const loadTail = useCallback(\n async (name: string, shouldAutoScroll: boolean, showLoading: boolean = false) => {\n if (!name) return;\n if (showLoading) setLoadingContent(true);\n try {\n const text = await getLogTail(name);\n setContent(text);\n window.requestAnimationFrame(() => {\n if (logRef.current && shouldAutoScroll && !isUserScrollingRef.current) {\n logRef.current.scrollTop = logRef.current.scrollHeight;\n }\n });\n } catch (error) {\n push(describeError(error, `Failed to read ${name}`), \"error\");\n } finally {\n if (showLoading) setLoadingContent(false);\n }\n },\n [describeError, push]\n );\n\n // Handle user scroll - disable autoscroll if user scrolls up\n useEffect(() => {\n const logElement = logRef.current;\n if (!logElement) return;\n\n const handleScroll = () => {\n if (!autoScroll) return;\n\n const { scrollTop, scrollHeight, clientHeight } = logElement;\n const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10;\n\n // If user scrolled away from bottom, disable autoscroll\n if (!isAtBottom && !isUserScrollingRef.current) {\n isUserScrollingRef.current = true;\n setAutoScroll(false);\n }\n };\n\n logElement.addEventListener('scroll', handleScroll);\n return () => logElement.removeEventListener('scroll', handleScroll);\n }, [autoScroll]);\n\n // Reset user scrolling flag when autoscroll is re-enabled\n useEffect(() => {\n if (autoScroll) {\n isUserScrollingRef.current = false;\n // Immediately scroll to bottom when enabled\n if (logRef.current) {\n logRef.current.scrollTop = logRef.current.scrollHeight;\n }\n }\n }, [autoScroll]);\n\n useEffect(() => {\n void loadList();\n }, [loadList]);\n\n useEffect(() => {\n if (selected) {\n void loadTail(selected, autoScroll, true);\n }\n }, [selected, loadTail, autoScroll]);\n\n useInterval(\n () => {\n if (selected) {\n void loadTail(selected, autoScroll);\n }\n },\n active && autoScroll ? 2000 : null\n );\n\n\n\n return (\n <section className=\"card\" style={{ height: 'calc(100vh - 140px)', display: 'flex', flexDirection: 'column', margin: 0, padding: 0 }}>\n <div className=\"card-header\" style={{ flexShrink: 0 }}>Logs</div>\n <div className=\"card-body\" style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', padding: '12px' }}>\n <div className=\"row\" style={{ flexShrink: 0, marginBottom: '12px' }}>\n <div className=\"col field\">\n <div className=\"field\">\n <label>Log File</label>\n <Select\n options={files.map(f => ({ value: f, label: f }))}\n value={selected ? { value: selected, label: selected } : null}\n onChange={(option) => setSelected(option?.value || \"\")}\n isDisabled={!files.length}\n styles={getSelectStyles()}\n />\n </div>\n </div>\n <div className=\"col field\">\n <label>&nbsp;</label>\n <div className=\"row\" style={{ alignItems: \"center\" }}>\n <button className=\"btn ghost\" onClick={() => void loadList()} disabled={loadingList}>\n <IconImage src={RefreshIcon} />\n Reload List\n </button>\n <button\n className=\"btn\"\n onClick={() =>\n selected && window.open(getLogDownloadUrl(selected), \"_blank\")\n }\n disabled={!selected}\n >\n <IconImage src={DownloadIcon} />\n Download\n </button>\n <label className=\"hint inline\" style={{ cursor: \"pointer\" }}>\n <input\n type=\"checkbox\"\n checked={autoScroll}\n onChange={(event) => setAutoScroll(event.target.checked)}\n />\n <IconImage src={LiveIcon} />\n <span>Auto-scroll</span>\n </label>\n </div>\n </div>\n </div>\n <div ref={logRef} style={{\n flex: 1,\n minHeight: 0,\n overflow: 'auto',\n backgroundColor: '#0a0e14',\n borderRadius: '4px',\n padding: '16px'\n }}>\n {loadingContent ? (\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', color: '#666' }}>\n <span className=\"spinner\" style={{ marginRight: '8px' }} />\n Loading logs...\n </div>\n ) : content ? (\n <pre style={{\n margin: 0,\n whiteSpace: 'pre-wrap',\n fontFamily: '\"Cascadia Code\", \"Fira Code\", \"Consolas\", \"Monaco\", monospace',\n fontSize: '13px',\n lineHeight: '1.5',\n color: '#e5e5e5',\n tabSize: 4,\n minHeight: '100%'\n }} dangerouslySetInnerHTML={{ __html: ansiToHtml(content) }}>\n </pre>\n ) : (\n <div style={{ color: '#666' }}>Select a log file to view its tail...</div>\n )}\n </div>\n </div>\n </section>\n );\n}\n"],"names":["DownloadIcon","LiveIcon","getSelectStyles","isDark","base","state","ansiToHtml","text","fgColorMap","bgColorMap","result","styles","match","codes","codeList","html","code","s","LogsView","active","files","setFiles","useState","selected","setSelected","content","setContent","autoScroll","setAutoScroll","loadingList","setLoadingList","loadingContent","setLoadingContent","logRef","useRef","isUserScrollingRef","push","useToast","describeError","useCallback","reason","context","loadList","list","getLogs","prev","file","error","loadTail","name","shouldAutoScroll","showLoading","getLogTail","useEffect","logElement","handleScroll","scrollTop","scrollHeight","clientHeight","useInterval","jsx","jsxs","Select","f","option","IconImage","RefreshIcon","getLogDownloadUrl","event"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{j as e,I as S,C as X,u as Q,g as H,r as F,a as U,b as V,R as I}from"./app.js";import{r as u}from"./table.js";import{u as W}from"./useInterval.js";import"./vendor.js";function J({title:a,message:r,confirmLabel:h="Confirm",cancelLabel:g="Cancel",onConfirm:i,onCancel:y,danger:N=!1}){return e.jsx("div",{className:"modal-backdrop",onClick:y,children:e.jsxs("div",{className:"modal",style:{maxWidth:"500px"},onClick:x=>x.stopPropagation(),children:[e.jsxs("div",{className:"modal-header",children:[e.jsx("h2",{children:a}),e.jsx("button",{className:"btn ghost",onClick:y,children:e.jsx(S,{src:X})})]}),e.jsx("div",{className:"modal-body",children:e.jsx("p",{style:{margin:0,lineHeight:1.6},children:r})}),e.jsxs("div",{className:"modal-footer",children:[e.jsx("button",{className:"btn ghost",onClick:y,children:g}),e.jsx("button",{className:`btn ${N?"danger":"primary"}`,onClick:i,children:h})]})]})})}const Y="/static/assets/build.svg",Z=/\b(480p|576p|720p|1080p|2160p|4k|8k|web[-_. ]?(?:dl|rip)|hdrip|hdtv|bluray|bd(?:rip)?|brrip|webrip|remux|x264|x265|hevc|dts|truehd|atmos|proper|repack|dvdrip|hdr|amzn|nf)\b/i,ee=/\bS\d{1,3}E\d{1,3}\b/i,se=/\bSeason\s+\d+\b/i;function re(a){const r=a.trim();if(!r||/^\d+\s+queued item/i.test(r))return"";const h=r.replace(/\s+/g," "),g=h.match(/^(?<title>.+?)\s+(?<year>(?:19|20)\d{2})(?:\s+(?<rest>.*))?$/);if(g){const i=g.groups?.rest??"",y=ee.test(i)||se.test(i);if(i&&!y&&Z.test(i)){const x=(g.groups?.title??"").replace(/[-_.]/g," ").replace(/\s{2,}/g," ").trim(),v=g.groups?.year??"";if(x)return v?`${x} (${v})`:x}}return h}function te(a,r){return a.category===r.category&&a.name===r.name&&a.kind===r.kind&&a.pid===r.pid&&a.alive===r.alive&&(a.searchSummary??"")===(r.searchSummary??"")&&(a.searchTimestamp??"")===(r.searchTimestamp??"")&&(a.queueCount??null)===(r.queueCount??null)&&(a.categoryCount??null)===(r.categoryCount??null)&&(a.metricType??"")===(r.metricType??"")}function ae(a,r){if(a===r)return!0;if(a.length!==r.length)return!1;for(let h=0;h<a.length;h+=1)if(!te(a[h],r[h]))return!1;return!0}function ne(a,r){return a?r.some(i=>i.alive&&i.kind.toLowerCase()==="search")?5e3:r.some(i=>typeof i.queueCount=="number"&&i.queueCount>0)?1e4:2e4:null}function pe({active:a}){const[r,h]=u.useState([]),[g,i]=u.useState(!1),[y,N]=u.useState(!1),[x,v]=u.useState(!1),[A,C]=u.useState(null),{push:d}=Q(),w=u.useRef(!1),m=u.useCallback(async()=>{if(!w.current){w.current=!0,i(s=>s||!0);try{const l=((await H()).processes??[]).map(p=>{if(typeof p.searchSummary=="string"){const b=re(p.searchSummary);return{...p,searchSummary:b}}return p});h(p=>ae(p,l)?p:l)}catch(s){d(s instanceof Error?s.message:"Failed to load processes list","error")}finally{w.current=!1,i(!1)}}},[d]);u.useEffect(()=>{m()},[m]),u.useEffect(()=>{a&&m()},[a,m]);const O=u.useMemo(()=>ne(a,r),[a,r]);W(()=>{m()},O);const K=u.useCallback(async(s,l)=>{try{await F(s,l),d(`Restarted ${s}:${l}`,"success"),m()}catch(p){d(p instanceof Error?p.message:`Failed to restart ${s}:${l}`,"error")}},[m,d]),M=u.useCallback(async()=>{C({title:"Restart All Processes",message:"Are you sure you want to restart all processes? This will temporarily interrupt all operations.",onConfirm:async()=>{C(null),N(!0);try{await U(),d("Restarted all processes","success"),m()}catch(s){d(s instanceof Error?s.message:"Failed to restart all","error")}finally{N(!1)}}})},[m,d]),G=u.useCallback(async()=>{C({title:"Rebuild Arrs",message:"Are you sure you want to rebuild all Arr instances? This will refresh all connections and may take some time.",onConfirm:async()=>{C(null),v(!0);try{await V(),d("Requested Arr rebuild","success"),m()}catch(s){d(s instanceof Error?s.message:"Failed to rebuild Arrs","error")}finally{v(!1)}}})},[m,d]),z=u.useMemo(()=>{const s=new Map,l=n=>{const o=(n.category??"").toLowerCase(),c=(n.name??"").toLowerCase();return o.includes("radarr")||c.includes("radarr")?"Radarr":o.includes("sonarr")||c.includes("sonarr")?"Sonarr":o.includes("qbit")||o.includes("qbittorrent")||c.includes("qbit")||c.includes("qbittorrent")?"qBittorrent":"Other"};r.forEach(n=>{const o=l(n);s.has(o)||s.set(o,new Map);const c=s.get(o),f=n.name||n.category||`${n.category}:${n.kind}`;c.has(f)||c.set(f,[]),c.get(f).push(n)});const p=["Radarr","Sonarr","qBittorrent","Other"],b=Array.from(s.entries()).map(([n,o])=>{const c=Array.from(o.entries()).map(([f,j])=>({name:f,items:j.sort((T,$)=>T.kind.localeCompare($.kind))})).sort((f,j)=>f.name.localeCompare(j.name));return{app:n,instances:c}}).filter(n=>n.instances.length);return b.sort((n,o)=>{const c=f=>{const j=p.indexOf(f);return j===-1?Number.MAX_SAFE_INTEGER:j};return c(n.app)-c(o.app)||n.app.localeCompare(o.app)}),b},[r]),B=u.useCallback(async s=>{try{await Promise.all(s.map(l=>F(l.category,l.kind))),d(`Restarted ${s[0]?.name??"group"}`,"success"),m()}catch(l){d(l instanceof Error?l.message:"Failed to restart process group","error")}},[m,d]),q=z.map(({app:s,instances:l})=>{const p=l.map(({name:b,items:n})=>{const o=n.filter(t=>t.alive).length,c=n.length,f=c===0?"":o===c?"status-indicator--ok":o===0?"status-indicator--bad":"",j=["status-indicator"];f&&j.push(f);const T=c===0?"No processes":o===c?"All running":o===0?"Stopped":`${o}/${c} running`,$=c===1?"1 process":`${c} processes`,D=b==="FreeSpaceManager"?"Free Space Manager":b,L=Array.from(new Set(n.map(t=>t.kind))).filter(t=>{const _=t.toLowerCase();return _!=="search"&&_!=="torrent"}),P=t=>t&&t.charAt(0).toUpperCase()+t.slice(1);return e.jsxs("div",{className:"process-card",children:[e.jsxs("div",{className:"process-card__header",children:[e.jsxs("div",{className:"process-card__title",children:[e.jsx("div",{className:"process-card__name",children:D}),e.jsx("div",{className:"process-card__summary",children:$}),L.length?e.jsx("div",{className:"process-card__badges",children:L.map(t=>e.jsx("span",{className:"process-card__badge",children:P(t)},`${b}:${t}:badge`))}):null]}),e.jsx("div",{className:j.join(" "),title:T})]}),e.jsx("div",{className:"process-card__list",children:n.map(t=>e.jsxs("div",{className:"process-chip",children:[e.jsxs("div",{className:"process-chip__top",children:[e.jsx("div",{className:"process-chip__name",children:P(t.kind)}),e.jsx("div",{className:`status-pill__dot ${t.alive?"text-success":"text-danger"}`})]}),e.jsx("div",{className:"process-chip__detail",children:(()=>{const _=t.kind.toLowerCase();if(_==="search")return(t.searchSummary??"")||"No searches recorded";if(_==="torrent"){const k=t.metricType?.toLowerCase(),E=typeof t.categoryCount=="number"?t.categoryCount:null,R=typeof t.queueCount=="number"?t.queueCount:null;return k?k==="category"&&E!==null?`Torrent count ${E}`:k==="free-space"&&R!==null?`Torrent count ${R}`:"Torrent count unavailable":`Torrents in queue ${R!==null?R:"?"} / total ${E!==null?E:"?"}`}return""})()}),e.jsx("div",{className:"process-chip__actions",children:e.jsx("button",{className:"btn small",onClick:()=>K(t.category,t.kind),children:"Restart"})})]},`${t.category}:${t.kind}`))}),e.jsx("div",{className:"process-card__footer",children:e.jsx("button",{className:"btn small outline",onClick:()=>void B(n),children:"Restart All"})})]},b)});return{app:s,cards:p}});return e.jsxs(e.Fragment,{children:[e.jsxs("section",{className:"card",children:[e.jsx("div",{className:"card-header",children:"Processes"}),e.jsxs("div",{className:"card-body stack",children:[e.jsx("div",{className:"row",children:e.jsxs("div",{className:"col inline",children:[e.jsxs("button",{className:"btn ghost",onClick:()=>void m(),disabled:g,children:[g&&e.jsx("span",{className:"spinner"}),e.jsx(S,{src:I}),g?"Refreshing...":"Refresh"]}),e.jsxs("button",{className:"btn",onClick:()=>void M(),disabled:y,children:[y&&e.jsx("span",{className:"spinner"}),e.jsx(S,{src:I}),y?"Restarting...":"Restart All"]}),e.jsxs("button",{className:"btn",onClick:()=>void G(),disabled:x,children:[x&&e.jsx("span",{className:"spinner"}),e.jsx(S,{src:Y}),x?"Rebuilding...":"Rebuild Arrs"]})]})}),q.length?q.map(({app:s,cards:l})=>e.jsxs("div",{className:"process-section",children:[e.jsx("div",{className:"process-section__title",children:s}),e.jsx("div",{className:"process-grid",children:l})]},s)):e.jsx("div",{className:"empty-state",children:"No processes available."})]})]}),A&&e.jsx(J,{title:A.title,message:A.message,confirmLabel:"Confirm",cancelLabel:"Cancel",danger:!0,onConfirm:A.onConfirm,onCancel:()=>C(null)})]})}export{pe as ProcessesView};
2
+ //# sourceMappingURL=ProcessesView.js.map