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; /** All software names displayed by UI is ran through getDisplayedSoftwareName */ 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; } // Match allowedCategories in cmd/maintained-apps/main.go 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; /** All software names displayed by UI is ran through getDisplayedSoftwareName */ 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; /** All software names displayed by UI is ran through getDisplayedSoftwareName */ 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; auto_update_enabled?: boolean; auto_update_window_start?: string; auto_update_window_end?: string; /** @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; /** All software names displayed by UI is ran through getDisplayedSoftwareName */ 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 & { 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; 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; /** All software names displayed by UI is ran through getDisplayedSoftwareName */ 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 | 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_installed", // Install attempt failed but version still present "failed_install_update_available", // Install/update failed; newer installer version available "failed_uninstall_installed", // Uninstall attempt failed but version still present "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 isIpadOrIphoneSoftware = (platform: string) => ["ios", "ipados"].includes(platform); 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 | null; windows: Omit | 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` );