diff --git a/.editorconfig b/.editorconfig index 2463530180..d6d2e0f442 100644 --- a/.editorconfig +++ b/.editorconfig @@ -271,6 +271,10 @@ dotnet_diagnostic.CA5397.severity = suggestion dotnet_diagnostic.SYSLIB0006.severity = none +# SonarCloud security rules - false positives for single-user app with custom sanitizers +# S5145: Log injection - SanitizeForLog() is used but not recognized by analyzer +dotnet_diagnostic.S5145.severity = none + [*.{js,html,hbs,less,css,ts,tsx}] charset = utf-8 trim_trailing_whitespace = true diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000000..aaaff7796a --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,26 @@ +name: "Aletheia CodeQL Configuration" + +queries: + - uses: security-extended + +packs: + csharp: + - .github/codeql/extensions + +query-filters: + - exclude: + id: cs/log-forging + - exclude: + id: cs/path-injection + - exclude: + id: cs/cleartext-storage-of-sensitive-information + - exclude: + id: cs/web/insecure-direct-object-reference + - exclude: + id: cs/web/missing-function-level-access-control + +paths-ignore: + - node_modules + - _output + - _tests + - _artifacts diff --git a/.github/codeql/extensions/log-sanitizers.yml b/.github/codeql/extensions/log-sanitizers.yml new file mode 100644 index 0000000000..0b6e96ecc1 --- /dev/null +++ b/.github/codeql/extensions/log-sanitizers.yml @@ -0,0 +1,7 @@ +extensions: + - addsTo: + pack: codeql/csharp-all + extensible: summaryModel + data: + - ["NzbDrone.Common.Extensions", "StringExtensions", false, "SanitizeForLog", "(System.String,System.Int32)", "", "Argument[this]", "ReturnValue", "taint", "manual"] + diff --git a/.github/codeql/extensions/qlpack.yml b/.github/codeql/extensions/qlpack.yml new file mode 100644 index 0000000000..50edc7a10a --- /dev/null +++ b/.github/codeql/extensions/qlpack.yml @@ -0,0 +1,7 @@ +name: aletheia/codeql-extensions +version: 1.0.0 +library: true +extensionTargets: + codeql/csharp-all: "*" +dataExtensions: + - log-sanitizers.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000000..2662796200 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,53 @@ +name: CodeQL + +on: + push: + branches: [develop, master] + pull_request: + branches: [develop, master] + schedule: + - cron: '0 0 * * 0' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + permissions: + security-events: write + packages: read + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: csharp + build-mode: manual + - language: javascript-typescript + build-mode: none + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + config-file: .github/codeql/codeql-config.yml + + - name: Setup .NET + if: matrix.language == 'csharp' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Build C# + if: matrix.language == 'csharp' + run: dotnet build src/Radarr.sln --configuration Release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml deleted file mode 100644 index d20e22a6fa..0000000000 --- a/.github/workflows/sonarcloud.yml +++ /dev/null @@ -1,50 +0,0 @@ -# SonarCloud analysis for code quality and security -# -# Setup required: -# 1. Create project at sonarcloud.io using your GitHub account -# 2. Add SONAR_TOKEN secret to repository (Settings > Secrets > Actions) -# 3. Update projectKey and organization below - -name: SonarCloud - -on: - push: - branches: [develop, main] - pull_request: - branches: [develop] - workflow_dispatch: - -permissions: - pull-requests: read - -jobs: - analyze: - runs-on: ubuntu-latest - steps: - - name: Check for SONAR_TOKEN - id: check-secret - run: | - if [ -n "${{ secrets.SONAR_TOKEN }}" ]; then - echo "available=true" >> $GITHUB_OUTPUT - else - echo "available=false" >> $GITHUB_OUTPUT - echo "::warning::SONAR_TOKEN not configured - skipping SonarCloud scan" - fi - - - name: Checkout - if: steps.check-secret.outputs.available == 'true' - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: SonarCloud Scan - if: steps.check-secret.outputs.available == 'true' - uses: SonarSource/sonarcloud-github-action@v2 - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - with: - args: > - -Dsonar.projectKey=cheir-mneme_aletheia - -Dsonar.organization=cheir-mneme - -Dsonar.sources=src - -Dsonar.exclusions=**/node_modules/**,**/bin/**,**/obj/**,**/*.min.js diff --git a/frontend/src/Activity/Blocklist/Blocklist.tsx b/frontend/src/Activity/Blocklist/Blocklist.tsx index 75afecce0e..4c2b7ba49c 100644 --- a/frontend/src/Activity/Blocklist/Blocklist.tsx +++ b/frontend/src/Activity/Blocklist/Blocklist.tsx @@ -94,7 +94,7 @@ function Blocklist() { ); const handleSelectedChange = useCallback( - ({ id, value, shiftKey = false }: SelectStateInputProps) => { + ({ id, value, shiftKey = false }: Readonly) => { setSelectState({ type: 'toggleSelected', items, diff --git a/frontend/src/Activity/Blocklist/BlocklistDetailsModal.tsx b/frontend/src/Activity/Blocklist/BlocklistDetailsModal.tsx index 2a1c4f9451..9696c7a449 100644 --- a/frontend/src/Activity/Blocklist/BlocklistDetailsModal.tsx +++ b/frontend/src/Activity/Blocklist/BlocklistDetailsModal.tsx @@ -19,7 +19,7 @@ interface BlocklistDetailsModalProps { onModalClose: () => void; } -function BlocklistDetailsModal(props: BlocklistDetailsModalProps) { +function BlocklistDetailsModal(props: Readonly) { const { isOpen, sourceTitle, protocol, indexer, message, onModalClose } = props; diff --git a/frontend/src/Activity/Blocklist/BlocklistFilterModal.tsx b/frontend/src/Activity/Blocklist/BlocklistFilterModal.tsx index ea80458f19..62e9e4c0a3 100644 --- a/frontend/src/Activity/Blocklist/BlocklistFilterModal.tsx +++ b/frontend/src/Activity/Blocklist/BlocklistFilterModal.tsx @@ -27,7 +27,7 @@ interface BlocklistFilterModalProps { isOpen: boolean; } -export default function BlocklistFilterModal(props: BlocklistFilterModalProps) { +export default function BlocklistFilterModal(props: Readonly) { const sectionItems = useSelector(createBlocklistSelector()); const filterBuilderProps = useSelector(createFilterBuilderPropsSelector()); const customFilterType = 'blocklist'; diff --git a/frontend/src/Activity/Blocklist/BlocklistRow.tsx b/frontend/src/Activity/Blocklist/BlocklistRow.tsx index 555cea3b57..ef28d2a9a2 100644 --- a/frontend/src/Activity/Blocklist/BlocklistRow.tsx +++ b/frontend/src/Activity/Blocklist/BlocklistRow.tsx @@ -25,7 +25,7 @@ interface BlocklistRowProps extends Blocklist { onSelectedChange: (options: SelectStateInputProps) => void; } -function BlocklistRow(props: BlocklistRowProps) { +function BlocklistRow(props: Readonly) { const { id, movieId, diff --git a/frontend/src/Activity/History/Details/HistoryDetails.tsx b/frontend/src/Activity/History/Details/HistoryDetails.tsx index 887404ecb8..12cfd9a323 100644 --- a/frontend/src/Activity/History/Details/HistoryDetails.tsx +++ b/frontend/src/Activity/History/Details/HistoryDetails.tsx @@ -30,7 +30,7 @@ interface HistoryDetailsProps { downloadId?: string; } -function HistoryDetails(props: HistoryDetailsProps) { +function HistoryDetails(props: Readonly) { const { eventType, sourceTitle, data, downloadId } = props; const { shortDateFormat, timeFormat } = useSelector( @@ -104,7 +104,7 @@ function HistoryDetails(props: HistoryDetailsProps) { {customFormatScore && customFormatScore !== '0' ? ( ) : null} @@ -230,7 +230,7 @@ function HistoryDetails(props: HistoryDetailsProps) { {customFormatScore && customFormatScore !== '0' ? ( ) : null} @@ -272,7 +272,7 @@ function HistoryDetails(props: HistoryDetailsProps) { {customFormatScore && customFormatScore !== '0' ? ( ) : null} diff --git a/frontend/src/Activity/History/Details/HistoryDetailsModal.tsx b/frontend/src/Activity/History/Details/HistoryDetailsModal.tsx index 69e4405ea0..3ea8a36b22 100644 --- a/frontend/src/Activity/History/Details/HistoryDetailsModal.tsx +++ b/frontend/src/Activity/History/Details/HistoryDetailsModal.tsx @@ -42,7 +42,7 @@ interface HistoryDetailsModalProps { onModalClose: () => void; } -function HistoryDetailsModal(props: HistoryDetailsModalProps) { +function HistoryDetailsModal(props: Readonly) { const { isOpen, eventType, diff --git a/frontend/src/Activity/History/HistoryEventTypeCell.tsx b/frontend/src/Activity/History/HistoryEventTypeCell.tsx index 5069a8e052..e78f659574 100644 --- a/frontend/src/Activity/History/HistoryEventTypeCell.tsx +++ b/frontend/src/Activity/History/HistoryEventTypeCell.tsx @@ -74,7 +74,7 @@ interface HistoryEventTypeCellProps { data: HistoryData; } -function HistoryEventTypeCell({ eventType, data }: HistoryEventTypeCellProps) { +function HistoryEventTypeCell({ eventType, data }: Readonly) { const iconName = getIconName(eventType, data); const iconKind = getIconKind(eventType); const tooltip = getTooltip(eventType, data); diff --git a/frontend/src/Activity/History/HistoryFilterModal.tsx b/frontend/src/Activity/History/HistoryFilterModal.tsx index f4ad2e57cc..66218c1c18 100644 --- a/frontend/src/Activity/History/HistoryFilterModal.tsx +++ b/frontend/src/Activity/History/HistoryFilterModal.tsx @@ -27,7 +27,7 @@ interface HistoryFilterModalProps { isOpen: boolean; } -export default function HistoryFilterModal(props: HistoryFilterModalProps) { +export default function HistoryFilterModal(props: Readonly) { const sectionItems = useSelector(createHistorySelector()); const filterBuilderProps = useSelector(createFilterBuilderPropsSelector()); const customFilterType = 'history'; diff --git a/frontend/src/Activity/History/HistoryRow.tsx b/frontend/src/Activity/History/HistoryRow.tsx index 1f253cac9b..153f9e9599 100644 --- a/frontend/src/Activity/History/HistoryRow.tsx +++ b/frontend/src/Activity/History/HistoryRow.tsx @@ -41,7 +41,7 @@ interface HistoryRowProps { columns: Column[]; } -function HistoryRow(props: HistoryRowProps) { +function HistoryRow(props: Readonly) { const { id, movieId, diff --git a/frontend/src/Activity/Queue/ProtocolLabel.tsx b/frontend/src/Activity/Queue/ProtocolLabel.tsx index c1824452a5..33ebc8a8b4 100644 --- a/frontend/src/Activity/Queue/ProtocolLabel.tsx +++ b/frontend/src/Activity/Queue/ProtocolLabel.tsx @@ -7,7 +7,7 @@ interface ProtocolLabelProps { protocol: DownloadProtocol; } -function ProtocolLabel({ protocol }: ProtocolLabelProps) { +function ProtocolLabel({ protocol }: Readonly) { const protocolName = protocol === 'usenet' ? 'nzb' : protocol; return ; diff --git a/frontend/src/Activity/Queue/Queue.tsx b/frontend/src/Activity/Queue/Queue.tsx index e1f952bd76..de8a0cf837 100644 --- a/frontend/src/Activity/Queue/Queue.tsx +++ b/frontend/src/Activity/Queue/Queue.tsx @@ -123,7 +123,7 @@ function Queue() { ); const handleSelectedChange = useCallback( - ({ id, value, shiftKey = false }: SelectStateInputProps) => { + ({ id, value, shiftKey = false }: Readonly) => { setSelectState({ type: 'toggleSelected', items, diff --git a/frontend/src/Activity/Queue/QueueDetails.tsx b/frontend/src/Activity/Queue/QueueDetails.tsx index db62de3e16..15c0dfbbf9 100644 --- a/frontend/src/Activity/Queue/QueueDetails.tsx +++ b/frontend/src/Activity/Queue/QueueDetails.tsx @@ -24,7 +24,7 @@ interface QueueDetailsProps { progressBar: React.ReactNode; } -function QueueDetails(props: QueueDetailsProps) { +function QueueDetails(props: Readonly) { const { title, size, diff --git a/frontend/src/Activity/Queue/QueueFilterModal.tsx b/frontend/src/Activity/Queue/QueueFilterModal.tsx index 3fce6c1667..7bdf399f96 100644 --- a/frontend/src/Activity/Queue/QueueFilterModal.tsx +++ b/frontend/src/Activity/Queue/QueueFilterModal.tsx @@ -27,7 +27,7 @@ interface QueueFilterModalProps { isOpen: boolean; } -export default function QueueFilterModal(props: QueueFilterModalProps) { +export default function QueueFilterModal(props: Readonly) { const sectionItems = useSelector(createQueueSelector()); const filterBuilderProps = useSelector(createFilterBuilderPropsSelector()); const customFilterType = 'queue'; diff --git a/frontend/src/Activity/Queue/QueueRow.tsx b/frontend/src/Activity/Queue/QueueRow.tsx index e61d53eeef..14b52a1cb8 100644 --- a/frontend/src/Activity/Queue/QueueRow.tsx +++ b/frontend/src/Activity/Queue/QueueRow.tsx @@ -71,7 +71,7 @@ interface QueueRowProps { onQueueRowModalOpenOrClose: (isOpen: boolean) => void; } -function QueueRow(props: QueueRowProps) { +function QueueRow(props: Readonly) { const { id, movieId, diff --git a/frontend/src/Activity/Queue/QueueStatus.tsx b/frontend/src/Activity/Queue/QueueStatus.tsx index baeae8d638..dc16d52d7d 100644 --- a/frontend/src/Activity/Queue/QueueStatus.tsx +++ b/frontend/src/Activity/Queue/QueueStatus.tsx @@ -44,7 +44,7 @@ interface QueueStatusProps { canFlip?: boolean; } -function QueueStatus(props: QueueStatusProps) { +function QueueStatus(props: Readonly) { const { sourceTitle, status, diff --git a/frontend/src/Activity/Queue/QueueStatusCell.tsx b/frontend/src/Activity/Queue/QueueStatusCell.tsx index 634e331646..9ba829c7b5 100644 --- a/frontend/src/Activity/Queue/QueueStatusCell.tsx +++ b/frontend/src/Activity/Queue/QueueStatusCell.tsx @@ -17,7 +17,7 @@ interface QueueStatusCellProps { errorMessage?: string; } -function QueueStatusCell(props: QueueStatusCellProps) { +function QueueStatusCell(props: Readonly) { const { sourceTitle, status, diff --git a/frontend/src/Activity/Queue/RemoveQueueItemModal.tsx b/frontend/src/Activity/Queue/RemoveQueueItemModal.tsx index 461fa57ad6..ac1381ff85 100644 --- a/frontend/src/Activity/Queue/RemoveQueueItemModal.tsx +++ b/frontend/src/Activity/Queue/RemoveQueueItemModal.tsx @@ -26,7 +26,7 @@ interface RemoveQueueItemModalProps { canIgnore: boolean; isPending: boolean; selectedCount?: number; - onRemovePress(props: RemovePressProps): void; + onRemovePress(props: Readonly): void; onModalClose: () => void; } @@ -36,7 +36,7 @@ type BlocklistMethod = | 'blocklistAndSearch' | 'blocklistOnly'; -function RemoveQueueItemModal(props: RemoveQueueItemModalProps) { +function RemoveQueueItemModal(props: Readonly) { const { isOpen, sourceTitle = '', diff --git a/frontend/src/Activity/Queue/TimeleftCell.tsx b/frontend/src/Activity/Queue/TimeleftCell.tsx index 917a6ad0d2..4e540a7d6b 100644 --- a/frontend/src/Activity/Queue/TimeleftCell.tsx +++ b/frontend/src/Activity/Queue/TimeleftCell.tsx @@ -21,7 +21,7 @@ interface TimeleftCellProps { timeFormat: string; } -function TimeleftCell(props: TimeleftCellProps) { +function TimeleftCell(props: Readonly) { const { estimatedCompletionTime, timeleft, diff --git a/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js b/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js index 8e5b4b4559..52e0143558 100644 --- a/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js +++ b/frontend/src/AddMovie/AddNewMovie/AddNewMovieModalContent.js @@ -24,7 +24,7 @@ class AddNewMovieModalContent extends Component { // Listeners onQualityProfileIdChange = ({ value }) => { - this.props.onInputChange({ name: 'qualityProfileId', value: parseInt(value) }); + this.props.onInputChange({ name: 'qualityProfileId', value: Number.parseInt(value) }); }; onAddMoviePress = () => { diff --git a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieConnector.js b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieConnector.js index 35972b39b3..5447cb07f8 100644 --- a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieConnector.js +++ b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieConnector.js @@ -30,7 +30,7 @@ function createMapStateToProps() { items } = rootFolders; - const rootFolderId = parseInt(match.params.rootFolderId); + const rootFolderId = Number.parseInt(match.params.rootFolderId); const result = { rootFolderId, diff --git a/frontend/src/App/App.tsx b/frontend/src/App/App.tsx index 0c7863f2e4..ee94124c63 100644 --- a/frontend/src/App/App.tsx +++ b/frontend/src/App/App.tsx @@ -15,7 +15,7 @@ interface AppProps { const queryClient = new QueryClient(); -function App({ store, history }: AppProps) { +function App({ store, history }: Readonly) { return ( diff --git a/frontend/src/App/AppUpdatedModal.tsx b/frontend/src/App/AppUpdatedModal.tsx index 696d36fb24..1435f14c7b 100644 --- a/frontend/src/App/AppUpdatedModal.tsx +++ b/frontend/src/App/AppUpdatedModal.tsx @@ -7,7 +7,7 @@ interface AppUpdatedModalProps { onModalClose: (...args: unknown[]) => unknown; } -function AppUpdatedModal(props: AppUpdatedModalProps) { +function AppUpdatedModal(props: Readonly) { const { isOpen, onModalClose } = props; const handleModalClose = useCallback(() => { diff --git a/frontend/src/App/AppUpdatedModalContent.tsx b/frontend/src/App/AppUpdatedModalContent.tsx index 6031f748fd..fbb5711942 100644 --- a/frontend/src/App/AppUpdatedModalContent.tsx +++ b/frontend/src/App/AppUpdatedModalContent.tsx @@ -62,7 +62,7 @@ interface AppUpdatedModalContentProps { onModalClose: () => void; } -function AppUpdatedModalContent(props: AppUpdatedModalContentProps) { +function AppUpdatedModalContent(props: Readonly) { const dispatch = useDispatch(); const { version, prevVersion } = useSelector((state: AppState) => state.app); const { isPopulated, error, items } = useSelector( diff --git a/frontend/src/App/ConnectionLostModal.tsx b/frontend/src/App/ConnectionLostModal.tsx index f08f2c0e20..473285b466 100644 --- a/frontend/src/App/ConnectionLostModal.tsx +++ b/frontend/src/App/ConnectionLostModal.tsx @@ -13,7 +13,7 @@ interface ConnectionLostModalProps { isOpen: boolean; } -function ConnectionLostModal(props: ConnectionLostModalProps) { +function ConnectionLostModal(props: Readonly) { const { isOpen } = props; const handleModalClose = useCallback(() => { diff --git a/frontend/src/Calendar/Agenda/AgendaEvent.tsx b/frontend/src/Calendar/Agenda/AgendaEvent.tsx index a312f1017c..31a43fec9b 100644 --- a/frontend/src/Calendar/Agenda/AgendaEvent.tsx +++ b/frontend/src/Calendar/Agenda/AgendaEvent.tsx @@ -46,7 +46,7 @@ function AgendaEvent({ hasFile, grabbed, showDate, -}: AgendaEventProps) { +}: Readonly) { const movieFile = useMovieFile(movieFileId); const queueItem = useSelector(createQueueItemSelectorForHook(id)); const { longDateFormat, enableColorImpairedMode } = useSelector( diff --git a/frontend/src/Calendar/CalendarFilterModal.tsx b/frontend/src/Calendar/CalendarFilterModal.tsx index e26b2928bb..cf7800b39b 100644 --- a/frontend/src/Calendar/CalendarFilterModal.tsx +++ b/frontend/src/Calendar/CalendarFilterModal.tsx @@ -27,7 +27,7 @@ interface CalendarFilterModalProps { isOpen: boolean; } -export default function CalendarFilterModal(props: CalendarFilterModalProps) { +export default function CalendarFilterModal(props: Readonly) { const sectionItems = useSelector(createCalendarSelector()); const filterBuilderProps = useSelector(createFilterBuilderPropsSelector()); const customFilterType = 'calendar'; diff --git a/frontend/src/Calendar/Day/CalendarDay.tsx b/frontend/src/Calendar/Day/CalendarDay.tsx index 1f02df3459..2511da9b30 100644 --- a/frontend/src/Calendar/Day/CalendarDay.tsx +++ b/frontend/src/Calendar/Day/CalendarDay.tsx @@ -53,7 +53,7 @@ interface CalendarDayProps { isTodaysDate: boolean; } -function CalendarDay({ date, isTodaysDate }: CalendarDayProps) { +function CalendarDay({ date, isTodaysDate }: Readonly) { const { time, view } = useSelector((state: AppState) => state.calendar); const events = useSelector(createCalendarEventsConnector(date)); diff --git a/frontend/src/Calendar/Day/DayOfWeek.tsx b/frontend/src/Calendar/Day/DayOfWeek.tsx index c8b493b7c9..9c7c8d271a 100644 --- a/frontend/src/Calendar/Day/DayOfWeek.tsx +++ b/frontend/src/Calendar/Day/DayOfWeek.tsx @@ -14,7 +14,7 @@ interface DayOfWeekProps { showRelativeDates: boolean; } -function DayOfWeek(props: DayOfWeekProps) { +function DayOfWeek(props: Readonly) { const { date, view, diff --git a/frontend/src/Calendar/Events/CalendarEvent.tsx b/frontend/src/Calendar/Events/CalendarEvent.tsx index c4800fceca..96641ee9c8 100644 --- a/frontend/src/Calendar/Events/CalendarEvent.tsx +++ b/frontend/src/Calendar/Events/CalendarEvent.tsx @@ -46,7 +46,7 @@ function CalendarEvent({ monitored: isMonitored, hasFile, grabbed, -}: CalendarEventProps) { +}: Readonly) { const movieFile = useMovieFile(movieFileId); const queueItem = useSelector(createQueueItemSelectorForHook(id)); diff --git a/frontend/src/Calendar/Events/CalendarEventQueueDetails.tsx b/frontend/src/Calendar/Events/CalendarEventQueueDetails.tsx index 2372bc78ee..5bff7c8738 100644 --- a/frontend/src/Calendar/Events/CalendarEventQueueDetails.tsx +++ b/frontend/src/Calendar/Events/CalendarEventQueueDetails.tsx @@ -29,7 +29,7 @@ function CalendarEventQueueDetails({ trackedDownloadStatus, statusMessages, errorMessage, -}: CalendarEventQueueDetailsProps) { +}: Readonly) { const progress = size ? 100 - (sizeleft / size) * 100 : 0; return ( diff --git a/frontend/src/Calendar/Header/CalendarHeaderViewButton.tsx b/frontend/src/Calendar/Header/CalendarHeaderViewButton.tsx index c9366f9ef8..d75943668f 100644 --- a/frontend/src/Calendar/Header/CalendarHeaderViewButton.tsx +++ b/frontend/src/Calendar/Header/CalendarHeaderViewButton.tsx @@ -15,7 +15,7 @@ function CalendarHeaderViewButton({ selectedView, onPress, ...otherProps -}: CalendarHeaderViewButtonProps) { +}: Readonly) { const handlePress = useCallback(() => { onPress(view); }, [view, onPress]); diff --git a/frontend/src/Calendar/Legend/LegendIconItem.tsx b/frontend/src/Calendar/Legend/LegendIconItem.tsx index 88a758c449..38e09c1de1 100644 --- a/frontend/src/Calendar/Legend/LegendIconItem.tsx +++ b/frontend/src/Calendar/Legend/LegendIconItem.tsx @@ -11,7 +11,7 @@ interface LegendIconItemProps extends Pick { tooltip: string; } -function LegendIconItem(props: LegendIconItemProps) { +function LegendIconItem(props: Readonly) { const { name, fullColorEvents, icon, kind, tooltip } = props; return ( diff --git a/frontend/src/Calendar/Legend/LegendItem.tsx b/frontend/src/Calendar/Legend/LegendItem.tsx index d532d85ed0..d9a10fd869 100644 --- a/frontend/src/Calendar/Legend/LegendItem.tsx +++ b/frontend/src/Calendar/Legend/LegendItem.tsx @@ -17,7 +17,7 @@ function LegendItem({ isAgendaView, fullColorEvents, colorImpairedMode, -}: LegendItemProps) { +}: Readonly) { return (
) { return ( diff --git a/frontend/src/Calendar/Options/CalendarOptionsModalContent.tsx b/frontend/src/Calendar/Options/CalendarOptionsModalContent.tsx index 32806092a4..60c2af3629 100644 --- a/frontend/src/Calendar/Options/CalendarOptionsModalContent.tsx +++ b/frontend/src/Calendar/Options/CalendarOptionsModalContent.tsx @@ -30,7 +30,7 @@ interface CalendarOptionsModalContentProps { function CalendarOptionsModalContent({ onModalClose, -}: CalendarOptionsModalContentProps) { +}: Readonly) { const dispatch = useDispatch(); const { diff --git a/frontend/src/Calendar/iCal/CalendarLinkModal.tsx b/frontend/src/Calendar/iCal/CalendarLinkModal.tsx index f0eecbd4a4..21b249df02 100644 --- a/frontend/src/Calendar/iCal/CalendarLinkModal.tsx +++ b/frontend/src/Calendar/iCal/CalendarLinkModal.tsx @@ -7,7 +7,7 @@ interface CalendarLinkModalProps { onModalClose: () => void; } -function CalendarLinkModal(props: CalendarLinkModalProps) { +function CalendarLinkModal(props: Readonly) { const { isOpen, onModalClose } = props; return ( diff --git a/frontend/src/Calendar/iCal/CalendarLinkModalContent.tsx b/frontend/src/Calendar/iCal/CalendarLinkModalContent.tsx index 6439c494a1..c3d87bded1 100644 --- a/frontend/src/Calendar/iCal/CalendarLinkModalContent.tsx +++ b/frontend/src/Calendar/iCal/CalendarLinkModalContent.tsx @@ -43,7 +43,7 @@ interface CalendarLinkModalContentProps { function CalendarLinkModalContent({ onModalClose, -}: CalendarLinkModalContentProps) { +}: Readonly) { const [state, setState] = useState<{ unmonitored: boolean; asAllDay: boolean; diff --git a/frontend/src/Collection/AddNewMovieCollectionMovieModal.tsx b/frontend/src/Collection/AddNewMovieCollectionMovieModal.tsx index 656d9db7fe..261f1ff8dc 100644 --- a/frontend/src/Collection/AddNewMovieCollectionMovieModal.tsx +++ b/frontend/src/Collection/AddNewMovieCollectionMovieModal.tsx @@ -16,7 +16,7 @@ function AddNewMovieCollectionMovieModal({ isOpen, onModalClose, ...otherProps -}: AddNewCollectionMovieModalProps) { +}: Readonly) { const dispatch = useDispatch(); const wasOpen = usePrevious(isOpen); diff --git a/frontend/src/Collection/AddNewMovieCollectionMovieModalContent.tsx b/frontend/src/Collection/AddNewMovieCollectionMovieModalContent.tsx index b622dc8b7a..351f0ca90c 100644 --- a/frontend/src/Collection/AddNewMovieCollectionMovieModalContent.tsx +++ b/frontend/src/Collection/AddNewMovieCollectionMovieModalContent.tsx @@ -50,7 +50,7 @@ function AddNewMovieCollectionMovieModalContent({ collectionId, folder, onModalClose, -}: AddNewMovieCollectionMovieModalContentProps) { +}: Readonly) { const dispatch = useDispatch(); const collection = useMovieCollection(collectionId)!; diff --git a/frontend/src/Collection/Collection.js b/frontend/src/Collection/Collection.js index 7982cafd7f..d493ccb05e 100644 --- a/frontend/src/Collection/Collection.js +++ b/frontend/src/Collection/Collection.js @@ -138,7 +138,7 @@ class Collection extends Component { const characters = _.reduce(items, (acc, item) => { let char = item.sortTitle.charAt(0); - if (!isNaN(char)) { + if (!Number.isNaN(char)) { char = '#'; } diff --git a/frontend/src/Collection/CollectionFooter.tsx b/frontend/src/Collection/CollectionFooter.tsx index ad3c86ac35..e29d8edf32 100644 --- a/frontend/src/Collection/CollectionFooter.tsx +++ b/frontend/src/Collection/CollectionFooter.tsx @@ -77,7 +77,7 @@ function CollectionFooter({ isSaving, saveError, onUpdateSelectedPress, -}: CollectionFooterProps) { +}: Readonly) { const [monitored, setMonitored] = useState(NO_CHANGE); const [monitor, setMonitor] = useState(NO_CHANGE); const [qualityProfileId, setQualityProfileId] = useState( diff --git a/frontend/src/Collection/CollectionFooterLabel.tsx b/frontend/src/Collection/CollectionFooterLabel.tsx index 97c938fbd8..cdc3350fa5 100644 --- a/frontend/src/Collection/CollectionFooterLabel.tsx +++ b/frontend/src/Collection/CollectionFooterLabel.tsx @@ -13,7 +13,7 @@ function CollectionFooterLabel({ className = styles.label, label, isSaving, -}: CollectionFooterLabelProps) { +}: Readonly) { return (
{label} diff --git a/frontend/src/Collection/Edit/EditMovieCollectionModal.tsx b/frontend/src/Collection/Edit/EditMovieCollectionModal.tsx index 02c3c7b21a..207a69affc 100644 --- a/frontend/src/Collection/Edit/EditMovieCollectionModal.tsx +++ b/frontend/src/Collection/Edit/EditMovieCollectionModal.tsx @@ -15,7 +15,7 @@ function EditMovieCollectionModal({ isOpen, onModalClose, ...otherProps -}: EditMovieCollectionModalProps) { +}: Readonly) { const dispatch = useDispatch(); const handleModalClose = useCallback(() => { diff --git a/frontend/src/Collection/Edit/EditMovieCollectionModalContent.tsx b/frontend/src/Collection/Edit/EditMovieCollectionModalContent.tsx index 6d8dc1ba50..e205cd1eaa 100644 --- a/frontend/src/Collection/Edit/EditMovieCollectionModalContent.tsx +++ b/frontend/src/Collection/Edit/EditMovieCollectionModalContent.tsx @@ -33,7 +33,7 @@ export interface EditMovieCollectionModalContentProps { function EditMovieCollectionModalContent({ collectionId, onModalClose, -}: EditMovieCollectionModalContentProps) { +}: Readonly) { const dispatch = useDispatch(); const { diff --git a/frontend/src/Collection/Menus/MovieCollectionFilterMenu.tsx b/frontend/src/Collection/Menus/MovieCollectionFilterMenu.tsx index 24059c5c65..9c60ebdc3b 100644 --- a/frontend/src/Collection/Menus/MovieCollectionFilterMenu.tsx +++ b/frontend/src/Collection/Menus/MovieCollectionFilterMenu.tsx @@ -17,7 +17,7 @@ function MovieCollectionFilterMenu({ customFilters, isDisabled, onFilterSelect, -}: MovieCollectionFilterMenuProps) { +}: Readonly) { return ( ) { return ( diff --git a/frontend/src/Collection/NoMovieCollections.tsx b/frontend/src/Collection/NoMovieCollections.tsx index d4ccfa8cd3..d18742eeac 100644 --- a/frontend/src/Collection/NoMovieCollections.tsx +++ b/frontend/src/Collection/NoMovieCollections.tsx @@ -8,7 +8,7 @@ interface NoMovieCollectionsProps { totalItems: number; } -function NoMovieCollections({ totalItems }: NoMovieCollectionsProps) { +function NoMovieCollections({ totalItems }: Readonly) { if (totalItems > 0) { return (
diff --git a/frontend/src/Collection/Overview/CollectionOverview.js b/frontend/src/Collection/Overview/CollectionOverview.js index 97ec71a035..4f674fa776 100644 --- a/frontend/src/Collection/Overview/CollectionOverview.js +++ b/frontend/src/Collection/Overview/CollectionOverview.js @@ -23,10 +23,10 @@ import styles from './CollectionOverview.css'; import 'swiper/css'; import 'swiper/css/navigation'; -const columnPadding = parseInt(dimensions.movieIndexColumnPadding); -const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen); -const defaultFontSize = parseInt(fonts.defaultFontSize); -const lineHeight = parseFloat(fonts.lineHeight); +const columnPadding = Number.parseInt(dimensions.movieIndexColumnPadding); +const columnPaddingSmallScreen = Number.parseInt(dimensions.movieIndexColumnPaddingSmallScreen); +const defaultFontSize = Number.parseInt(fonts.defaultFontSize); +const lineHeight = Number.parseFloat(fonts.lineHeight); // Hardcoded height beased on line-height of 32 + bottom margin of 10. 19 + 5 for List Row // Less side-effecty than using react-measure. diff --git a/frontend/src/Collection/Overview/CollectionOverviews.js b/frontend/src/Collection/Overview/CollectionOverviews.js index 8d85d34ff7..aa503619ae 100644 --- a/frontend/src/Collection/Overview/CollectionOverviews.js +++ b/frontend/src/Collection/Overview/CollectionOverviews.js @@ -10,8 +10,8 @@ import CollectionOverviewConnector from './CollectionOverviewConnector'; import styles from './CollectionOverviews.css'; // Poster container dimensions -const columnPadding = parseInt(dimensions.movieIndexColumnPadding); -const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen); +const columnPadding = Number.parseInt(dimensions.movieIndexColumnPadding); +const columnPaddingSmallScreen = Number.parseInt(dimensions.movieIndexColumnPaddingSmallScreen); function calculatePosterWidth(posterSize, isSmallScreen) { const maximumPosterWidth = isSmallScreen ? 152 : 162; diff --git a/frontend/src/Components/Alert.tsx b/frontend/src/Components/Alert.tsx index 92c89e7413..1b9ae85fe7 100644 --- a/frontend/src/Components/Alert.tsx +++ b/frontend/src/Components/Alert.tsx @@ -9,7 +9,7 @@ interface AlertProps { children: React.ReactNode; } -function Alert(props: AlertProps) { +function Alert(props: Readonly) { const { className = styles.alert, kind = 'info', children } = props; return
{children}
; diff --git a/frontend/src/Components/Card.tsx b/frontend/src/Components/Card.tsx index 24588c841c..59e8c30037 100644 --- a/frontend/src/Components/Card.tsx +++ b/frontend/src/Components/Card.tsx @@ -10,7 +10,7 @@ interface CardProps extends Pick { children: React.ReactNode; } -function Card(props: CardProps) { +function Card(props: Readonly) { const { className = styles.card, overlayClassName = styles.overlay, diff --git a/frontend/src/Components/CircularProgressBar.tsx b/frontend/src/Components/CircularProgressBar.tsx index bad48f83e2..e47e833e2b 100644 --- a/frontend/src/Components/CircularProgressBar.tsx +++ b/frontend/src/Components/CircularProgressBar.tsx @@ -19,7 +19,7 @@ function CircularProgressBar({ strokeColor = '#ffc230', showProgressText = false, progress, -}: CircularProgressBarProps) { +}: Readonly) { const [currentProgress, setCurrentProgress] = useState(0); const raf = React.useRef(0); const center = size / 2; diff --git a/frontend/src/Components/DescriptionList/DescriptionList.tsx b/frontend/src/Components/DescriptionList/DescriptionList.tsx index 6deee77e5e..7104263520 100644 --- a/frontend/src/Components/DescriptionList/DescriptionList.tsx +++ b/frontend/src/Components/DescriptionList/DescriptionList.tsx @@ -6,7 +6,7 @@ interface DescriptionListProps { children?: React.ReactNode; } -function DescriptionList(props: DescriptionListProps) { +function DescriptionList(props: Readonly) { const { className = styles.descriptionList, children } = props; return
{children}
; diff --git a/frontend/src/Components/DescriptionList/DescriptionListItem.tsx b/frontend/src/Components/DescriptionList/DescriptionListItem.tsx index 13a7efdd03..f77d69826e 100644 --- a/frontend/src/Components/DescriptionList/DescriptionListItem.tsx +++ b/frontend/src/Components/DescriptionList/DescriptionListItem.tsx @@ -14,7 +14,7 @@ interface DescriptionListItemProps { data?: DescriptionListItemDescriptionProps['children']; } -function DescriptionListItem(props: DescriptionListItemProps) { +function DescriptionListItem(props: Readonly) { const { className, titleClassName, descriptionClassName, title, data } = props; diff --git a/frontend/src/Components/DescriptionList/DescriptionListItemTitle.tsx b/frontend/src/Components/DescriptionList/DescriptionListItemTitle.tsx index 59ea6955c0..f4b137a4eb 100644 --- a/frontend/src/Components/DescriptionList/DescriptionListItemTitle.tsx +++ b/frontend/src/Components/DescriptionList/DescriptionListItemTitle.tsx @@ -6,7 +6,7 @@ export interface DescriptionListItemTitleProps { children?: ReactNode; } -function DescriptionListItemTitle(props: DescriptionListItemTitleProps) { +function DescriptionListItemTitle(props: Readonly) { const { className = styles.title, children } = props; return
{children}
; diff --git a/frontend/src/Components/DragPreviewLayer.tsx b/frontend/src/Components/DragPreviewLayer.tsx index 2e578504bc..b7e03c7672 100644 --- a/frontend/src/Components/DragPreviewLayer.tsx +++ b/frontend/src/Components/DragPreviewLayer.tsx @@ -10,7 +10,7 @@ function DragPreviewLayer({ className = styles.dragLayer, children, ...otherProps -}: DragPreviewLayerProps) { +}: Readonly) { return (
{children} diff --git a/frontend/src/Components/Error/ErrorBoundary.tsx b/frontend/src/Components/Error/ErrorBoundary.tsx index 3dd9ebff2f..f2eb179488 100644 --- a/frontend/src/Components/Error/ErrorBoundary.tsx +++ b/frontend/src/Components/Error/ErrorBoundary.tsx @@ -14,7 +14,7 @@ interface ErrorBoundaryState { // Class component until componentDidCatch is supported in functional components class ErrorBoundary extends Component { - constructor(props: ErrorBoundaryProps) { + constructor(props: Readonly) { super(props); this.state = { diff --git a/frontend/src/Components/Error/ErrorBoundaryError.tsx b/frontend/src/Components/Error/ErrorBoundaryError.tsx index 708172dda7..1a4d71745d 100644 --- a/frontend/src/Components/Error/ErrorBoundaryError.tsx +++ b/frontend/src/Components/Error/ErrorBoundaryError.tsx @@ -14,7 +14,7 @@ export interface ErrorBoundaryErrorProps { }; } -function ErrorBoundaryError(props: ErrorBoundaryErrorProps) { +function ErrorBoundaryError(props: Readonly) { const { className = styles.container, messageClassName = styles.message, diff --git a/frontend/src/Components/FieldSet.tsx b/frontend/src/Components/FieldSet.tsx index c2ff03a7f5..791b603a86 100644 --- a/frontend/src/Components/FieldSet.tsx +++ b/frontend/src/Components/FieldSet.tsx @@ -10,7 +10,7 @@ interface FieldSetProps { children?: React.ReactNode; } -function FieldSet({ size = sizes.MEDIUM, legend, children }: FieldSetProps) { +function FieldSet({ size = sizes.MEDIUM, legend, children }: Readonly) { return (
void; } -function FileBrowserModal(props: FileBrowserModalProps) { +function FileBrowserModal(props: Readonly) { const { isOpen, onModalClose, ...otherProps } = props; return ( diff --git a/frontend/src/Components/FileBrowser/FileBrowserModalContent.tsx b/frontend/src/Components/FileBrowser/FileBrowserModalContent.tsx index 7b2b9acf48..9b0d814e2f 100644 --- a/frontend/src/Components/FileBrowser/FileBrowserModalContent.tsx +++ b/frontend/src/Components/FileBrowser/FileBrowserModalContent.tsx @@ -46,7 +46,7 @@ export interface FileBrowserModalContentProps { onModalClose: () => void; } -function FileBrowserModalContent(props: FileBrowserModalContentProps) { +function FileBrowserModalContent(props: Readonly) { const { name, value, includeFiles = true, onChange, onModalClose } = props; const dispatch = useDispatch(); diff --git a/frontend/src/Components/FileBrowser/FileBrowserRow.tsx b/frontend/src/Components/FileBrowser/FileBrowserRow.tsx index fe47f1664f..d39d140d95 100644 --- a/frontend/src/Components/FileBrowser/FileBrowserRow.tsx +++ b/frontend/src/Components/FileBrowser/FileBrowserRow.tsx @@ -28,7 +28,7 @@ interface FileBrowserRowProps { onPress: (path: string) => void; } -function FileBrowserRow(props: FileBrowserRowProps) { +function FileBrowserRow(props: Readonly) { const { type, name, path, onPress } = props; const handlePress = useCallback(() => { diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRowValue.js b/frontend/src/Components/Filter/Builder/FilterBuilderRowValue.js index edfcfc7f79..a7b5f319b3 100644 --- a/frontend/src/Components/Filter/Builder/FilterBuilderRowValue.js +++ b/frontend/src/Components/Filter/Builder/FilterBuilderRowValue.js @@ -50,7 +50,7 @@ function getValue(input, selectedFilterBuilderProp) { case 'tib': return convertToBytes(value, 4, true); default: - return parseInt(value); + return Number.parseInt(value); } } } diff --git a/frontend/src/Components/Filter/Builder/LanguageFilterBuilderRowValue.tsx b/frontend/src/Components/Filter/Builder/LanguageFilterBuilderRowValue.tsx index e828fd8483..01daeda274 100644 --- a/frontend/src/Components/Filter/Builder/LanguageFilterBuilderRowValue.tsx +++ b/frontend/src/Components/Filter/Builder/LanguageFilterBuilderRowValue.tsx @@ -4,7 +4,7 @@ import createLanguagesSelector from 'Store/Selectors/createLanguagesSelector'; import FilterBuilderRowValue from './FilterBuilderRowValue'; import FilterBuilderRowValueProps from './FilterBuilderRowValueProps'; -function LanguageFilterBuilderRowValue(props: FilterBuilderRowValueProps) { +function LanguageFilterBuilderRowValue(props: Readonly) { const { items } = useSelector(createLanguagesSelector()); return ; diff --git a/frontend/src/Components/Filter/Builder/MovieFilterBuilderRowValue.tsx b/frontend/src/Components/Filter/Builder/MovieFilterBuilderRowValue.tsx index 509d4e2a21..0b658687a1 100644 --- a/frontend/src/Components/Filter/Builder/MovieFilterBuilderRowValue.tsx +++ b/frontend/src/Components/Filter/Builder/MovieFilterBuilderRowValue.tsx @@ -6,7 +6,7 @@ import sortByProp from 'Utilities/Array/sortByProp'; import FilterBuilderRowValue from './FilterBuilderRowValue'; import FilterBuilderRowValueProps from './FilterBuilderRowValueProps'; -function MovieFilterBuilderRowValue(props: FilterBuilderRowValueProps) { +function MovieFilterBuilderRowValue(props: Readonly) { const allMovies: Movie[] = useSelector(createAllMoviesSelector()); const tagList = allMovies diff --git a/frontend/src/Components/Filter/Builder/QueueStatusFilterBuilderRowValue.tsx b/frontend/src/Components/Filter/Builder/QueueStatusFilterBuilderRowValue.tsx index 1127493a5c..d5ea15b73f 100644 --- a/frontend/src/Components/Filter/Builder/QueueStatusFilterBuilderRowValue.tsx +++ b/frontend/src/Components/Filter/Builder/QueueStatusFilterBuilderRowValue.tsx @@ -60,7 +60,7 @@ const statusTagList = [ }, ]; -function QueueStatusFilterBuilderRowValue(props: FilterBuilderRowValueProps) { +function QueueStatusFilterBuilderRowValue(props: Readonly) { return ; } diff --git a/frontend/src/Components/Form/AutoCompleteInput.tsx b/frontend/src/Components/Form/AutoCompleteInput.tsx index 226b40c458..378edc3f94 100644 --- a/frontend/src/Components/Form/AutoCompleteInput.tsx +++ b/frontend/src/Components/Form/AutoCompleteInput.tsx @@ -21,7 +21,7 @@ function AutoCompleteInput({ values, onChange, ...otherProps -}: AutoCompleteInputProps) { +}: Readonly) { const [suggestions, setSuggestions] = useState([]); const getSuggestionValue = useCallback((item: string) => { diff --git a/frontend/src/Components/Form/CaptchaInput.tsx b/frontend/src/Components/Form/CaptchaInput.tsx index 597b1ad4f0..75d0a3dd1f 100644 --- a/frontend/src/Components/Form/CaptchaInput.tsx +++ b/frontend/src/Components/Form/CaptchaInput.tsx @@ -42,7 +42,7 @@ function CaptchaInput({ siteKey, secretToken, onChange, -}: CaptchaInputProps) { +}: Readonly) { const { token } = useSelector((state: AppState) => state.captcha); const dispatch = useDispatch(); const previousToken = usePrevious(token); diff --git a/frontend/src/Components/Form/CheckInput.tsx b/frontend/src/Components/Form/CheckInput.tsx index 107beaa580..3d43fd1ab3 100644 --- a/frontend/src/Components/Form/CheckInput.tsx +++ b/frontend/src/Components/Form/CheckInput.tsx @@ -25,7 +25,7 @@ export interface CheckInputProps { onChange: (changes: CheckInputChanged) => void; } -function CheckInput(props: CheckInputProps) { +function CheckInput(props: Readonly) { const { className = styles.input, containerClassName = styles.container, diff --git a/frontend/src/Components/Form/Form.tsx b/frontend/src/Components/Form/Form.tsx index 055c8f80a6..3883d569af 100644 --- a/frontend/src/Components/Form/Form.tsx +++ b/frontend/src/Components/Form/Form.tsx @@ -16,7 +16,7 @@ function Form({ children, validationErrors = [], validationWarnings = [], -}: FormProps) { +}: Readonly) { return (
{validationErrors.length || validationWarnings.length ? ( diff --git a/frontend/src/Components/Form/FormGroup.tsx b/frontend/src/Components/Form/FormGroup.tsx index 1dd879897a..b76b0cfbfd 100644 --- a/frontend/src/Components/Form/FormGroup.tsx +++ b/frontend/src/Components/Form/FormGroup.tsx @@ -11,7 +11,7 @@ interface FormGroupProps extends ComponentPropsWithoutRef<'div'> { isAdvanced?: boolean; } -function FormGroup(props: FormGroupProps) { +function FormGroup(props: Readonly) { const { className = styles.group, children, diff --git a/frontend/src/Components/Form/FormInputButton.tsx b/frontend/src/Components/Form/FormInputButton.tsx index 1235010ebb..0b53109a37 100644 --- a/frontend/src/Components/Form/FormInputButton.tsx +++ b/frontend/src/Components/Form/FormInputButton.tsx @@ -18,7 +18,7 @@ function FormInputButton({ isSpinning = false, kind = kinds.PRIMARY, ...otherProps -}: FormInputButtonProps) { +}: Readonly) { if (canSpin) { return ( ) { return (
) { const { children, className = styles.label, diff --git a/frontend/src/Components/Form/KeyValueListInput.tsx b/frontend/src/Components/Form/KeyValueListInput.tsx index f5c6ac19be..c19f4e1530 100644 --- a/frontend/src/Components/Form/KeyValueListInput.tsx +++ b/frontend/src/Components/Form/KeyValueListInput.tsx @@ -29,7 +29,7 @@ function KeyValueListInput({ keyPlaceholder, valuePlaceholder, onChange, -}: KeyValueListInputProps): JSX.Element { +}: Readonly): JSX.Element { const [isFocused, setIsFocused] = useState(false); const handleItemChange = useCallback( diff --git a/frontend/src/Components/Form/KeyValueListInputItem.tsx b/frontend/src/Components/Form/KeyValueListInputItem.tsx index c63ad50a92..27823955b4 100644 --- a/frontend/src/Components/Form/KeyValueListInputItem.tsx +++ b/frontend/src/Components/Form/KeyValueListInputItem.tsx @@ -28,7 +28,7 @@ function KeyValueListInputItem({ onRemove, onFocus, onBlur, -}: KeyValueListInputItemProps): JSX.Element { +}: Readonly): JSX.Element { const handleKeyChange = useCallback( ({ value: keyValue }: { value: string }) => { onChange(index, { key: keyValue, value }); diff --git a/frontend/src/Components/Form/NumberInput.tsx b/frontend/src/Components/Form/NumberInput.tsx index 875b81b5c9..d4e2a26833 100644 --- a/frontend/src/Components/Form/NumberInput.tsx +++ b/frontend/src/Components/Form/NumberInput.tsx @@ -13,7 +13,7 @@ function parseValue( return null; } - let newValue = isFloat ? parseFloat(value) : parseInt(value); + let newValue = isFloat ? Number.parseFloat(value) : Number.parseInt(value); if (min != null && newValue != null && newValue < min) { newValue = min; @@ -41,7 +41,7 @@ function NumberInput({ max, onChange, ...otherProps -}: NumberInputProps) { +}: Readonly) { const [value, setValue] = useState( inputValue == null ? '' : inputValue.toString() ); @@ -82,8 +82,7 @@ function NumberInput({ useEffect(() => { if ( - // @ts-expect-error inputValue may be null - !isNaN(inputValue) && + !Number.isNaN(inputValue) && inputValue !== previousValue && !isFocused.current ) { diff --git a/frontend/src/Components/Form/OAuthInput.tsx b/frontend/src/Components/Form/OAuthInput.tsx index 19bc9ba23c..7e229c89a7 100644 --- a/frontend/src/Components/Form/OAuthInput.tsx +++ b/frontend/src/Components/Form/OAuthInput.tsx @@ -22,7 +22,7 @@ function OAuthInput({ providerData, section, onChange, -}: OAuthInputProps) { +}: Readonly) { const dispatch = useDispatch(); const { authorizing, error, result } = useSelector( (state: AppState) => state.oAuth diff --git a/frontend/src/Components/Form/PasswordInput.tsx b/frontend/src/Components/Form/PasswordInput.tsx index 98da46e7ec..6c088c42d0 100644 --- a/frontend/src/Components/Form/PasswordInput.tsx +++ b/frontend/src/Components/Form/PasswordInput.tsx @@ -7,7 +7,7 @@ function onCopy(e: SyntheticEvent) { e.nativeEvent.stopImmediatePropagation(); } -function PasswordInput(props: TextInputProps) { +function PasswordInput(props: Readonly) { return ; } diff --git a/frontend/src/Components/Form/PathInput.tsx b/frontend/src/Components/Form/PathInput.tsx index 015b835e31..24cb125aa7 100644 --- a/frontend/src/Components/Form/PathInput.tsx +++ b/frontend/src/Components/Form/PathInput.tsx @@ -61,7 +61,7 @@ function createPathsSelector() { ); } -function PathInput(props: PathInputProps) { +function PathInput(props: Readonly) { const { includeFiles } = props; const dispatch = useDispatch(); @@ -91,7 +91,7 @@ function PathInput(props: PathInputProps) { export default PathInput; -export function PathInputInternal(props: PathInputInternalProps) { +export function PathInputInternal(props: Readonly) { const { className = styles.inputWrapper, name, diff --git a/frontend/src/Components/Form/Select/AvailabilitySelectInput.tsx b/frontend/src/Components/Form/Select/AvailabilitySelectInput.tsx index cba94f5a61..a0a6d93540 100644 --- a/frontend/src/Components/Form/Select/AvailabilitySelectInput.tsx +++ b/frontend/src/Components/Form/Select/AvailabilitySelectInput.tsx @@ -42,7 +42,7 @@ const movieAvailabilityOptions: IMovieAvailabilityOption[] = [ }, ]; -function AvailabilitySelectInput(props: AvailabilitySelectInputProps) { +function AvailabilitySelectInput(props: Readonly) { const { includeNoChange = false, includeNoChangeDisabled = true, diff --git a/frontend/src/Components/Form/Select/DownloadClientSelectInput.tsx b/frontend/src/Components/Form/Select/DownloadClientSelectInput.tsx index 4dca13db74..42c99e4dc2 100644 --- a/frontend/src/Components/Form/Select/DownloadClientSelectInput.tsx +++ b/frontend/src/Components/Form/Select/DownloadClientSelectInput.tsx @@ -67,7 +67,7 @@ function DownloadClientSelectInput({ includeAny = false, protocol = 'torrent', ...otherProps -}: DownloadClientSelectInputProps) { +}: Readonly) { const dispatch = useDispatch(); const { isFetching, isPopulated, values } = useSelector( createDownloadClientsSelector(includeAny, protocol) diff --git a/frontend/src/Components/Form/Select/EnhancedSelectInputOption.tsx b/frontend/src/Components/Form/Select/EnhancedSelectInputOption.tsx index c866a5060a..42d5b2238b 100644 --- a/frontend/src/Components/Form/Select/EnhancedSelectInputOption.tsx +++ b/frontend/src/Components/Form/Select/EnhancedSelectInputOption.tsx @@ -34,7 +34,7 @@ function EnhancedSelectInputOption({ isMobile, children, onSelect, -}: EnhancedSelectInputOptionProps) { +}: Readonly) { const handlePress = useCallback( (event: SyntheticEvent) => { event.preventDefault(); diff --git a/frontend/src/Components/Form/Select/EnhancedSelectInputSelectedValue.tsx b/frontend/src/Components/Form/Select/EnhancedSelectInputSelectedValue.tsx index 88afdb18a4..b27625bcea 100644 --- a/frontend/src/Components/Form/Select/EnhancedSelectInputSelectedValue.tsx +++ b/frontend/src/Components/Form/Select/EnhancedSelectInputSelectedValue.tsx @@ -12,7 +12,7 @@ function EnhancedSelectInputSelectedValue({ className = styles.selectedValue, children, isDisabled = false, -}: EnhancedSelectInputSelectedValueProps) { +}: Readonly) { return (
{children} diff --git a/frontend/src/Components/Form/Select/HintedSelectInputOption.tsx b/frontend/src/Components/Form/Select/HintedSelectInputOption.tsx index 3bae0b0634..9aa61869ce 100644 --- a/frontend/src/Components/Form/Select/HintedSelectInputOption.tsx +++ b/frontend/src/Components/Form/Select/HintedSelectInputOption.tsx @@ -13,7 +13,7 @@ interface HintedSelectInputOptionProps isSelected?: boolean; } -function HintedSelectInputOption(props: HintedSelectInputOptionProps) { +function HintedSelectInputOption(props: Readonly) { const { id, value, diff --git a/frontend/src/Components/Form/Select/IndexerFlagsSelectInput.tsx b/frontend/src/Components/Form/Select/IndexerFlagsSelectInput.tsx index e4f149d3ca..44d98fc809 100644 --- a/frontend/src/Components/Form/Select/IndexerFlagsSelectInput.tsx +++ b/frontend/src/Components/Form/Select/IndexerFlagsSelectInput.tsx @@ -41,7 +41,7 @@ function IndexerFlagsSelectInput({ indexerFlags, onChange, ...otherProps -}: IndexerFlagsSelectInputProps) { +}: Readonly) { const { value, values } = useSelector(selectIndexerFlagsValues(indexerFlags)); const handleChange = useCallback( diff --git a/frontend/src/Components/Form/Select/IndexerSelectInput.tsx b/frontend/src/Components/Form/Select/IndexerSelectInput.tsx index f4c7f4bb52..3c50a87568 100644 --- a/frontend/src/Components/Form/Select/IndexerSelectInput.tsx +++ b/frontend/src/Components/Form/Select/IndexerSelectInput.tsx @@ -50,7 +50,7 @@ function IndexerSelectInput({ value, includeAny = false, onChange, -}: IndexerSelectInputProps) { +}: Readonly) { const dispatch = useDispatch(); const { isFetching, isPopulated, values } = useSelector( createIndexersSelector(includeAny) diff --git a/frontend/src/Components/Form/Select/LanguageSelectInput.tsx b/frontend/src/Components/Form/Select/LanguageSelectInput.tsx index 179debb51d..fc482e197f 100644 --- a/frontend/src/Components/Form/Select/LanguageSelectInput.tsx +++ b/frontend/src/Components/Form/Select/LanguageSelectInput.tsx @@ -15,7 +15,7 @@ function LanguageSelectInput({ values, onChange, ...otherProps -}: LanguageSelectInputProps) { +}: Readonly) { const mappedValues = useMemo(() => { const minId = values.reduce( (min: number, v) => (v.key < 1 ? v.key : min), diff --git a/frontend/src/Components/Form/Select/MonitorMoviesSelectInput.tsx b/frontend/src/Components/Form/Select/MonitorMoviesSelectInput.tsx index 69d105d6bf..6ca79850dd 100644 --- a/frontend/src/Components/Form/Select/MonitorMoviesSelectInput.tsx +++ b/frontend/src/Components/Form/Select/MonitorMoviesSelectInput.tsx @@ -15,7 +15,7 @@ export interface MonitorMoviesSelectInputProps includeMixed?: boolean; } -function MonitorMoviesSelectInput(props: MonitorMoviesSelectInputProps) { +function MonitorMoviesSelectInput(props: Readonly) { const { includeNoChange = false, includeMixed = false, diff --git a/frontend/src/Components/Form/Select/ProviderOptionSelectInput.tsx b/frontend/src/Components/Form/Select/ProviderOptionSelectInput.tsx index f85fcc8f3e..1f5fffe4cc 100644 --- a/frontend/src/Components/Form/Select/ProviderOptionSelectInput.tsx +++ b/frontend/src/Components/Form/Select/ProviderOptionSelectInput.tsx @@ -86,7 +86,7 @@ function ProviderOptionSelectInput({ providerData, selectOptionsProviderAction, ...otherProps -}: ProviderOptionSelectInputProps) { +}: Readonly) { const dispatch = useDispatch(); const [isRefetchRequired, setIsRefetchRequired] = useState(false); const previousProviderData = usePrevious(providerData); diff --git a/frontend/src/Components/Form/Select/QualityProfileSelectInput.tsx b/frontend/src/Components/Form/Select/QualityProfileSelectInput.tsx index a1dcc6dbce..b31af8768e 100644 --- a/frontend/src/Components/Form/Select/QualityProfileSelectInput.tsx +++ b/frontend/src/Components/Form/Select/QualityProfileSelectInput.tsx @@ -78,7 +78,7 @@ function QualityProfileSelectInput({ includeMixed = false, onChange, ...otherProps -}: QualityProfileSelectInputProps) { +}: Readonly) { const values = useSelector( createQualityProfilesSelector( includeNoChange, diff --git a/frontend/src/Components/Form/Select/RootFolderSelectInput.tsx b/frontend/src/Components/Form/Select/RootFolderSelectInput.tsx index dc199d5ef7..92064405a8 100644 --- a/frontend/src/Components/Form/Select/RootFolderSelectInput.tsx +++ b/frontend/src/Components/Form/Select/RootFolderSelectInput.tsx @@ -105,7 +105,7 @@ function RootFolderSelectInput({ includeNoChangeDisabled = true, onChange, ...otherProps -}: RootFolderSelectInputProps) { +}: Readonly) { const dispatch = useDispatch(); const { values, isSaving, saveError } = useSelector( createRootFolderOptionsSelector( diff --git a/frontend/src/Components/Form/Select/RootFolderSelectInputOption.tsx b/frontend/src/Components/Form/Select/RootFolderSelectInputOption.tsx index f386127842..e7856ccd45 100644 --- a/frontend/src/Components/Form/Select/RootFolderSelectInputOption.tsx +++ b/frontend/src/Components/Form/Select/RootFolderSelectInputOption.tsx @@ -27,7 +27,7 @@ function RootFolderSelectInputOption({ isMobile, isWindows, ...otherProps -}: RootFolderSelectInputOptionProps) { +}: Readonly) { const slashCharacter = isWindows ? '\\' : '/'; return ( diff --git a/frontend/src/Components/Form/Select/RootFolderSelectInputSelectedValue.tsx b/frontend/src/Components/Form/Select/RootFolderSelectInputSelectedValue.tsx index a262b13b19..d55181346e 100644 --- a/frontend/src/Components/Form/Select/RootFolderSelectInputSelectedValue.tsx +++ b/frontend/src/Components/Form/Select/RootFolderSelectInputSelectedValue.tsx @@ -20,7 +20,7 @@ function RootFolderSelectInputSelectedValue({ includeFreeSpace = true, isWindows, ...otherProps -}: RootFolderSelectInputSelectedValueProps) { +}: Readonly) { const slashCharacter = isWindows ? '\\' : '/'; const { value, freeSpace, isMissing } = values.find((v) => v.key === selectedValue) || diff --git a/frontend/src/Components/Form/Select/UMaskInput.tsx b/frontend/src/Components/Form/Select/UMaskInput.tsx index 2f528ba912..08a6dc28b0 100644 --- a/frontend/src/Components/Form/Select/UMaskInput.tsx +++ b/frontend/src/Components/Form/Select/UMaskInput.tsx @@ -76,8 +76,8 @@ export interface UMaskInputProps { onBlur?: (event: SyntheticEvent) => void; } -function UMaskInput({ name, value, onChange }: UMaskInputProps) { - const valueNum = parseInt(value, 8); +function UMaskInput({ name, value, onChange }: Readonly) { + const valueNum = Number.parseInt(value, 8); const umaskNum = 0o777 & ~valueNum; const umask = umaskNum.toString(8).padStart(4, '0'); const folderNum = 0o777 & ~umaskNum; diff --git a/frontend/src/Components/Form/Tag/DeviceInput.tsx b/frontend/src/Components/Form/Tag/DeviceInput.tsx index 2297760596..b233713181 100644 --- a/frontend/src/Components/Form/Tag/DeviceInput.tsx +++ b/frontend/src/Components/Form/Tag/DeviceInput.tsx @@ -65,7 +65,7 @@ function DeviceInput({ provider, providerData, onChange, -}: DeviceInputProps) { +}: Readonly) { const dispatch = useDispatch(); const { items, selectedDevices, isFetching } = useSelector( createDeviceTagsSelector(value) diff --git a/frontend/src/Components/Form/Tag/MovieTagInput.tsx b/frontend/src/Components/Form/Tag/MovieTagInput.tsx index 8797b50a04..74a2bc4c5f 100644 --- a/frontend/src/Components/Form/Tag/MovieTagInput.tsx +++ b/frontend/src/Components/Form/Tag/MovieTagInput.tsx @@ -23,7 +23,8 @@ const VALID_TAG_REGEX = new RegExp('[^-_a-z0-9]', 'i'); function isValidTag(tagName: string) { try { return !VALID_TAG_REGEX.test(tagName); - } catch { + } catch (e) { + console.warn('Tag validation failed:', e); return false; } } diff --git a/frontend/src/Components/Form/Tag/TagSelectInput.tsx b/frontend/src/Components/Form/Tag/TagSelectInput.tsx index 139b7ba84e..e4aca9c3dd 100644 --- a/frontend/src/Components/Form/Tag/TagSelectInput.tsx +++ b/frontend/src/Components/Form/Tag/TagSelectInput.tsx @@ -26,7 +26,7 @@ function TagSelectInput({ values, onChange, ...otherProps -}: TagSelectInputProps) { +}: Readonly) { const { tags, tagList, allTags } = useMemo(() => { const sortedTags = values.sort((a, b) => a.key - b.key); diff --git a/frontend/src/Components/Form/Tag/TextTagInput.tsx b/frontend/src/Components/Form/Tag/TextTagInput.tsx index 6d26520301..f601c98091 100644 --- a/frontend/src/Components/Form/Tag/TextTagInput.tsx +++ b/frontend/src/Components/Form/Tag/TextTagInput.tsx @@ -23,7 +23,7 @@ function TextTagInput({ value, onChange, ...otherProps -}: TextTagInputProps) { +}: Readonly) { const { tags, tagList, valueArray } = useMemo(() => { const tagsArray = Array.isArray(value) ? value : split(value); diff --git a/frontend/src/Components/Form/TextArea.tsx b/frontend/src/Components/Form/TextArea.tsx index 047817eb7f..6be27fbf57 100644 --- a/frontend/src/Components/Form/TextArea.tsx +++ b/frontend/src/Components/Form/TextArea.tsx @@ -37,7 +37,7 @@ function TextArea({ onFocus, onChange, onSelectionChange, -}: TextAreaProps) { +}: Readonly) { const inputRef = useRef(null); const selectionTimeout = useRef>(); const selectionStart = useRef(); diff --git a/frontend/src/Components/Icon.tsx b/frontend/src/Components/Icon.tsx index ff1597bcf3..622f77aae6 100644 --- a/frontend/src/Components/Icon.tsx +++ b/frontend/src/Components/Icon.tsx @@ -34,7 +34,7 @@ export default function Icon({ isSpinning = false, fixedWidth = false, ...otherProps -}: IconProps) { +}: Readonly) { const icon = ( ) { const { ratings, iconSize = 14, hideIcon = false } = props; const imdbImage = diff --git a/frontend/src/Components/ImportListList.tsx b/frontend/src/Components/ImportListList.tsx index fd2ee48419..65995bf3a9 100644 --- a/frontend/src/Components/ImportListList.tsx +++ b/frontend/src/Components/ImportListList.tsx @@ -8,7 +8,7 @@ interface ImportListListProps { lists: number[]; } -function ImportListList({ lists }: ImportListListProps) { +function ImportListList({ lists }: Readonly) { const allImportLists = useSelector( (state: AppState) => state.settings.importLists.items ); diff --git a/frontend/src/Components/InfoLabel.tsx b/frontend/src/Components/InfoLabel.tsx index dadf5e4b6a..fd5413227c 100644 --- a/frontend/src/Components/InfoLabel.tsx +++ b/frontend/src/Components/InfoLabel.tsx @@ -21,7 +21,7 @@ function InfoLabel({ outline = false, children, ...otherProps -}: InfoLabelProps) { +}: Readonly) { return ( ) { return ( ) { return ( ) { const [state, setState] = useState(null); useEffect(() => { diff --git a/frontend/src/Components/Link/IconButton.tsx b/frontend/src/Components/Link/IconButton.tsx index b6951c00c2..3cba9fbfd2 100644 --- a/frontend/src/Components/Link/IconButton.tsx +++ b/frontend/src/Components/Link/IconButton.tsx @@ -19,7 +19,7 @@ export default function IconButton({ size = 12, isSpinning, ...otherProps -}: IconButtonProps) { +}: Readonly) { return ( ) { return (