Update UI to support filtering by software install status (#18888)
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
- Added functionality to filter hosts by software installer status.
|
||||
- Added endpoints to upload, delete, and download software installers.
|
||||
- Added endpoints to get host software install results.
|
||||
- Updated activity feeds to include software installer activities.
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useContext, useEffect, useMemo } from "react";
|
||||
import { InjectedRouter } from "react-router";
|
||||
import { findLastIndex, trimStart } from "lodash";
|
||||
import { findLastIndex, over, trimStart } from "lodash";
|
||||
|
||||
import { AppContext } from "context/app";
|
||||
import { TableContext } from "context/table";
|
||||
@@ -16,6 +16,30 @@ import {
|
||||
import { IUser, IUserRole } from "interfaces/user";
|
||||
import permissions from "utilities/permissions";
|
||||
import sort from "utilities/sort";
|
||||
import { HOSTS_QUERY_PARAMS } from "services/entities/hosts";
|
||||
|
||||
type OnTeamChangeFuncShouldStripParam = (
|
||||
teamIdForApi: number | undefined
|
||||
) => boolean;
|
||||
|
||||
type OnTeamChangeFuncShouldReplaceParam = (
|
||||
teamIdForApi: number | undefined
|
||||
) => [boolean, string];
|
||||
|
||||
/**
|
||||
* This type is used to define functions that determine whether a query parameter should be stripped or replaced
|
||||
* when the team id changes.
|
||||
*
|
||||
* The key is the name of the query parameter and the value is a function that receives the new team
|
||||
* id with a return type of either:
|
||||
* - boolean indicating whether the query parameter should be stripped
|
||||
* - tuple of a boolean and a string, where the boolean indicates whether the query parameter should be replaced
|
||||
* and the string is the new value for the query parameter
|
||||
*/
|
||||
export type IConfigOverrideParamsOnTeamChange = Record<
|
||||
string,
|
||||
OnTeamChangeFuncShouldReplaceParam | OnTeamChangeFuncShouldStripParam
|
||||
>;
|
||||
|
||||
const splitQueryStringParts = (queryString: string) =>
|
||||
trimStart(queryString, "?")
|
||||
@@ -27,7 +51,8 @@ const joinQueryStringParts = (parts: string[]) =>
|
||||
|
||||
const rebuildQueryStringWithTeamId = (
|
||||
queryString: string,
|
||||
newTeamId: number
|
||||
newTeamId: number,
|
||||
configAdditionalParams?: IConfigOverrideParamsOnTeamChange
|
||||
) => {
|
||||
const parts = splitQueryStringParts(queryString);
|
||||
|
||||
@@ -67,6 +92,41 @@ const rebuildQueryStringWithTeamId = (
|
||||
parts.splice(teamIndex, 1); // just remove the old team part
|
||||
}
|
||||
|
||||
if (configAdditionalParams) {
|
||||
Object.entries(configAdditionalParams).forEach(([paramName, fn]) => {
|
||||
let shouldStrip = false;
|
||||
let shouldReplace = false;
|
||||
let replaceString = "";
|
||||
|
||||
const val = fn(newTeamId);
|
||||
if (Array.isArray(val)) {
|
||||
[shouldReplace, replaceString] = val;
|
||||
} else if (typeof val === "boolean") {
|
||||
shouldStrip = val;
|
||||
}
|
||||
|
||||
if (shouldStrip || shouldReplace) {
|
||||
const paramIndex = parts.findIndex((p) =>
|
||||
p.startsWith(`${paramName}=`)
|
||||
);
|
||||
|
||||
if (shouldStrip && paramIndex !== -1) {
|
||||
parts.splice(paramIndex, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldReplace) {
|
||||
const newPart = `${paramName}=${replaceString}`;
|
||||
if (paramIndex === -1) {
|
||||
parts.splice(paramIndex, 1, newPart);
|
||||
} else {
|
||||
parts.push(newPart);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return joinQueryStringParts(parts);
|
||||
};
|
||||
|
||||
@@ -223,6 +283,7 @@ export const useTeamIdParam = ({
|
||||
includeNoTeam,
|
||||
permittedAccessByTeamRole,
|
||||
resetSelectedRowsOnTeamChange = true,
|
||||
overrideParamsOnTeamChange,
|
||||
}: {
|
||||
location?: {
|
||||
pathname: string;
|
||||
@@ -235,6 +296,7 @@ export const useTeamIdParam = ({
|
||||
includeNoTeam: boolean;
|
||||
permittedAccessByTeamRole?: Record<IUserRole, boolean>;
|
||||
resetSelectedRowsOnTeamChange?: boolean;
|
||||
overrideParamsOnTeamChange?: IConfigOverrideParamsOnTeamChange;
|
||||
}) => {
|
||||
const { hash, pathname, query, search } = location;
|
||||
const {
|
||||
@@ -282,11 +344,18 @@ export const useTeamIdParam = ({
|
||||
|
||||
router.replace(
|
||||
pathname
|
||||
.concat(rebuildQueryStringWithTeamId(search, teamId))
|
||||
.concat(
|
||||
rebuildQueryStringWithTeamId(
|
||||
search,
|
||||
teamId,
|
||||
overrideParamsOnTeamChange
|
||||
)
|
||||
)
|
||||
.concat(hash || "")
|
||||
);
|
||||
},
|
||||
[
|
||||
overrideParamsOnTeamChange,
|
||||
resetSelectedRowsOnTeamChange,
|
||||
router,
|
||||
pathname,
|
||||
|
||||
@@ -148,17 +148,29 @@ export const formatSoftwareType = ({
|
||||
}
|
||||
return type;
|
||||
};
|
||||
/*
|
||||
* SoftwareInstallStatus represents the possible states of software install operations.
|
||||
*
|
||||
*/
|
||||
export type ISoftwareInstallStatus = "pending" | "installed" | "failed";
|
||||
|
||||
/**
|
||||
*
|
||||
* This list comprises all possible states of software install operations.
|
||||
*/
|
||||
export const SOFTWARE_INSTALL_STATUSES = [
|
||||
"failed",
|
||||
"installed",
|
||||
"pending",
|
||||
] as const;
|
||||
|
||||
/*
|
||||
* SoftwareInstallStatus represents the possible states of software install operations.
|
||||
*/
|
||||
export type SoftwareInstallStatus = typeof SOFTWARE_INSTALL_STATUSES[number];
|
||||
|
||||
export const isValidSoftwareInstallStatus = (
|
||||
s: string | undefined
|
||||
): s is SoftwareInstallStatus =>
|
||||
!!s && SOFTWARE_INSTALL_STATUSES.includes(s as SoftwareInstallStatus);
|
||||
|
||||
/**
|
||||
* ISoftwareInstallResult is the shape of a software install result object
|
||||
* returned by the Fleet API.
|
||||
*
|
||||
*/
|
||||
export interface ISoftwareInstallResult {
|
||||
install_uuid: string;
|
||||
@@ -167,7 +179,7 @@ export interface ISoftwareInstallResult {
|
||||
software_package: string;
|
||||
host_id: number;
|
||||
host_display_name: string;
|
||||
status: ISoftwareInstallStatus;
|
||||
status: SoftwareInstallStatus;
|
||||
detail: string;
|
||||
output: string;
|
||||
pre_install_query_output: string;
|
||||
@@ -200,7 +212,7 @@ export interface IHostSoftware {
|
||||
package_available_for_install?: string | null;
|
||||
source: string;
|
||||
bundle_identifier?: string;
|
||||
status: ISoftwareInstallStatus | null;
|
||||
status: SoftwareInstallStatus | null;
|
||||
last_install: ISoftwareLastInstall | null;
|
||||
installed_versions: ISoftwareInstallVersion[] | null;
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
|
||||
import endpoints from "utilities/endpoints";
|
||||
import { ISoftwareInstallStatus, ISoftwarePackage } from "interfaces/software";
|
||||
import { SoftwareInstallStatus, ISoftwarePackage } from "interfaces/software";
|
||||
import PATHS from "router/paths";
|
||||
import { AppContext } from "context/app";
|
||||
import { buildQueryStringFromParams } from "utilities/url";
|
||||
@@ -27,7 +27,7 @@ interface IStatusDisplayOption {
|
||||
}
|
||||
|
||||
const STATUS_DISPLAY_OPTIONS: Record<
|
||||
ISoftwareInstallStatus,
|
||||
SoftwareInstallStatus,
|
||||
IStatusDisplayOption
|
||||
> = {
|
||||
installed: {
|
||||
@@ -49,7 +49,7 @@ const STATUS_DISPLAY_OPTIONS: Record<
|
||||
|
||||
interface IPackageStatusCountProps {
|
||||
softwareId: number;
|
||||
status: ISoftwareInstallStatus;
|
||||
status: SoftwareInstallStatus;
|
||||
count: number;
|
||||
teamId?: number;
|
||||
}
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ import { useQuery } from "react-query";
|
||||
import {
|
||||
ISoftwareInstallResult,
|
||||
ISoftwareInstallResults,
|
||||
ISoftwareInstallStatus,
|
||||
SoftwareInstallStatus,
|
||||
} from "interfaces/software";
|
||||
import softwareAPI from "services/entities/software";
|
||||
|
||||
@@ -18,13 +18,13 @@ import { IconNames } from "components/icons";
|
||||
|
||||
const baseClass = "software-install-details";
|
||||
|
||||
const STATUS_ICONS: Record<ISoftwareInstallStatus, IconNames> = {
|
||||
const STATUS_ICONS: Record<SoftwareInstallStatus, IconNames> = {
|
||||
pending: "pending-outline",
|
||||
installed: "success-outline",
|
||||
failed: "error-outline",
|
||||
} as const;
|
||||
|
||||
const STATUS_PREDICATES: Record<ISoftwareInstallStatus, string> = {
|
||||
const STATUS_PREDICATES: Record<SoftwareInstallStatus, string> = {
|
||||
pending: "will install",
|
||||
installed: "installed",
|
||||
failed: "failed to install",
|
||||
|
||||
@@ -10,6 +10,7 @@ export const MANAGE_HOSTS_PAGE_FILTER_KEYS = [
|
||||
"policy_response",
|
||||
"macos_settings",
|
||||
"software_id",
|
||||
HOSTS_QUERY_PARAMS.SOFTWARE_STATUS,
|
||||
"status",
|
||||
"mdm_id",
|
||||
"mdm_enrollment_status",
|
||||
@@ -32,6 +33,7 @@ export const MANAGE_HOSTS_PAGE_LABEL_INCOMPATIBLE_QUERY_PARAMS = [
|
||||
"software_id",
|
||||
"software_version_id",
|
||||
"software_title_id",
|
||||
HOSTS_QUERY_PARAMS.SOFTWARE_STATUS,
|
||||
"bootstrap_package",
|
||||
"macos_settings",
|
||||
HOSTS_QUERY_PARAMS.OS_SETTINGS,
|
||||
|
||||
@@ -23,6 +23,7 @@ import hostsAPI, {
|
||||
ILoadHostsResponse,
|
||||
ISortOption,
|
||||
MacSettingsStatusQueryParam,
|
||||
HOSTS_QUERY_PARAMS,
|
||||
} from "services/entities/hosts";
|
||||
import hostCountAPI, {
|
||||
IHostsCountQueryKey,
|
||||
@@ -49,7 +50,11 @@ import { getErrorReason } from "interfaces/errors";
|
||||
import { ILabel } from "interfaces/label";
|
||||
import { IOperatingSystemVersion } from "interfaces/operating_system";
|
||||
import { IPolicy, IStoredPolicyResponse } from "interfaces/policy";
|
||||
import { ITeam } from "interfaces/team";
|
||||
import {
|
||||
isValidSoftwareInstallStatus,
|
||||
SoftwareInstallStatus,
|
||||
} from "interfaces/software";
|
||||
import { API_NO_TEAM_ID, ITeam } from "interfaces/team";
|
||||
import { IEmptyTableProps } from "interfaces/empty_table";
|
||||
import {
|
||||
DiskEncryptionStatus,
|
||||
@@ -162,6 +167,11 @@ const ManageHostsPage = ({
|
||||
router,
|
||||
includeAllTeams: true,
|
||||
includeNoTeam: true,
|
||||
overrideParamsOnTeamChange: {
|
||||
// remove the software status filter when selecting all teams or no team
|
||||
[HOSTS_QUERY_PARAMS.SOFTWARE_STATUS]: (newTeamId?: number) =>
|
||||
!newTeamId || newTeamId < 1,
|
||||
},
|
||||
});
|
||||
|
||||
const hostHiddenColumns = localStorage.getItem("hostHiddenColumns");
|
||||
@@ -232,6 +242,11 @@ const ManageHostsPage = ({
|
||||
queryParams?.software_title_id !== undefined
|
||||
? parseInt(queryParams.software_title_id, 10)
|
||||
: undefined;
|
||||
const softwareStatus = isValidSoftwareInstallStatus(
|
||||
queryParams?.[HOSTS_QUERY_PARAMS.SOFTWARE_STATUS]
|
||||
)
|
||||
? (queryParams[HOSTS_QUERY_PARAMS.SOFTWARE_STATUS] as SoftwareInstallStatus)
|
||||
: undefined;
|
||||
const status = isAcceptableStatus(queryParams?.status)
|
||||
? queryParams?.status
|
||||
: undefined;
|
||||
@@ -380,6 +395,7 @@ const ManageHostsPage = ({
|
||||
softwareId,
|
||||
softwareTitleId,
|
||||
softwareVersionId,
|
||||
softwareStatus,
|
||||
status,
|
||||
mdmId,
|
||||
mdmEnrollmentStatus,
|
||||
@@ -423,6 +439,7 @@ const ManageHostsPage = ({
|
||||
softwareId,
|
||||
softwareTitleId,
|
||||
softwareVersionId,
|
||||
softwareStatus,
|
||||
status,
|
||||
mdmId,
|
||||
mdmEnrollmentStatus,
|
||||
@@ -517,10 +534,10 @@ const ManageHostsPage = ({
|
||||
useEffect(() => {
|
||||
if (
|
||||
location.search.match(
|
||||
/software_id|software_version_id|software_title_id/gi
|
||||
/software_id|software_version_id|software_title_id|software_status/gi
|
||||
)
|
||||
) {
|
||||
// regex matches any of "software_id", "software_version_id", or "software_title_id"
|
||||
// regex matches any of "software_id", "software_version_id", "software_title_id", or "software_status"
|
||||
// so we don't set the filtered hosts path in those cases
|
||||
return;
|
||||
}
|
||||
@@ -712,6 +729,25 @@ const ManageHostsPage = ({
|
||||
);
|
||||
};
|
||||
|
||||
const handleSoftwareInstallStatausChange = (
|
||||
newStatus: SoftwareInstallStatus
|
||||
) => {
|
||||
handleResetPageIndex();
|
||||
|
||||
router.replace(
|
||||
getNextLocationPath({
|
||||
pathPrefix: PATHS.MANAGE_HOSTS,
|
||||
routeTemplate,
|
||||
routeParams,
|
||||
queryParams: {
|
||||
...queryParams,
|
||||
[HOSTS_QUERY_PARAMS.SOFTWARE_STATUS]: newStatus,
|
||||
page: 0, // resets page index
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onAddLabelClick = () => {
|
||||
router.push(`${PATHS.NEW_LABEL}`);
|
||||
};
|
||||
@@ -815,6 +851,10 @@ const ManageHostsPage = ({
|
||||
newQueryParams.software_version_id = softwareVersionId;
|
||||
} else if (softwareTitleId) {
|
||||
newQueryParams.software_title_id = softwareTitleId;
|
||||
if (softwareStatus && teamIdForApi && teamIdForApi > 0) {
|
||||
// software_status is only valid when software_title_id is present and a team is selected
|
||||
newQueryParams[HOSTS_QUERY_PARAMS.SOFTWARE_STATUS] = softwareStatus;
|
||||
}
|
||||
} else if (mdmId) {
|
||||
newQueryParams.mdm_id = mdmId;
|
||||
} else if (mdmEnrollmentStatus) {
|
||||
@@ -864,6 +904,7 @@ const ManageHostsPage = ({
|
||||
softwareId,
|
||||
softwareVersionId,
|
||||
softwareTitleId,
|
||||
softwareStatus,
|
||||
mdmId,
|
||||
mdmEnrollmentStatus,
|
||||
munkiIssueId,
|
||||
@@ -1062,6 +1103,7 @@ const ManageHostsPage = ({
|
||||
softwareId,
|
||||
softwareTitleId,
|
||||
softwareVersionId,
|
||||
softwareStatus,
|
||||
osName,
|
||||
osVersionId,
|
||||
osVersion,
|
||||
@@ -1114,6 +1156,7 @@ const ManageHostsPage = ({
|
||||
softwareId,
|
||||
softwareTitleId,
|
||||
softwareVersionId,
|
||||
softwareStatus,
|
||||
osName,
|
||||
osVersionId,
|
||||
osVersion,
|
||||
@@ -1325,6 +1368,7 @@ const ManageHostsPage = ({
|
||||
softwareId,
|
||||
softwareTitleId,
|
||||
softwareVersionId,
|
||||
softwareStatus,
|
||||
status,
|
||||
mdmId,
|
||||
mdmEnrollmentStatus,
|
||||
@@ -1527,6 +1571,7 @@ const ManageHostsPage = ({
|
||||
softwareId ||
|
||||
softwareTitleId ||
|
||||
softwareVersionId ||
|
||||
softwareStatus ||
|
||||
osName ||
|
||||
osVersionId ||
|
||||
osVersion ||
|
||||
@@ -1665,6 +1710,7 @@ const ManageHostsPage = ({
|
||||
softwareId,
|
||||
softwareTitleId,
|
||||
softwareVersionId,
|
||||
softwareStatus,
|
||||
mdmId,
|
||||
mdmEnrollmentStatus,
|
||||
lowDiskSpaceHosts,
|
||||
@@ -1696,6 +1742,9 @@ const ManageHostsPage = ({
|
||||
handleChangeBootstrapPackageStatusFilter
|
||||
}
|
||||
onChangeMacSettingsFilter={handleMacSettingsStatusDropdownChange}
|
||||
onChangeSoftwareInstallStatusFilter={
|
||||
handleSoftwareInstallStatausChange
|
||||
}
|
||||
onClickEditLabel={onEditLabelClick}
|
||||
onClickDeleteLabel={toggleDeleteLabelModal}
|
||||
isSandboxMode={isSandboxMode}
|
||||
|
||||
+43
-8
@@ -15,6 +15,8 @@ import {
|
||||
} from "interfaces/mdm";
|
||||
import { IMunkiIssuesAggregate } from "interfaces/macadmins";
|
||||
import { IPolicy } from "interfaces/policy";
|
||||
import { SoftwareInstallStatus } from "interfaces/software";
|
||||
|
||||
import {
|
||||
HOSTS_QUERY_PARAMS,
|
||||
MacSettingsStatusQueryParam,
|
||||
@@ -70,6 +72,7 @@ interface IHostsFilterBlockProps {
|
||||
osSettingsStatus?: MdmProfileStatus;
|
||||
diskEncryptionStatus?: DiskEncryptionStatus;
|
||||
bootstrapPackageStatus?: BootstrapPackageStatus;
|
||||
softwareStatus?: SoftwareInstallStatus;
|
||||
};
|
||||
selectedLabel?: ILabel;
|
||||
isOnlyObserver?: boolean;
|
||||
@@ -84,6 +87,9 @@ interface IHostsFilterBlockProps {
|
||||
onChangeMacSettingsFilter: (
|
||||
newMacSettingsStatus: MacSettingsStatusQueryParam
|
||||
) => void;
|
||||
onChangeSoftwareInstallStatusFilter: (
|
||||
newStatus: SoftwareInstallStatus
|
||||
) => void;
|
||||
onClickEditLabel: (evt: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onClickDeleteLabel: () => void;
|
||||
isSandboxMode?: boolean;
|
||||
@@ -117,6 +123,7 @@ const HostsFilterBlock = ({
|
||||
osSettingsStatus,
|
||||
diskEncryptionStatus,
|
||||
bootstrapPackageStatus,
|
||||
softwareStatus,
|
||||
},
|
||||
selectedLabel,
|
||||
isOnlyObserver,
|
||||
@@ -127,6 +134,7 @@ const HostsFilterBlock = ({
|
||||
onChangeDiskEncryptionStatusFilter,
|
||||
onChangeBootstrapPackageStatusFilter,
|
||||
onChangeMacSettingsFilter,
|
||||
onChangeSoftwareInstallStatusFilter,
|
||||
onClickEditLabel,
|
||||
onClickDeleteLabel,
|
||||
isSandboxMode = false,
|
||||
@@ -254,7 +262,7 @@ const HostsFilterBlock = ({
|
||||
);
|
||||
};
|
||||
|
||||
const renderSoftwareFilterBlock = () => {
|
||||
const renderSoftwareFilterBlock = (additionalClearParams?: string[]) => {
|
||||
if (!softwareDetails) return null;
|
||||
|
||||
const { name, version } = softwareDetails;
|
||||
@@ -264,6 +272,16 @@ const HostsFilterBlock = ({
|
||||
}
|
||||
label = label.trim() || "Unknown software";
|
||||
|
||||
const clearParams = [
|
||||
"software_id",
|
||||
"software_version_id",
|
||||
"software_title_id",
|
||||
];
|
||||
|
||||
if (additionalClearParams?.length) {
|
||||
clearParams.push(...additionalClearParams);
|
||||
}
|
||||
|
||||
// const TooltipDescription = (
|
||||
// <span>
|
||||
// Hosts with {name || "Unknown software"},
|
||||
@@ -275,13 +293,7 @@ const HostsFilterBlock = ({
|
||||
return (
|
||||
<FilterPill
|
||||
label={label}
|
||||
onClear={() =>
|
||||
handleClearFilter([
|
||||
"software_id",
|
||||
"software_version_id",
|
||||
"software_title_id",
|
||||
])
|
||||
}
|
||||
onClear={() => handleClearFilter(clearParams)}
|
||||
// tooltipDescription={TooltipDescription}
|
||||
/>
|
||||
);
|
||||
@@ -452,6 +464,26 @@ const HostsFilterBlock = ({
|
||||
);
|
||||
};
|
||||
|
||||
const renderSoftwareInstallStatusBlock = () => {
|
||||
const OPTIONS = [
|
||||
{ value: "installed", label: "Installed" },
|
||||
{ value: "failed", label: "Failed" },
|
||||
{ value: "pending", label: "Pending" },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dropdown
|
||||
value={softwareStatus}
|
||||
className={`${baseClass}__sw-install-status-dropdown`}
|
||||
options={OPTIONS}
|
||||
onChange={onChangeSoftwareInstallStatusFilter}
|
||||
/>
|
||||
{renderSoftwareFilterBlock([HOSTS_QUERY_PARAMS.SOFTWARE_STATUS])}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const showSelectedLabel = selectedLabel && selectedLabel.type !== "all";
|
||||
|
||||
if (
|
||||
@@ -461,6 +493,7 @@ const HostsFilterBlock = ({
|
||||
softwareId ||
|
||||
softwareTitleId ||
|
||||
softwareVersionId ||
|
||||
softwareStatus ||
|
||||
mdmId ||
|
||||
mdmEnrollmentStatus ||
|
||||
lowDiskSpaceHosts ||
|
||||
@@ -501,6 +534,8 @@ const HostsFilterBlock = ({
|
||||
return renderPoliciesFilterBlock();
|
||||
case !!macSettingsStatus:
|
||||
return renderMacSettingsStatusFilterBlock();
|
||||
case !!softwareStatus:
|
||||
return renderSoftwareInstallStatusBlock();
|
||||
case !!softwareId || !!softwareVersionId || !!softwareTitleId:
|
||||
return renderSoftwareFilterBlock();
|
||||
case !!mdmId:
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
|
||||
// NOTE: Look more into this styling
|
||||
&__os_settings-dropdown,
|
||||
&__macsettings-dropdown {
|
||||
&__macsettings-dropdown,
|
||||
&__sw-install-status-dropdown {
|
||||
.Select-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
import { ISoftwareInstallStatus } from "interfaces/software";
|
||||
import { SoftwareInstallStatus } from "interfaces/software";
|
||||
|
||||
import { IHostActivityItemComponentPropsWithShowDetails } from "../../ActivityConfig";
|
||||
import HostActivityItem from "../../HostActivityItem";
|
||||
@@ -8,7 +8,7 @@ import ShowDetailsButton from "../../ShowDetailsButton";
|
||||
|
||||
const baseClass = "installed-software-activity-item";
|
||||
|
||||
const STATUS_PREDICATES: Record<ISoftwareInstallStatus, string> = {
|
||||
const STATUS_PREDICATES: Record<SoftwareInstallStatus, string> = {
|
||||
failed: "failed to install",
|
||||
installed: "installed",
|
||||
pending: "told Fleet to install",
|
||||
@@ -21,7 +21,7 @@ export const getSoftwareInstallStatusPredicate = (
|
||||
return STATUS_PREDICATES.pending;
|
||||
}
|
||||
return (
|
||||
STATUS_PREDICATES[status as ISoftwareInstallStatus] ||
|
||||
STATUS_PREDICATES[status as SoftwareInstallStatus] ||
|
||||
STATUS_PREDICATES.pending
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { cloneDeep } from "lodash";
|
||||
|
||||
import {
|
||||
IHostSoftware,
|
||||
ISoftwareInstallStatus,
|
||||
SoftwareInstallStatus,
|
||||
formatSoftwareType,
|
||||
} from "interfaces/software";
|
||||
import {
|
||||
@@ -49,7 +49,7 @@ type IVulnerabilitiesCellProps = IInstalledVersionsCellProps;
|
||||
|
||||
const generateActions = (
|
||||
softwareId: number,
|
||||
status: ISoftwareInstallStatus | null,
|
||||
status: SoftwareInstallStatus | null,
|
||||
installingSoftwareId: number | null,
|
||||
canInstall: boolean,
|
||||
packageToInstall?: string | null
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { ReactNode } from "react";
|
||||
|
||||
import { ISoftwareInstallStatus } from "interfaces/software";
|
||||
import { SoftwareInstallStatus } from "interfaces/software";
|
||||
import { dateAgo } from "utilities/date_format";
|
||||
|
||||
import Icon from "components/Icon";
|
||||
@@ -9,7 +9,7 @@ import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
|
||||
const baseClass = "install-status-cell";
|
||||
|
||||
type IStatusValue = ISoftwareInstallStatus | "avaiableForInstall";
|
||||
type IStatusValue = SoftwareInstallStatus | "avaiableForInstall";
|
||||
|
||||
type IStatusDisplayConfig = {
|
||||
iconName: "success" | "pending-outline" | "error" | "install";
|
||||
@@ -56,7 +56,7 @@ const CELL_DISPLAY_OPTIONS: Record<IStatusValue, IStatusDisplayConfig> = {
|
||||
};
|
||||
|
||||
interface IInstallStatusCellProps {
|
||||
status: ISoftwareInstallStatus | null;
|
||||
status: SoftwareInstallStatus | null;
|
||||
packageToInstall?: string | null;
|
||||
installedAt?: string;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ export interface IHostCountLoadOptions {
|
||||
softwareId?: number;
|
||||
softwareTitleId?: number;
|
||||
softwareVersionId?: number;
|
||||
softwareStatus?: string;
|
||||
lowDiskSpaceHosts?: number;
|
||||
mdmId?: number;
|
||||
mdmEnrollmentStatus?: string;
|
||||
@@ -67,6 +68,7 @@ export default {
|
||||
const softwareId = options?.softwareId;
|
||||
const softwareTitleId = options?.softwareTitleId;
|
||||
const softwareVersionId = options?.softwareVersionId;
|
||||
const softwareStatus = options?.softwareStatus;
|
||||
const macSettingsStatus = options?.macSettingsStatus;
|
||||
const status = options?.status;
|
||||
const mdmId = options?.mdmId;
|
||||
@@ -89,7 +91,10 @@ export default {
|
||||
macSettingsStatus,
|
||||
osSettings,
|
||||
}),
|
||||
// TODO: shouldn't macSettingsStatus be included in the mutually exclusive query params too?
|
||||
// If so, this todo applies in other places.
|
||||
...reconcileMutuallyExclusiveHostParams({
|
||||
teamId,
|
||||
label,
|
||||
policyId,
|
||||
policyResponse,
|
||||
@@ -98,6 +103,7 @@ export default {
|
||||
munkiIssueId,
|
||||
softwareId,
|
||||
softwareTitleId,
|
||||
softwareStatus,
|
||||
softwareVersionId,
|
||||
lowDiskSpaceHosts,
|
||||
osName,
|
||||
|
||||
@@ -9,7 +9,12 @@ import {
|
||||
reconcileMutuallyInclusiveHostParams,
|
||||
} from "utilities/url";
|
||||
import { SelectedPlatform } from "interfaces/platform";
|
||||
import { ISoftwareTitle, ISoftware, IHostSoftware } from "interfaces/software";
|
||||
import {
|
||||
IHostSoftware,
|
||||
ISoftwareTitle,
|
||||
ISoftware,
|
||||
SoftwareInstallStatus,
|
||||
} from "interfaces/software";
|
||||
import {
|
||||
DiskEncryptionStatus,
|
||||
BootstrapPackageStatus,
|
||||
@@ -46,6 +51,7 @@ export type IUnlockHostResponse =
|
||||
export const HOSTS_QUERY_PARAMS = {
|
||||
OS_SETTINGS: "os_settings",
|
||||
DISK_ENCRYPTION: "os_settings_disk_encryption",
|
||||
SOFTWARE_STATUS: "software_status",
|
||||
} as const;
|
||||
|
||||
export interface ILoadHostsQueryKey extends ILoadHostsOptions {
|
||||
@@ -67,6 +73,7 @@ export interface ILoadHostsOptions {
|
||||
softwareId?: number;
|
||||
softwareTitleId?: number;
|
||||
softwareVersionId?: number;
|
||||
softwareStatus?: SoftwareInstallStatus;
|
||||
status?: HostStatus;
|
||||
mdmId?: number;
|
||||
mdmEnrollmentStatus?: string;
|
||||
@@ -97,6 +104,7 @@ export interface IExportHostsOptions {
|
||||
softwareId?: number;
|
||||
softwareTitleId?: number;
|
||||
softwareVersionId?: number;
|
||||
softwareStatus?: SoftwareInstallStatus;
|
||||
status?: HostStatus;
|
||||
mdmId?: number;
|
||||
munkiIssueId?: number;
|
||||
@@ -126,6 +134,7 @@ export interface IActionByFilter {
|
||||
softwareId?: number | null;
|
||||
softwareTitleId?: number | null;
|
||||
softwareVersionId?: number | null;
|
||||
softwareStatus?: SoftwareInstallStatus;
|
||||
osName?: string;
|
||||
osVersion?: string;
|
||||
osVersionId?: number | null;
|
||||
@@ -220,6 +229,7 @@ export default {
|
||||
softwareId,
|
||||
softwareTitleId,
|
||||
softwareVersionId,
|
||||
softwareStatus,
|
||||
osName,
|
||||
osVersion,
|
||||
osVersionId,
|
||||
@@ -245,6 +255,7 @@ export default {
|
||||
software_id: softwareId,
|
||||
software_title_id: softwareTitleId,
|
||||
software_version_id: softwareVersionId,
|
||||
[HOSTS_QUERY_PARAMS.SOFTWARE_STATUS]: softwareStatus,
|
||||
os_name: osName,
|
||||
os_version: osVersion,
|
||||
os_version_id: osVersionId,
|
||||
@@ -270,6 +281,7 @@ export default {
|
||||
const softwareId = options?.softwareId;
|
||||
const softwareTitleId = options?.softwareTitleId;
|
||||
const softwareVersionId = options?.softwareVersionId;
|
||||
const softwareStatus = options?.softwareStatus;
|
||||
const macSettingsStatus = options?.macSettingsStatus;
|
||||
const osName = options?.osName;
|
||||
const osVersionId = options?.osVersionId;
|
||||
@@ -301,6 +313,7 @@ export default {
|
||||
osSettings,
|
||||
}),
|
||||
...reconcileMutuallyExclusiveHostParams({
|
||||
teamId,
|
||||
label,
|
||||
policyId,
|
||||
policyResponse,
|
||||
@@ -310,6 +323,7 @@ export default {
|
||||
softwareId,
|
||||
softwareTitleId,
|
||||
softwareVersionId,
|
||||
softwareStatus,
|
||||
osName,
|
||||
osVersionId,
|
||||
osVersion,
|
||||
@@ -354,6 +368,7 @@ export default {
|
||||
softwareId,
|
||||
softwareTitleId,
|
||||
softwareVersionId,
|
||||
softwareStatus,
|
||||
status,
|
||||
mdmId,
|
||||
mdmEnrollmentStatus,
|
||||
@@ -388,6 +403,7 @@ export default {
|
||||
osSettings,
|
||||
}),
|
||||
...reconcileMutuallyExclusiveHostParams({
|
||||
teamId,
|
||||
label,
|
||||
policyId,
|
||||
policyResponse,
|
||||
@@ -397,6 +413,7 @@ export default {
|
||||
softwareId,
|
||||
softwareTitleId,
|
||||
softwareVersionId,
|
||||
softwareStatus,
|
||||
lowDiskSpaceHosts,
|
||||
osVersionId,
|
||||
osName,
|
||||
@@ -462,6 +479,7 @@ export default {
|
||||
softwareId,
|
||||
softwareTitleId,
|
||||
softwareVersionId,
|
||||
softwareStatus,
|
||||
osName,
|
||||
osVersion,
|
||||
osVersionId,
|
||||
@@ -488,6 +506,7 @@ export default {
|
||||
software_id: softwareId,
|
||||
software_title_id: softwareTitleId,
|
||||
software_version_id: softwareVersionId,
|
||||
[HOSTS_QUERY_PARAMS.SOFTWARE_STATUS]: softwareStatus,
|
||||
os_name: osName,
|
||||
os_version: osVersion,
|
||||
os_version_id: osVersionId,
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
HOSTS_QUERY_PARAMS,
|
||||
MacSettingsStatusQueryParam,
|
||||
} from "services/entities/hosts";
|
||||
import { isValidSoftwareInstallStatus } from "interfaces/software";
|
||||
|
||||
type QueryValues = string | number | boolean | undefined | null;
|
||||
export type QueryParams = Record<string, QueryValues>;
|
||||
@@ -23,6 +24,7 @@ interface IMutuallyInclusiveHostParams {
|
||||
}
|
||||
|
||||
interface IMutuallyExclusiveHostParams {
|
||||
teamId?: number;
|
||||
label?: string;
|
||||
policyId?: number;
|
||||
policyResponse?: string;
|
||||
@@ -33,6 +35,7 @@ interface IMutuallyExclusiveHostParams {
|
||||
softwareId?: number;
|
||||
softwareVersionId?: number;
|
||||
softwareTitleId?: number;
|
||||
softwareStatus?: string;
|
||||
osVersionId?: number;
|
||||
osName?: string;
|
||||
osVersion?: string;
|
||||
@@ -77,6 +80,48 @@ export const buildQueryStringFromParams = (queryParams: QueryParams) => {
|
||||
return queryString;
|
||||
};
|
||||
|
||||
export const reconcileSoftwareParams = ({
|
||||
teamId,
|
||||
softwareId,
|
||||
softwareVersionId,
|
||||
softwareTitleId,
|
||||
softwareStatus,
|
||||
}: Pick<
|
||||
IMutuallyExclusiveHostParams,
|
||||
| "teamId"
|
||||
| "softwareId"
|
||||
| "softwareVersionId"
|
||||
| "softwareTitleId"
|
||||
| "softwareStatus"
|
||||
>) => {
|
||||
if (
|
||||
isValidSoftwareInstallStatus(softwareStatus) &&
|
||||
softwareTitleId &&
|
||||
teamId &&
|
||||
teamId > 0
|
||||
) {
|
||||
return {
|
||||
software_title_id: softwareTitleId,
|
||||
[HOSTS_QUERY_PARAMS.SOFTWARE_STATUS]: softwareStatus,
|
||||
team_id: teamId,
|
||||
};
|
||||
}
|
||||
|
||||
if (softwareTitleId) {
|
||||
return { software_title_id: softwareTitleId };
|
||||
}
|
||||
|
||||
if (softwareVersionId) {
|
||||
return { software_version_id: softwareVersionId };
|
||||
}
|
||||
|
||||
if (softwareId) {
|
||||
return { software_id: softwareId };
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
export const reconcileMutuallyInclusiveHostParams = ({
|
||||
label,
|
||||
teamId,
|
||||
@@ -102,9 +147,12 @@ export const reconcileMutuallyInclusiveHostParams = ({
|
||||
reconciled[HOSTS_QUERY_PARAMS.OS_SETTINGS] = osSettings;
|
||||
reconciled.team_id = teamId ?? 0;
|
||||
}
|
||||
|
||||
return reconciled;
|
||||
};
|
||||
|
||||
export const reconcileMutuallyExclusiveHostParams = ({
|
||||
teamId,
|
||||
label,
|
||||
policyId,
|
||||
policyResponse,
|
||||
@@ -115,6 +163,7 @@ export const reconcileMutuallyExclusiveHostParams = ({
|
||||
softwareId,
|
||||
softwareVersionId,
|
||||
softwareTitleId,
|
||||
softwareStatus,
|
||||
osVersionId,
|
||||
osName,
|
||||
osVersion,
|
||||
@@ -147,8 +196,17 @@ export const reconcileMutuallyExclusiveHostParams = ({
|
||||
return { mdm_enrollment_status: mdmEnrollmentStatus };
|
||||
case !!munkiIssueId:
|
||||
return { munki_issue_id: munkiIssueId };
|
||||
case !!softwareTitleId:
|
||||
return { software_title_id: softwareTitleId };
|
||||
case !!softwareStatus ||
|
||||
!!softwareTitleId ||
|
||||
!!softwareVersionId ||
|
||||
!!softwareId:
|
||||
return reconcileSoftwareParams({
|
||||
teamId,
|
||||
softwareId,
|
||||
softwareVersionId,
|
||||
softwareTitleId,
|
||||
softwareStatus,
|
||||
});
|
||||
case !!softwareVersionId:
|
||||
return { software_version_id: softwareVersionId };
|
||||
case !!softwareId:
|
||||
|
||||
Reference in New Issue
Block a user