From 4792d7a759ff1f778d6708f026b577b0f28c1952 Mon Sep 17 00:00:00 2001 From: gillespi314 <73313222+gillespi314@users.noreply.github.com> Date: Tue, 19 Jul 2022 14:28:06 -0500 Subject: [PATCH] Add UI for Fleet Sandbox to download prepackaged installers (#6721) --- assets/images/icon-apple-black-24x24@2x.png | Bin 0 -> 1073 bytes .../icon-apple-vibrant-blue-24x24@2x.png | Bin 0 -> 1046 bytes .../icon-circle-check-blue-48x48@2x.png | Bin 0 -> 2972 bytes assets/images/icon-linux-black-24x24@2x.png | Bin 0 -> 1698 bytes .../icon-linux-vibrant-blue-24x24@2x.png | Bin 0 -> 1651 bytes assets/images/icon-windows-black-24x24@2x.png | Bin 0 -> 517 bytes .../icon-windows-vibrant-blue-24x24@2x.png | Bin 0 -> 527 bytes ...ssue-5757-download-orbit-installer-sandbox | 1 + docs/Contributing/Testing.md | 6 +- .../AddHostsModal/AddHostsModal.tsx | 38 +++- .../DownloadInstallers/DownloadInstallers.tsx | 185 ++++++++++++++++++ .../DownloadInstallers/_styles.scss | 104 ++++++++++ .../PlatformWrapper/PlatformWrapper.tsx | 19 +- frontend/interfaces/installer.ts | 27 +++ frontend/interfaces/team.ts | 2 +- .../TeamDetailsWrapper/TeamDetailsWrapper.tsx | 12 +- .../hosts/ManageHostsPage/ManageHostsPage.tsx | 33 ++-- frontend/services/entities/installers.ts | 25 +++ frontend/services/index.ts | 10 +- frontend/utilities/endpoints.ts | 1 + tools/installerstore/README.md | 5 +- 21 files changed, 430 insertions(+), 38 deletions(-) create mode 100644 assets/images/icon-apple-black-24x24@2x.png create mode 100644 assets/images/icon-apple-vibrant-blue-24x24@2x.png create mode 100644 assets/images/icon-circle-check-blue-48x48@2x.png create mode 100644 assets/images/icon-linux-black-24x24@2x.png create mode 100644 assets/images/icon-linux-vibrant-blue-24x24@2x.png create mode 100644 assets/images/icon-windows-black-24x24@2x.png create mode 100644 assets/images/icon-windows-vibrant-blue-24x24@2x.png create mode 100644 changes/issue-5757-download-orbit-installer-sandbox create mode 100644 frontend/components/AddHostsModal/DownloadInstallers/DownloadInstallers.tsx create mode 100644 frontend/components/AddHostsModal/DownloadInstallers/_styles.scss create mode 100644 frontend/interfaces/installer.ts create mode 100644 frontend/services/entities/installers.ts diff --git a/assets/images/icon-apple-black-24x24@2x.png b/assets/images/icon-apple-black-24x24@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..50f389ab87f7c4c1e4847ed9bbdd500abaaeeb1f GIT binary patch literal 1073 zcmV-11kU@3P)C7W!(x)n!r2D4RXKvp|_Z_A&B5USbm#fCa8zDH)B<`OHvG~jR zRE;yvWf}u+na||KWcG?gyeve!T@VZT9v7*TxP5mC@7{{=Xd9Ex`uor5%%PYtC`HW7 zEfMZ|(erpGhyeO5*RfoRiPSVPZGwPDFNk?VA%bxbgoe*_RTx6Y{~?x#754jX%8_Yl$;%rC-|6;wp7r(f`eC;nOrO>lLn9v zfi2x|kY*y0K)SGj0g5z!)PD=FFUt)jp%H;D`iKm4u^JMP8CKb!=_)lLy$`dnNgu!o z(%P|8dFSs?YNEYi(FRHa5`mh~O$&(bJGGK zu&Axj1TT;t5YWPhFd>y;o1_n%Fo8Z2oy1NXL`eYqNMxvSVJY%OAj(bcBjGrfRwO9G z(hl}cGL_Z?EcY5=LV+ZZ<7=Z0xck>(G79e`Vjbv?80NJ3^z9&lU`+@*Bz=HgFRc$U zaD^rei}XScx!h+J5oIbqGrLreu>VaPfm>8%Dh}a9hr1)jEHrW?H*39(msHZ%v-1sX z?pH`ZVD)hRM)qFz#Qk284N6UPj#@0z6YxF!vHQgsEXkXsA!IS8_dIJL2~D*j&dFQr zZ3sP3)=eAoAliTKbSdmdC+;@~MwEj&J*4r&28&h9>A$on($W4THH#Esb24JcV$5dH z{vr|ynVw^SC*#a+@&8*O5E#2cms1x~h1at4KM^|$ls>-JoXRUzXJir*AW}sTM0T9` zWIGA$Q_+0XaGi!qAQlM;5V2wr5dy^$-ovMA=jC!cV++m85DyL-)LaZJ&<|B9LLPHK z0oz9zj4<$StE?3;fzGoXdHHw*?#5&P7}EAo&wCFWC{hFrU5v+sT2IHcOJ0V9CaV^s rocoUuneEaUA2z5_b@kipj~0Ib%Q%tp%+Dkq00000NkvXXu0mjftm@VY literal 0 HcmV?d00001 diff --git a/assets/images/icon-apple-vibrant-blue-24x24@2x.png b/assets/images/icon-apple-vibrant-blue-24x24@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4dd07cfd50dfb1e5b5f9c5442a23db46de70d257 GIT binary patch literal 1046 zcmV+x1nK*UP)NBeiP%VN7e_$SddDpG6ec7DwjcsxBauuQSwQi{!=!s$*PO(lZ*B1AH5J1o z5!WFiVk3b<>?(!`P;NCE*oCO~h8R#OQ09J#dX52t9vO10UQb-Y6o|zWcgv7-Ud5&q zxHc^T8*$}{F6V^i_&vd40G2zCV#1 zg8}4(lz9;1<-?$qzX(K!1ayMzl~5%3hcoy6e>w*suQVjM<(a>f2(8D29<)*)Hc8M1 zYo(l9(tCOrLL&kbIEZ+LSPcmAo-95oc4D{fJ z9OwZ)XNt8miw+EqLkw0qU`yde-{APBdYpPgcPYRI%m9MkNhkbpBAN+BqYkIi&}F6@&s4Q zAFad51tp0_<3*c8Z5#9i3q$#B^1<_rO%98#I!moyQk^3(ER= z1Dnt5PQE1mx{Z!N54avx1C{oZ15qUi-_@Kk2nFuDoi75Bu;n=+zFDZcB|1>`^g>m| zOQlgkkhp;O@#ExFtf>Y=b&48|M3WOu+#CHSneuK*MryK@OIDRv=w3kc4(rf|Xu=%| zlV8jr#R%SqS4$~;@7GnDs#AnSS8fkuCnMGdm#Vf_sWi0T@+d`t3BGpR!`^#>j!qgz z6#`)D?kPV|UQl9{g+Nvkb-^&&VcNM9LSUgOgt5YPrt9V9L}!*gz5fD#0Q#VYTjOze Q`Tzg`07*qoM6N<$f?xK{?EnA( literal 0 HcmV?d00001 diff --git a/assets/images/icon-circle-check-blue-48x48@2x.png b/assets/images/icon-circle-check-blue-48x48@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..67a2b7b5332b9e4cc4ebd1471e0711aea1d24d2c GIT binary patch literal 2972 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 literal 0 HcmV?d00001 diff --git a/assets/images/icon-linux-vibrant-blue-24x24@2x.png b/assets/images/icon-linux-vibrant-blue-24x24@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2da7e23c0dce31e31be23aa7ab8a376d8c70f7cb GIT binary patch literal 1651 zcmV-(28{WMP)cb z(D@2Wq-YTdirjM{B1F(4gmXpM@D!X*iqzO7WX4hJDk3gHW?(}jyd9U!SR~e0=@k%F zjk9i#OCow#-*aj(B1R^`1ol}!$}k}i;Z}VQ|9}xN5{XF@xCiZT?>a9aBNSxBdWGIh zhkyXfYcIVG$cRiUv9dx%SJ4?v7j&z;Pd;n+!SWKvYukb0G z*-}VI%m{WuePi4DOfZ}y1kY`Y%ZxZYyual{b7I3)TQaz2WjEt%!8kf>D86yvLa z4=BpL(sUSphgA!}apq8|*TLr$8V@Ezf=)vZitTG^gr&B(^XHsXY??YCN52T0S|*Hq z9fq8qSb~4R5>_NuRysujd=_ayG{!mgx`frkCKDvwHkex?&D%x4i}Z~6yTPCskxJsX zHCl2p_cP2T^~x7&M8)g)KnV=J%J(Vgr)=6HqvmGL13Dvbb7RKt|? zNJa)Jm}%p4Cc6#xhfYEv0`I;7nfmzSgUCHmQRtu zv4=~JY!~7pTjNTIE^;Jkj93L4y9Y37@YdifZZkjP2|bM4BSsQ_;vWr>2WZ+xmsv2= zQIPMbMuNy3N5y}e8X_brE8TK(RUmp$&Qq8c6ldV-UQGhHAk=dj^>Tm=a`NSWi%){%|5I)}l3*?W1I`U9 z)TB(SL5tJa7m^&YmxxhoJdyGaUyVC5y+WLyRU`^nM}< zN&Le_tv(8pM^8enM2#HBo#Uwa(9q1eE2icxshM5At!l>AV8_UnjL+k&h+ib=MW>%) z9BnhMNEbvZ30VztC~h|V%El-}`UK_^2oTqCtxk@ttK#dcUQNm_30+woBKAK%{jf42 zFOQ528L+;pC0$LdkxGRRH6CdPBpPi7nr4*;kNqQJNRAHJPc_)gTc^L74q5UyIDhki zJ|=}I{s$^K%P=&m^jXVFpg(3h#0s^)-j^BK!;4y3SFJPPTx;!HLqw;=r>RFaFQt-@ zP0QuSvoX)AN5w_{AwDr3&;*fco!I(peJ28f*wF_nyXi9=43(1uF{`-5;=(E$@_`ro;1CTFPg&bFkLi*^_=wI$3f_;2wLf@p}7% zg-oKX?G_yjuNn^+ANs!i`M+0xF`heE@{|kboAB=*M#V!w{Glmn0oZqkLd>{ z?h0yg?GSUVa{Mm;G5F5Ge;h8!S6mVsOEzA3Ay~3xA@jeb2eS_=_e+>2i)sYu__K(g z;Exo|$Tbdbl$~wg3UbSNDB&OUg(`#EY+c=g4 z2>;Vm=|wg0-xuaTTD1q-1=owb*K)S-b88Tu#_6ACGh=IKx7CXlJ@2P#CYvjaoRw1A zCI?BUr@USg_I#)KjE`F_)_Skn^kV6$`Y!8D^LJ&R?Yk6oXXo6H)A>(%baJj~c0QUR ztofWJXUS5Z3_s6|eJZ*aH2ki(o=7skp}LRpmqe5ME&dcqV5~8Cy85}Sb4q9e0PFDA A+5i9m literal 0 HcmV?d00001 diff --git a/assets/images/icon-windows-vibrant-blue-24x24@2x.png b/assets/images/icon-windows-vibrant-blue-24x24@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5de3e9280fd14b27d21329789313d3478497323f GIT binary patch literal 527 zcmV+q0`UEbP)vNr%vK)pe|0p36tn86h{5jR@Gf-86fIRZ!E2yRrwGS3-44A^O!R?2J5 z{L=49CjVcXHZy$)HDE9x|MML}BxsSKP+tn3M&jJGHK1bH!Y)6kji`@sy&S+QkTQ*tV4e|Q|k$&kAe5eTmYK{}&#tCra1h{bm+&BSl zYA=u&jS{YI2#V2cHfHMs&zmZUFTs&bVwB&P>IDeeP=HwB)p(4v{%za^6!%af>Hmdv zcx}~&0(f4$o<|UYFR)l2d2wGMZkzx&PJkOHz>O2&#tCp^oev;2Is_DM2Xz5dn1nzS zolW)5tPkegl#3RNI1m+;%B;`~hxiH`A+uIas!)wAO={PjF6NnbGrcTbs?@qmnNR9* zBzOu(2U4Ui5U~RVq}T+~kn$_cBUL#88~aiqww2N?JAvBK>n#H*<7@aYd;@c2$E%OQ Rl`a4P002ovPDHLkV1ni-(LDeF literal 0 HcmV?d00001 diff --git a/changes/issue-5757-download-orbit-installer-sandbox b/changes/issue-5757-download-orbit-installer-sandbox new file mode 100644 index 0000000000..80fb1c9a86 --- /dev/null +++ b/changes/issue-5757-download-orbit-installer-sandbox @@ -0,0 +1 @@ +- Added Fleet Sandbox UI to download pre-packaged installers \ No newline at end of file diff --git a/docs/Contributing/Testing.md b/docs/Contributing/Testing.md index 2f63c85c7c..42b0ef2d5b 100644 --- a/docs/Contributing/Testing.md +++ b/docs/Contributing/Testing.md @@ -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: -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. 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. @@ -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 ``` +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!`. diff --git a/frontend/components/AddHostsModal/AddHostsModal.tsx b/frontend/components/AddHostsModal/AddHostsModal.tsx index c867539b78..13cad6defe 100644 --- a/frontend/components/AddHostsModal/AddHostsModal.tsx +++ b/frontend/components/AddHostsModal/AddHostsModal.tsx @@ -1,24 +1,52 @@ import React from "react"; +import { ITeamSummary } from "interfaces/team"; +import DataError from "components/DataError"; import Modal from "components/Modal"; -import { ITeam } from "interfaces/team"; -import { IEnrollSecret } from "interfaces/enroll_secret"; +import Spinner from "components/Spinner"; + import PlatformWrapper from "./PlatformWrapper/PlatformWrapper"; +import DownloadInstallers from "./DownloadInstallers/DownloadInstallers"; const baseClass = "add-hosts-modal"; interface IAddHostsModal { + currentTeam?: ITeamSummary; + enrollSecret?: string; + isLoading: boolean; + isSandboxMode?: boolean; onCancel: () => void; - selectedTeam: ITeam | { name: string; secrets: IEnrollSecret[] | null }; } const AddHostsModal = ({ + currentTeam, + enrollSecret, + isLoading, + isSandboxMode, onCancel, - selectedTeam, }: IAddHostsModal): JSX.Element => { + const renderModalContent = () => { + if (isLoading) { + return ; + } + if (!enrollSecret) { + return ; + } + + // 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 ? ( + + ) : ( + + ); + }; + return ( - + {renderModalContent()} ); }; diff --git a/frontend/components/AddHostsModal/DownloadInstallers/DownloadInstallers.tsx b/frontend/components/AddHostsModal/DownloadInstallers/DownloadInstallers.tsx new file mode 100644 index 0000000000..316e5d4a85 --- /dev/null +++ b/frontend/components/AddHostsModal/DownloadInstallers/DownloadInstallers.tsx @@ -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 ( + {platform} + ); + case "macOS": + return ( + {platform} + ); + case "Windows": + return ( + {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 ( +
+ +
+ ); + } + + if (isDownloadSuccess) { + const installerPlatform = + (selectedInstaller && + `${INSTALLER_PLATFORM_BY_TYPE[selectedInstaller]} `) || + ""; + return ( +
+ download successful +

You’re almost there

+

{`Run the installer on a ${installerPlatform}laptop, workstation, or sever to add it to Fleet.`}

+ +
+ ); + } + + return ( +
+

Which platform is your host running?

+
+ {displayOrder.map((platform) => { + const installerType = INSTALLER_TYPE_BY_PLATFORM[platform]; + const isSelected = selectedInstaller === installerType; + return ( +
onClickSelector(installerType)} + > + + {displayIcon(platform, isSelected)} + {platform} + +
+ ); + })} +
+ setIncludeDesktop(value)} + value={includeDesktop} + > + <> + Include  + Lightweight application that allows end users to see information about their device.

