diff --git a/frontend/components/ActionsDropdown/ActionsDropdown.tsx b/frontend/components/ActionsDropdown/ActionsDropdown.tsx index bbbe12cb83e..2b9cbad09e9 100644 --- a/frontend/components/ActionsDropdown/ActionsDropdown.tsx +++ b/frontend/components/ActionsDropdown/ActionsDropdown.tsx @@ -31,7 +31,7 @@ interface IActionsDropdownProps { variant?: "button" | "brand-button" | "small-button"; } -const getOptionBackgroundColor = (state: any) => { +const getOptionBackgroundColor = (state: { isFocused: boolean }) => { return state.isFocused ? COLORS["ui-fleet-black-5"] : "transparent"; }; diff --git a/frontend/components/ActivityDetails/InstallDetails/VppInstallDetailsModal/VppInstallDetailsModal.tests.tsx b/frontend/components/ActivityDetails/InstallDetails/VppInstallDetailsModal/VppInstallDetailsModal.tests.tsx index 598960d6b31..e4d2d5887ff 100644 --- a/frontend/components/ActivityDetails/InstallDetails/VppInstallDetailsModal/VppInstallDetailsModal.tests.tsx +++ b/frontend/components/ActivityDetails/InstallDetails/VppInstallDetailsModal/VppInstallDetailsModal.tests.tsx @@ -7,7 +7,6 @@ import { baseUrl, } from "test/test-utils"; import mockServer from "test/mock-server"; -import { getDeviceVppCommandResultHandler } from "test/handlers/device-handler"; import { createMockHostAppStoreApp, createMockHostSoftware, diff --git a/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx b/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx index acecc019820..98e87ecfa7a 100644 --- a/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx +++ b/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx @@ -60,9 +60,9 @@ const platformSubNav: IPlatformSubNav[] = [ interface IPlatformWrapperProps { enrollSecret: string; onCancel: () => void; - certificate: any; + certificate: string | undefined; isFetchingCertificate: boolean; - fetchCertificateError: any; + fetchCertificateError: Error | null; config: IConfig | null; } @@ -419,7 +419,7 @@ const PlatformWrapper = ({
{fetchCertificateError ? ( - {fetchCertificateError} + {fetchCertificateError?.message} ) : ( diff --git a/frontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tsx b/frontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tsx index 1c6230d6072..5ada4596e7f 100644 --- a/frontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tsx +++ b/frontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable.tsx @@ -11,7 +11,7 @@ import PATHS from "router/paths"; import { AppContext } from "context/app"; import { ISoftwareVulnerability } from "interfaces/software"; -import { CONTACT_FLEET_LINK, GITHUB_NEW_ISSUE_LINK } from "utilities/constants"; +import { CONTACT_FLEET_LINK } from "utilities/constants"; import { DisplayPlatform } from "interfaces/platform"; import { getPathWithQueryParams } from "utilities/url"; import TableContainer from "components/TableContainer"; @@ -108,7 +108,7 @@ const SoftwareVulnerabilitiesTable = ({ const tableHeaders = useMemo( () => generateTableConfig(Boolean(isPremiumTier), router, teamIdForApi), - [isPremiumTier] + [isPremiumTier, router, teamIdForApi] ); const renderVulnerabilitiesCount = () => ( diff --git a/frontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTableConfig.tsx b/frontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTableConfig.tsx index a4b8835322a..88f7513f969 100644 --- a/frontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTableConfig.tsx +++ b/frontend/pages/SoftwarePage/components/tables/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTableConfig.tsx @@ -30,7 +30,7 @@ interface ICellProps { }; } -interface ITextCellProps extends ICellProps { +interface ITextCellProps { cell: { value: string | number; }; @@ -40,7 +40,9 @@ interface IDataColumn { title: string; Header: ((props: IHeaderProps) => JSX.Element) | string; accessor: string; - Cell: (props: ITextCellProps) => JSX.Element; + Cell: + | ((props: ITextCellProps) => JSX.Element) + | ((props: ICellProps) => JSX.Element); disableHidden?: boolean; disableSortBy?: boolean; sortType?: string; diff --git a/frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx b/frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx index 0ae039af014..6002ee7b35a 100644 --- a/frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx +++ b/frontend/pages/admin/IntegrationsPage/IntegrationNavItems.tsx @@ -1,6 +1,7 @@ import PATHS from "router/paths"; import { ISideNavItem } from "../components/SideNav/SideNav"; +import { IAppConfigFormProps } from "../OrgSettingsPage/cards/constants"; import Integrations from "./cards/Integrations"; import MdmSettings from "./cards/MdmSettings"; import Calendars from "./cards/Calendars"; @@ -11,8 +12,8 @@ import IdentityProviders from "./cards/IdentityProviders"; import Sso from "./cards/Sso"; import GlobalHostStatusWebhook from "../IntegrationsPage/cards/GlobalHostStatusWebhook"; -const getIntegrationSettingsNavItems = (): ISideNavItem[] => { - const items: ISideNavItem[] = [ +const getIntegrationSettingsNavItems = (): ISideNavItem[] => { + const items: ISideNavItem[] = [ { title: "Ticket destinations", urlSection: "ticket-destinations", diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx index 019e8cca399..765884f1dca 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/CertificateAuthorities.tsx @@ -19,8 +19,6 @@ import DeleteCertificateAuthorityModal from "./components/DeleteCertificateAutho import AddCertAuthorityModal from "./components/AddCertAuthorityModal"; import EditCertAuthorityModal from "./components/EditCertAuthorityModal"; -const baseClass = "certificate-authorities"; - const CertificateAuthorities = () => { const { isPremiumTier } = useContext(AppContext); diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx index 91fc8765a84..c7fdeeb3c55 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx @@ -12,8 +12,6 @@ import { validateFormData, } from "./helpers"; -const baseClass = "ndes-form"; - export interface ICustomSCEPFormData { name: string; scepURL: string; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/SmallstepForm/SmallstepForm.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/SmallstepForm/SmallstepForm.tsx index 3c1408bd573..2da50b0977d 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/SmallstepForm/SmallstepForm.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/SmallstepForm/SmallstepForm.tsx @@ -8,8 +8,6 @@ import TooltipWrapper from "components/TooltipWrapper"; import { generateFormValidations, validateFormData } from "./helpers"; -const baseClass = "smallstep-form"; - export interface ISmallstepFormData { name: string; scepURL: string; diff --git a/frontend/pages/admin/IntegrationsPage/cards/ChangeManagement/ChangeManagement.tests.tsx b/frontend/pages/admin/IntegrationsPage/cards/ChangeManagement/ChangeManagement.tests.tsx index c85daa42461..245bbd40ba9 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/ChangeManagement/ChangeManagement.tests.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/ChangeManagement/ChangeManagement.tests.tsx @@ -23,6 +23,7 @@ const createPatchConfigHandler = (spy: jest.Mock) => { spy(body); // Echo back a full config with the gitops fields from the request return HttpResponse.json( + // eslint-disable-next-line @typescript-eslint/no-explicit-any createMockConfig({ gitops: (body as any).gitops }) ); }); diff --git a/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx b/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx index 6264fda0f47..5c3f91ba1f5 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx @@ -21,7 +21,7 @@ import { import Button from "components/buttons/Button"; import Checkbox from "components/forms/fields/Checkbox"; import { AppContext } from "context/app"; -import Spinner from "components/Spinner"; + import PremiumFeatureMessage from "components/PremiumFeatureMessage"; import { useQuery } from "react-query"; import DataError from "components/DataError"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/components/OktaConditionalAccessModal/OktaConditionalAccessModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/components/OktaConditionalAccessModal/OktaConditionalAccessModal.tsx index f0ce880bb3f..ad7a377f62a 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/components/OktaConditionalAccessModal/OktaConditionalAccessModal.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/components/OktaConditionalAccessModal/OktaConditionalAccessModal.tsx @@ -121,13 +121,14 @@ const OktaConditionalAccessModal = ({ conditionalAccessAPI.getIdpAppleProfile, { ...DEFAULT_USE_QUERY_OPTIONS, - onError: (e: any) => { + onError: (e: unknown) => { // When responseType is "text", error responses come back as JSON strings // that need to be parsed manually let errorReason = ""; try { - if (e.data && typeof e.data === "string") { - const parsedError = JSON.parse(e.data); + const err = e as Record; + if (err.data && typeof err.data === "string") { + const parsedError = JSON.parse(err.data); errorReason = parsedError.errors?.[0]?.reason || ""; } else { errorReason = getErrorReason(e); diff --git a/frontend/pages/admin/IntegrationsPage/cards/GlobalHostStatusWebhook/GlobalHostStatusWebhook.tsx b/frontend/pages/admin/IntegrationsPage/cards/GlobalHostStatusWebhook/GlobalHostStatusWebhook.tsx index 1020f937577..3e7286d6d3a 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/GlobalHostStatusWebhook/GlobalHostStatusWebhook.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/GlobalHostStatusWebhook/GlobalHostStatusWebhook.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo } from "react"; +import React, { useCallback, useState, useEffect, useMemo } from "react"; import { IInputFieldParseTarget } from "interfaces/form_field"; import { @@ -73,7 +73,7 @@ const GlobalHostStatusWebhook = ({ setFormErrors({}); }; - const validateForm = () => { + const validateForm = useCallback(() => { const errors: IGlobalHostStatusWebhookFormErrors = {}; if (enableHostStatusWebhook) { @@ -85,11 +85,11 @@ const GlobalHostStatusWebhook = ({ } setFormErrors(errors); - }; + }, [enableHostStatusWebhook, destination_url]); useEffect(() => { validateForm(); - }, [enableHostStatusWebhook]); + }, [validateForm]); const toggleHostStatusWebhookPreviewModal = () => { setShowHostStatusWebhookPreviewModal(!showHostStatusWebhookPreviewModal); @@ -125,8 +125,7 @@ const GlobalHostStatusWebhook = ({ hostStatusWebhookHostPercentage, (val) => `${val}%` ), - // intentionally omit dependency so options only computed initially - [] + [hostStatusWebhookHostPercentage] ); const windowOptions = useMemo( @@ -136,8 +135,7 @@ const GlobalHostStatusWebhook = ({ hostStatusWebhookWindow, (val) => `${val} day${val !== 1 ? "s" : ""}` ), - // intentionally omit dependency so options only computed initially - [] + [hostStatusWebhookWindow] ); return (
diff --git a/frontend/pages/admin/IntegrationsPage/cards/IdentityProviders/components/EndUserAuthSection/EndUserAuthSection.tsx b/frontend/pages/admin/IntegrationsPage/cards/IdentityProviders/components/EndUserAuthSection/EndUserAuthSection.tsx index 09c8c1284a3..2ab45029a05 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/IdentityProviders/components/EndUserAuthSection/EndUserAuthSection.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/IdentityProviders/components/EndUserAuthSection/EndUserAuthSection.tsx @@ -125,7 +125,14 @@ const EndUserAuthSection = ({ renderFlash("error", "Couldn't update. Please try again."); } }, - [formData, setFormData, renderFlash, setDirty] + [ + formData, + setFormData, + renderFlash, + setDirty, + announceChanges, + originalFormData, + ] ); const renderContent = () => { diff --git a/frontend/pages/admin/IntegrationsPage/cards/Integrations/Integrations.tsx b/frontend/pages/admin/IntegrationsPage/cards/Integrations/Integrations.tsx index 020337e49fd..70835bcccfe 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/Integrations/Integrations.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/Integrations/Integrations.tsx @@ -187,7 +187,13 @@ const Integrations = (): JSX.Element => { setTestingConnection(false); }); }, - [toggleAddIntegrationModal] + [ + jiraIntegrations, + zendeskIntegrations, + refetchIntegrations, + renderFlash, + toggleAddIntegrationModal, + ] ); const onDeleteSubmit = useCallback(() => { @@ -245,7 +251,16 @@ const Integrations = (): JSX.Element => { toggleDeleteIntegrationModal(); }); } - }, [integrationEditing, toggleDeleteIntegrationModal]); + }, [ + integrationEditing, + integrations?.jira, + integrations?.zendesk, + jiraIntegrations, + zendeskIntegrations, + refetchIntegrations, + renderFlash, + toggleDeleteIntegrationModal, + ]); const onActionSelection = useCallback( (action: string, integration: IIntegrationTableData): void => { diff --git a/frontend/pages/admin/IntegrationsPage/cards/Integrations/components/IntegrationForm/IntegrationForm.tsx b/frontend/pages/admin/IntegrationsPage/cards/Integrations/components/IntegrationForm/IntegrationForm.tsx index aed03800af0..2bcc37da9db 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/Integrations/components/IntegrationForm/IntegrationForm.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/Integrations/components/IntegrationForm/IntegrationForm.tsx @@ -148,7 +148,7 @@ const IntegrationForm = ({ url, email: email || "", api_token: apiToken, - group_id: parseInt(groupId as any, 10) || 0, + group_id: parseInt(String(groupId), 10) || 0, }, ]; } diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/AndroidMdmPage.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/AndroidMdmPage.tsx index 5073375b413..6054a80126b 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/AndroidMdmPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/AndroidMdmPage/AndroidMdmPage.tsx @@ -71,6 +71,8 @@ const TurnOnAndroidMdm = ({ router }: ITurnOnAndroidMdmProps) => { abortController.abort(); }; } + + return undefined; }, [setupSse, router, renderFlash, handleSSE]); const onConnectMdm = async () => { @@ -89,10 +91,12 @@ const TurnOnAndroidMdm = ({ router }: ITurnOnAndroidMdmProps) => { `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},top=${top},left=${left}` ); setSetupSse(true); - } catch (e: any) { + } catch (e: unknown) { + const errData = (e as { data?: { errors?: Array<{ reason?: string }> } }) + ?.data; if ( - e.data?.errors && - e.data.errors[0].reason?.includes("android enterprise already exists") + errData?.errors && + errData.errors[0]?.reason?.includes("android enterprise already exists") ) { renderFlash( "error", diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/TeamsCell/TeamsCell.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/TeamsCell/TeamsCell.tsx index 31849d20ef6..26edeedc76e 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/TeamsCell/TeamsCell.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/TeamsCell/TeamsCell.tsx @@ -1,5 +1,4 @@ import React from "react"; -import classnames from "classnames"; import { ITokenTeam } from "interfaces/mdm"; import { getTeamDisplayName } from "interfaces/team"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/MdmSettingsSection.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/MdmSettingsSection.tsx index 4d5b155bbdd..7997acf4acc 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/MdmSettingsSection.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/MdmSettingsSection.tsx @@ -1,10 +1,9 @@ -import React, { useContext } from "react"; +import React from "react"; import { InjectedRouter } from "react-router"; import { AxiosError } from "axios"; import PATHS from "router/paths"; import { IMdmApple } from "interfaces/mdm"; -import { AppContext } from "context/app"; import Spinner from "components/Spinner"; import DataError from "components/DataError"; diff --git a/frontend/pages/admin/IntegrationsPage/cards/Sso/Sso.tsx b/frontend/pages/admin/IntegrationsPage/cards/Sso/Sso.tsx index c1191eac53e..308e6f2bcea 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/Sso/Sso.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/Sso/Sso.tsx @@ -23,8 +23,6 @@ import { newFormDataIdp, } from "../IdentityProviders/components/EndUserAuthSection/helpers"; -const baseClass = "app-config-form"; - interface ISsoFormData { idpName: string; enableSso: boolean; diff --git a/frontend/pages/admin/OrgSettingsPage/cards/Advanced/Advanced.tsx b/frontend/pages/admin/OrgSettingsPage/cards/Advanced/Advanced.tsx index 79af1025f06..ed874f8dd97 100644 --- a/frontend/pages/admin/OrgSettingsPage/cards/Advanced/Advanced.tsx +++ b/frontend/pages/admin/OrgSettingsPage/cards/Advanced/Advanced.tsx @@ -137,9 +137,7 @@ const Advanced = ({ // it's safe to assume that frequency is a number (frequency: number | string) => `${frequency as number} days` ), - // intentionally leave activityExpiryWindow out of the dependencies, so that the custom - // options are maintained even if the user changes the frequency in the UI - [deleteActivities] + [activityExpiryWindow] ); const onInputChange = ({ name, value }: IInputFieldParseTarget) => { diff --git a/frontend/pages/admin/OrgSettingsPage/cards/Agents/Agents.tsx b/frontend/pages/admin/OrgSettingsPage/cards/Agents/Agents.tsx index 744802c14f0..ec0e5e726b3 100644 --- a/frontend/pages/admin/OrgSettingsPage/cards/Agents/Agents.tsx +++ b/frontend/pages/admin/OrgSettingsPage/cards/Agents/Agents.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import yaml from "js-yaml"; import paths from "router/paths"; import { constructErrorString, agentOptionsToYaml } from "utilities/yaml"; @@ -48,7 +48,7 @@ const Agents = ({ setFormData({ ...formData, agentOptions: value }); }; - const validateForm = () => { + const validateForm = useCallback(() => { const errors: IAgentOptionsFormErrors = {}; if (agentOptions) { @@ -59,18 +59,18 @@ const Agents = ({ } setFormErrors(errors); - }; + }, [agentOptions]); // onChange basic yaml validation only useEffect(() => { validateForm(); - }, [agentOptions]); + }, [validateForm]); const onFormSubmit = (evt: React.MouseEvent) => { evt.preventDefault(); // Formatting of API not UI and allows empty agent options - const formDataToSubmit: any = agentOptions + const formDataToSubmit: { agent_options: unknown } = agentOptions ? { agent_options: yaml.load(agentOptions), } diff --git a/frontend/pages/admin/OrgSettingsPage/cards/FleetDesktop/FleetDesktop.tsx b/frontend/pages/admin/OrgSettingsPage/cards/FleetDesktop/FleetDesktop.tsx index 138a36c9cc7..d560aaa7705 100644 --- a/frontend/pages/admin/OrgSettingsPage/cards/FleetDesktop/FleetDesktop.tsx +++ b/frontend/pages/admin/OrgSettingsPage/cards/FleetDesktop/FleetDesktop.tsx @@ -23,7 +23,6 @@ interface IFleetDesktopFormErrors { transparencyURL?: string | null; alternativeBrowserHost?: string | null; } -const baseClass = "app-config-form"; const FleetDesktop = ({ appConfig, diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/AgentOptionsPage/AgentOptionsPage.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/AgentOptionsPage/AgentOptionsPage.tsx index e36d5f7d51f..3339c9c9783 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/AgentOptionsPage/AgentOptionsPage.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/AgentOptionsPage/AgentOptionsPage.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState, useEffect } from "react"; +import React, { useCallback, useContext, useState, useEffect } from "react"; import { useQuery } from "react-query"; import { useErrorHandler } from "react-error-boundary"; import yaml from "js-yaml"; @@ -52,7 +52,7 @@ const AgentOptionsPage = ({ const [teamName, setTeamName] = useState(""); const [formData, setFormData] = useState<{ agentOptions?: string }>({}); - const [formErrors, setFormErrors] = useState({}); + const [formErrors, setFormErrors] = useState>({}); const [isUpdatingAgentOptions, setIsUpdatingAgentOptions] = useState(false); const { agentOptions } = formData; @@ -79,8 +79,8 @@ const AgentOptionsPage = ({ } ); - const validateForm = () => { - const errors: any = {}; + const validateForm = useCallback(() => { + const errors: Record = {}; if (agentOptions) { const { error: yamlError, valid: yamlValid } = validateYaml(agentOptions); @@ -90,12 +90,12 @@ const AgentOptionsPage = ({ } setFormErrors(errors); - }; + }, [agentOptions]); // onChange basic yaml validation only useEffect(() => { validateForm(); - }, [formData]); + }, [validateForm]); const onFormSubmit = (evt: React.MouseEvent) => { evt.preventDefault(); diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx index 4fcbf7e3425..d81e7af807a 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx @@ -1,5 +1,4 @@ import React, { useState, useCallback, useContext } from "react"; -import { upperFirst } from "lodash"; import { useQuery } from "react-query"; import { useErrorHandler } from "react-error-boundary"; import { InjectedRouter } from "react-router"; @@ -294,7 +293,7 @@ const TeamDetailsWrapper = ({ const onDeleteSubmit = useCallback(async () => { if (!teamIdForApi) { - return false; + return; } setIsUpdatingTeams(true); diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamSettings/TeamSettings.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamSettings/TeamSettings.tsx index 47c2a5cae5b..2fe1407de97 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamSettings/TeamSettings.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamSettings/TeamSettings.tsx @@ -206,9 +206,7 @@ const TeamSettings = ({ location, router }: ITeamSubnavProps) => { ) ); } - // no need for isInitialTeamConfig dependence, since this effect should only run on initial - // config load - }, [teamConfig]); + }, [teamConfig, isInitialTeamConfig]); const onInputChange = useCallback( (newVal: { name: FormNames; value: string | number | boolean }) => { diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/UsersPage.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/UsersPage.tsx index 7d98199d8c1..60c1e13f351 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/UsersPage.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/UsersPage.tsx @@ -373,7 +373,7 @@ const UsersPage = ({ location, router }: ITeamSubnavProps): JSX.Element => { } return ; - }, [teamUsers?.length]); + }, [teamUsers?.length, searchString]); const columnConfigs = useMemo( () => generateColumnConfigs(onActionSelection), diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/UsersPageTableConfig.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/UsersPageTableConfig.tsx index 7fe00a0508a..6566d9bf100 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/UsersPageTableConfig.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/UsersPageTableConfig.tsx @@ -10,7 +10,7 @@ import TooltipTruncatedTextCell from "components/TableContainer/DataTable/Toolti import ActionsDropdown from "components/ActionsDropdown"; import CustomLink from "components/CustomLink"; import TooltipWrapper from "components/TooltipWrapper"; -import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper"; + import PillBadge from "components/PillBadge"; interface IHeaderProps { diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/components/AddUsersModal/AddUsersModal.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/components/AddUsersModal/AddUsersModal.tsx index f51a3275485..99196b2e4ef 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/components/AddUsersModal/AddUsersModal.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/components/AddUsersModal/AddUsersModal.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useState } from "react"; +import { Option } from "react-select"; import { INewTeamUser, INewTeamUsersBody, ITeam } from "interfaces/team"; import endpoints from "utilities/endpoints"; @@ -24,11 +25,11 @@ const AddUsersModal = ({ onCreateNewTeamUser, team, }: IAddUsersModal): JSX.Element => { - const [selectedUsers, setSelectedUsers] = useState([]); + const [selectedUsers, setSelectedUsers] = useState([]); const onChangeDropdown = useCallback( - (values: any) => { - setSelectedUsers(values); + (values: Option | Option[] | null) => { + setSelectedUsers((values || []) as IDropdownOption[]); }, [setSelectedUsers] ); @@ -56,7 +57,7 @@ const AddUsersModal = ({ onChange={onChangeDropdown} placeholder="Search users by name" disabledOptions={disabledUsers} - value={selectedUsers} + value={(selectedUsers as unknown) as Option[]} autoFocus />
diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/components/AutocompleteDropdown/AutocompleteDropdown.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/components/AutocompleteDropdown/AutocompleteDropdown.tsx index 62d5e17fee1..13b3ea951e4 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/components/AutocompleteDropdown/AutocompleteDropdown.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/components/AutocompleteDropdown/AutocompleteDropdown.tsx @@ -70,6 +70,7 @@ const AutocompleteDropdown = ({ // We disable any filtering client side as the server filters the results // for us. + // eslint-disable-next-line @typescript-eslint/no-explicit-any const filterOptions = useCallback((options: any) => { return options; }, []); @@ -90,9 +91,11 @@ const AutocompleteDropdown = ({ // we have decided to use callbacks as those seemed to make the component work // More info is here: // https://stackoverflow.com/questions/52984105/react-select-async-loadoptions-is-not-loading-options-properly + // eslint-disable-next-line @typescript-eslint/no-explicit-any const getOptions = debounce((input: string, callback: any) => { if (!input) { - return callback([]); + callback([]); + return; } fetch(createUrl(resourceUrl, input), { diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/components/EmptyUsersTable.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/components/EmptyUsersTable.tsx index 11fc436457f..adedcb056d9 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/components/EmptyUsersTable.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/components/EmptyUsersTable.tsx @@ -13,7 +13,6 @@ interface IEmptyUsersTableProps { isTeamAdmin: boolean; toggleAddUserModal: () => void; toggleCreateMemberModal: () => void; - disabled?: boolean; } const infoLink = ( @@ -30,7 +29,7 @@ const CreateUserButton = ({ toggleAddUserModal, toggleCreateMemberModal, disabled = false, -}: Omit) => { +}: Omit & { disabled?: boolean }) => { if (!isGlobalAdmin && !isTeamAdmin) { return null; } diff --git a/frontend/pages/admin/TeamManagementPage/components/CreateTeamModal/CreateTeamModal.tsx b/frontend/pages/admin/TeamManagementPage/components/CreateTeamModal/CreateTeamModal.tsx index 40d9311e38a..1d19e25331b 100644 --- a/frontend/pages/admin/TeamManagementPage/components/CreateTeamModal/CreateTeamModal.tsx +++ b/frontend/pages/admin/TeamManagementPage/components/CreateTeamModal/CreateTeamModal.tsx @@ -4,7 +4,7 @@ import { ITeamFormData } from "services/entities/teams"; import Modal from "components/Modal"; import Button from "components/buttons/Button"; -import InfoBanner from "components/InfoBanner/InfoBanner"; + import InputField from "components/forms/fields/InputField"; const baseClass = "create-team-modal"; @@ -40,7 +40,7 @@ const CreateTeamModal = ({ ); const onFormSubmit = useCallback( - (evt: any) => { + (evt: React.FormEvent) => { evt.preventDefault(); onSubmit({ name: name.trim(), diff --git a/frontend/pages/admin/TeamManagementPage/components/RenameTeamModal/RenameTeamModal.tests.tsx b/frontend/pages/admin/TeamManagementPage/components/RenameTeamModal/RenameTeamModal.tests.tsx index fa6669a8808..503addcb0c9 100644 --- a/frontend/pages/admin/TeamManagementPage/components/RenameTeamModal/RenameTeamModal.tests.tsx +++ b/frontend/pages/admin/TeamManagementPage/components/RenameTeamModal/RenameTeamModal.tests.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { screen, waitFor } from "@testing-library/react"; +import { screen } from "@testing-library/react"; import { createCustomRenderer } from "test/test-utils"; import userEvent from "@testing-library/user-event"; diff --git a/frontend/pages/admin/UserManagementPage/components/SelectRoleForm/SelectRoleForm.tsx b/frontend/pages/admin/UserManagementPage/components/SelectRoleForm/SelectRoleForm.tsx index 0ab3012f9fe..b2d7ae858cb 100644 --- a/frontend/pages/admin/UserManagementPage/components/SelectRoleForm/SelectRoleForm.tsx +++ b/frontend/pages/admin/UserManagementPage/components/SelectRoleForm/SelectRoleForm.tsx @@ -23,7 +23,10 @@ const generateSelectedTeamData = ( return allTeams.map( (teamItem): ITeam => ({ ...teamItem, - role: teamItem.id === updatedTeam?.id ? updatedTeam.role! : teamItem.role, + role: + teamItem.id === updatedTeam?.id + ? updatedTeam.role ?? teamItem.role + : teamItem.role, }) ); }; diff --git a/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx b/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx index 858fabf355d..27f5de0c0c1 100644 --- a/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx +++ b/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx @@ -190,9 +190,9 @@ const UserForm = ({ // If SSO is globally disabled but user previously signed in via SSO, // require password is automatically selected on first render if (!canUseSso && !isNewUser && isSsoEnabled) { - setFormData({ ...formData, sso_enabled: false }); + setFormData((prev) => ({ ...prev, sso_enabled: false })); } - }, []); + }, [canUseSso, isNewUser, isSsoEnabled]); const onInputChange = ({ name, value }: IInputFieldParseTarget) => { const newFormData = { ...formData, [name]: value }; diff --git a/frontend/pages/admin/UserManagementPage/helpers/userManagementHelpers.ts b/frontend/pages/admin/UserManagementPage/helpers/userManagementHelpers.ts index 944ab996ebf..c2b5f832aa8 100644 --- a/frontend/pages/admin/UserManagementPage/helpers/userManagementHelpers.ts +++ b/frontend/pages/admin/UserManagementPage/helpers/userManagementHelpers.ts @@ -2,7 +2,7 @@ import { isEqual } from "lodash"; import { CustomOptionType } from "components/forms/fields/DropdownWrapper/DropdownWrapper"; import { IInvite } from "interfaces/invite"; -import { IUser, IUserUpdateBody, IUpdateUserFormData } from "interfaces/user"; +import { IUser, IUpdateUserFormData } from "interfaces/user"; import { IUserFormData } from "../components/UserForm/UserForm"; type ICurrentUserData = Pick< @@ -34,7 +34,7 @@ const generateUpdateData = ( "sso_enabled", "mfa_enabled", ]; - return Object.keys(formData).reduce( + return Object.keys(formData).reduce( (updatedAttributes, attr) => { // attribute can be updated and is different from the current value. if ( diff --git a/frontend/pages/admin/components/DownloadFileButtons/DownloadABMKey.tsx b/frontend/pages/admin/components/DownloadFileButtons/DownloadABMKey.tsx index 10147396634..a8967d139d2 100644 --- a/frontend/pages/admin/components/DownloadFileButtons/DownloadABMKey.tsx +++ b/frontend/pages/admin/components/DownloadFileButtons/DownloadABMKey.tsx @@ -49,7 +49,7 @@ const useDownloadABMKey = ({ onError && onError(e); } }, - [onError, onSuccess] + [onError, onSuccess, renderFlash] ); const memoized = useMemo( diff --git a/frontend/pages/admin/components/HostStatusWebhookPreviewModal/HostStatusWebhookPreviewModal.tsx b/frontend/pages/admin/components/HostStatusWebhookPreviewModal/HostStatusWebhookPreviewModal.tsx index 3b16696da96..a0a67609548 100644 --- a/frontend/pages/admin/components/HostStatusWebhookPreviewModal/HostStatusWebhookPreviewModal.tsx +++ b/frontend/pages/admin/components/HostStatusWebhookPreviewModal/HostStatusWebhookPreviewModal.tsx @@ -1,9 +1,8 @@ import React from "react"; -import { syntaxHighlight } from "utilities/helpers"; - import Button from "components/buttons/Button"; import Modal from "components/Modal"; +import SyntaxHighlightedCode from "components/SyntaxHighlightedCode"; const baseClass = "host-status-webhook-preview-modal"; @@ -46,11 +45,7 @@ const HostStatusWebhookPreviewModal = ({ An example request sent to your configured Destination URL.

-
+        
       
); - const onExportHostsResults = async ( - evt: React.MouseEvent - ) => { - evt.preventDefault(); + const onExportHostsResults = useCallback( + async (evt: React.MouseEvent) => { + evt.preventDefault(); - const hiddenColumnsStorage = localStorage.getItem("hostHiddenColumns"); - let currentHiddenColumns = []; - let visibleColumns; - if (hiddenColumnsStorage) { - currentHiddenColumns = JSON.parse(hiddenColumnsStorage); - } + const hiddenColumnsStorage = localStorage.getItem("hostHiddenColumns"); + let currentHiddenColumns = []; + let visibleColumns; + if (hiddenColumnsStorage) { + currentHiddenColumns = JSON.parse(hiddenColumnsStorage); + } - if (config && currentUser) { - const tableColumns = generateVisibleTableColumns({ - hiddenColumns: currentHiddenColumns, - isFreeTier, - isOnlyObserver, + if (config && currentUser) { + const tableColumns = generateVisibleTableColumns({ + hiddenColumns: currentHiddenColumns, + isFreeTier, + isOnlyObserver, + teamId: teamIdForApi, + }); + + const columnIds = tableColumns + .map((column) => (column.id ? column.id : "")) + // "selection" colum does not include any relevent data for the CSV + // so we filter it out. + .filter((element) => element !== "" && element !== "selection"); + visibleColumns = columnIds.join(","); + } + + let options = { + selectedLabels, + globalFilter: searchQuery, + sortBy, teamId: teamIdForApi, - }); + policyId, + policyResponse, + macSettingsStatus, + softwareId, + softwareTitleId, + softwareVersionId, + softwareStatus, + status, + mdmId, + mdmEnrollmentStatus, + munkiIssueId, + lowDiskSpaceHosts, + osName, + osVersionId, + osVersion, + osSettings: osSettingsStatus, + bootstrapPackageStatus, + vulnerability, + visibleColumns, + configProfileUUID, + configProfileStatus, + scriptBatchExecutionStatus, + scriptBatchExecutionId, + }; - const columnIds = tableColumns - .map((column) => (column.id ? column.id : "")) - // "selection" colum does not include any relevent data for the CSV - // so we filter it out. - .filter((element) => element !== "" && element !== "selection"); - visibleColumns = columnIds.join(","); - } + options = { + ...options, + teamId: teamIdForApi, + }; + + if ( + queryParams.fleet_id !== API_ALL_TEAMS_ID && + queryParams.fleet_id !== "" + ) { + options.teamId = queryParams.fleet_id; + } + + try { + const exportHostResults = await hostsAPI.exportHosts(options); + + const formattedTime = format(new Date(), "yyyy-MM-dd"); + const filename = `${CSV_HOSTS_TITLE} ${formattedTime}.csv`; + const file = new global.window.File([exportHostResults], filename, { + type: "text/csv", + }); - let options = { + FileSaver.saveAs(file); + } catch (error) { + console.error(error); + renderFlash("error", "Could not export hosts. Please try again."); + } + }, + [ + config, + currentUser, + isFreeTier, + isOnlyObserver, + teamIdForApi, selectedLabels, - globalFilter: searchQuery, + searchQuery, sortBy, - teamId: teamIdForApi, policyId, policyResponse, macSettingsStatus, @@ -1613,43 +1678,17 @@ const ManageHostsPage = ({ osName, osVersionId, osVersion, - osSettings: osSettingsStatus, + osSettingsStatus, bootstrapPackageStatus, vulnerability, - visibleColumns, configProfileUUID, configProfileStatus, scriptBatchExecutionStatus, scriptBatchExecutionId, - }; - - options = { - ...options, - teamId: teamIdForApi, - }; - - if ( - queryParams.fleet_id !== API_ALL_TEAMS_ID && - queryParams.fleet_id !== "" - ) { - options.teamId = queryParams.fleet_id; - } - - try { - const exportHostResults = await hostsAPI.exportHosts(options); - - const formattedTime = format(new Date(), "yyyy-MM-dd"); - const filename = `${CSV_HOSTS_TITLE} ${formattedTime}.csv`; - const file = new global.window.File([exportHostResults], filename, { - type: "text/csv", - }); - - FileSaver.saveAs(file); - } catch (error) { - console.error(error); - renderFlash("error", "Could not export hosts. Please try again."); - } - }; + queryParams.fleet_id, + renderFlash, + ] + ); const renderHostCount = useCallback(() => { return ( @@ -1669,7 +1708,7 @@ const ManageHostsPage = ({ )} ); - }, [isLoadingHostsCount, totalFilteredHostsCount]); + }, [totalFilteredHostsCount, onExportHostsResults]); const renderCustomControls = () => { // we filter out the status labels as we dont want to display them in the label diff --git a/frontend/pages/hosts/ManageHostsPage/components/CustomValueContainer/CustomValueContainer.tsx b/frontend/pages/hosts/ManageHostsPage/components/CustomValueContainer/CustomValueContainer.tsx index 27bda6d19af..79a93c46b71 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/CustomValueContainer/CustomValueContainer.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/CustomValueContainer/CustomValueContainer.tsx @@ -5,17 +5,15 @@ import Icon from "components/Icon"; const baseClass = "custom-dropdown-indicator"; -const CustomDropdownIndicator = ({ props }: ValueContainerProps | any) => { - const { children } = props; - // no access to hover state here from react-select so that is done in the scss - // file of LabelFilterSelect. - +const CustomValueContainer = ({ children, ...props }: ValueContainerProps) => { return ( - - + + {!!children && ( + + )} {children} - + ); }; -export default CustomDropdownIndicator; +export default CustomValueContainer; diff --git a/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tsx b/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tsx index b3133497bcf..0d848f61b24 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/FilterPill/FilterPill.tsx @@ -7,7 +7,6 @@ import Button from "components/buttons/Button"; import Icon from "components/Icon"; import { IconNames } from "components/icons"; import TooltipWrapper from "components/TooltipWrapper"; -import { set } from "lodash"; interface IFilterPillProps { label: string; diff --git a/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx b/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx index 184ac6988fe..70aa157b433 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx @@ -63,9 +63,9 @@ interface IHostsFilterBlockProps { params: { munkiIssueDetails: IMunkiIssuesAggregate | null; policyResponse: PolicyResponse; - policyId?: any; + policyId?: number; policy?: IPolicy; - macSettingsStatus?: any; + macSettingsStatus?: MacSettingsStatusQueryParam; softwareId?: number; softwareTitleId?: number; softwareVersionId?: number; diff --git a/frontend/pages/hosts/ManageHostsPage/components/LabelFilterSelect/LabelFilterSelect.tsx b/frontend/pages/hosts/ManageHostsPage/components/LabelFilterSelect/LabelFilterSelect.tsx index 0ae8da18d8e..1305b55ed0f 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/LabelFilterSelect/LabelFilterSelect.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/LabelFilterSelect/LabelFilterSelect.tsx @@ -2,6 +2,7 @@ import React, { useMemo, useRef, useState } from "react"; import Select, { GroupBase, SelectInstance, + ValueContainerProps, components, MenuProps, } from "react-select-5"; @@ -25,11 +26,13 @@ import CustomDropdownIndicator from "../CustomDropdownIndicator"; // group heading. More info here: // https://react-select.com/typescript#custom-select-props declare module "react-select-5/dist/declarations/src/Select" { + /* eslint-disable @typescript-eslint/no-unused-vars */ export interface Props< Option, IsMulti extends boolean, Group extends GroupBase