mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Fleet UI: VPP auto install software on failed policies, filter software compatible to policy's target platform, etc (#25202)
This commit is contained in:
parent
588ccd967a
commit
43628439d8
26 changed files with 235 additions and 149 deletions
2
changes/23528-install-software-policy-filter
Normal file
2
changes/23528-install-software-policy-filter
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
- Improved software installation for failed policies: Added platform-specific filtering in the software dropdown, ensuring only compatible software are displayed based on each policy's targeted platforms
|
||||
- Add VPP app to automatic installation dropdown for failed policies and auto install information on VPP app details page
|
||||
|
|
@ -9,7 +9,7 @@ import { find } from "lodash";
|
|||
|
||||
import { osqueryTables } from "utilities/osquery_tables";
|
||||
import { IOsQueryTable, DEFAULT_OSQUERY_TABLE } from "interfaces/osquery_table";
|
||||
import { SelectedPlatformString } from "interfaces/platform";
|
||||
import { CommaSeparatedPlatformString } from "interfaces/platform";
|
||||
|
||||
enum ACTIONS {
|
||||
SET_LAST_EDITED_QUERY_INFO = "SET_LAST_EDITED_QUERY_INFO",
|
||||
|
|
@ -25,7 +25,7 @@ interface ISetLastEditedQueryInfo {
|
|||
lastEditedQueryBody?: string;
|
||||
lastEditedQueryResolution?: string;
|
||||
lastEditedQueryCritical?: boolean;
|
||||
lastEditedQueryPlatform?: SelectedPlatformString | null;
|
||||
lastEditedQueryPlatform?: CommaSeparatedPlatformString | null;
|
||||
defaultPolicy?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ type InitialStateType = {
|
|||
lastEditedQueryBody: string;
|
||||
lastEditedQueryResolution: string;
|
||||
lastEditedQueryCritical: boolean;
|
||||
lastEditedQueryPlatform: SelectedPlatformString | null;
|
||||
lastEditedQueryPlatform: CommaSeparatedPlatformString | null;
|
||||
defaultPolicy: boolean;
|
||||
setLastEditedQueryId: (value: number | null) => void;
|
||||
setLastEditedQueryName: (value: string) => void;
|
||||
|
|
@ -63,7 +63,9 @@ type InitialStateType = {
|
|||
setLastEditedQueryBody: (value: string) => void;
|
||||
setLastEditedQueryResolution: (value: string) => void;
|
||||
setLastEditedQueryCritical: (value: boolean) => void;
|
||||
setLastEditedQueryPlatform: (value: SelectedPlatformString | null) => void;
|
||||
setLastEditedQueryPlatform: (
|
||||
value: CommaSeparatedPlatformString | null
|
||||
) => void;
|
||||
setDefaultPolicy: (value: boolean) => void;
|
||||
policyTeamId: number;
|
||||
setPolicyTeamId: (id: number) => void;
|
||||
|
|
@ -216,7 +218,9 @@ const PolicyProvider = ({ children }: Props): JSX.Element => {
|
|||
[]
|
||||
);
|
||||
const setLastEditedQueryPlatform = useCallback(
|
||||
(lastEditedQueryPlatform: SelectedPlatformString | null | undefined) => {
|
||||
(
|
||||
lastEditedQueryPlatform: CommaSeparatedPlatformString | null | undefined
|
||||
) => {
|
||||
dispatch({
|
||||
type: ACTIONS.SET_LAST_EDITED_QUERY_INFO,
|
||||
lastEditedQueryPlatform,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { find } from "lodash";
|
|||
import { osqueryTables } from "utilities/osquery_tables";
|
||||
import { DEFAULT_QUERY } from "utilities/constants";
|
||||
import { DEFAULT_OSQUERY_TABLE, IOsQueryTable } from "interfaces/osquery_table";
|
||||
import { SelectedPlatformString } from "interfaces/platform";
|
||||
import { CommaSeparatedPlatformString } from "interfaces/platform";
|
||||
import { QueryLoggingOption } from "interfaces/schedulable_query";
|
||||
import {
|
||||
DEFAULT_TARGETS,
|
||||
|
|
@ -26,7 +26,7 @@ type InitialStateType = {
|
|||
lastEditedQueryObserverCanRun: boolean;
|
||||
lastEditedQueryFrequency: number;
|
||||
lastEditedQueryAutomationsEnabled: boolean;
|
||||
lastEditedQueryPlatforms: SelectedPlatformString;
|
||||
lastEditedQueryPlatforms: CommaSeparatedPlatformString;
|
||||
lastEditedQueryMinOsqueryVersion: string;
|
||||
lastEditedQueryLoggingType: QueryLoggingOption;
|
||||
lastEditedQueryDiscardData: boolean;
|
||||
|
|
@ -40,7 +40,7 @@ type InitialStateType = {
|
|||
setLastEditedQueryObserverCanRun: (value: boolean) => void;
|
||||
setLastEditedQueryFrequency: (value: number) => void;
|
||||
setLastEditedQueryAutomationsEnabled: (value: boolean) => void;
|
||||
setLastEditedQueryPlatforms: (value: SelectedPlatformString) => void;
|
||||
setLastEditedQueryPlatforms: (value: CommaSeparatedPlatformString) => void;
|
||||
setLastEditedQueryMinOsqueryVersion: (value: string) => void;
|
||||
setLastEditedQueryLoggingType: (value: string) => void;
|
||||
setLastEditedQueryDiscardData: (value: boolean) => void;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from "react";
|
|||
import { forEach } from "lodash";
|
||||
|
||||
import {
|
||||
SelectedPlatformString,
|
||||
CommaSeparatedPlatformString,
|
||||
QUERYABLE_PLATFORMS,
|
||||
QueryablePlatform,
|
||||
} from "interfaces/platform";
|
||||
|
|
@ -21,7 +21,7 @@ export interface IPlatformSelector {
|
|||
}
|
||||
|
||||
const usePlatformSelector = (
|
||||
platformContext: SelectedPlatformString | null | undefined,
|
||||
platformContext: CommaSeparatedPlatformString | null | undefined,
|
||||
baseClass = "",
|
||||
disabled = false,
|
||||
installSoftware: IPolicySoftwareToInstall | undefined,
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export const VULN_SUPPORTED_PLATFORMS: Platform[] = ["darwin", "windows"];
|
|||
|
||||
export type SelectedPlatform = QueryablePlatform | "all";
|
||||
|
||||
export type SelectedPlatformString =
|
||||
export type CommaSeparatedPlatformString =
|
||||
| ""
|
||||
| QueryablePlatform
|
||||
| `${QueryablePlatform},${QueryablePlatform}`
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import PropTypes from "prop-types";
|
||||
import { SelectedPlatformString } from "interfaces/platform";
|
||||
import { CommaSeparatedPlatformString } from "interfaces/platform";
|
||||
import { IScript } from "./script";
|
||||
|
||||
// Legacy PropTypes used on host interface
|
||||
|
|
@ -36,7 +36,7 @@ export interface IPolicy {
|
|||
author_name: string;
|
||||
author_email: string;
|
||||
resolution: string;
|
||||
platform: SelectedPlatformString;
|
||||
platform: CommaSeparatedPlatformString;
|
||||
team_id: number | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
|
|
@ -99,7 +99,7 @@ export interface IPolicyFormData {
|
|||
description?: string | number | boolean | undefined;
|
||||
resolution?: string | number | boolean | undefined;
|
||||
critical?: boolean;
|
||||
platform?: SelectedPlatformString;
|
||||
platform?: CommaSeparatedPlatformString;
|
||||
name?: string | number | boolean | undefined;
|
||||
query?: string | number | boolean | undefined;
|
||||
team_id?: number | null;
|
||||
|
|
@ -118,6 +118,6 @@ export interface IPolicyNew {
|
|||
query: string;
|
||||
resolution: string;
|
||||
critical: boolean;
|
||||
platform: SelectedPlatformString;
|
||||
platform: CommaSeparatedPlatformString;
|
||||
mdm_required?: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import PropTypes from "prop-types";
|
|||
import { IFormField } from "./form_field";
|
||||
import { IPack } from "./pack";
|
||||
import {
|
||||
SelectedPlatformString,
|
||||
CommaSeparatedPlatformString,
|
||||
QueryablePlatform,
|
||||
SelectedPlatform,
|
||||
} from "./platform";
|
||||
|
|
@ -19,7 +19,7 @@ export interface ISchedulableQuery {
|
|||
query: string;
|
||||
team_id: number | null;
|
||||
interval: number;
|
||||
platform: SelectedPlatformString; // Might more accurately be called `platforms_to_query` or `targeted_platforms` – comma-sepparated string of platforms to query, default all platforms if ommitted
|
||||
platform: CommaSeparatedPlatformString; // Might more accurately be called `platforms_to_query` or `targeted_platforms` – comma-separated string of platforms to query, default all platforms if omitted
|
||||
min_osquery_version: string;
|
||||
automations_enabled: boolean;
|
||||
logging: QueryLoggingOption;
|
||||
|
|
@ -90,7 +90,7 @@ export interface ICreateQueryRequestBody {
|
|||
discard_data?: boolean;
|
||||
team_id?: number; // global query if ommitted
|
||||
interval?: number; // default 0 means never run
|
||||
platform?: SelectedPlatformString; // Might more accurately be called `platforms_to_query` – comma-sepparated string of platforms to query, default all platforms if ommitted
|
||||
platform?: CommaSeparatedPlatformString; // Might more accurately be called `platforms_to_query` – comma-separated string of platforms to query, default all platforms if omitted
|
||||
min_osquery_version?: string; // default all versions if ommitted
|
||||
automations_enabled?: boolean; // whether to send data to the configured log destination according to the query's `interval`. Default false if ommitted.
|
||||
logging?: QueryLoggingOption;
|
||||
|
|
@ -109,7 +109,7 @@ export interface IModifyQueryRequestBody
|
|||
observer_can_run?: boolean;
|
||||
discard_data?: boolean;
|
||||
frequency?: number;
|
||||
platform?: SelectedPlatformString;
|
||||
platform?: CommaSeparatedPlatformString;
|
||||
min_osquery_version?: string;
|
||||
automations_enabled?: boolean;
|
||||
}
|
||||
|
|
@ -144,7 +144,7 @@ export interface IEditQueryFormFields {
|
|||
discard_data: IFormField<boolean>;
|
||||
frequency: IFormField<number>;
|
||||
automations_enabled: IFormField<boolean>;
|
||||
platforms: IFormField<SelectedPlatformString>;
|
||||
platforms: IFormField<CommaSeparatedPlatformString>;
|
||||
min_osquery_version: IFormField<string>;
|
||||
logging: IFormField<QueryLoggingOption>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export interface ISoftware {
|
|||
name: string; // e.g., "Figma.app"
|
||||
version: string; // e.g., "2.1.11"
|
||||
bundle_identifier?: string | null; // e.g., "com.figma.Desktop"
|
||||
source: string; // "apps" | "ipados_apps" | "ios_apps" | "programs" | ?
|
||||
source: string; // "apps" | "ipados_apps" | "ios_apps" | "programs" | "rpm_packages" | "deb_packages" | ?
|
||||
generated_cpe: string;
|
||||
vulnerabilities: ISoftwareVulnerability[] | null;
|
||||
hosts_count?: number;
|
||||
|
|
@ -56,7 +56,7 @@ export interface ISoftwareTitleVersion {
|
|||
hosts_count?: number;
|
||||
}
|
||||
|
||||
export interface ISoftwarePackagePolicy {
|
||||
export interface ISoftwareInstallPolicy {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ export interface ISoftwarePackage {
|
|||
pending_uninstall: number;
|
||||
failed_uninstall: number;
|
||||
};
|
||||
automatic_install_policies?: ISoftwarePackagePolicy[] | null;
|
||||
automatic_install_policies?: ISoftwareInstallPolicy[] | null;
|
||||
install_during_setup?: boolean;
|
||||
labels_include_any: ILabelSoftwareTitle[] | null;
|
||||
labels_exclude_any: ILabelSoftwareTitle[] | null;
|
||||
|
|
@ -105,6 +105,17 @@ export interface IAppStoreApp {
|
|||
failed: number;
|
||||
};
|
||||
install_during_setup?: boolean;
|
||||
automatic_install_policies?: ISoftwareInstallPolicy[] | null;
|
||||
last_install?: {
|
||||
install_uuid: string;
|
||||
command_uuid: string;
|
||||
installed_at: string;
|
||||
} | null;
|
||||
last_uninstall?: {
|
||||
script_execution_id: string;
|
||||
uninstalled_at: string;
|
||||
} | null;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export interface ISoftwareTitle {
|
||||
|
|
@ -185,6 +196,32 @@ export const SOURCE_TYPE_CONVERSION = {
|
|||
|
||||
export type SoftwareSource = keyof typeof SOURCE_TYPE_CONVERSION;
|
||||
|
||||
/** Map installable software source to platform */
|
||||
export const INSTALLABLE_SOURCE_PLATFORM_CONVERSION = {
|
||||
apt_sources: "linux",
|
||||
deb_packages: "linux",
|
||||
portage_packages: "linux",
|
||||
rpm_packages: "linux",
|
||||
yum_sources: "linux",
|
||||
npm_packages: null,
|
||||
atom_packages: null,
|
||||
python_packages: null,
|
||||
apps: "darwin",
|
||||
ios_apps: "ios",
|
||||
ipados_apps: "ipados",
|
||||
chrome_extensions: null,
|
||||
firefox_addons: null,
|
||||
safari_extensions: null,
|
||||
homebrew_packages: "darwin",
|
||||
programs: "windows",
|
||||
ie_extensions: null,
|
||||
chocolatey_packages: "windows",
|
||||
pkg_packages: "darwin",
|
||||
vscode_extensions: null,
|
||||
} as const;
|
||||
|
||||
export type InstallableSoftwareSource = keyof typeof INSTALLABLE_SOURCE_PLATFORM_CONVERSION;
|
||||
|
||||
const BROWSER_TYPE_CONVERSION = {
|
||||
chrome: "Chrome",
|
||||
chromium: "Chromium",
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ const FleetMaintainedAppDetailsPage = ({
|
|||
isError: isErrorFleetApp,
|
||||
} = useQuery(
|
||||
["fleet-maintained-app", appId],
|
||||
() => softwareAPI.getFleetMainainedApp(appId),
|
||||
() => softwareAPI.getFleetMaintainedApp(appId),
|
||||
{
|
||||
...DEFAULT_USE_QUERY_OPTIONS,
|
||||
enabled: isPremiumTier,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
|
||||
import { ISoftwarePackagePolicy } from "interfaces/software";
|
||||
import { ISoftwareInstallPolicy } from "interfaces/software";
|
||||
|
||||
import Modal from "components/Modal";
|
||||
import Button from "components/buttons/Button";
|
||||
|
|
@ -11,7 +11,7 @@ const baseClass = "automatic-install-modal";
|
|||
|
||||
interface IPoliciesListItemProps {
|
||||
teamId: number;
|
||||
policy: ISoftwarePackagePolicy;
|
||||
policy: ISoftwareInstallPolicy;
|
||||
}
|
||||
|
||||
const PoliciesListItem = ({ teamId, policy }: IPoliciesListItemProps) => {
|
||||
|
|
@ -24,7 +24,7 @@ const PoliciesListItem = ({ teamId, policy }: IPoliciesListItemProps) => {
|
|||
|
||||
interface IPoliciesListProps {
|
||||
teamId: number;
|
||||
policies: ISoftwarePackagePolicy[];
|
||||
policies: ISoftwareInstallPolicy[];
|
||||
}
|
||||
|
||||
const PoliciesList = ({ teamId, policies }: IPoliciesListProps) => {
|
||||
|
|
@ -39,7 +39,7 @@ const PoliciesList = ({ teamId, policies }: IPoliciesListProps) => {
|
|||
|
||||
interface IAutomaticInstallModalProps {
|
||||
teamId: number;
|
||||
policies: ISoftwarePackagePolicy[];
|
||||
policies: ISoftwareInstallPolicy[];
|
||||
onExit: () => void;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const DELETE_SW_INSTALLED_DURING_SETUP_ERROR_MSG =
|
|||
interface IDeleteSoftwareModalProps {
|
||||
softwareId: number;
|
||||
teamId: number;
|
||||
softwarePackageName?: string;
|
||||
softwareInstallerName?: string;
|
||||
onExit: () => void;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ interface IDeleteSoftwareModalProps {
|
|||
const DeleteSoftwareModal = ({
|
||||
softwareId,
|
||||
teamId,
|
||||
softwarePackageName,
|
||||
softwareInstallerName,
|
||||
onExit,
|
||||
onSuccess,
|
||||
}: IDeleteSoftwareModalProps) => {
|
||||
|
|
@ -36,7 +36,7 @@ const DeleteSoftwareModal = ({
|
|||
const onDeleteSoftware = useCallback(async () => {
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
await softwareAPI.deleteSoftwarePackage(softwareId, teamId);
|
||||
await softwareAPI.deleteSoftwareInstaller(softwareId, teamId);
|
||||
renderFlash("success", "Software deleted successfully!");
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
|
|
@ -64,9 +64,9 @@ const DeleteSoftwareModal = ({
|
|||
<p>
|
||||
Software won't be uninstalled from existing hosts, but any
|
||||
pending installs and uninstalls{" "}
|
||||
{softwarePackageName ? (
|
||||
{softwareInstallerName ? (
|
||||
<>
|
||||
for <b> {softwarePackageName}</b>{" "}
|
||||
for <b> {softwareInstallerName}</b>{" "}
|
||||
</>
|
||||
) : (
|
||||
""
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@ import React, {
|
|||
import PATHS from "router/paths";
|
||||
import { AppContext } from "context/app";
|
||||
import { NotificationContext } from "context/notification";
|
||||
import { ISoftwarePackage } from "interfaces/software";
|
||||
import {
|
||||
ISoftwarePackage,
|
||||
IAppStoreApp,
|
||||
isSoftwarePackage,
|
||||
} from "interfaces/software";
|
||||
import softwareAPI from "services/entities/software";
|
||||
|
||||
import { buildQueryStringFromParams } from "utilities/url";
|
||||
|
|
@ -131,19 +135,19 @@ const STATUS_DISPLAY_OPTIONS: Record<
|
|||
},
|
||||
};
|
||||
|
||||
interface IPackageStatusCountProps {
|
||||
interface IInstallerStatusCountProps {
|
||||
softwareId: number;
|
||||
status: SoftwareInstallDisplayStatus;
|
||||
count: number;
|
||||
teamId?: number;
|
||||
}
|
||||
|
||||
const PackageStatusCount = ({
|
||||
const InstallerStatusCount = ({
|
||||
softwareId,
|
||||
status,
|
||||
count,
|
||||
teamId,
|
||||
}: IPackageStatusCountProps) => {
|
||||
}: IInstallerStatusCountProps) => {
|
||||
const displayData = STATUS_DISPLAY_OPTIONS[status];
|
||||
const linkUrl = `${PATHS.MANAGE_HOSTS}?${buildQueryStringFromParams({
|
||||
software_title_id: softwareId,
|
||||
|
|
@ -177,14 +181,14 @@ const PackageStatusCount = ({
|
|||
};
|
||||
|
||||
interface IActionsDropdownProps {
|
||||
isSoftwarePackage: boolean;
|
||||
isPackage: boolean;
|
||||
onDownloadClick: () => void;
|
||||
onDeleteClick: () => void;
|
||||
onEditSoftwareClick: () => void;
|
||||
}
|
||||
|
||||
const SoftwareActionsDropdown = ({
|
||||
isSoftwarePackage,
|
||||
isPackage,
|
||||
onDownloadClick,
|
||||
onDeleteClick,
|
||||
onEditSoftwareClick,
|
||||
|
|
@ -212,7 +216,7 @@ const SoftwareActionsDropdown = ({
|
|||
onChange={onSelect}
|
||||
placeholder="Actions"
|
||||
options={
|
||||
isSoftwarePackage
|
||||
isPackage
|
||||
? [...SOFTWARE_PACKAGE_DROPDOWN_OPTIONS]
|
||||
: [...APP_STORE_APP_DROPDOWN_OPTIONS]
|
||||
}
|
||||
|
|
@ -222,7 +226,7 @@ const SoftwareActionsDropdown = ({
|
|||
);
|
||||
};
|
||||
|
||||
interface ISoftwarePackageCardProps {
|
||||
interface ISoftwareInstallerCardProps {
|
||||
name: string;
|
||||
version: string;
|
||||
uploadedAt: string; // TODO: optional?
|
||||
|
|
@ -234,8 +238,7 @@ interface ISoftwarePackageCardProps {
|
|||
isSelfService: boolean;
|
||||
softwareId: number;
|
||||
teamId: number;
|
||||
// NOTE: we will only have this if we are working with a software package.
|
||||
softwarePackage?: ISoftwarePackage;
|
||||
softwareInstaller: ISoftwarePackage | IAppStoreApp;
|
||||
onDelete: () => void;
|
||||
refetchSoftwareTitle: () => void;
|
||||
}
|
||||
|
|
@ -243,18 +246,19 @@ interface ISoftwarePackageCardProps {
|
|||
// NOTE: This component is dependent on having either a software package
|
||||
// (ISoftwarePackage) or an app store app (IAppStoreApp). If we add more types
|
||||
// of packages we should consider refactoring this to be more dynamic.
|
||||
const SoftwarePackageCard = ({
|
||||
const SoftwareInstallerCard = ({
|
||||
name,
|
||||
version,
|
||||
uploadedAt,
|
||||
status,
|
||||
isSelfService,
|
||||
softwarePackage,
|
||||
softwareInstaller,
|
||||
softwareId,
|
||||
teamId,
|
||||
onDelete,
|
||||
refetchSoftwareTitle,
|
||||
}: ISoftwarePackageCardProps) => {
|
||||
}: ISoftwareInstallerCardProps) => {
|
||||
const isPackage = isSoftwarePackage(softwareInstaller);
|
||||
const {
|
||||
isGlobalAdmin,
|
||||
isGlobalMaintainer,
|
||||
|
|
@ -304,7 +308,7 @@ const SoftwarePackageCard = ({
|
|||
}, [renderFlash, softwareId, name, teamId]);
|
||||
|
||||
const renderIcon = () => {
|
||||
return softwarePackage ? (
|
||||
return isPackage ? (
|
||||
<Graphic name="file-pkg" />
|
||||
) : (
|
||||
<SoftwareIcon name="appStore" size="medium" />
|
||||
|
|
@ -338,13 +342,13 @@ const SoftwarePackageCard = ({
|
|||
<div className={`${baseClass}__main-info`}>
|
||||
{renderIcon()}
|
||||
<div className={`${baseClass}__info`}>
|
||||
<SoftwareName name={softwarePackage?.name || name} />
|
||||
<SoftwareName name={softwareInstaller?.name || name} />
|
||||
<span className={`${baseClass}__details`}>{renderDetails()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${baseClass}__actions-wrapper`}>
|
||||
{softwarePackage?.automatic_install_policies &&
|
||||
softwarePackage?.automatic_install_policies.length > 0 && (
|
||||
{softwareInstaller?.automatic_install_policies &&
|
||||
softwareInstaller?.automatic_install_policies.length > 0 && (
|
||||
<TooltipWrapper
|
||||
showArrow
|
||||
position="top"
|
||||
|
|
@ -361,7 +365,7 @@ const SoftwarePackageCard = ({
|
|||
{isSelfService && <Tag icon="user" text="Self-service" />}
|
||||
{showActions && (
|
||||
<SoftwareActionsDropdown
|
||||
isSoftwarePackage={!!softwarePackage}
|
||||
isPackage={isPackage}
|
||||
onDownloadClick={onDownloadClick}
|
||||
onDeleteClick={onDeleteClick}
|
||||
onEditSoftwareClick={onEditSoftwareClick}
|
||||
|
|
@ -369,31 +373,31 @@ const SoftwarePackageCard = ({
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${baseClass}__package-statuses`}>
|
||||
<PackageStatusCount
|
||||
<div className={`${baseClass}__installer-statuses`}>
|
||||
<InstallerStatusCount
|
||||
softwareId={softwareId}
|
||||
status="installed"
|
||||
count={status.installed}
|
||||
teamId={teamId}
|
||||
/>
|
||||
<PackageStatusCount
|
||||
<InstallerStatusCount
|
||||
softwareId={softwareId}
|
||||
status="pending"
|
||||
count={status.pending}
|
||||
teamId={teamId}
|
||||
/>
|
||||
<PackageStatusCount
|
||||
<InstallerStatusCount
|
||||
softwareId={softwareId}
|
||||
status="failed"
|
||||
count={status.failed}
|
||||
teamId={teamId}
|
||||
/>
|
||||
</div>
|
||||
{showEditSoftwareModal && softwarePackage && (
|
||||
{showEditSoftwareModal && isPackage && (
|
||||
<EditSoftwareModal
|
||||
softwareId={softwareId}
|
||||
teamId={teamId}
|
||||
software={softwarePackage}
|
||||
software={softwareInstaller}
|
||||
onExit={() => setShowEditSoftwareModal(false)}
|
||||
refetchSoftwareTitle={refetchSoftwareTitle}
|
||||
/>
|
||||
|
|
@ -401,18 +405,18 @@ const SoftwarePackageCard = ({
|
|||
{showDeleteModal && (
|
||||
<DeleteSoftwareModal
|
||||
softwareId={softwareId}
|
||||
softwarePackageName={softwarePackage?.name}
|
||||
softwareInstallerName={softwareInstaller?.name}
|
||||
teamId={teamId}
|
||||
onExit={() => setShowDeleteModal(false)}
|
||||
onSuccess={onDeleteSuccess}
|
||||
/>
|
||||
)}
|
||||
{showAutomaticInstallModal &&
|
||||
softwarePackage?.automatic_install_policies &&
|
||||
softwarePackage?.automatic_install_policies.length > 0 && (
|
||||
softwareInstaller?.automatic_install_policies &&
|
||||
softwareInstaller?.automatic_install_policies.length > 0 && (
|
||||
<AutomaticInstallModal
|
||||
teamId={teamId}
|
||||
policies={softwarePackage.automatic_install_policies}
|
||||
policies={softwareInstaller.automatic_install_policies}
|
||||
onExit={() => setShowAutomaticInstallModal(false)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -420,4 +424,4 @@ const SoftwarePackageCard = ({
|
|||
);
|
||||
};
|
||||
|
||||
export default SoftwarePackageCard;
|
||||
export default SoftwareInstallerCard;
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
font-size: $xx-small;
|
||||
}
|
||||
|
||||
&__package-statuses {
|
||||
&__installer-statuses {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ const SoftwareTitleDetailsPage = ({
|
|||
const packageCardData = getPackageCardInfo(title);
|
||||
return (
|
||||
<SoftwarePackageCard
|
||||
softwarePackage={packageCardData.softwarePackage}
|
||||
softwareInstaller={packageCardData.softwarePackage}
|
||||
name={packageCardData.name}
|
||||
version={packageCardData.version}
|
||||
uploadedAt={packageCardData.uploadedAt}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ describe("SoftwareTitleDetailsPage helpers", () => {
|
|||
};
|
||||
const packageCardInfo = getPackageCardInfo(softwareTitle);
|
||||
expect(packageCardInfo).toEqual({
|
||||
softwarePackage: undefined,
|
||||
softwarePackage: softwareTitle.app_store_app,
|
||||
name: "Test Software", // apps should display the software title name (backend should ensure the app name and software title name match)
|
||||
version: "1.0.1",
|
||||
uploadedAt: "",
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export const getPackageCardInfo = (softwareTitle: ISoftwareTitleDetails) => {
|
|||
const isPackage = isSoftwarePackage(packageData);
|
||||
|
||||
return {
|
||||
softwarePackage: isPackage ? packageData : undefined,
|
||||
softwarePackage: packageData,
|
||||
name: (isPackage && packageData.name) || softwareTitle.name,
|
||||
version:
|
||||
(isSoftwarePackage(packageData)
|
||||
|
|
|
|||
|
|
@ -75,18 +75,20 @@ const getSoftwareNameCellData = (
|
|||
if (software_package) {
|
||||
hasPackage = true;
|
||||
isSelfService = software_package.self_service;
|
||||
if (
|
||||
installType =
|
||||
software_package.automatic_install_policies &&
|
||||
software_package.automatic_install_policies.length > 0
|
||||
) {
|
||||
installType = "automatic";
|
||||
} else {
|
||||
installType = "manual";
|
||||
}
|
||||
? "automatic"
|
||||
: "manual";
|
||||
} else if (app_store_app) {
|
||||
hasPackage = true;
|
||||
isSelfService = app_store_app.self_service;
|
||||
iconUrl = app_store_app.icon_url;
|
||||
installType =
|
||||
app_store_app.automatic_install_policies &&
|
||||
app_store_app.automatic_install_policies.length > 0
|
||||
? "automatic"
|
||||
: "manual";
|
||||
}
|
||||
|
||||
const isAllTeams = teamId === undefined;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ const [
|
|||
DEFAULT_AUTOMATION_UPDATE_ERR_MSG,
|
||||
] = [
|
||||
"Successfully updated policy automations.",
|
||||
"Could not update policy automations. Please try again.",
|
||||
"Could not update policy automations.",
|
||||
];
|
||||
|
||||
const baseClass = "manage-policies-page";
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
import React, { useCallback, useState } from "react";
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
|
||||
import { useQuery } from "react-query";
|
||||
import { omit } from "lodash";
|
||||
|
||||
import { IPolicyStats } from "interfaces/policy";
|
||||
import {
|
||||
CommaSeparatedPlatformString,
|
||||
Platform,
|
||||
PLATFORM_DISPLAY_NAMES,
|
||||
} from "interfaces/platform";
|
||||
import softwareAPI, {
|
||||
ISoftwareTitlesQueryKey,
|
||||
ISoftwareTitlesResponse,
|
||||
|
|
@ -19,30 +24,21 @@ import Checkbox from "components/forms/fields/Checkbox";
|
|||
import TooltipTruncatedText from "components/TooltipTruncatedText";
|
||||
import CustomLink from "components/CustomLink";
|
||||
import Button from "components/buttons/Button";
|
||||
import { ISoftwareTitle } from "interfaces/software";
|
||||
import {
|
||||
INSTALLABLE_SOURCE_PLATFORM_CONVERSION,
|
||||
InstallableSoftwareSource,
|
||||
ISoftwareTitle,
|
||||
} from "interfaces/software";
|
||||
import TooltipWrapper from "components/TooltipWrapper";
|
||||
|
||||
const getPlatformDisplayFromPackageExtension = (ext: string | undefined) => {
|
||||
switch (ext) {
|
||||
case "pkg":
|
||||
case "zip":
|
||||
case "dmg":
|
||||
return "macOS";
|
||||
case "deb":
|
||||
case "rpm":
|
||||
return "Linux";
|
||||
case "exe":
|
||||
case "msi":
|
||||
return "Windows";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const AFI_SOFTWARE_BATCH_SIZE = 1000;
|
||||
const SOFTWARE_TITLE_LIST_LENGTH = 1000;
|
||||
|
||||
const baseClass = "install-software-modal";
|
||||
|
||||
const formatSoftwarePlatform = (source: InstallableSoftwareSource) => {
|
||||
return INSTALLABLE_SOURCE_PLATFORM_CONVERSION[source] || null;
|
||||
};
|
||||
|
||||
interface ISwDropdownField {
|
||||
name: string;
|
||||
value: number;
|
||||
|
|
@ -52,10 +48,16 @@ interface IFormPolicy {
|
|||
id: number;
|
||||
installSoftwareEnabled: boolean;
|
||||
swIdToInstall?: number;
|
||||
platform: CommaSeparatedPlatformString;
|
||||
}
|
||||
|
||||
export type IInstallSoftwareFormData = IFormPolicy[];
|
||||
|
||||
interface IEnhancedSoftwareTitle extends ISoftwareTitle {
|
||||
platform: Platform | null;
|
||||
extension?: string;
|
||||
}
|
||||
|
||||
interface IInstallSoftwareModal {
|
||||
onExit: () => void;
|
||||
onSubmit: (formData: IInstallSoftwareFormData) => void;
|
||||
|
|
@ -63,6 +65,7 @@ interface IInstallSoftwareModal {
|
|||
policies: IPolicyStats[];
|
||||
teamId: number;
|
||||
}
|
||||
|
||||
const InstallSoftwareModal = ({
|
||||
onExit,
|
||||
onSubmit,
|
||||
|
|
@ -76,6 +79,7 @@ const InstallSoftwareModal = ({
|
|||
id: policy.id,
|
||||
installSoftwareEnabled: !!policy.install_software,
|
||||
swIdToInstall: policy.install_software?.software_title_id,
|
||||
platform: policy.platform,
|
||||
}))
|
||||
);
|
||||
|
||||
|
|
@ -84,32 +88,40 @@ const InstallSoftwareModal = ({
|
|||
);
|
||||
|
||||
const {
|
||||
data: titlesAFI,
|
||||
isLoading: isTitlesAFILoading,
|
||||
isError: isTitlesAFIError,
|
||||
data: titlesAvailableForInstall,
|
||||
isLoading: isTitlesAvailableForInstallLoading,
|
||||
isError: isTitlesAvailableForInstallError,
|
||||
} = useQuery<
|
||||
ISoftwareTitlesResponse,
|
||||
Error,
|
||||
ISoftwareTitle[],
|
||||
IEnhancedSoftwareTitle[],
|
||||
[ISoftwareTitlesQueryKey]
|
||||
>(
|
||||
[
|
||||
{
|
||||
scope: "software-titles",
|
||||
page: 0,
|
||||
perPage: AFI_SOFTWARE_BATCH_SIZE,
|
||||
perPage: SOFTWARE_TITLE_LIST_LENGTH,
|
||||
query: "",
|
||||
orderDirection: "desc",
|
||||
orderKey: "hosts_count",
|
||||
teamId,
|
||||
availableForInstall: true,
|
||||
packagesOnly: true,
|
||||
platform: "darwin,windows,linux",
|
||||
},
|
||||
],
|
||||
({ queryKey: [queryKey] }) =>
|
||||
softwareAPI.getSoftwareTitles(omit(queryKey, "scope")),
|
||||
{
|
||||
select: (data) => data.software_titles,
|
||||
select: (data): IEnhancedSoftwareTitle[] =>
|
||||
data.software_titles.map((title) => {
|
||||
const extension = title.software_package?.name.split(".").pop();
|
||||
return {
|
||||
...title,
|
||||
platform: formatSoftwarePlatform(title.source),
|
||||
extension,
|
||||
};
|
||||
}),
|
||||
...DEFAULT_USE_QUERY_OPTIONS,
|
||||
}
|
||||
);
|
||||
|
|
@ -152,19 +164,55 @@ const InstallSoftwareModal = ({
|
|||
[formData]
|
||||
);
|
||||
|
||||
const availableSoftwareOptions = titlesAFI?.map((title) => {
|
||||
const splitName = title.software_package?.name.split(".") ?? "";
|
||||
const ext =
|
||||
splitName.length > 1 ? splitName[splitName.length - 1] : undefined;
|
||||
const platformString = ext
|
||||
? `${getPlatformDisplayFromPackageExtension(ext)} (.${ext}) • `
|
||||
: "";
|
||||
return {
|
||||
label: title.name,
|
||||
value: title.id,
|
||||
helpText: `${platformString}${title.software_package?.version ?? ""}`,
|
||||
// Filters and transforms software titles into dropdown options
|
||||
// to include only software compatible with the policy's platform(s)
|
||||
const availableSoftwareOptions = useCallback(
|
||||
(policy: IFormPolicy) => {
|
||||
const policyPlatforms = policy.platform.split(",");
|
||||
return titlesAvailableForInstall
|
||||
?.filter(
|
||||
(title) => title.platform && policyPlatforms.includes(title.platform)
|
||||
)
|
||||
.map((title) => {
|
||||
const vppOption = title.source === "apps" && !!title.app_store_app;
|
||||
const platformString = () => {
|
||||
if (vppOption) {
|
||||
return "macOS (App Store) • ";
|
||||
}
|
||||
|
||||
return title.extension
|
||||
? `${
|
||||
title.platform && PLATFORM_DISPLAY_NAMES[title.platform]
|
||||
} (.${title.extension}) • `
|
||||
: "";
|
||||
};
|
||||
const versionString = () => {
|
||||
return vppOption
|
||||
? title.app_store_app?.version
|
||||
: title.software_package?.version ?? "";
|
||||
};
|
||||
|
||||
return {
|
||||
label: title.name,
|
||||
value: title.id,
|
||||
helpText: `${platformString()}${versionString()}`,
|
||||
};
|
||||
});
|
||||
},
|
||||
[titlesAvailableForInstall]
|
||||
);
|
||||
|
||||
// Cache availableSoftwareOptions for each unique platform
|
||||
const memoizedAvailableSoftwareOptions = useMemo(() => {
|
||||
const cache = new Map();
|
||||
return (policy: IFormPolicy) => {
|
||||
const key = policy.platform;
|
||||
if (!cache.has(key)) {
|
||||
cache.set(key, availableSoftwareOptions(policy));
|
||||
}
|
||||
return cache.get(key);
|
||||
};
|
||||
});
|
||||
}, [availableSoftwareOptions]);
|
||||
|
||||
const renderPolicySwInstallOption = (policy: IFormPolicy) => {
|
||||
const {
|
||||
|
|
@ -194,7 +242,7 @@ const InstallSoftwareModal = ({
|
|||
</Checkbox>
|
||||
{enabled && (
|
||||
<Dropdown
|
||||
options={availableSoftwareOptions}
|
||||
options={memoizedAvailableSoftwareOptions(policy)} // Options filtered for policy's platform(s)
|
||||
value={swIdToInstall}
|
||||
onChange={onSelectPolicySoftware}
|
||||
placeholder="Select software"
|
||||
|
|
@ -208,13 +256,13 @@ const InstallSoftwareModal = ({
|
|||
};
|
||||
|
||||
const renderContent = () => {
|
||||
if (isTitlesAFIError) {
|
||||
if (isTitlesAvailableForInstallError) {
|
||||
return <DataError />;
|
||||
}
|
||||
if (isTitlesAFILoading) {
|
||||
if (isTitlesAvailableForInstallLoading) {
|
||||
return <Spinner />;
|
||||
}
|
||||
if (!titlesAFI?.length) {
|
||||
if (!titlesAvailableForInstall?.length) {
|
||||
return (
|
||||
<div className={`${baseClass}__no-software`}>
|
||||
<b>No software available for install</b>
|
||||
|
|
@ -226,16 +274,6 @@ const InstallSoftwareModal = ({
|
|||
);
|
||||
}
|
||||
|
||||
const compatibleTipContent = (
|
||||
<>
|
||||
.pkg for macOS.
|
||||
<br />
|
||||
.msi or .exe for Windows.
|
||||
<br />
|
||||
.deb for Linux.
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`${baseClass} form`}>
|
||||
<div className="form-field">
|
||||
|
|
@ -246,11 +284,8 @@ const InstallSoftwareModal = ({
|
|||
)}
|
||||
</ul>
|
||||
<span className="form-field__help-text">
|
||||
Selected software, if{" "}
|
||||
<TooltipWrapper tipContent={compatibleTipContent}>
|
||||
compatible
|
||||
</TooltipWrapper>{" "}
|
||||
with the host, will be installed when hosts fail the chosen policy.{" "}
|
||||
Selected software, if compatible with the host, will be installed
|
||||
when hosts fail the chosen policy.{" "}
|
||||
<CustomLink
|
||||
url="https://fleetdm.com/learn-more-about/policy-automation-install-software"
|
||||
text="Learn more"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import usePlatformCompatibility from "hooks/usePlatformCompatibility";
|
|||
import usePlatformSelector from "hooks/usePlatformSelector";
|
||||
|
||||
import { IPolicy, IPolicyFormData } from "interfaces/policy";
|
||||
import { SelectedPlatformString } from "interfaces/platform";
|
||||
import { CommaSeparatedPlatformString } from "interfaces/platform";
|
||||
import { DEFAULT_POLICIES } from "pages/policies/constants";
|
||||
|
||||
import Avatar from "components/Avatar";
|
||||
|
|
@ -253,7 +253,7 @@ const PolicyForm = ({
|
|||
|
||||
const newPlatformString = selectedPlatforms.join(
|
||||
","
|
||||
) as SelectedPlatformString;
|
||||
) as CommaSeparatedPlatformString;
|
||||
|
||||
if (!defaultPolicy) {
|
||||
setLastEditedQueryPlatform(newPlatformString);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { AppContext } from "context/app";
|
|||
import { PolicyContext } from "context/policy";
|
||||
import { IPlatformSelector } from "hooks/usePlatformSelector";
|
||||
import { IPolicyFormData } from "interfaces/policy";
|
||||
import { SelectedPlatformString } from "interfaces/platform";
|
||||
import { CommaSeparatedPlatformString } from "interfaces/platform";
|
||||
import useDeepEffect from "hooks/useDeepEffect";
|
||||
|
||||
// @ts-ignore
|
||||
|
|
@ -96,7 +96,7 @@ const SaveNewPolicyModal = ({
|
|||
|
||||
const newPlatformString = platformSelector
|
||||
.getSelectedPlatforms()
|
||||
.join(",") as SelectedPlatformString;
|
||||
.join(",") as CommaSeparatedPlatformString;
|
||||
setLastEditedQueryPlatform(newPlatformString);
|
||||
|
||||
const { valid: validName, errors: newErrors } = validatePolicyName(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { IPolicyNew } from "interfaces/policy";
|
||||
import { SelectedPlatformString } from "interfaces/platform";
|
||||
import { CommaSeparatedPlatformString } from "interfaces/platform";
|
||||
|
||||
const DEFAULT_POLICY_PLATFORM: SelectedPlatformString = "";
|
||||
const DEFAULT_POLICY_PLATFORM: CommaSeparatedPlatformString = "";
|
||||
|
||||
export const DEFAULT_POLICY = {
|
||||
id: 1,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
import {
|
||||
isScheduledQueryablePlatform,
|
||||
ScheduledQueryablePlatform,
|
||||
SelectedPlatformString,
|
||||
CommaSeparatedPlatformString,
|
||||
} from "interfaces/platform";
|
||||
import { API_ALL_TEAMS_ID } from "interfaces/team";
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ interface IBoolCellProps extends IRowProps {
|
|||
}
|
||||
interface IPlatformCellProps extends IRowProps {
|
||||
cell: {
|
||||
value: SelectedPlatformString;
|
||||
value: CommaSeparatedPlatformString;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ import {
|
|||
ICreateQueryRequestBody,
|
||||
QueryLoggingOption,
|
||||
} from "interfaces/schedulable_query";
|
||||
import { SelectedPlatformString } from "interfaces/platform";
|
||||
import { CommaSeparatedPlatformString } from "interfaces/platform";
|
||||
|
||||
import queryAPI from "services/entities/queries";
|
||||
|
||||
|
|
@ -278,12 +278,12 @@ const EditQueryForm = ({
|
|||
// else if Remove OS if All is chosen
|
||||
if (valArray.indexOf("") === 0 && valArray.length > 1) {
|
||||
setLastEditedQueryPlatforms(
|
||||
pull(valArray, "").join(",") as SelectedPlatformString
|
||||
pull(valArray, "").join(",") as CommaSeparatedPlatformString
|
||||
);
|
||||
} else if (valArray.length > 1 && valArray.indexOf("") > -1) {
|
||||
setLastEditedQueryPlatforms("");
|
||||
} else {
|
||||
setLastEditedQueryPlatforms(values as SelectedPlatformString);
|
||||
setLastEditedQueryPlatforms(values as CommaSeparatedPlatformString);
|
||||
}
|
||||
},
|
||||
[setLastEditedQueryPlatforms]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
SCHEDULE_PLATFORM_DROPDOWN_OPTIONS,
|
||||
} from "utilities/constants";
|
||||
|
||||
import { SelectedPlatformString } from "interfaces/platform";
|
||||
import { CommaSeparatedPlatformString } from "interfaces/platform";
|
||||
import {
|
||||
ICreateQueryRequestBody,
|
||||
ISchedulableQuery,
|
||||
|
|
@ -77,7 +77,7 @@ const SaveQueryModal = ({
|
|||
const [
|
||||
selectedPlatformOptions,
|
||||
setSelectedPlatformOptions,
|
||||
] = useState<SelectedPlatformString>(existingQuery?.platform ?? "");
|
||||
] = useState<CommaSeparatedPlatformString>(existingQuery?.platform ?? "");
|
||||
const [
|
||||
selectedMinOsqueryVersionOptions,
|
||||
setSelectedMinOsqueryVersionOptions,
|
||||
|
|
@ -149,12 +149,12 @@ const SaveQueryModal = ({
|
|||
if (valArray.indexOf("") === 0 && valArray.length > 1) {
|
||||
// TODO - inmprove type safety of all 3 options
|
||||
setSelectedPlatformOptions(
|
||||
pull(valArray, "").join(",") as SelectedPlatformString
|
||||
pull(valArray, "").join(",") as CommaSeparatedPlatformString
|
||||
);
|
||||
} else if (valArray.length > 1 && valArray.indexOf("") > -1) {
|
||||
setSelectedPlatformOptions("");
|
||||
} else {
|
||||
setSelectedPlatformOptions(values as SelectedPlatformString);
|
||||
setSelectedPlatformOptions(values as CommaSeparatedPlatformString);
|
||||
}
|
||||
},
|
||||
[setSelectedPlatformOptions]
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
IFleetMaintainedAppDetails,
|
||||
ISoftwarePackage,
|
||||
} from "interfaces/software";
|
||||
import { CommaSeparatedPlatformString } from "interfaces/platform";
|
||||
import {
|
||||
buildQueryStringFromParams,
|
||||
convertParamsToSnakeCase,
|
||||
|
|
@ -20,7 +21,6 @@ import { IPackageFormData } from "pages/SoftwarePage/components/PackageForm/Pack
|
|||
import { IEditPackageFormData } from "pages/SoftwarePage/SoftwareTitleDetailsPage/EditSoftwareModal/EditSoftwareModal";
|
||||
import { IAddFleetMaintainedData } from "pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage";
|
||||
import { listNamesFromSelectedLabels } from "components/TargetLabelSelector/TargetLabelSelector";
|
||||
import { join } from "path";
|
||||
|
||||
export interface ISoftwareApiParams {
|
||||
page?: number;
|
||||
|
|
@ -75,6 +75,7 @@ export interface ISoftwareVersionsQueryKey extends ISoftwareApiParams {
|
|||
export interface ISoftwareTitlesQueryKey extends ISoftwareApiParams {
|
||||
// used to trigger software refetches from sibling pages
|
||||
addedSoftwareToken?: string | null;
|
||||
platform?: CommaSeparatedPlatformString;
|
||||
scope: "software-titles";
|
||||
}
|
||||
|
||||
|
|
@ -373,7 +374,8 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
deleteSoftwarePackage: (softwareId: number, teamId: number) => {
|
||||
// Endpoint for deleting packages or VPP
|
||||
deleteSoftwareInstaller: (softwareId: number, teamId: number) => {
|
||||
const { SOFTWARE_AVAILABLE_FOR_INSTALL } = endpoints;
|
||||
const path = `${SOFTWARE_AVAILABLE_FOR_INSTALL(
|
||||
softwareId
|
||||
|
|
@ -407,7 +409,7 @@ export default {
|
|||
return sendRequest("GET", path);
|
||||
},
|
||||
|
||||
getFleetMainainedApp: (id: number): Promise<IFleetMaintainedAppResponse> => {
|
||||
getFleetMaintainedApp: (id: number): Promise<IFleetMaintainedAppResponse> => {
|
||||
const { SOFTWARE_FLEET_MAINTAINED_APP } = endpoints;
|
||||
const path = `${SOFTWARE_FLEET_MAINTAINED_APP(id)}`;
|
||||
return sendRequest("GET", path);
|
||||
|
|
|
|||
Loading…
Reference in a new issue