Check team config for software UI (#8338)

This commit is contained in:
gillespi314 2022-10-19 14:00:39 -05:00 committed by GitHub
parent 6d4c885f22
commit 9f20f01e37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 190 additions and 149 deletions

View File

@ -13,7 +13,7 @@ interface ITeamsDropdownHeaderProps {
router: InjectedRouter; router: InjectedRouter;
location: { location: {
pathname: string; pathname: string;
query: { team_id?: string; vulnerable?: boolean }; query: { team_id?: string; vulnerable?: string };
search: string; search: string;
}; };
baseClass: string; baseClass: string;

View File

@ -113,6 +113,11 @@ export interface IConfigFormData {
transparency_url: string; transparency_url: string;
} }
export interface IConfigFeatures {
enable_host_users: boolean;
enable_software_inventory: boolean;
}
export interface IConfig { export interface IConfig {
org_info: { org_info: {
org_name: string; org_name: string;
@ -154,10 +159,7 @@ export interface IConfig {
host_expiry_enabled: boolean; host_expiry_enabled: boolean;
host_expiry_window: number; host_expiry_window: number;
}; };
features: { features: IConfigFeatures;
enable_host_users: boolean;
enable_software_inventory: boolean;
};
agent_options: string; agent_options: string;
update_interval: { update_interval: {
osquery_detail: number; osquery_detail: number;
@ -220,3 +222,5 @@ export type IAutomationsConfig = Pick<
IConfig, IConfig,
"webhook_settings" | "integrations" "webhook_settings" | "integrations"
>; >;
export const CONFIG_DEFAULT_RECENT_VULNERABILITY_MAX_AGE_IN_DAYS = 30;

View File

@ -1,7 +1,7 @@
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { IConfigFeatures, IWebhookSettings } from "./config";
import enrollSecretInterface, { IEnrollSecret } from "./enroll_secret"; import enrollSecretInterface, { IEnrollSecret } from "./enroll_secret";
import { IIntegrations } from "./integration"; import { IIntegrations } from "./integration";
import { IWebhookFailingPolicies } from "./webhook";
export default PropTypes.shape({ export default PropTypes.shape({
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
@ -33,6 +33,7 @@ export interface ITeam extends ITeamSummary {
display_text?: string; display_text?: string;
count?: number; count?: number;
created_at?: string; created_at?: string;
features?: IConfigFeatures;
agent_options?: { agent_options?: {
[key: string]: any; [key: string]: any;
}; };
@ -42,13 +43,19 @@ export interface ITeam extends ITeamSummary {
role?: string; // role value is included when the team is in the context of a user role?: string; // role value is included when the team is in the context of a user
} }
/**
* The webhook settings of a team
*/
export type ITeamWebhookSettings = Pick<
IWebhookSettings,
"vulnerabilities_webhook" | "failing_policies_webhook"
>;
/** /**
* The integrations and webhook settings of a team * The integrations and webhook settings of a team
*/ */
export interface ITeamAutomationsConfig { export interface ITeamAutomationsConfig {
webhook_settings: { webhook_settings: ITeamWebhookSettings;
failing_policies_webhook: IWebhookFailingPolicies;
};
integrations: IIntegrations; integrations: IIntegrations;
} }

View File

@ -196,7 +196,11 @@ const Homepage = (): JSX.Element => {
} }
); );
const isSoftwareEnabled = config?.features?.enable_software_inventory; const featuresConfig = currentTeam?.id
? teams?.find((t) => t.id === currentTeam.id)?.features
: config?.features;
const isSoftwareEnabled = !!featuresConfig?.enable_software_inventory;
const SOFTWARE_DEFAULT_SORT_DIRECTION = "desc"; const SOFTWARE_DEFAULT_SORT_DIRECTION = "desc";
const SOFTWARE_DEFAULT_SORT_HEADER = "hosts_count"; const SOFTWARE_DEFAULT_SORT_HEADER = "hosts_count";
const SOFTWARE_DEFAULT_PAGE_SIZE = 8; const SOFTWARE_DEFAULT_PAGE_SIZE = 8;

View File

@ -61,7 +61,7 @@ const Software = ({
) : ( ) : (
<TableContainer <TableContainer
columns={tableHeaders} columns={tableHeaders}
data={software?.software || []} data={(isSoftwareEnabled && software?.software) || []}
isLoading={isSoftwareFetching} isLoading={isSoftwareFetching}
defaultSortHeader={"hosts_count"} defaultSortHeader={"hosts_count"}
defaultSortDirection={SOFTWARE_DEFAULT_SORT_DIRECTION} defaultSortDirection={SOFTWARE_DEFAULT_SORT_DIRECTION}
@ -89,7 +89,7 @@ const Software = ({
) : ( ) : (
<TableContainer <TableContainer
columns={tableHeaders} columns={tableHeaders}
data={software?.software || []} data={(isSoftwareEnabled && software?.software) || []}
isLoading={isSoftwareFetching} isLoading={isSoftwareFetching}
defaultSortHeader={SOFTWARE_DEFAULT_SORT_HEADER} defaultSortHeader={SOFTWARE_DEFAULT_SORT_HEADER}
defaultSortDirection={SOFTWARE_DEFAULT_SORT_DIRECTION} defaultSortDirection={SOFTWARE_DEFAULT_SORT_DIRECTION}

View File

@ -9,14 +9,12 @@ import classnames from "classnames";
import { pick } from "lodash"; import { pick } from "lodash";
import PATHS from "router/paths"; import PATHS from "router/paths";
import configAPI from "services/entities/config";
import hostAPI from "services/entities/hosts"; import hostAPI from "services/entities/hosts";
import queryAPI from "services/entities/queries"; import queryAPI from "services/entities/queries";
import teamAPI, { ILoadTeamsResponse } from "services/entities/teams"; import teamAPI, { ILoadTeamsResponse } from "services/entities/teams";
import { AppContext } from "context/app"; import { AppContext } from "context/app";
import { PolicyContext } from "context/policy"; import { PolicyContext } from "context/policy";
import { NotificationContext } from "context/notification"; import { NotificationContext } from "context/notification";
import { IConfig } from "interfaces/config";
import { import {
IHost, IHost,
IDeviceMappingResponse, IDeviceMappingResponse,
@ -97,11 +95,12 @@ const HostDetailsPage = ({
}: IHostDetailsProps): JSX.Element => { }: IHostDetailsProps): JSX.Element => {
const hostIdFromURL = parseInt(host_id, 10); const hostIdFromURL = parseInt(host_id, 10);
const { const {
config,
currentUser,
isGlobalAdmin, isGlobalAdmin,
isPremiumTier, isPremiumTier,
isOnlyObserver, isOnlyObserver,
isGlobalMaintainer, isGlobalMaintainer,
currentUser,
} = useContext(AppContext); } = useContext(AppContext);
const { const {
setLastEditedQueryName, setLastEditedQueryName,
@ -197,14 +196,6 @@ const HostDetailsPage = ({
} }
); );
const { data: features } = useQuery<
IConfig,
Error,
{ enable_host_users: boolean; enable_software_inventory: boolean }
>(["config"], () => configAPI.loadAll(), {
select: (data: IConfig) => data.features,
});
const refetchExtensions = () => { const refetchExtensions = () => {
deviceMapping !== null && refetchDeviceMapping(); deviceMapping !== null && refetchDeviceMapping();
macadmins !== null && refetchMacadmins(); macadmins !== null && refetchMacadmins();
@ -298,6 +289,10 @@ const HostDetailsPage = ({
} }
); );
const featuresConfig = host?.team_id
? teams?.find((t) => t.id === host.team_id)?.features
: config?.features;
useEffect(() => { useEffect(() => {
setUsersState(() => { setUsersState(() => {
return ( return (
@ -603,14 +598,16 @@ const HostDetailsPage = ({
usersState={usersState} usersState={usersState}
isLoading={isLoadingHost} isLoading={isLoadingHost}
onUsersTableSearchChange={onUsersTableSearchChange} onUsersTableSearchChange={onUsersTableSearchChange}
hostUsersEnabled={features?.enable_host_users} hostUsersEnabled={featuresConfig?.enable_host_users}
/> />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<SoftwareCard <SoftwareCard
isLoading={isLoadingHost} isLoading={isLoadingHost}
software={hostSoftware} software={hostSoftware}
softwareInventoryEnabled={features?.enable_software_inventory} softwareInventoryEnabled={
featuresConfig?.enable_software_inventory
}
deviceType={host?.platform === "darwin" ? "macos" : ""} deviceType={host?.platform === "darwin" ? "macos" : ""}
/> />
{macadmins && ( {macadmins && (

View File

@ -8,9 +8,10 @@ import { PolicyContext } from "context/policy";
import { TableContext } from "context/table"; import { TableContext } from "context/table";
import { NotificationContext } from "context/notification"; import { NotificationContext } from "context/notification";
import { IAutomationsConfig, IConfig } from "interfaces/config"; import { IConfig, IWebhookSettings } from "interfaces/config";
import { IIntegrations } from "interfaces/integration";
import { IPolicyStats, ILoadAllPoliciesResponse } from "interfaces/policy"; import { IPolicyStats, ILoadAllPoliciesResponse } from "interfaces/policy";
import { ITeamAutomationsConfig, ITeamConfig } from "interfaces/team"; import { ITeamConfig } from "interfaces/team";
import PATHS from "router/paths"; import PATHS from "router/paths";
import configAPI from "services/entities/config"; import configAPI from "services/entities/config";
@ -200,9 +201,10 @@ const ManagePolicyPage = ({
const toggleShowInheritedPolicies = () => const toggleShowInheritedPolicies = () =>
setShowInheritedPolicies(!showInheritedPolicies); setShowInheritedPolicies(!showInheritedPolicies);
const handleUpdateAutomations = async ( const handleUpdateAutomations = async (requestBody: {
requestBody: IAutomationsConfig | ITeamAutomationsConfig webhook_settings: Pick<IWebhookSettings, "failing_policies_webhook">;
) => { integrations: IIntegrations;
}) => {
setIsUpdatingAutomations(true); setIsUpdatingAutomations(true);
try { try {
await (teamId await (teamId

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
import { Link } from "react-router"; import { Link } from "react-router";
import { isEmpty, noop, omit } from "lodash"; import { isEmpty, noop, omit } from "lodash";
import { IAutomationsConfig } from "interfaces/config"; import { IAutomationsConfig, IWebhookSettings } from "interfaces/config";
import { IIntegration, IIntegrations } from "interfaces/integration"; import { IIntegration, IIntegrations } from "interfaces/integration";
import { IPolicy } from "interfaces/policy"; import { IPolicy } from "interfaces/policy";
import { ITeamAutomationsConfig } from "interfaces/team"; import { ITeamAutomationsConfig } from "interfaces/team";
@ -29,7 +29,10 @@ interface IManageAutomationsModalProps {
isUpdatingAutomations: boolean; isUpdatingAutomations: boolean;
showPreviewPayloadModal: boolean; showPreviewPayloadModal: boolean;
onExit: () => void; onExit: () => void;
handleSubmit: (formData: IAutomationsConfig | ITeamAutomationsConfig) => void; handleSubmit: (formData: {
webhook_settings: Pick<IWebhookSettings, "failing_policies_webhook">;
integrations: IIntegrations;
}) => void;
togglePreviewPayloadModal: () => void; togglePreviewPayloadModal: () => void;
} }

View File

@ -11,18 +11,23 @@ import { useDebouncedCallback } from "use-debounce";
import { AppContext } from "context/app"; import { AppContext } from "context/app";
import { NotificationContext } from "context/notification"; import { NotificationContext } from "context/notification";
import { IConfig } from "interfaces/config"; import {
IConfig,
CONFIG_DEFAULT_RECENT_VULNERABILITY_MAX_AGE_IN_DAYS,
} from "interfaces/config";
import { import {
IJiraIntegration, IJiraIntegration,
IZendeskIntegration, IZendeskIntegration,
IIntegration, IIntegrations,
} from "interfaces/integration"; } from "interfaces/integration";
import { ITeamConfig } from "interfaces/team";
import { IWebhookSoftwareVulnerabilities } from "interfaces/webhook"; // @ts-ignore import { IWebhookSoftwareVulnerabilities } from "interfaces/webhook"; // @ts-ignore
import configAPI from "services/entities/config"; import configAPI from "services/entities/config";
import softwareAPI, { import softwareAPI, {
ISoftwareResponse, ISoftwareResponse,
ISoftwareCountResponse, ISoftwareCountResponse,
} from "services/entities/software"; } from "services/entities/software";
import teamsAPI, { ILoadTeamResponse } from "services/entities/teams";
import { import {
GITHUB_NEW_ISSUE_LINK, GITHUB_NEW_ISSUE_LINK,
VULNERABLE_DROPDOWN_OPTIONS, VULNERABLE_DROPDOWN_OPTIONS,
@ -50,7 +55,7 @@ interface IManageSoftwarePageProps {
router: InjectedRouter; router: InjectedRouter;
location: { location: {
pathname: string; pathname: string;
query: { vulnerable?: boolean }; query: { vulnerable?: string };
search: string; search: string;
}; };
} }
@ -66,6 +71,11 @@ interface ISoftwareQueryKey {
teamId?: number; teamId?: number;
} }
interface ISoftwareConfigQueryKey {
scope: string;
teamId?: number;
}
interface ISoftwareAutomations { interface ISoftwareAutomations {
webhook_settings: { webhook_settings: {
vulnerabilities_webhook: IWebhookSoftwareVulnerabilities; vulnerabilities_webhook: IWebhookSoftwareVulnerabilities;
@ -89,9 +99,8 @@ const ManageSoftwarePage = ({
}: IManageSoftwarePageProps): JSX.Element => { }: IManageSoftwarePageProps): JSX.Element => {
const { const {
availableTeams, availableTeams,
config: globalConfig,
currentTeam, currentTeam,
isGlobalAdmin,
isGlobalMaintainer,
isOnGlobalTeam, isOnGlobalTeam,
isPremiumTier, isPremiumTier,
} = useContext(AppContext); } = useContext(AppContext);
@ -99,17 +108,10 @@ const ManageSoftwarePage = ({
const DEFAULT_SORT_HEADER = isPremiumTier ? "vulnerabilities" : "hosts_count"; const DEFAULT_SORT_HEADER = isPremiumTier ? "vulnerabilities" : "hosts_count";
const [isSoftwareEnabled, setIsSoftwareEnabled] = useState(false); // TODO: refactor usage of vulnerable query param in accordance with new patterns for query params
const [ // and management of URL state
isVulnerabilityAutomationsEnabled,
setIsVulnerabilityAutomationsEnabled,
] = useState(false);
const [
recentVulnerabilityMaxAge,
setRecentVulnerabilityMaxAge,
] = useState<number>();
const [filterVuln, setFilterVuln] = useState( const [filterVuln, setFilterVuln] = useState(
location?.query?.vulnerable || false location?.query?.vulnerable === "true" || false
); );
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [sortDirection, setSortDirection] = useState< const [sortDirection, setSortDirection] = useState<
@ -123,44 +125,68 @@ const ManageSoftwarePage = ({
const [showPreviewPayloadModal, setShowPreviewPayloadModal] = useState(false); const [showPreviewPayloadModal, setShowPreviewPayloadModal] = useState(false);
const [showPreviewTicketModal, setShowPreviewTicketModal] = useState(false); const [showPreviewTicketModal, setShowPreviewTicketModal] = useState(false);
// TODO: experiment to see if we need this state and effect or can we rely solely on the router/location for the dropdown state?
useEffect(() => { useEffect(() => {
setFilterVuln(!!location.query.vulnerable); setFilterVuln(location?.query?.vulnerable === "true" || false);
// TODO: handle invalid values for vulnerable param
}, [location]); }, [location]);
const { data: config } = useQuery(["config"], configAPI.loadAll, { // softwareConfig is either the global config or the team config of the currently selected team
onSuccess: (data) => { const {
setIsSoftwareEnabled(data?.features?.enable_software_inventory); data: softwareConfig,
let jiraIntegrationEnabled = false; error: softwareConfigError,
if (data.integrations.jira) { isFetching: isFetchingSoftwareConfig,
jiraIntegrationEnabled = data?.integrations.jira.some( refetch: refetchSoftwareConfig,
(integration: IIntegration) => { } = useQuery<
return integration.enable_software_vulnerabilities; IConfig | ILoadTeamResponse,
} Error,
); IConfig | ITeamConfig,
} ISoftwareConfigQueryKey[]
let zendeskIntegrationEnabled = false; >(
if (data.integrations.zendesk) { [{ scope: "softwareConfig", teamId: currentTeam?.id }],
zendeskIntegrationEnabled = data?.integrations.zendesk.some( ({ queryKey }) => {
(integration: IIntegration) => { const { teamId } = queryKey[0];
return integration.enable_software_vulnerabilities; return teamId ? teamsAPI.load(teamId) : configAPI.loadAll();
}
);
}
setIsVulnerabilityAutomationsEnabled(
data?.webhook_settings?.vulnerabilities_webhook
.enable_vulnerabilities_webhook ||
jiraIntegrationEnabled ||
zendeskIntegrationEnabled
);
// Convert from nanosecond to nearest day
setRecentVulnerabilityMaxAge(
Math.round(
data?.vulnerabilities?.recent_vulnerability_max_age / 86400000000000
)
);
}, },
}); {
select: (data) => ("team" in data ? data.team : data),
}
);
const isSoftwareConfigLoaded =
!isFetchingSoftwareConfig && !softwareConfigError && !!softwareConfig;
const isSoftwareEnabled = !!softwareConfig?.features
?.enable_software_inventory;
const vulnWebhookSettings =
softwareConfig?.webhook_settings?.vulnerabilities_webhook;
const isVulnWebhookEnabled = !!vulnWebhookSettings?.enable_vulnerabilities_webhook;
const isVulnIntegrationEnabled = (integrations?: IIntegrations) => {
return (
!!integrations?.jira?.some((j) => j.enable_software_vulnerabilities) ||
!!integrations?.zendesk?.some((z) => z.enable_software_vulnerabilities)
);
};
const isAnyVulnAutomationEnabled =
isVulnWebhookEnabled ||
isVulnIntegrationEnabled(softwareConfig?.integrations);
const recentVulnerabilityMaxAge = (() => {
let maxAgeInNanoseconds: number | undefined;
if (softwareConfig && "vulnerabilities" in softwareConfig) {
maxAgeInNanoseconds =
softwareConfig.vulnerabilities.recent_vulnerability_max_age;
} else {
maxAgeInNanoseconds =
globalConfig?.vulnerabilities.recent_vulnerability_max_age;
}
return maxAgeInNanoseconds
? Math.round(maxAgeInNanoseconds / 86400000000000) // convert from nanoseconds to days
: CONFIG_DEFAULT_RECENT_VULNERABILITY_MAX_AGE_IN_DAYS;
})();
const { const {
data: software, data: software,
@ -191,8 +217,9 @@ const ManageSoftwarePage = ({
({ queryKey }) => softwareAPI.load(queryKey[0]), ({ queryKey }) => softwareAPI.load(queryKey[0]),
{ {
enabled: enabled:
isOnGlobalTeam || isSoftwareConfigLoaded &&
!!availableTeams?.find((t) => t.id === currentTeam?.id), (isOnGlobalTeam ||
!!availableTeams?.find((t) => t.id === currentTeam?.id)),
keepPreviousData: true, keepPreviousData: true,
staleTime: 30000, // stale time can be adjusted if fresher data is desired based on software inventory interval staleTime: 30000, // stale time can be adjusted if fresher data is desired based on software inventory interval
} }
@ -221,8 +248,9 @@ const ManageSoftwarePage = ({
}, },
{ {
enabled: enabled:
isOnGlobalTeam || isSoftwareConfigLoaded &&
!!availableTeams?.find((t) => t.id === currentTeam?.id), (isOnGlobalTeam ||
!!availableTeams?.find((t) => t.id === currentTeam?.id)),
keepPreviousData: true, keepPreviousData: true,
staleTime: 30000, // stale time can be adjusted if fresher data is desired based on software inventory interval staleTime: 30000, // stale time can be adjusted if fresher data is desired based on software inventory interval
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
@ -231,21 +259,6 @@ const ManageSoftwarePage = ({
} }
); );
const canAddOrRemoveSoftwareWebhook = isGlobalAdmin || isGlobalMaintainer;
const {
data: softwareVulnerabilitiesWebhook,
isLoading: isLoadingSoftwareVulnerabilitiesWebhook,
refetch: refetchSoftwareVulnerabilitiesWebhook,
} = useQuery<IConfig, Error, IWebhookSoftwareVulnerabilities>(
["config"],
() => configAPI.loadAll(),
{
enabled: canAddOrRemoveSoftwareWebhook,
select: (data: IConfig) => data.webhook_settings.vulnerabilities_webhook,
}
);
const onQueryChange = useDebouncedCallback( const onQueryChange = useDebouncedCallback(
async ({ async ({
pageIndex: newPageIndex, pageIndex: newPageIndex,
@ -270,8 +283,9 @@ const ManageSoftwarePage = ({
300 300
); );
const toggleManageAutomationsModal = () => const toggleManageAutomationsModal = useCallback(() => {
setShowManageAutomationsModal(!showManageAutomationsModal); setShowManageAutomationsModal(!showManageAutomationsModal);
}, [setShowManageAutomationsModal, showManageAutomationsModal]);
const togglePreviewPayloadModal = useCallback(() => { const togglePreviewPayloadModal = useCallback(() => {
setShowPreviewPayloadModal(!showPreviewPayloadModal); setShowPreviewPayloadModal(!showPreviewPayloadModal);
@ -281,10 +295,6 @@ const ManageSoftwarePage = ({
setShowPreviewTicketModal(!showPreviewTicketModal); setShowPreviewTicketModal(!showPreviewTicketModal);
}, [setShowPreviewTicketModal, showPreviewTicketModal]); }, [setShowPreviewTicketModal, showPreviewTicketModal]);
const onManageAutomationsClick = () => {
toggleManageAutomationsModal();
};
const onCreateWebhookSubmit = async ( const onCreateWebhookSubmit = async (
configSoftwareAutomations: ISoftwareAutomations configSoftwareAutomations: ISoftwareAutomations
) => { ) => {
@ -295,7 +305,7 @@ const ManageSoftwarePage = ({
"success", "success",
"Successfully updated vulnerability automations." "Successfully updated vulnerability automations."
); );
refetchSoftwareVulnerabilitiesWebhook(); refetchSoftwareConfig();
}); });
} catch { } catch {
renderFlash( renderFlash(
@ -311,28 +321,34 @@ const ManageSoftwarePage = ({
setPageIndex(0); setPageIndex(0);
}; };
const renderHeaderButtons = ( // TODO: refactor/replace team dropdown header component in accordance with new patterns
state: IHeaderButtonsState const renderHeaderButtons = useCallback(
): JSX.Element | null => { (state: IHeaderButtonsState): JSX.Element | null => {
if ( const {
!softwareError && teamId,
state.isGlobalAdmin && isLoading,
(!state.isPremiumTier || state.teamId === 0) && isGlobalAdmin,
!state.isLoading isPremiumTier: isPremium,
) { } = state;
return ( const canManageAutomations =
<Button isGlobalAdmin && (!isPremium || teamId === 0);
onClick={onManageAutomationsClick} if (canManageAutomations && !softwareError && !isLoading) {
className={`${baseClass}__manage-automations button`} return (
variant="brand" <Button
> onClick={toggleManageAutomationsModal}
<span>Manage automations</span> className={`${baseClass}__manage-automations button`}
</Button> variant="brand"
); >
} <span>Manage automations</span>
return null; </Button>
}; );
}
return null;
},
[softwareError, toggleManageAutomationsModal]
);
// TODO: refactor/replace team dropdown header component in accordance with new patterns
const renderHeaderDescription = (state: ITeamsDropdownState) => { const renderHeaderDescription = (state: ITeamsDropdownState) => {
return ( return (
<p> <p>
@ -351,6 +367,7 @@ const ManageSoftwarePage = ({
); );
}; };
// TODO: refactor/replace team dropdown header component in accordance with new patterns
const renderHeader = useCallback(() => { const renderHeader = useCallback(() => {
return ( return (
<TeamsDropdownHeader <TeamsDropdownHeader
@ -363,12 +380,12 @@ const ManageSoftwarePage = ({
buttons={(state) => buttons={(state) =>
renderHeaderButtons({ renderHeaderButtons({
...state, ...state,
isLoading: isLoadingSoftwareVulnerabilitiesWebhook, isLoading: !isSoftwareConfigLoaded,
}) })
} }
/> />
); );
}, [router, location, isLoadingSoftwareVulnerabilitiesWebhook]); }, [router, location, isSoftwareConfigLoaded, renderHeaderButtons]);
const renderSoftwareCount = useCallback(() => { const renderSoftwareCount = useCallback(() => {
const count = softwareCount; const count = softwareCount;
@ -386,7 +403,6 @@ const ManageSoftwarePage = ({
); );
} }
// TODO: Use setInterval to keep last updated time current?
if (count) { if (count) {
return ( return (
<div <div
@ -412,7 +428,7 @@ const ManageSoftwarePage = ({
isSoftwareEnabled, isSoftwareEnabled,
]); ]);
// TODO: retool this with react-router location descriptor objects // TODO: refactor in accordance with new patterns for query params and management of URL state
const buildUrlQueryString = (queryString: string, vulnerable: boolean) => { const buildUrlQueryString = (queryString: string, vulnerable: boolean) => {
queryString = queryString.startsWith("?") queryString = queryString.startsWith("?")
? queryString.slice(1) ? queryString.slice(1)
@ -438,6 +454,7 @@ const ManageSoftwarePage = ({
return queryString; return queryString;
}; };
// TODO: refactor in accordance with new patterns for query params and management of URL state
const onVulnFilterChange = useCallback( const onVulnFilterChange = useCallback(
(vulnerable: boolean) => { (vulnerable: boolean) => {
setFilterVuln(vulnerable); setFilterVuln(vulnerable);
@ -499,14 +516,17 @@ const ManageSoftwarePage = ({
[isPremiumTier] [isPremiumTier]
); );
return !availableTeams || !config ? ( return !availableTeams ||
!globalConfig ||
(!softwareConfig && !softwareConfigError) ? (
<Spinner /> <Spinner />
) : ( ) : (
<MainContent> <MainContent>
<div className={`${baseClass}__wrapper`}> <div className={`${baseClass}__wrapper`}>
{renderHeader()} {renderHeader()}
<div className={`${baseClass}__table`}> <div className={`${baseClass}__table`}>
{softwareError && !isFetchingSoftware ? ( {(softwareError && !isFetchingSoftware) ||
(softwareConfigError && !isFetchingSoftwareConfig) ? (
<TableDataError /> <TableDataError />
) : ( ) : (
<TableContainer <TableContainer
@ -552,18 +572,9 @@ const ManageSoftwarePage = ({
togglePreviewTicketModal={togglePreviewTicketModal} togglePreviewTicketModal={togglePreviewTicketModal}
showPreviewPayloadModal={showPreviewPayloadModal} showPreviewPayloadModal={showPreviewPayloadModal}
showPreviewTicketModal={showPreviewTicketModal} showPreviewTicketModal={showPreviewTicketModal}
softwareVulnerabilityAutomationEnabled={ softwareVulnerabilityAutomationEnabled={isAnyVulnAutomationEnabled}
isVulnerabilityAutomationsEnabled softwareVulnerabilityWebhookEnabled={isVulnWebhookEnabled}
} currentDestinationUrl={vulnWebhookSettings?.destination_url || ""}
softwareVulnerabilityWebhookEnabled={
softwareVulnerabilitiesWebhook &&
softwareVulnerabilitiesWebhook.enable_vulnerabilities_webhook
}
currentDestinationUrl={
(softwareVulnerabilitiesWebhook &&
softwareVulnerabilitiesWebhook.destination_url) ||
""
}
recentVulnerabilityMaxAge={recentVulnerabilityMaxAge} recentVulnerabilityMaxAge={recentVulnerabilityMaxAge}
/> />
)} )}

View File

@ -11,7 +11,10 @@ import {
IIntegrations, IIntegrations,
IIntegrationType, IIntegrationType,
} from "interfaces/integration"; } from "interfaces/integration";
import { IConfig } from "interfaces/config"; import {
IConfig,
CONFIG_DEFAULT_RECENT_VULNERABILITY_MAX_AGE_IN_DAYS,
} from "interfaces/config";
import configAPI from "services/entities/config"; import configAPI from "services/entities/config";
import ReactTooltip from "react-tooltip"; import ReactTooltip from "react-tooltip";
@ -26,7 +29,7 @@ import InputField from "components/forms/fields/InputField";
import { IWebhookSoftwareVulnerabilities } from "interfaces/webhook"; import { IWebhookSoftwareVulnerabilities } from "interfaces/webhook";
import useDeepEffect from "hooks/useDeepEffect"; import useDeepEffect from "hooks/useDeepEffect";
import _, { size } from "lodash"; import { size } from "lodash";
import PreviewPayloadModal from "../PreviewPayloadModal"; import PreviewPayloadModal from "../PreviewPayloadModal";
import PreviewTicketModal from "../PreviewTicketModal"; import PreviewTicketModal from "../PreviewTicketModal";
@ -327,7 +330,9 @@ const ManageAutomationsModal = ({
<p> <p>
A ticket will be created in your <b>Integration</b> if a detected A ticket will be created in your <b>Integration</b> if a detected
vulnerability (CVE) was published in the last{" "} vulnerability (CVE) was published in the last{" "}
{recentVulnerabilityMaxAge || "30"} days. {recentVulnerabilityMaxAge ||
CONFIG_DEFAULT_RECENT_VULNERABILITY_MAX_AGE_IN_DAYS}{" "}
days.
</p> </p>
</div> </div>
{(jiraIntegrationsIndexed && jiraIntegrationsIndexed.length > 0) || {(jiraIntegrationsIndexed && jiraIntegrationsIndexed.length > 0) ||

View File

@ -5,10 +5,12 @@ import { pick } from "lodash";
import { buildQueryStringFromParams } from "utilities/url"; import { buildQueryStringFromParams } from "utilities/url";
import { IEnrollSecret } from "interfaces/enroll_secret"; import { IEnrollSecret } from "interfaces/enroll_secret";
import { IIntegrations } from "interfaces/integration";
import { import {
INewMembersBody, INewMembersBody,
IRemoveMembersBody, IRemoveMembersBody,
ITeamConfig, ITeamConfig,
ITeamWebhookSettings,
} from "interfaces/team"; } from "interfaces/team";
interface ILoadTeamsParams { interface ILoadTeamsParams {
@ -33,6 +35,12 @@ export interface ITeamFormData {
name: string; name: string;
} }
export interface IUpdateTeamFormData {
name: string;
webhook_settings: Partial<ITeamWebhookSettings>;
integrations: IIntegrations;
}
export default { export default {
create: (formData: ITeamFormData) => { create: (formData: ITeamFormData) => {
const { TEAMS } = endpoints; const { TEAMS } = endpoints;
@ -65,7 +73,7 @@ export default {
return sendRequest("GET", path); return sendRequest("GET", path);
}, },
update: ( update: (
{ name, webhook_settings, integrations }: Partial<ITeamConfig>, { name, webhook_settings, integrations }: Partial<IUpdateTeamFormData>,
teamId?: number teamId?: number
): Promise<ITeamConfig> => { ): Promise<ITeamConfig> => {
if (typeof teamId === "undefined") { if (typeof teamId === "undefined") {