mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 17:05:18 +00:00
Check team config for software UI (#8338)
This commit is contained in:
parent
6d4c885f22
commit
9f20f01e37
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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}
|
||||||
|
@ -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 && (
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -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) ||
|
||||||
|
@ -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") {
|
||||||
|
Loading…
Reference in New Issue
Block a user