Add UI for Fleet Sandbox to download prepackaged installers (#6721)
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 517 B |
|
After Width: | Height: | Size: 527 B |
@@ -0,0 +1 @@
|
|||||||
|
- Added Fleet Sandbox UI to download pre-packaged installers
|
||||||
@@ -409,7 +409,8 @@ awslocal kinesis get-records --shard-iterator AAAAAAAAAAERtiUrWGI0sq99TtpKnmDu6h
|
|||||||
|
|
||||||
Pre-built installers are kept in a blob storage like AWS S3. As part of your your local development there's a [MinIO](https://min.io/) instance running on http://localhost:9000. To test the pre-built installers functionality locally:
|
Pre-built installers are kept in a blob storage like AWS S3. As part of your your local development there's a [MinIO](https://min.io/) instance running on http://localhost:9000. To test the pre-built installers functionality locally:
|
||||||
|
|
||||||
1. Build the installers you want using `fleetctl package`.
|
1. Build the installers you want using `fleetctl package`. Be sure to include the `--insecure` flag
|
||||||
|
for local testing.
|
||||||
2. Use the [installerstore](../../tools/installerstore/README.md) tool to upload them to your MinIO instance.
|
2. Use the [installerstore](../../tools/installerstore/README.md) tool to upload them to your MinIO instance.
|
||||||
3. Configure your fleet server setting `FLEET_PACKAGING_GLOBAL_ENROLL_SECRET` to match your global enroll secret.
|
3. Configure your fleet server setting `FLEET_PACKAGING_GLOBAL_ENROLL_SECRET` to match your global enroll secret.
|
||||||
4. Set `FLEET_DEMO=1`, as the endpoint to retrieve the installer is only available in the sandbox.
|
4. Set `FLEET_DEMO=1`, as the endpoint to retrieve the installer is only available in the sandbox.
|
||||||
@@ -418,4 +419,7 @@ Pre-built installers are kept in a blob storage like AWS S3. As part of your you
|
|||||||
FLEET_DEMO=1 FLEET_PACKAGING_GLOBAL_ENROLL_SECRET=xyz ./build/fleet serve --dev
|
FLEET_DEMO=1 FLEET_PACKAGING_GLOBAL_ENROLL_SECRET=xyz ./build/fleet serve --dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Be sure to replace the `FLEET_PACKAGING_GLOBAL_ENROLL_SECRET` value above with the global enroll
|
||||||
|
secret from the `fleetctl package` command used to build the installers.
|
||||||
|
|
||||||
MinIO also offers a web interface at http://localhost:9001. Credentials are `minio` / `minio123!`.
|
MinIO also offers a web interface at http://localhost:9001. Credentials are `minio` / `minio123!`.
|
||||||
|
|||||||
@@ -1,24 +1,52 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { ITeamSummary } from "interfaces/team";
|
||||||
|
import DataError from "components/DataError";
|
||||||
import Modal from "components/Modal";
|
import Modal from "components/Modal";
|
||||||
import { ITeam } from "interfaces/team";
|
import Spinner from "components/Spinner";
|
||||||
import { IEnrollSecret } from "interfaces/enroll_secret";
|
|
||||||
import PlatformWrapper from "./PlatformWrapper/PlatformWrapper";
|
import PlatformWrapper from "./PlatformWrapper/PlatformWrapper";
|
||||||
|
import DownloadInstallers from "./DownloadInstallers/DownloadInstallers";
|
||||||
|
|
||||||
const baseClass = "add-hosts-modal";
|
const baseClass = "add-hosts-modal";
|
||||||
|
|
||||||
interface IAddHostsModal {
|
interface IAddHostsModal {
|
||||||
|
currentTeam?: ITeamSummary;
|
||||||
|
enrollSecret?: string;
|
||||||
|
isLoading: boolean;
|
||||||
|
isSandboxMode?: boolean;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
selectedTeam: ITeam | { name: string; secrets: IEnrollSecret[] | null };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddHostsModal = ({
|
const AddHostsModal = ({
|
||||||
|
currentTeam,
|
||||||
|
enrollSecret,
|
||||||
|
isLoading,
|
||||||
|
isSandboxMode,
|
||||||
onCancel,
|
onCancel,
|
||||||
selectedTeam,
|
|
||||||
}: IAddHostsModal): JSX.Element => {
|
}: IAddHostsModal): JSX.Element => {
|
||||||
|
const renderModalContent = () => {
|
||||||
|
if (isLoading) {
|
||||||
|
return <Spinner />;
|
||||||
|
}
|
||||||
|
if (!enrollSecret) {
|
||||||
|
return <DataError />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Currently, prepacked installers in Fleet Sandbox use the global enroll secret,
|
||||||
|
// and Fleet Sandbox runs Fleet Free so the currentTeam check here is an
|
||||||
|
// additional precaution/reminder to revisit this in connection with future changes.
|
||||||
|
// See https://github.com/fleetdm/fleet/issues/4970#issuecomment-1187679407.
|
||||||
|
return isSandboxMode && !currentTeam ? (
|
||||||
|
<DownloadInstallers onCancel={onCancel} enrollSecret={enrollSecret} />
|
||||||
|
) : (
|
||||||
|
<PlatformWrapper onCancel={onCancel} enrollSecret={enrollSecret} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal onExit={onCancel} title={"Add hosts"} className={baseClass}>
|
<Modal onExit={onCancel} title={"Add hosts"} className={baseClass}>
|
||||||
<PlatformWrapper onCancel={onCancel} selectedTeam={selectedTeam} />
|
{renderModalContent()}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import FileSaver from "file-saver";
|
||||||
|
|
||||||
|
import {
|
||||||
|
IInstallerPlatform,
|
||||||
|
IInstallerType,
|
||||||
|
INSTALLER_PLATFORM_BY_TYPE,
|
||||||
|
INSTALLER_TYPE_BY_PLATFORM,
|
||||||
|
} from "interfaces/installer";
|
||||||
|
import installerAPI from "services/entities/installers";
|
||||||
|
|
||||||
|
import Button from "components/buttons/Button";
|
||||||
|
import Checkbox from "components/forms/fields/Checkbox";
|
||||||
|
import DataError from "components/DataError";
|
||||||
|
import Spinner from "components/Spinner";
|
||||||
|
import TooltipWrapper from "components/TooltipWrapper";
|
||||||
|
|
||||||
|
import AppleIcon from "./../../../../assets/images/icon-apple-black-24x24@2x.png";
|
||||||
|
import AppleIconVibrant from "./../../../../assets/images/icon-apple-vibrant-blue-24x24@2x.png";
|
||||||
|
import LinuxIcon from "./../../../../assets/images/icon-linux-black-24x24@2x.png";
|
||||||
|
import LinuxIconVibrant from "./../../../../assets/images/icon-linux-vibrant-blue-24x24@2x.png";
|
||||||
|
import WindowsIcon from "./../../../../assets/images/icon-windows-black-24x24@2x.png";
|
||||||
|
import WindowsIconVibrant from "./../../../../assets/images/icon-windows-vibrant-blue-24x24@2x.png";
|
||||||
|
import SuccessIcon from "./../../../../assets/images/icon-circle-check-blue-48x48@2x.png";
|
||||||
|
|
||||||
|
interface IDownloadInstallersProps {
|
||||||
|
enrollSecret: string;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseClass = "download-installers";
|
||||||
|
|
||||||
|
const displayOrder = [
|
||||||
|
"macOS",
|
||||||
|
"Windows",
|
||||||
|
"Linux (RPM)",
|
||||||
|
"Linux (deb)",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const displayIcon = (platform: IInstallerPlatform, isSelected: boolean) => {
|
||||||
|
switch (platform) {
|
||||||
|
case "Linux (RPM)":
|
||||||
|
case "Linux (deb)":
|
||||||
|
return (
|
||||||
|
<img src={isSelected ? LinuxIconVibrant : LinuxIcon} alt={platform} />
|
||||||
|
);
|
||||||
|
case "macOS":
|
||||||
|
return (
|
||||||
|
<img src={isSelected ? AppleIconVibrant : AppleIcon} alt={platform} />
|
||||||
|
);
|
||||||
|
case "Windows":
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={isSelected ? WindowsIconVibrant : WindowsIcon}
|
||||||
|
alt={platform}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const DownloadInstallers = ({
|
||||||
|
enrollSecret,
|
||||||
|
onCancel,
|
||||||
|
}: IDownloadInstallersProps): JSX.Element => {
|
||||||
|
const [includeDesktop, setIncludeDesktop] = useState(true);
|
||||||
|
const [isDownloadError, setIsDownloadError] = useState(false);
|
||||||
|
const [isDownloading, setIsDownloading] = useState(false);
|
||||||
|
const [isDownloadSuccess, setIsDownloadSuccess] = useState(false);
|
||||||
|
const [selectedInstaller, setSelectedInstaller] = useState<
|
||||||
|
IInstallerType | undefined
|
||||||
|
>();
|
||||||
|
|
||||||
|
const downloadInstaller = async (installerType?: IInstallerType) => {
|
||||||
|
if (!installerType) {
|
||||||
|
// do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsDownloading(true);
|
||||||
|
try {
|
||||||
|
const blob: BlobPart = await installerAPI.downloadInstaller({
|
||||||
|
enrollSecret,
|
||||||
|
installerType,
|
||||||
|
includeDesktop,
|
||||||
|
});
|
||||||
|
const filename = `fleet-osquery.${installerType}`;
|
||||||
|
const file = new global.window.File([blob], filename, {
|
||||||
|
type: "application/octet-stream",
|
||||||
|
});
|
||||||
|
FileSaver.saveAs(file);
|
||||||
|
setIsDownloadSuccess(true);
|
||||||
|
} catch {
|
||||||
|
setIsDownloadError(true);
|
||||||
|
} finally {
|
||||||
|
setIsDownloading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickSelector = (type: IInstallerType) => {
|
||||||
|
if (isDownloading) {
|
||||||
|
// do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type === selectedInstaller) {
|
||||||
|
setSelectedInstaller(undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSelectedInstaller(type);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isDownloadError) {
|
||||||
|
return (
|
||||||
|
<div className={`${baseClass}__error`}>
|
||||||
|
<DataError />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDownloadSuccess) {
|
||||||
|
const installerPlatform =
|
||||||
|
(selectedInstaller &&
|
||||||
|
`${INSTALLER_PLATFORM_BY_TYPE[selectedInstaller]} `) ||
|
||||||
|
"";
|
||||||
|
return (
|
||||||
|
<div className={`${baseClass}__success`}>
|
||||||
|
<img src={SuccessIcon} alt="download successful" />
|
||||||
|
<h2>You’re almost there</h2>
|
||||||
|
<p>{`Run the installer on a ${installerPlatform}laptop, workstation, or sever to add it to Fleet.`}</p>
|
||||||
|
<Button onClick={onCancel}>Got it</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${baseClass}`}>
|
||||||
|
<p>Which platform is your host running?</p>
|
||||||
|
<div className={`${baseClass}__select-installer`}>
|
||||||
|
{displayOrder.map((platform) => {
|
||||||
|
const installerType = INSTALLER_TYPE_BY_PLATFORM[platform];
|
||||||
|
const isSelected = selectedInstaller === installerType;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={installerType}
|
||||||
|
className={`${baseClass}__selector ${
|
||||||
|
isSelected ? `${baseClass}__selector--selected` : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => onClickSelector(installerType)}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{displayIcon(platform, isSelected)}
|
||||||
|
{platform}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
name="include-fleet-desktop"
|
||||||
|
onChange={(value: boolean) => setIncludeDesktop(value)}
|
||||||
|
value={includeDesktop}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
Include
|
||||||
|
<TooltipWrapper
|
||||||
|
tipContent={
|
||||||
|
"<p>Lightweight application that allows end users to see information about their device.</p>"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Fleet Desktop
|
||||||
|
</TooltipWrapper>
|
||||||
|
</>
|
||||||
|
</Checkbox>
|
||||||
|
<Button
|
||||||
|
className={`${baseClass}__button--download`}
|
||||||
|
disabled={!selectedInstaller}
|
||||||
|
onClick={() => downloadInstaller(selectedInstaller)}
|
||||||
|
>
|
||||||
|
{isDownloading ? <Spinner /> : "Download installer"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DownloadInstallers;
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
.download-installers {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
padding-top: $pad-small;
|
||||||
|
padding-bottom: $pad-medium;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component__tooltip-wrapper__tip-text {
|
||||||
|
p {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field.form-field--checkbox {
|
||||||
|
padding-bottom: $pad-large;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__select-installer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
padding-bottom: $pad-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__selector {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: $small;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
gap: 16px;
|
||||||
|
width: 286px;
|
||||||
|
|
||||||
|
border: 1px solid #c5c7d1;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
img {
|
||||||
|
height: 24px;
|
||||||
|
width: auto;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $core-vibrant-blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--selected {
|
||||||
|
color: $core-vibrant-blue;
|
||||||
|
background-color: $ui-vibrant-blue-10;
|
||||||
|
border-color: $core-vibrant-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button--download {
|
||||||
|
width: 154px;
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__success {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 48px 0 70px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 48px;
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 16px 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
.data-error__inner {
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,23 +53,21 @@ const platformSubNav: IPlatformSubNav[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
interface IPlatformWrapperProp {
|
interface IPlatformWrapperProps {
|
||||||
selectedTeam: ITeam | { name: string; secrets: IEnrollSecret[] | null };
|
enrollSecret: string;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseClass = "platform-wrapper";
|
const baseClass = "platform-wrapper";
|
||||||
|
|
||||||
const PlatformWrapper = ({
|
const PlatformWrapper = ({
|
||||||
selectedTeam,
|
enrollSecret,
|
||||||
onCancel,
|
onCancel,
|
||||||
}: IPlatformWrapperProp): JSX.Element => {
|
}: IPlatformWrapperProps): JSX.Element => {
|
||||||
const { config, isPreviewMode } = useContext(AppContext);
|
const { config, isPreviewMode } = useContext(AppContext);
|
||||||
const { renderFlash } = useContext(NotificationContext);
|
const { renderFlash } = useContext(NotificationContext);
|
||||||
const [copyMessage, setCopyMessage] = useState<Record<string, string>>({});
|
const [copyMessage, setCopyMessage] = useState<Record<string, string>>({});
|
||||||
const [includeFleetDesktop, setIncludeFleetDesktop] = useState<boolean>(
|
const [includeFleetDesktop, setIncludeFleetDesktop] = useState<boolean>(true);
|
||||||
false
|
|
||||||
);
|
|
||||||
const [showPlainOsquery, setShowPlainOsquery] = useState<boolean>(false);
|
const [showPlainOsquery, setShowPlainOsquery] = useState<boolean>(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -127,11 +125,6 @@ const PlatformWrapper = ({
|
|||||||
--carver_continue_endpoint=/api/v1/osquery/carve/block
|
--carver_continue_endpoint=/api/v1/osquery/carve/block
|
||||||
--carver_block_size=2000000`;
|
--carver_block_size=2000000`;
|
||||||
|
|
||||||
let enrollSecret: string;
|
|
||||||
if (selectedTeam.secrets) {
|
|
||||||
enrollSecret = selectedTeam.secrets[0].secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDownloadEnrollSecret = (evt: React.MouseEvent) => {
|
const onDownloadEnrollSecret = (evt: React.MouseEvent) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
|
||||||
@@ -407,7 +400,7 @@ const PlatformWrapper = ({
|
|||||||
<>
|
<>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
name="include-fleet-desktop"
|
name="include-fleet-desktop"
|
||||||
onChange={() => setIncludeFleetDesktop(!includeFleetDesktop)}
|
onChange={(value: boolean) => setIncludeFleetDesktop(value)}
|
||||||
value={includeFleetDesktop}
|
value={includeFleetDesktop}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
export type IInstallerType = "pkg" | "msi" | "rpm" | "deb";
|
||||||
|
|
||||||
|
export type IInstallerPlatform =
|
||||||
|
| "Windows"
|
||||||
|
| "macOS"
|
||||||
|
| "Linux (RPM)"
|
||||||
|
| "Linux (deb)";
|
||||||
|
|
||||||
|
export const INSTALLER_TYPE_BY_PLATFORM: Record<
|
||||||
|
IInstallerPlatform,
|
||||||
|
IInstallerType
|
||||||
|
> = {
|
||||||
|
macOS: "pkg",
|
||||||
|
Windows: "msi",
|
||||||
|
"Linux (RPM)": "rpm",
|
||||||
|
"Linux (deb)": "deb",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const INSTALLER_PLATFORM_BY_TYPE: Record<
|
||||||
|
IInstallerType,
|
||||||
|
IInstallerPlatform
|
||||||
|
> = {
|
||||||
|
pkg: "macOS",
|
||||||
|
msi: "Windows",
|
||||||
|
rpm: "Linux (RPM)",
|
||||||
|
deb: "Linux (deb)",
|
||||||
|
} as const;
|
||||||
@@ -16,7 +16,7 @@ export default PropTypes.shape({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id, name, and optional description for a team entity
|
* The id, name, description, and host count for a team entity
|
||||||
*/
|
*/
|
||||||
export interface ITeamSummary {
|
export interface ITeamSummary {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
@@ -487,11 +487,15 @@ const TeamDetailsWrapper = ({
|
|||||||
</TabsWrapper>
|
</TabsWrapper>
|
||||||
{showAddHostsModal && (
|
{showAddHostsModal && (
|
||||||
<AddHostsModal
|
<AddHostsModal
|
||||||
|
currentTeam={currentTeam}
|
||||||
|
enrollSecret={teamSecrets?.[0]?.secret}
|
||||||
|
isLoading={isLoadingTeams}
|
||||||
|
// TODO: Currently, prepacked installers in Fleet Sandbox use the global enroll secret,
|
||||||
|
// and Fleet Sandbox runs Fleet Free so explicitly setting isSandboxMode here is an
|
||||||
|
// additional precaution/reminder to revisit this in connection with future changes.
|
||||||
|
// See https://github.com/fleetdm/fleet/issues/4970#issuecomment-1187679407.
|
||||||
|
isSandboxMode={false}
|
||||||
onCancel={toggleAddHostsModal}
|
onCancel={toggleAddHostsModal}
|
||||||
selectedTeam={{
|
|
||||||
name: currentTeam.name,
|
|
||||||
secrets: teamSecrets || null,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showManageEnrollSecretsModal && (
|
{showManageEnrollSecretsModal && (
|
||||||
|
|||||||
@@ -314,13 +314,6 @@ const ManageHostsPage = ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const addHostsTeam = currentTeam
|
|
||||||
? { name: currentTeam.name, secrets: teamSecrets || null }
|
|
||||||
: {
|
|
||||||
name: "No team",
|
|
||||||
secrets: globalSecrets || null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: teams,
|
data: teams,
|
||||||
isLoading: isLoadingTeams,
|
isLoading: isLoadingTeams,
|
||||||
@@ -1187,9 +1180,25 @@ const ManageHostsPage = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderAddHostsModal = () => (
|
const renderAddHostsModal = () => {
|
||||||
<AddHostsModal onCancel={toggleAddHostsModal} selectedTeam={addHostsTeam} />
|
const enrollSecret =
|
||||||
);
|
// TODO: Currently, prepacked installers in Fleet Sandbox use the global enroll secret,
|
||||||
|
// and Fleet Sandbox runs Fleet Free so the isSandboxMode check here is an
|
||||||
|
// additional precaution/reminder to revisit this in connection with future changes.
|
||||||
|
// See https://github.com/fleetdm/fleet/issues/4970#issuecomment-1187679407.
|
||||||
|
currentTeam && !isSandboxMode
|
||||||
|
? teamSecrets?.[0].secret
|
||||||
|
: globalSecrets?.[0].secret;
|
||||||
|
return (
|
||||||
|
<AddHostsModal
|
||||||
|
currentTeam={currentTeam}
|
||||||
|
enrollSecret={enrollSecret}
|
||||||
|
isLoading={isLoadingTeams || isGlobalSecretsLoading}
|
||||||
|
isSandboxMode={!!isSandboxMode}
|
||||||
|
onCancel={toggleAddHostsModal}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const renderTransferHostModal = () => {
|
const renderTransferHostModal = () => {
|
||||||
if (!teams) {
|
if (!teams) {
|
||||||
@@ -1344,7 +1353,9 @@ const ManageHostsPage = ({
|
|||||||
isHostCountLoading ? "count-loading" : ""
|
isHostCountLoading ? "count-loading" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>{`${count} host${count === 1 ? "" : "s"}`}</span>
|
{count !== undefined && (
|
||||||
|
<span>{`${count} host${count === 1 ? "" : "s"}`}</span>
|
||||||
|
)}
|
||||||
{count ? (
|
{count ? (
|
||||||
<Button
|
<Button
|
||||||
className={`${baseClass}__export-btn`}
|
className={`${baseClass}__export-btn`}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
import { IInstallerType } from "interfaces/installer";
|
||||||
|
import sendRequest from "services";
|
||||||
|
import ENDPOINTS from "utilities/endpoints";
|
||||||
|
|
||||||
|
export interface IDownloadInstallerRequestParams {
|
||||||
|
enrollSecret: string;
|
||||||
|
includeDesktop: boolean;
|
||||||
|
installerType: IInstallerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
downloadInstaller: ({
|
||||||
|
enrollSecret,
|
||||||
|
includeDesktop,
|
||||||
|
installerType,
|
||||||
|
}: IDownloadInstallerRequestParams): Promise<BlobPart> => {
|
||||||
|
const path = `${ENDPOINTS.DOWNLOAD_INSTALLER}/${encodeURIComponent(
|
||||||
|
enrollSecret
|
||||||
|
)}/${installerType}?desktop=${includeDesktop}`;
|
||||||
|
console.log("path: ", path);
|
||||||
|
|
||||||
|
return sendRequest("GET", path, undefined, "blob");
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
import axios, { AxiosError, AxiosResponse } from "axios";
|
import axios, {
|
||||||
|
AxiosError,
|
||||||
|
AxiosResponse,
|
||||||
|
ResponseType as AxiosResponseType,
|
||||||
|
} from "axios";
|
||||||
import local from "utilities/local";
|
import local from "utilities/local";
|
||||||
import URL_PREFIX from "router/url_prefix";
|
import URL_PREFIX from "router/url_prefix";
|
||||||
|
|
||||||
const sendRequest = async (
|
const sendRequest = async (
|
||||||
method: "GET" | "POST" | "PATCH" | "DELETE",
|
method: "GET" | "POST" | "PATCH" | "DELETE",
|
||||||
path: string,
|
path: string,
|
||||||
data?: unknown
|
data?: unknown,
|
||||||
|
responseType: AxiosResponseType = "json"
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
const { origin } = global.window.location;
|
const { origin } = global.window.location;
|
||||||
|
|
||||||
@@ -17,6 +22,7 @@ const sendRequest = async (
|
|||||||
method,
|
method,
|
||||||
url,
|
url,
|
||||||
data,
|
data,
|
||||||
|
responseType,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export default {
|
|||||||
return `/${API_VERSION}/fleet/email/change/${token}`;
|
return `/${API_VERSION}/fleet/email/change/${token}`;
|
||||||
},
|
},
|
||||||
DEVICE_USER_DETAILS: `/${API_VERSION}/fleet/device`,
|
DEVICE_USER_DETAILS: `/${API_VERSION}/fleet/device`,
|
||||||
|
DOWNLOAD_INSTALLER: `/${API_VERSION}/fleet/download_installer`,
|
||||||
ENABLE_USER: (id: number): string => {
|
ENABLE_USER: (id: number): string => {
|
||||||
return `/${API_VERSION}/fleet/users/${id}/enable`;
|
return `/${API_VERSION}/fleet/users/${id}/enable`;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ GLOBAL OPTIONS:
|
|||||||
### Example
|
### Example
|
||||||
|
|
||||||
To upload a file for testing to your local MinIO server, you can run this
|
To upload a file for testing to your local MinIO server, you can run this
|
||||||
command from the root of the repo:
|
command from the root of the repo (be sure to replace the `--enroll-secret`
|
||||||
|
string with the value you wish to test and set the `--fleet-desktop` boolean
|
||||||
|
to your desired value):
|
||||||
|
|
||||||
```
|
```
|
||||||
go run tools/installerstore/main.go \
|
go run tools/installerstore/main.go \
|
||||||
@@ -46,6 +48,7 @@ go run tools/installerstore/main.go \
|
|||||||
--disable-ssl=true \
|
--disable-ssl=true \
|
||||||
--force-s3-path-style=true \
|
--force-s3-path-style=true \
|
||||||
--create-bucket=true \
|
--create-bucket=true \
|
||||||
|
--fleet-desktop=true \
|
||||||
fleet-osquery.pkg
|
fleet-osquery.pkg
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||