This pull request adds support for two new software categories, "Security" and "Utilities", across the application. The changes ensure these categories are available in the database, frontend type definitions, UI elements, documentation, and are fully tested in both migration and integration test suites. **Database and Migration Updates:** * Added "Security" and "Utilities" entries to the `software_categories` table and updated initial data in `schema.sql`. * Introduced a new migration (`20251210000000_AddSecurityAndUtilitiesCategories.go`) to insert/remove these categories, with an accompanying test to verify migration behavior. [[1]](diffhunk://#diff-57da59e73fff8f2ffccd167299027899614281c591b79715b7000bed0e9d8516R1-R31) [[2]](diffhunk://#diff-5e6db34b45e83ec5cf2b9cb41e4bfd4ee934f456dd9c0ae4313a448d39319c72R1-R28) **Frontend and Documentation Updates:** * Updated the `SoftwareCategory` type and category lists to include "Security" and "Utilities", ensuring they appear in the UI and are selectable. [[1]](diffhunk://#diff-4297079e443d574eb530c70ef48de3cab80e56f783c7b395d58c31c29be6bb0eL77-R79) [[2]](diffhunk://#diff-405dcd4f0bd6881e4b20a75212467d13c143ddc486b5c9d29ed9035033c32361R25-R26) * Added documentation for the new categories in `yaml-files.md`. **Testing Enhancements:** * Added and updated tests to verify the new categories are displayed and handled correctly in both frontend and backend integration tests. [[1]](diffhunk://#diff-d151ee297fdaf54f8ea7027bc46de12247c43406b464265f24ade5a49cb19e49R210-R211) [[2]](diffhunk://#diff-bbd0c5a6bc2f9a24e633031d4c6a3f5b0be7cbfe78ef1b56cdf9a7a2c32e21e2R19358-R19365) [[3]](diffhunk://#diff-2bd8ca2ddaad7aac0c438a2afd76a26872378249f757c9c81a31005d0e57cf1fR18447-R18460)
821 lines
26 KiB
TypeScript
821 lines
26 KiB
TypeScript
import { startCase } from "lodash";
|
|
import PropTypes from "prop-types";
|
|
|
|
import { IconNames } from "components/icons";
|
|
|
|
import { HOST_APPLE_PLATFORMS, Platform } from "./platform";
|
|
import vulnerabilityInterface from "./vulnerability";
|
|
import { ILabelSoftwareTitle } from "./label";
|
|
import { ICommandResult } from "./command";
|
|
|
|
export default PropTypes.shape({
|
|
type: PropTypes.string,
|
|
name: PropTypes.string,
|
|
version: PropTypes.string,
|
|
source: PropTypes.string,
|
|
id: PropTypes.number,
|
|
vulnerabilities: PropTypes.arrayOf(vulnerabilityInterface),
|
|
});
|
|
|
|
export interface ISoftwareResponse {
|
|
counts_updated_at: string;
|
|
software: ISoftware[];
|
|
}
|
|
|
|
export interface ISoftwareCountResponse {
|
|
count: number;
|
|
}
|
|
|
|
export interface IGetSoftwareByIdResponse {
|
|
software: ISoftware;
|
|
}
|
|
|
|
// TODO: old software interface. replaced with ISoftwareVersion
|
|
// check to see if we still need this.
|
|
export interface ISoftware {
|
|
id: number;
|
|
name: string; // e.g., "Figma.app"
|
|
/** Custom name set per team by admin */
|
|
display_name?: string; // e.g. "Figma for Desktop"
|
|
version: string; // e.g., "2.1.11"
|
|
bundle_identifier?: string | null; // e.g., "com.figma.Desktop"
|
|
application_id?: string | null; // e.g., "us.zoom.videomeetings" for Android apps
|
|
source: string; // "apps" | "ipados_apps" | "ios_apps" | "programs" | "rpm_packages" | "deb_packages" | "android_apps" | ?
|
|
generated_cpe: string;
|
|
vulnerabilities: ISoftwareVulnerability[] | null;
|
|
hosts_count?: number;
|
|
last_opened_at?: string | null; // e.g., "2021-08-18T15:11:35Z”
|
|
installed_paths?: string[];
|
|
extension_for?: string;
|
|
vendor?: string;
|
|
icon_url: string | null; // Only available on team view if an admin uploaded an icon to a team's software
|
|
}
|
|
|
|
export type IVulnerabilitySoftware = Omit<
|
|
ISoftware,
|
|
"vulnerabilities" | "icon_url"
|
|
> & {
|
|
resolved_in_version: string;
|
|
};
|
|
|
|
export interface ISoftwareTitleVersion {
|
|
id: number;
|
|
version: string;
|
|
vulnerabilities: string[] | null; // TODO: does this return null or is it omitted?
|
|
hosts_count?: number;
|
|
}
|
|
|
|
export interface ISoftwareInstallPolicy {
|
|
id: number;
|
|
name: string;
|
|
}
|
|
|
|
export type SoftwareCategory =
|
|
| "Browsers"
|
|
| "Communication"
|
|
| "Developer tools"
|
|
| "Productivity"
|
|
| "Security"
|
|
| "Utilities";
|
|
|
|
export interface ISoftwarePackageStatus {
|
|
installed: number;
|
|
pending_install: number;
|
|
failed_install: number;
|
|
pending_uninstall: number;
|
|
failed_uninstall: number;
|
|
}
|
|
|
|
export interface ISoftwareAppStoreAppStatus {
|
|
installed: number;
|
|
pending: number;
|
|
failed: number;
|
|
}
|
|
|
|
export interface ISoftwarePackage {
|
|
name: string;
|
|
/** Not included in SoftwareTitle software.software_package response, hoisted up one level
|
|
* Custom name set per team by admin
|
|
*/
|
|
display_name?: string;
|
|
title_id: number;
|
|
url: string;
|
|
version: string;
|
|
uploaded_at: string;
|
|
install_script: string;
|
|
uninstall_script: string;
|
|
pre_install_query?: string;
|
|
post_install_script?: string;
|
|
automatic_install?: boolean; // POST only
|
|
self_service: boolean;
|
|
icon_url: string | null;
|
|
status: ISoftwarePackageStatus;
|
|
automatic_install_policies?: ISoftwareInstallPolicy[] | null;
|
|
install_during_setup?: boolean;
|
|
labels_include_any: ILabelSoftwareTitle[] | null;
|
|
labels_exclude_any: ILabelSoftwareTitle[] | null;
|
|
categories?: SoftwareCategory[] | null;
|
|
fleet_maintained_app_id?: number | null;
|
|
hash_sha256?: string | null;
|
|
}
|
|
|
|
export interface IAppStoreApp {
|
|
name: string;
|
|
/** Not included in SoftwareTitle software.app_store_app response, hoisted up one level
|
|
* Custom name set per team by admin
|
|
*/
|
|
display_name?: string;
|
|
app_store_id: string; // API returns this as a string
|
|
latest_version: string;
|
|
created_at: string;
|
|
icon_url: string;
|
|
self_service: boolean;
|
|
platform: typeof HOST_APPLE_PLATFORMS[number] | "android";
|
|
status: ISoftwareAppStoreAppStatus;
|
|
install_during_setup?: boolean;
|
|
automatic_install_policies?: ISoftwareInstallPolicy[] | null;
|
|
automatic_install?: boolean;
|
|
last_install?: IAppLastInstall | null;
|
|
last_uninstall?: {
|
|
script_execution_id: string;
|
|
uninstalled_at: string;
|
|
} | null;
|
|
version?: string;
|
|
labels_include_any: ILabelSoftwareTitle[] | null;
|
|
labels_exclude_any: ILabelSoftwareTitle[] | null;
|
|
categories?: SoftwareCategory[] | null;
|
|
configuration?: string;
|
|
}
|
|
|
|
/**
|
|
* package: includes FMA, custom packages, and are defined under software_package
|
|
* app-store: includes VPP, Google Play Store apps and are defined under app_store_app
|
|
*/
|
|
export type InstallerType = "package" | "app-store";
|
|
|
|
export const isSoftwarePackage = (
|
|
data: ISoftwarePackage | IAppStoreApp
|
|
): data is ISoftwarePackage =>
|
|
(data as ISoftwarePackage).install_script !== undefined;
|
|
|
|
export interface ISoftwareTitle {
|
|
id: number;
|
|
name: string;
|
|
/** Custom name set per team by admin */
|
|
display_name?: string;
|
|
icon_url: string | null;
|
|
versions_count: number;
|
|
source: SoftwareSource;
|
|
extension_for?: SoftwareExtensionFor;
|
|
hosts_count: number;
|
|
versions: ISoftwareTitleVersion[] | null;
|
|
software_package: ISoftwarePackage | null;
|
|
app_store_app: IAppStoreApp | null;
|
|
/** @deprecated Use extension_for instead */
|
|
browser?: string;
|
|
}
|
|
|
|
export interface ISoftwareTitleDetails {
|
|
id: number;
|
|
name: string;
|
|
/** Custom name set per team by admin */
|
|
display_name?: string;
|
|
icon_url: string | null;
|
|
software_package: ISoftwarePackage | null;
|
|
app_store_app: IAppStoreApp | null;
|
|
source: SoftwareSource;
|
|
extension_for?: SoftwareExtensionFor;
|
|
hosts_count: number;
|
|
versions: ISoftwareTitleVersion[] | null;
|
|
counts_updated_at?: string;
|
|
bundle_identifier?: string;
|
|
versions_count?: number;
|
|
/** @deprecated Use extension_for instead */
|
|
browser?: string;
|
|
}
|
|
|
|
export interface ISoftwareVulnerability {
|
|
cve: string;
|
|
details_link: string;
|
|
cvss_score?: number | null;
|
|
epss_probability?: number | null;
|
|
cisa_known_exploit?: boolean | null;
|
|
cve_published?: string | null;
|
|
cve_description?: string | null;
|
|
resolved_in_version?: string | null;
|
|
created_at?: string | null;
|
|
}
|
|
|
|
export interface ISoftwareVersion {
|
|
id: number;
|
|
name: string; // e.g., "Figma.app"
|
|
/** Custom name set per team by admin */
|
|
display_name?: string; // e.g. "Figma for Desktop"
|
|
version: string; // e.g., "2.1.11"
|
|
bundle_identifier?: string; // e.g., "com.figma.Desktop"
|
|
source: SoftwareSource;
|
|
extension_for: SoftwareExtensionFor;
|
|
release: string; // TODO: on software/verions/:id?
|
|
vendor: string;
|
|
arch: string; // e.g., "x86_64" // TODO: on software/verions/:id?
|
|
generated_cpe: string;
|
|
vulnerabilities: ISoftwareVulnerability[] | null;
|
|
hosts_count?: number;
|
|
/** @deprecated Use extension_for instead */
|
|
browser?: string;
|
|
}
|
|
|
|
export const SOURCE_TYPE_CONVERSION = {
|
|
apt_sources: "Package (APT)",
|
|
deb_packages: "Package (deb)",
|
|
portage_packages: "Package (Portage)",
|
|
rpm_packages: "Package (RPM)",
|
|
yum_sources: "Package (YUM)",
|
|
npm_packages: "Package (npm)",
|
|
pacman_packages: "Package (pacman)",
|
|
atom_packages: "Package (Atom)", // Atom packages were removed from software inventory. Mapping is maintained for backwards compatibility. (2023-12-04)
|
|
python_packages: "Package (Python)",
|
|
tgz_packages: "Package (tar)",
|
|
apps: "Application (macOS)",
|
|
ios_apps: "Application (iOS)",
|
|
ipados_apps: "Application (iPadOS)",
|
|
android_apps: "Application (Android)",
|
|
chrome_extensions: "Browser plugin", // chrome_extensions can include any chrome-based browser (e.g., edge), so we rely instead on the `extension_for` field computed by Fleet server and fallback to this value if it is not present.
|
|
firefox_addons: "Browser plugin (Firefox)",
|
|
safari_extensions: "Browser plugin (Safari)",
|
|
homebrew_packages: "Package (Homebrew)",
|
|
programs: "Program (Windows)",
|
|
ie_extensions: "Browser plugin (IE)",
|
|
chocolatey_packages: "Package (Chocolatey)",
|
|
pkg_packages: "Package (pkg)",
|
|
vscode_extensions: "IDE extension", // vscode_extensions can include any vscode-based editor (e.g., Cursor, Trae, Windsurf), so we rely instead on the `extension_for` field computed by Fleet server and fallback to this value if it is not present.
|
|
sh_packages: "Payload-free (Linux)",
|
|
ps1_packages: "Payload-free (Windows)",
|
|
jetbrains_plugins: "IDE extension", // jetbrains_plugins can include any JetBrains IDE (e.g., IntelliJ, PyCharm, WebStorm), so we rely instead on the `extension_for` field computed by Fleet server and fallback to this value if it is not present.
|
|
} as const;
|
|
|
|
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",
|
|
pacman_packages: "linux",
|
|
tgz_packages: "linux",
|
|
npm_packages: null,
|
|
atom_packages: null,
|
|
python_packages: null,
|
|
apps: "darwin",
|
|
ios_apps: "ios",
|
|
ipados_apps: "ipados",
|
|
android_apps: "android", // 4.76 Currently hidden upstream as not installable
|
|
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,
|
|
sh_packages: "linux", // 4.76 Added support for Linux hosts only
|
|
ps1_packages: "windows",
|
|
jetbrains_plugins: null,
|
|
} as const;
|
|
|
|
export const SCRIPT_PACKAGE_SOURCES = ["sh_packages", "ps1_packages"];
|
|
|
|
export const NO_VERSION_OR_HOST_DATA_SOURCES = [
|
|
"tgz_packages",
|
|
...SCRIPT_PACKAGE_SOURCES,
|
|
];
|
|
|
|
export type InstallableSoftwareSource = keyof typeof INSTALLABLE_SOURCE_PLATFORM_CONVERSION;
|
|
|
|
const EXTENSION_FOR_TYPE_CONVERSION = {
|
|
// chrome versions
|
|
chrome: "Chrome",
|
|
chromium: "Chromium",
|
|
opera: "Opera",
|
|
yandex: "Yandex",
|
|
brave: "Brave",
|
|
edge: "Edge",
|
|
edge_beta: "Edge Beta",
|
|
|
|
// vscode versions
|
|
vscode: "VSCode",
|
|
vscode_insiders: "VSCode Insiders",
|
|
vscodium: "VSCodium",
|
|
vscodium_insiders: "VSCodium Insiders",
|
|
trae: "Trae",
|
|
windsurf: "Windsurf",
|
|
cursor: "Cursor",
|
|
|
|
// jebtbrains versions
|
|
clion: "CLion",
|
|
datagrip: "DataGrip",
|
|
goland: "GoLand",
|
|
intellij_idea: "IntelliJ IDEA",
|
|
intellij_idea_community_edition: "IntelliJ IDEA Community Edition",
|
|
phpstorm: "PhpStorm",
|
|
pycharm: "PyCharm",
|
|
pycharm_community_edition: "PyCharm Community Edition",
|
|
resharper: "ReSharper",
|
|
rider: "Rider",
|
|
rubymine: "RubyMine",
|
|
rust_rov: "RustRover",
|
|
webstorm: "WebStorm",
|
|
} as const;
|
|
|
|
export type SoftwareExtensionFor =
|
|
| keyof typeof EXTENSION_FOR_TYPE_CONVERSION
|
|
| "";
|
|
|
|
export const formatSoftwareType = ({
|
|
source,
|
|
extension_for,
|
|
}: {
|
|
source: SoftwareSource;
|
|
extension_for?: SoftwareExtensionFor;
|
|
}) => {
|
|
let type: string = SOURCE_TYPE_CONVERSION[source] || "Unknown";
|
|
if (extension_for) {
|
|
type += ` (${
|
|
EXTENSION_FOR_TYPE_CONVERSION[extension_for] || startCase(extension_for)
|
|
})`;
|
|
}
|
|
return type;
|
|
};
|
|
|
|
/**
|
|
* This list comprises all possible states of software install operations.
|
|
*/
|
|
export const SOFTWARE_UNINSTALL_STATUSES = [
|
|
"uninstalled",
|
|
"pending_uninstall",
|
|
"failed_uninstall",
|
|
] as const;
|
|
|
|
export type SoftwareUninstallStatus = typeof SOFTWARE_UNINSTALL_STATUSES[number];
|
|
|
|
export const SOFTWARE_INSTALL_STATUSES = [
|
|
"installed",
|
|
"pending_install",
|
|
"failed_install",
|
|
] as const;
|
|
|
|
// Payload-free (script) software statuses
|
|
export const SOFTWARE_SCRIPT_STATUSES = [
|
|
"ran_script",
|
|
"pending_script",
|
|
"failed_script",
|
|
] as const;
|
|
|
|
export type SoftwareInstallStatus = typeof SOFTWARE_INSTALL_STATUSES[number];
|
|
|
|
export const SOFTWARE_INSTALL_UNINSTALL_STATUSES = [
|
|
...SOFTWARE_INSTALL_STATUSES,
|
|
...SOFTWARE_UNINSTALL_STATUSES,
|
|
// Payload-free (script) software statuses use API's SOFTWARE_INSTALL_STATUSES
|
|
] as const;
|
|
|
|
/*
|
|
* SoftwareInstallUninstallStatus represents the possible states of software install operations.
|
|
*/
|
|
export type SoftwareInstallUninstallStatus = typeof SOFTWARE_INSTALL_UNINSTALL_STATUSES[number];
|
|
|
|
/** Include payload-free statuses */
|
|
export const ENAHNCED_SOFTWARE_INSTALL_UNINSTALL_STATUSES = [
|
|
...SOFTWARE_INSTALL_STATUSES,
|
|
...SOFTWARE_UNINSTALL_STATUSES,
|
|
...SOFTWARE_SCRIPT_STATUSES, // Payload-free (script) software
|
|
] as const;
|
|
|
|
/*
|
|
* EnhancedSoftwareInstallUninstallStatus represents the possible states of software install operations including payload-free used in the UI.
|
|
*/
|
|
export type EnhancedSoftwareInstallUninstallStatus = typeof ENAHNCED_SOFTWARE_INSTALL_UNINSTALL_STATUSES[number];
|
|
|
|
export const isValidSoftwareInstallUninstallStatus = (
|
|
s: string | undefined | null
|
|
): s is EnhancedSoftwareInstallUninstallStatus =>
|
|
!!s &&
|
|
ENAHNCED_SOFTWARE_INSTALL_UNINSTALL_STATUSES.includes(
|
|
s as EnhancedSoftwareInstallUninstallStatus
|
|
);
|
|
|
|
export const SOFTWARE_AGGREGATE_STATUSES = [
|
|
"installed",
|
|
"pending",
|
|
"failed",
|
|
] as const;
|
|
|
|
export type SoftwareAggregateStatus = typeof SOFTWARE_AGGREGATE_STATUSES[number];
|
|
|
|
export const isValidSoftwareAggregateStatus = (
|
|
s: string | undefined | null
|
|
): s is SoftwareAggregateStatus =>
|
|
!!s && SOFTWARE_AGGREGATE_STATUSES.includes(s as SoftwareAggregateStatus);
|
|
|
|
export const isSoftwareUninstallStatus = (
|
|
s: string | undefined | null
|
|
): s is SoftwareUninstallStatus =>
|
|
!!s && SOFTWARE_UNINSTALL_STATUSES.includes(s as SoftwareUninstallStatus);
|
|
|
|
// not a typeguard, as above 2 functions are
|
|
export const isPendingStatus = (s: string | undefined | null) =>
|
|
["pending_install", "pending_uninstall"].includes(s || "");
|
|
|
|
export const resolveUninstallStatus = (
|
|
activityStatus?: string
|
|
): SoftwareUninstallStatus => {
|
|
let resolvedStatus = activityStatus;
|
|
if (resolvedStatus === "pending") {
|
|
resolvedStatus = "pending_uninstall";
|
|
}
|
|
if (resolvedStatus === "failed") {
|
|
resolvedStatus = "failed_uninstall";
|
|
}
|
|
if (!isSoftwareUninstallStatus(resolvedStatus)) {
|
|
console.warn(
|
|
`Unexpected uninstall status "${activityStatus}" for activity. Defaulting to "pending_uninstall".`
|
|
);
|
|
resolvedStatus = "pending_uninstall";
|
|
}
|
|
return resolvedStatus as SoftwareUninstallStatus;
|
|
};
|
|
|
|
/**
|
|
* ISoftwareInstallResult is the shape of a software install result object
|
|
* returned by the Fleet API.
|
|
*/
|
|
export interface ISoftwareInstallResult {
|
|
host_display_name?: string;
|
|
install_uuid: string;
|
|
software_title: string;
|
|
software_title_id: number;
|
|
software_package: string;
|
|
host_id: number;
|
|
status: SoftwareInstallUninstallStatus;
|
|
detail: string;
|
|
output: string;
|
|
pre_install_query_output: string;
|
|
post_install_script_output: string;
|
|
created_at: string;
|
|
updated_at: string | null;
|
|
self_service: boolean;
|
|
}
|
|
|
|
// Script results are only install results, never uninstall
|
|
export type ISoftwareScriptResult = Omit<ISoftwareInstallResult, "status"> & {
|
|
status: SoftwareInstallStatus;
|
|
};
|
|
|
|
export interface ISoftwareInstallResults {
|
|
results: ISoftwareInstallResult;
|
|
}
|
|
|
|
/** For Software .ipa installs, we use the install results API to return MDM command results */
|
|
export interface ISoftwareIpaInstallResults {
|
|
results: ICommandResult;
|
|
}
|
|
|
|
// ISoftwareInstallerType defines the supported installer types for
|
|
// software uploaded by the IT admin.
|
|
export type ISoftwareInstallerType = "pkg" | "msi" | "deb" | "rpm" | "exe";
|
|
|
|
export interface ISoftwareLastInstall {
|
|
install_uuid: string;
|
|
installed_at: string;
|
|
}
|
|
|
|
export interface IAppLastInstall {
|
|
command_uuid: string;
|
|
installed_at: string;
|
|
}
|
|
|
|
interface SignatureInformation {
|
|
installed_path: string;
|
|
team_identifier: string;
|
|
hash_sha256: string | null;
|
|
}
|
|
export interface ISoftwareLastUninstall {
|
|
script_execution_id: string;
|
|
uninstalled_at: string;
|
|
}
|
|
|
|
export interface ISoftwareInstallVersion {
|
|
version: string;
|
|
bundle_identifier: string;
|
|
last_opened_at: string | null;
|
|
vulnerabilities: string[] | null;
|
|
installed_paths: string[];
|
|
signature_information?: SignatureInformation[];
|
|
}
|
|
|
|
export interface IHostSoftwarePackage {
|
|
name: string;
|
|
self_service: boolean;
|
|
icon_url: string | null;
|
|
version: string;
|
|
last_install: ISoftwareLastInstall | null;
|
|
last_uninstall: ISoftwareLastUninstall | null;
|
|
categories?: SoftwareCategory[] | null;
|
|
automatic_install_policies?: ISoftwareInstallPolicy[] | null;
|
|
platform?: Platform;
|
|
}
|
|
|
|
export interface IHostAppStoreApp {
|
|
app_store_id: string;
|
|
platform: Platform;
|
|
self_service: boolean;
|
|
icon_url: string;
|
|
version: string;
|
|
last_install: IAppLastInstall | null;
|
|
categories?: SoftwareCategory[] | null;
|
|
automatic_install_policies?: ISoftwareInstallPolicy[] | null;
|
|
}
|
|
|
|
export interface IHostSoftware {
|
|
id: number;
|
|
name: string; // e.g., "mock software.app"
|
|
/** Custom name set per team by admin */
|
|
display_name?: string; // e.g. "Mock Software"
|
|
icon_url: string | null;
|
|
software_package: IHostSoftwarePackage | null;
|
|
app_store_app: IHostAppStoreApp | null;
|
|
source: SoftwareSource;
|
|
extension_for?: SoftwareExtensionFor;
|
|
bundle_identifier?: string;
|
|
status: Exclude<SoftwareInstallUninstallStatus, "uninstalled"> | null;
|
|
installed_versions: ISoftwareInstallVersion[] | null;
|
|
}
|
|
|
|
/**
|
|
* Comprehensive list of possible UI software statuses for host > software > library/self-service.
|
|
*
|
|
* These are more detailed than the raw API `.status` and are determined by:
|
|
* - Whether the host is online or offline
|
|
* - If the fleet-installed version is newer than any in installed_versions
|
|
* - Special handling for tarballs (tgz_packages)
|
|
* - Cases where the software inventory has not yet updated to reflect a recent change
|
|
* (i.e., last_install date vs host software's updated_at date)
|
|
*/
|
|
// Error UI statuses
|
|
export const HOST_SOFTWARE_UI_ERROR_STATUSES = [
|
|
"failed_install", // Install attempt failed
|
|
"failed_install_update_available", // Install/update failed; newer installer version available
|
|
"failed_uninstall", // Uninstall attempt failed
|
|
"failed_uninstall_update_available", // Uninstall/update failed; newer installer version available
|
|
"failed_script", // Script package failed to run
|
|
] as const;
|
|
export type HostSoftwareUiErrorStatus = typeof HOST_SOFTWARE_UI_ERROR_STATUSES[number];
|
|
export const isSoftwareErrorStatus = (
|
|
status: IHostSoftwareUiStatus
|
|
): status is HostSoftwareUiErrorStatus =>
|
|
HOST_SOFTWARE_UI_ERROR_STATUSES.includes(status as HostSoftwareUiErrorStatus);
|
|
|
|
// Pending UI statuses for OFFLINE hosts
|
|
export const HOST_SOFTWARE_UI_PENDING_STATUSES = [
|
|
"pending_install", // Install scheduled (no newer installer version)
|
|
"pending_uninstall", // Uninstall scheduled
|
|
"pending_update", // Update scheduled (no newer installer version)
|
|
"pending_script", // Fleet-initiated script run scheduled
|
|
] as const;
|
|
export type HostSoftwareUiPendingStatus = typeof HOST_SOFTWARE_UI_PENDING_STATUSES[number];
|
|
export const isSoftwarePendingStatus = (
|
|
status: IHostSoftwareUiStatus
|
|
): status is HostSoftwareUiPendingStatus =>
|
|
HOST_SOFTWARE_UI_PENDING_STATUSES.includes(
|
|
status as HostSoftwareUiPendingStatus
|
|
);
|
|
|
|
// In-progress UI statuses for ONLINE hosts
|
|
export const HOST_SOFTWARE_UI_IN_PROGRESS_STATUSES = [
|
|
"installing", // Fleet-initiated install in progress
|
|
"updating", // Update (install) in progress with newer fleet installer
|
|
"uninstalling", // Fleet-initiated uninstall in progress
|
|
"running_script", // Fleet-initiated script run in progress
|
|
] as const;
|
|
export type HostSoftwareUiInProgressStatus = typeof HOST_SOFTWARE_UI_IN_PROGRESS_STATUSES[number];
|
|
export const isSoftwareInProgressStatus = (
|
|
status: IHostSoftwareUiStatus
|
|
): status is HostSoftwareUiInProgressStatus =>
|
|
HOST_SOFTWARE_UI_IN_PROGRESS_STATUSES.includes(
|
|
status as HostSoftwareUiInProgressStatus
|
|
);
|
|
|
|
// Success/steady-state UI statuses
|
|
export const HOST_SOFTWARE_UI_SUCCESS_STATUSES = [
|
|
"installed", // Present in inventory; no newer fleet installer version (tarballs: successful install only)
|
|
"uninstalled", // Not present in inventory (tarballs: successful uninstall or never installed)
|
|
"recently_updated", // Update applied (installer newer than inventory), but inventory not yet refreshed
|
|
"recently_installed", // Install applied (installer NOT newer than inventory), but inventory not yet refreshed
|
|
"recently_uninstalled", // Uninstall applied, but inventory not yet refreshed
|
|
"ran_script", // Script package ran successfully
|
|
"never_ran_script", // Script package never ran before
|
|
] as const;
|
|
export type HostSoftwareUiSuccessStatus = typeof HOST_SOFTWARE_UI_SUCCESS_STATUSES[number];
|
|
export const isSoftwareSuccessStatus = (
|
|
status: IHostSoftwareUiStatus
|
|
): status is HostSoftwareUiSuccessStatus =>
|
|
HOST_SOFTWARE_UI_SUCCESS_STATUSES.includes(
|
|
status as HostSoftwareUiSuccessStatus
|
|
);
|
|
|
|
// Update-available UI status
|
|
export const HOST_SOFTWARE_UI_UPDATE_AVAILABLE_STATUSES = [
|
|
"update_available", // In inventory, but newer fleet installer version is available
|
|
] as const;
|
|
export type HostSoftwareUiUpdateAvailableStatus = typeof HOST_SOFTWARE_UI_UPDATE_AVAILABLE_STATUSES[number];
|
|
export const isSoftwareUpdateAvailableStatus = (
|
|
status: IHostSoftwareUiStatus
|
|
): status is HostSoftwareUiUpdateAvailableStatus =>
|
|
HOST_SOFTWARE_UI_UPDATE_AVAILABLE_STATUSES.includes(
|
|
status as HostSoftwareUiUpdateAvailableStatus
|
|
);
|
|
|
|
// Master UI status type, combining all:
|
|
export type IHostSoftwareUiStatus =
|
|
| HostSoftwareUiErrorStatus
|
|
| HostSoftwareUiPendingStatus
|
|
| HostSoftwareUiSuccessStatus
|
|
| HostSoftwareUiInProgressStatus
|
|
| HostSoftwareUiUpdateAvailableStatus;
|
|
|
|
/**
|
|
* Extends IHostSoftware with a computed `ui_status` field.
|
|
*
|
|
* The `ui_status` categorizes software installation state for the UI by
|
|
* combining the `status`, `installed_versions` info, and other factors
|
|
* like host online state (via getUiStatus helper function), enabling
|
|
* more detailed and status labels needed for the status and actions columns.
|
|
*/
|
|
export interface IHostSoftwareWithUiStatus extends IHostSoftware {
|
|
ui_status: IHostSoftwareUiStatus;
|
|
}
|
|
|
|
/**
|
|
* Allows unified data model for rendering of host VPP software installs and uninstalls
|
|
* Optional as pending may not have a commandUuid
|
|
*/
|
|
export type IVPPHostSoftware = IHostSoftware & {
|
|
commandUuid?: string;
|
|
};
|
|
|
|
export type IHostSoftwareUninstall = IHostSoftwareWithUiStatus & {
|
|
scriptExecutionId: string;
|
|
};
|
|
|
|
export type IDeviceSoftware = IHostSoftware;
|
|
export type IDeviceSoftwareWithUiStatus = IHostSoftwareWithUiStatus;
|
|
|
|
const INSTALL_STATUS_PREDICATES: Record<
|
|
EnhancedSoftwareInstallUninstallStatus | "pending",
|
|
string
|
|
> = {
|
|
pending: "pending",
|
|
installed: "installed",
|
|
uninstalled: "uninstalled",
|
|
pending_install: "told Fleet to install",
|
|
failed_install: "failed to install",
|
|
pending_uninstall: "told Fleet to uninstall",
|
|
failed_uninstall: "failed to uninstall",
|
|
ran_script: "ran", // Payload-free (script) software
|
|
failed_script: "failed to run", // Payload-free (script) software
|
|
pending_script: "told Fleet to run", // Payload-free (script) software
|
|
} as const;
|
|
|
|
export const getInstallUninstallStatusPredicate = (
|
|
status: string | undefined,
|
|
isScriptPackage = false
|
|
) => {
|
|
if (!status) {
|
|
return INSTALL_STATUS_PREDICATES.pending;
|
|
}
|
|
|
|
// If it is a script package, map install statuses to script-specific predicates
|
|
if (isScriptPackage) {
|
|
switch (status.toLowerCase()) {
|
|
case "installed":
|
|
return INSTALL_STATUS_PREDICATES.ran_script;
|
|
case "pending_install":
|
|
return INSTALL_STATUS_PREDICATES.pending_script;
|
|
case "failed_install":
|
|
return INSTALL_STATUS_PREDICATES.failed_script;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// For all other cases, return the matching predicate or default to pending
|
|
return (
|
|
INSTALL_STATUS_PREDICATES[
|
|
status.toLowerCase() as keyof typeof INSTALL_STATUS_PREDICATES
|
|
] || INSTALL_STATUS_PREDICATES.pending
|
|
);
|
|
};
|
|
|
|
export const aggregateInstallStatusCounts = (
|
|
packageStatuses: ISoftwarePackage["status"]
|
|
) => ({
|
|
installed: packageStatuses.installed,
|
|
pending: packageStatuses.pending_install + packageStatuses.pending_uninstall,
|
|
failed: packageStatuses.failed_install + packageStatuses.failed_uninstall,
|
|
});
|
|
|
|
export const INSTALL_STATUS_ICONS: Record<
|
|
EnhancedSoftwareInstallUninstallStatus | "pending" | "failed",
|
|
IconNames
|
|
> = {
|
|
pending: "pending-outline",
|
|
pending_install: "pending-outline",
|
|
installed: "success-outline",
|
|
uninstalled: "success-outline",
|
|
failed: "error-outline",
|
|
failed_install: "error-outline",
|
|
pending_uninstall: "pending-outline",
|
|
failed_uninstall: "error-outline",
|
|
ran_script: "success-outline", // Payload-free (script) software
|
|
failed_script: "error-outline", // Payload-free (script) software
|
|
pending_script: "pending-outline", // Payload-free (script) software
|
|
} as const;
|
|
|
|
type IHostSoftwarePackageWithLastInstall = IHostSoftwarePackage & {
|
|
last_install: ISoftwareLastInstall;
|
|
};
|
|
|
|
export const hasHostSoftwarePackageLastInstall = (
|
|
software: IHostSoftware
|
|
): software is IHostSoftware & {
|
|
software_package: IHostSoftwarePackageWithLastInstall;
|
|
} => {
|
|
return !!software.software_package?.last_install;
|
|
};
|
|
|
|
type IHostAppWithLastInstall = IHostAppStoreApp & {
|
|
last_install: IAppLastInstall;
|
|
};
|
|
|
|
export const hasHostSoftwareAppLastInstall = (
|
|
software: IHostSoftware
|
|
): software is IHostSoftware & {
|
|
app_store_app: IHostAppWithLastInstall;
|
|
} => {
|
|
return !!software.app_store_app?.last_install;
|
|
};
|
|
|
|
export const isIpadOrIphoneSoftwareSource = (source: string) =>
|
|
["ios_apps", "ipados_apps"].includes(source);
|
|
|
|
export const isAndroidSoftwareSource = (source: string) =>
|
|
source === "android_apps";
|
|
|
|
export interface IFleetMaintainedApp {
|
|
id: number;
|
|
name: string;
|
|
version: string;
|
|
platform: FleetMaintainedAppPlatform;
|
|
software_title_id?: number; // null unless the team already has the software added (as a Fleet-maintained app, App Store (app), or custom package)
|
|
}
|
|
|
|
export type FleetMaintainedAppPlatform = Extract<
|
|
Platform,
|
|
"darwin" | "windows"
|
|
>;
|
|
|
|
export interface ICombinedFMA {
|
|
name: string;
|
|
macos: Omit<IFleetMaintainedApp, "name"> | null;
|
|
windows: Omit<IFleetMaintainedApp, "name"> | null;
|
|
}
|
|
export interface IFleetMaintainedAppDetails {
|
|
id: number;
|
|
name: string;
|
|
version: string;
|
|
platform: FleetMaintainedAppPlatform;
|
|
pre_install_script: string;
|
|
install_script: string;
|
|
post_install_script: string;
|
|
uninstall_script: string;
|
|
url: string;
|
|
slug: string;
|
|
software_title_id?: number; // null unless the team already has the software added (as a Fleet-maintained app, App Store (app), or custom package)
|
|
categories: SoftwareCategory[] | null;
|
|
}
|
|
|
|
export const ROLLING_ARCH_LINUX_NAMES = [
|
|
"Arch Linux",
|
|
"Arch Linux ARM",
|
|
"Manjaro Linux",
|
|
"Manjaro Linux ARM",
|
|
"Manjaro ARM Linux",
|
|
];
|
|
|
|
export const ROLLING_ARCH_LINUX_VERSIONS = ROLLING_ARCH_LINUX_NAMES.map(
|
|
(name) => `${name} rolling`
|
|
);
|