**Related issue:** Resolves #48416 When adding a Fleet-maintained app, a large-installer download that's canceled or times out now returns a clear message pointing at the likely proxy/load-balancer timeout, instead of a raw `context canceled`. # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved error messages when adding Fleet-maintained apps times out or is canceled during large installer downloads. * Added clearer guidance for configuring server, proxy, and load balancer timeouts. * Properly handles additional timeout and upstream cancellation responses, including HTTP 408, 499, and 504. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
130 lines
4.1 KiB
TypeScript
130 lines
4.1 KiB
TypeScript
import React from "react";
|
|
|
|
export const ADD_SOFTWARE_ERROR_PREFIX = "Couldn't add.";
|
|
export const DEFAULT_ADD_SOFTWARE_ERROR_MESSAGE = `${ADD_SOFTWARE_ERROR_PREFIX} Please try again.`;
|
|
export const REQUEST_TIMEOUT_ERROR_MESSAGE = `${ADD_SOFTWARE_ERROR_PREFIX} The request timed out. Make sure your server, and any proxy or load balancer in front of Fleet, allows enough time to transfer large installers.`;
|
|
|
|
export const DIFFERENT_FILE_TYPE_MESSAGE =
|
|
"The selected package is for a different file type.";
|
|
|
|
/**
|
|
* Ensures that a string ends with a period.
|
|
* If the string is empty or already ends with a period, it is returned unchanged.
|
|
*/
|
|
export const ensurePeriod = (str: string) => {
|
|
if (str && !str.endsWith(".")) {
|
|
return `${str}.`;
|
|
}
|
|
return str;
|
|
};
|
|
|
|
/**
|
|
* Renders backend "already has ..." conflicts as flash-message JSX, bolding
|
|
* the software title and fleet name per design. Returns null if the reason
|
|
* doesn't match a known pattern so callers can fall back to generic handling.
|
|
*/
|
|
export const formatAlreadyAvailableInstallMessage = (msg: string) => {
|
|
// Strip the legacy "Couldn't add software." prefix if present.
|
|
const cleaned = msg.replace(/^Couldn't add software\.?\s*/, "");
|
|
|
|
const fmaMatch = cleaned.match(
|
|
/^(.+?) already has a Fleet-maintained app on the (.+?) fleet\./
|
|
);
|
|
if (fmaMatch) {
|
|
return (
|
|
<>
|
|
{ADD_SOFTWARE_ERROR_PREFIX} <b>{fmaMatch[1]}</b> already has a
|
|
Fleet-maintained app on the <b>{fmaMatch[2]}</b> fleet.
|
|
</>
|
|
);
|
|
}
|
|
|
|
const vppMatch = cleaned.match(
|
|
/^(.+?) already has an Apple App Store \(VPP\) on the (.+?) fleet\./
|
|
);
|
|
if (vppMatch) {
|
|
return (
|
|
<>
|
|
{ADD_SOFTWARE_ERROR_PREFIX} <b>{vppMatch[1]}</b> already has an Apple
|
|
App Store (VPP) on the <b>{vppMatch[2]}</b> fleet.
|
|
</>
|
|
);
|
|
}
|
|
|
|
const packageMatch = cleaned.match(
|
|
/^(.+?) already has a software package on the (.+?) fleet\./
|
|
);
|
|
if (packageMatch) {
|
|
return (
|
|
<>
|
|
{ADD_SOFTWARE_ERROR_PREFIX} <b>{packageMatch[1]}</b> already has a
|
|
software package on the <b>{packageMatch[2]}</b> fleet.
|
|
</>
|
|
);
|
|
}
|
|
|
|
const limitMatch = cleaned.match(
|
|
/^(.+?) already has (\d+) packages\. Before adding, delete one you no longer use\./
|
|
);
|
|
if (limitMatch) {
|
|
return (
|
|
<>
|
|
{ADD_SOFTWARE_ERROR_PREFIX} <b>{limitMatch[1]}</b> already has{" "}
|
|
{limitMatch[2]} packages. Before adding, delete one you no longer use.
|
|
</>
|
|
);
|
|
}
|
|
|
|
// Legacy generic conflict — kept as a fallback in case a code path still
|
|
// emits it. Matches "<title> already has an installer available for the
|
|
// <fleet> fleet."
|
|
const legacyInstallerMatch = cleaned.match(
|
|
/^(.+?) already has an installer available for the (.+?) fleet\./
|
|
);
|
|
if (legacyInstallerMatch) {
|
|
return (
|
|
<>
|
|
{ADD_SOFTWARE_ERROR_PREFIX} <b>{legacyInstallerMatch[1]}</b> already has
|
|
an installer available for the <b>{legacyInstallerMatch[2]}</b> fleet.
|
|
</>
|
|
);
|
|
}
|
|
|
|
// Legacy quote-style: `SoftwareInstaller "X" already exists with fleet "Y".`
|
|
// or `In-house app "X" already exists with fleet "Y".` (emitted by
|
|
// `alreadyExists(...).WithTeamName(...)` in the mysql layer).
|
|
const legacyQuotedMatch = cleaned.match(
|
|
/^(?:SoftwareInstaller|In-house app) "(.+?)" already.+ fleet "(.+?)"\./
|
|
);
|
|
if (legacyQuotedMatch) {
|
|
return (
|
|
<>
|
|
{ADD_SOFTWARE_ERROR_PREFIX} <b>{legacyQuotedMatch[1]}</b> already has an
|
|
installer available for the <b>{legacyQuotedMatch[2]}</b> fleet.
|
|
</>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Format the backend "different file type" error using the software title
|
|
* from the calling flow's context. Returns null if the reason doesn't match
|
|
* or no title is provided so callers can fall back.
|
|
*/
|
|
export const formatDifferentFileTypeMessage = (
|
|
msg: string,
|
|
softwareTitle?: string
|
|
) => {
|
|
if (!softwareTitle || !msg.includes(DIFFERENT_FILE_TYPE_MESSAGE)) {
|
|
return null;
|
|
}
|
|
return (
|
|
<>
|
|
{ADD_SOFTWARE_ERROR_PREFIX} <b>{softwareTitle}</b> already has an
|
|
installer of a different file type.
|
|
</>
|
|
);
|
|
};
|