mirror of
https://github.com/fleetdm/fleet
synced 2026-05-11 03:00:58 +00:00
## For #31226 New features: - Dynamic header for each possible state of a batch script run: Started, Scheduled, and Finished (corresponds to tabs at `/controls/scripts/progress` - Unique tabs for each possible status of hosts targeted by a batch script run: Ran, Errored, Pending, Incompatible, Canceled. - Within each tab, sortable, paginated host results with output preview and execution time. - View script/run details, cancel a batch, view manage hosts page filtered for the script batch run and a status. - Global script batch runs activities and and Scripts progress rows now navigate to this details page. Cleanups and improvements: - Expand tab count badge options using “alert”/“pending” variants across hosts, policies, and query results. - Misc cleanups and improvements  - [x] Changes file added for user-visible changes in `changes/`, - [x] Updated automated tests - new tests tracked for follow-up work - [x] QA'd all new/changed functionality manually --------- Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
355 lines
9.9 KiB
TypeScript
355 lines
9.9 KiB
TypeScript
import {
|
|
IHostScript,
|
|
IScript,
|
|
ScriptBatchHostStatus,
|
|
ScriptBatchStatus,
|
|
} from "interfaces/script";
|
|
import sendRequest from "services";
|
|
|
|
import {
|
|
createMockBatchScriptSummary,
|
|
createMockScriptBatchHostResults,
|
|
} from "__mocks__/scriptMock";
|
|
|
|
import endpoints from "utilities/endpoints";
|
|
import { buildQueryStringFromParams } from "utilities/url";
|
|
import {
|
|
ListEntitiesResponseCommon,
|
|
OrderDirection,
|
|
PaginationParams,
|
|
} from "./common";
|
|
/** Single script response from GET /script/:id */
|
|
export type IScriptResponse = IScript;
|
|
|
|
/** All scripts response from GET /scripts */
|
|
export interface IScriptsResponse {
|
|
scripts: IScript[] | null;
|
|
meta: {
|
|
has_next_results: boolean;
|
|
has_previous_results: boolean;
|
|
};
|
|
}
|
|
|
|
export interface IListScriptsApiParams {
|
|
page?: number;
|
|
per_page?: number;
|
|
team_id?: number;
|
|
}
|
|
|
|
export interface IListScriptsQueryKey extends IListScriptsApiParams {
|
|
scope: "scripts";
|
|
}
|
|
|
|
/**
|
|
* Script Result response from GET /scripts/results/:id
|
|
*/
|
|
export interface IScriptResultResponse {
|
|
hostname: string;
|
|
host_id: number;
|
|
execution_id: string;
|
|
script_contents: string;
|
|
script_id: number | null; // null for ad-hoc script run via API
|
|
exit_code: number | null;
|
|
output: string;
|
|
message: string;
|
|
runtime: number;
|
|
host_timeout: boolean;
|
|
created_at: string;
|
|
}
|
|
|
|
/**
|
|
* Request params for for GET /hosts/:id/scripts
|
|
*/
|
|
export interface IHostScriptsRequestParams {
|
|
host_id: number;
|
|
page?: number;
|
|
per_page?: number;
|
|
}
|
|
|
|
export interface IHostScriptsQueryKey extends IHostScriptsRequestParams {
|
|
scope: "host_scripts";
|
|
}
|
|
|
|
/**
|
|
* Script response from GET /hosts/:id/scripts
|
|
*/
|
|
export interface IHostScriptsResponse {
|
|
scripts: IHostScript[];
|
|
meta: {
|
|
has_next_results: boolean;
|
|
has_previous_results: boolean;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Request body for POST /scripts/run
|
|
*
|
|
* https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/api-for-contributors.md#run-script-asynchronously
|
|
*/
|
|
export interface IScriptRunRequest {
|
|
host_id: number;
|
|
script_id: number; // script_id is not required by the API currently, but we require it here to ensure it is always provided
|
|
// script_contents: string; // script_contents is only supported for the CLI currently
|
|
}
|
|
|
|
/**
|
|
* Response body for POST /scripts/run
|
|
*
|
|
* https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/api-for-contributors.md#run-script-asynchronously
|
|
*/
|
|
export interface IScriptRunResponse {
|
|
host_id: number;
|
|
execution_id: string;
|
|
}
|
|
|
|
export interface IScriptBatchSupportedFilters {
|
|
// a search string, not a Fleet.Query
|
|
query?: string;
|
|
label_id?: number;
|
|
team_id?: number;
|
|
status: any; // TODO - improve upstream typing
|
|
}
|
|
interface IRunScriptBatchRequestBase {
|
|
script_id: number;
|
|
not_before?: string; // ISO 8601 date-time string
|
|
}
|
|
|
|
interface IByFilters extends IRunScriptBatchRequestBase {
|
|
host_ids?: never;
|
|
filters: IScriptBatchSupportedFilters;
|
|
}
|
|
|
|
interface IByHostIds extends IRunScriptBatchRequestBase {
|
|
host_ids: number[];
|
|
filters?: never;
|
|
}
|
|
/** Request body for POST /scripts/run/batch */
|
|
export type IRunScriptBatchRequest = IByFilters | IByHostIds;
|
|
|
|
/** 202 successful response body for POST /scripts/run/batch */
|
|
export interface IRunScriptBatchResponse {
|
|
batch_execution_id: string;
|
|
}
|
|
export interface IScriptBatchSummaryParams {
|
|
batch_execution_id: string;
|
|
}
|
|
export interface IScriptBatchSummaryQueryKey extends IScriptBatchSummaryParams {
|
|
scope: "script_batch_summary";
|
|
}
|
|
|
|
export interface IScriptBatchHostCountsV1 {
|
|
ran: number;
|
|
pending: number;
|
|
errored: number;
|
|
canceled: number;
|
|
}
|
|
export type ScriptBatchHostCountV1 = keyof IScriptBatchHostCountsV1;
|
|
// 200 successful response
|
|
|
|
export interface IScriptBatchSummaryV1 extends IScriptBatchHostCountsV1 {
|
|
team_id: number;
|
|
script_name: string;
|
|
created_at: string;
|
|
// below fields not yet used by the UI
|
|
targeted: number;
|
|
script_id: number;
|
|
}
|
|
|
|
export interface IScriptBatchHostCountsV2 {
|
|
targeted_host_count: number;
|
|
ran_host_count: number;
|
|
errored_host_count: number;
|
|
pending_host_count: number;
|
|
incompatible_host_count: number;
|
|
canceled_host_count: number;
|
|
}
|
|
|
|
export interface IScriptBatchSummaryV2 extends IScriptBatchHostCountsV2 {
|
|
batch_execution_id: string;
|
|
/** ISO 8601 date-time string. When the script batch run was created (NOT when it is/was scheduled
|
|
* to run by, which is represented by `not_before`. */
|
|
created_at: string;
|
|
script_id: number;
|
|
script_name: string;
|
|
team_id: number;
|
|
status: ScriptBatchStatus;
|
|
canceled: boolean;
|
|
/** ISO 8601 date-time string. Always present as of Fleet 4.73.0 - `null`able for backwards compatibility with older batch runs. */
|
|
not_before: string | null;
|
|
// /** ISO 8601 date-time string. If present, this script batch run has started. */
|
|
started_at: string | null;
|
|
/** ISO 8601 date-time string. If present, this script has completed running. */
|
|
finished_at: string | null;
|
|
}
|
|
|
|
export interface IScriptBatchSummariesParams {
|
|
team_id: number;
|
|
status: ScriptBatchStatus;
|
|
page: number;
|
|
per_page: number;
|
|
}
|
|
export interface IScriptBatchSummariesQueryKey
|
|
extends IScriptBatchSummariesParams {
|
|
scope: "script_batch_summaries";
|
|
}
|
|
|
|
export interface IScriptBatchSummariesResponse
|
|
extends ListEntitiesResponseCommon {
|
|
batch_executions: IScriptBatchSummaryV2[] | null; // should not return `null`, but API currently does sometimes. Remove this option when it's fixed.
|
|
}
|
|
|
|
export type ScriptBatchHostsOrderKey = "display_name" | "script_executed_at";
|
|
export interface IScriptBatchHostResultsParams extends PaginationParams {
|
|
batch_execution_id: string;
|
|
status: ScriptBatchHostStatus;
|
|
order_key: ScriptBatchHostsOrderKey;
|
|
order_direction: OrderDirection;
|
|
}
|
|
|
|
export interface IScriptBatchHostResultsQueryKey
|
|
extends IScriptBatchHostResultsParams {
|
|
scope: "script_batch_host_results";
|
|
}
|
|
|
|
export interface IScriptBatchHostResult {
|
|
id: number;
|
|
display_name: string;
|
|
script_status: ScriptBatchHostStatus;
|
|
script_execution_id: string | null; // if status === pending, this may be `null` or contain a value dependending on whether the script exectution is this hosts next scheduled activity on the server
|
|
// /** ISO 8601 date-time string. `null` if pending, cancelled, or incompatible. */
|
|
script_executed_at: string | null;
|
|
/** `null` if pending, cancelled, or incompatible. */
|
|
script_output_preview: string | null;
|
|
}
|
|
export interface IScriptBatchHostResultsResponse
|
|
extends ListEntitiesResponseCommon {
|
|
hosts: IScriptBatchHostResult[];
|
|
}
|
|
|
|
export default {
|
|
getHostScripts({ host_id, page, per_page }: IHostScriptsRequestParams) {
|
|
const { HOST_SCRIPTS } = endpoints;
|
|
const path = `${HOST_SCRIPTS(host_id)}?${buildQueryStringFromParams({
|
|
page,
|
|
per_page,
|
|
})}`;
|
|
|
|
return sendRequest("GET", path);
|
|
},
|
|
|
|
getScripts(params: IListScriptsApiParams): Promise<IScriptsResponse> {
|
|
const { SCRIPTS } = endpoints;
|
|
const path = `${SCRIPTS}?${buildQueryStringFromParams({ ...params })}`;
|
|
|
|
return sendRequest("GET", path);
|
|
},
|
|
|
|
getScript(id: number) {
|
|
const { SCRIPT } = endpoints;
|
|
return sendRequest("GET", SCRIPT(id));
|
|
},
|
|
|
|
uploadScript(file: File, teamId?: number) {
|
|
const { SCRIPTS } = endpoints;
|
|
|
|
const formData = new FormData();
|
|
formData.append("script", file);
|
|
|
|
if (teamId) {
|
|
formData.append("team_id", teamId.toString());
|
|
}
|
|
|
|
return sendRequest("POST", SCRIPTS, formData);
|
|
},
|
|
|
|
downloadScript(id: number) {
|
|
const { SCRIPT } = endpoints;
|
|
const path = `${SCRIPT(id)}?${buildQueryStringFromParams({
|
|
alt: "media",
|
|
})}`;
|
|
return sendRequest("GET", path);
|
|
},
|
|
|
|
updateScript(id: number, contents: string, name: string) {
|
|
const { SCRIPT } = endpoints;
|
|
const path = `${SCRIPT(id)}`;
|
|
|
|
const file = new File([contents], name);
|
|
const formData = new FormData();
|
|
formData.append("script", file);
|
|
|
|
return sendRequest("PATCH", path, formData);
|
|
},
|
|
|
|
deleteScript(id: number) {
|
|
const { SCRIPT } = endpoints;
|
|
return sendRequest("DELETE", SCRIPT(id));
|
|
},
|
|
|
|
getScriptResult(executionId: string) {
|
|
const { SCRIPT_RESULT } = endpoints;
|
|
return sendRequest("GET", SCRIPT_RESULT(executionId));
|
|
},
|
|
|
|
runScript(request: IScriptRunRequest): Promise<IScriptRunResponse> {
|
|
const { SCRIPT_RUN } = endpoints;
|
|
return sendRequest("POST", SCRIPT_RUN, request);
|
|
},
|
|
runScriptBatch(
|
|
request: IRunScriptBatchRequest
|
|
): Promise<IRunScriptBatchResponse> {
|
|
const { SCRIPT_RUN_BATCH } = endpoints;
|
|
return sendRequest("POST", SCRIPT_RUN_BATCH, request);
|
|
},
|
|
cancelScriptBatch(batchExecutionId: string) {
|
|
const { SCRIPT_CANCEL_BATCH } = endpoints;
|
|
return sendRequest("POST", SCRIPT_CANCEL_BATCH(batchExecutionId));
|
|
},
|
|
/** calls the deprecated endpoint */
|
|
getRunScriptBatchSummaryV1({
|
|
batch_execution_id,
|
|
}: IScriptBatchSummaryParams): Promise<IScriptBatchSummaryV1> {
|
|
return sendRequest(
|
|
"GET",
|
|
`${endpoints.SCRIPT_RUN_BATCH_SUMMARY_V1(batch_execution_id)}`
|
|
);
|
|
},
|
|
getRunScriptBatchSummaryV2({
|
|
batch_execution_id,
|
|
}: IScriptBatchSummaryParams): Promise<IScriptBatchSummaryV2> {
|
|
return sendRequest(
|
|
"GET",
|
|
`${endpoints.SCRIPT_RUN_BATCH_SUMMARY_V2(batch_execution_id)}`
|
|
);
|
|
},
|
|
getRunScriptBatchSummaries(
|
|
params: IScriptBatchSummariesParams
|
|
): Promise<IScriptBatchSummariesResponse> {
|
|
const path = `${
|
|
endpoints.SCRIPT_RUN_BATCH_SUMMARIES
|
|
}?${buildQueryStringFromParams({ ...params })}`;
|
|
return sendRequest("GET", path);
|
|
},
|
|
getScriptBatchHostResults(
|
|
params: IScriptBatchHostResultsParams
|
|
): Promise<IScriptBatchHostResultsResponse> {
|
|
const {
|
|
batch_execution_id,
|
|
status,
|
|
page,
|
|
per_page,
|
|
order_key,
|
|
order_direction,
|
|
} = params;
|
|
const path = `${endpoints.SCRIPT_BATCH_HOST_RESULTS(
|
|
batch_execution_id
|
|
)}?${buildQueryStringFromParams({
|
|
status,
|
|
page,
|
|
per_page,
|
|
order_key: order_key === "script_executed_at" ? "updated_at" : order_key, // map to server field name
|
|
order_direction,
|
|
})}`;
|
|
return sendRequest("GET", path);
|
|
},
|
|
};
|