" + } + > + Fleet Desktop +
+ +
+ +
+ ); +}; + +export default DownloadInstallers; diff --git a/frontend/components/AddHostsModal/DownloadInstallers/_styles.scss b/frontend/components/AddHostsModal/DownloadInstallers/_styles.scss new file mode 100644 index 0000000000..ae45b4a810 --- /dev/null +++ b/frontend/components/AddHostsModal/DownloadInstallers/_styles.scss @@ -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; + } + } +} diff --git a/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx b/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx index 2560c5408a..63e2e8230f 100644 --- a/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx +++ b/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx @@ -53,23 +53,21 @@ const platformSubNav: IPlatformSubNav[] = [ }, ]; -interface IPlatformWrapperProp { - selectedTeam: ITeam | { name: string; secrets: IEnrollSecret[] | null }; +interface IPlatformWrapperProps { + enrollSecret: string; onCancel: () => void; } const baseClass = "platform-wrapper"; const PlatformWrapper = ({ - selectedTeam, + enrollSecret, onCancel, -}: IPlatformWrapperProp): JSX.Element => { +}: IPlatformWrapperProps): JSX.Element => { const { config, isPreviewMode } = useContext(AppContext); const { renderFlash } = useContext(NotificationContext); const [copyMessage, setCopyMessage] = useState>({}); - const [includeFleetDesktop, setIncludeFleetDesktop] = useState( - false - ); + const [includeFleetDesktop, setIncludeFleetDesktop] = useState(true); const [showPlainOsquery, setShowPlainOsquery] = useState(false); const { @@ -127,11 +125,6 @@ const PlatformWrapper = ({ --carver_continue_endpoint=/api/v1/osquery/carve/block --carver_block_size=2000000`; - let enrollSecret: string; - if (selectedTeam.secrets) { - enrollSecret = selectedTeam.secrets[0].secret; - } - const onDownloadEnrollSecret = (evt: React.MouseEvent) => { evt.preventDefault(); @@ -407,7 +400,7 @@ const PlatformWrapper = ({ <> setIncludeFleetDesktop(!includeFleetDesktop)} + onChange={(value: boolean) => setIncludeFleetDesktop(value)} value={includeFleetDesktop} > <> diff --git a/frontend/interfaces/installer.ts b/frontend/interfaces/installer.ts new file mode 100644 index 0000000000..341dac572a --- /dev/null +++ b/frontend/interfaces/installer.ts @@ -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; diff --git a/frontend/interfaces/team.ts b/frontend/interfaces/team.ts index 6f7f14ed78..20a462ba88 100644 --- a/frontend/interfaces/team.ts +++ b/frontend/interfaces/team.ts @@ -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 { id: number; diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx index 5e47b86509..7a4e28be9d 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx @@ -487,11 +487,15 @@ const TeamDetailsWrapper = ({ {showAddHostsModal && ( )} {showManageEnrollSecretsModal && ( diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx index e52a9c7d4e..ae8fd3b4dc 100644 --- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx +++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx @@ -314,13 +314,6 @@ const ManageHostsPage = ({ } ); - const addHostsTeam = currentTeam - ? { name: currentTeam.name, secrets: teamSecrets || null } - : { - name: "No team", - secrets: globalSecrets || null, - }; - const { data: teams, isLoading: isLoadingTeams, @@ -1187,9 +1180,25 @@ const ManageHostsPage = ({ /> ); - const renderAddHostsModal = () => ( - - ); + const renderAddHostsModal = () => { + 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 ( + + ); + }; const renderTransferHostModal = () => { if (!teams) { @@ -1344,7 +1353,9 @@ const ManageHostsPage = ({ isHostCountLoading ? "count-loading" : "" }`} > - {`${count} host${count === 1 ? "" : "s"}`} + {count !== undefined && ( + {`${count} host${count === 1 ? "" : "s"}`} + )} {count ? (