diff --git a/changes/issue-2290-generate-orbit-installer b/changes/issue-2290-generate-orbit-installer new file mode 100644 index 0000000000..9983f96898 --- /dev/null +++ b/changes/issue-2290-generate-orbit-installer @@ -0,0 +1 @@ +* Introduce Generate installer modal to replace Add new host modal for better UX \ No newline at end of file diff --git a/cypress/README.md b/cypress/README.md index ab80dd2069..ae6996780a 100644 --- a/cypress/README.md +++ b/cypress/README.md @@ -39,4 +39,4 @@ As much as possible, assert that the code is only selecting 1 item or that the f - [Fleet testing documentation](../docs/03-Contributing/02-Testing.md) - [Cypress documentation](https://docs.cypress.io/api/table-of-contents) -- [React testing-library query documentation](https://testing-library.com/docs/queries/about) +- [React testing-library query documentation](https://testing-library.com/docs/queries/about/) diff --git a/cypress/integration/all/app/hosts.spec.ts b/cypress/integration/all/app/hosts.spec.ts index 9e2dc82341..db3cf0d216 100644 --- a/cypress/integration/all/app/hosts.spec.ts +++ b/cypress/integration/all/app/hosts.spec.ts @@ -28,10 +28,10 @@ describe( () => { cy.visit("/hosts/manage"); - cy.contains("button", /add new host/i).click(); + cy.contains("button", /generate installer/i).click(); // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(1000); - cy.get('a[href*="showSecret"]').click(); + cy.wait(2000); + cy.findByText(/rpm/i).click(); cy.contains("a", /download/i) .first() .click(); @@ -42,13 +42,11 @@ describe( if (Cypress.platform !== "win32") { // windows has issues with downloads location cy.readFile( - path.join(Cypress.config("downloadsFolder"), "secret.txt"), + path.join(Cypress.config("downloadsFolder"), "fleet.pem"), { timeout: 5000, } - ).then((contents) => { - cy.get("input[disabled]").should("have.value", contents); - }); + ); } cy.visit("/hosts/manage"); @@ -141,8 +139,8 @@ describe( cy.findByRole("button", { name: /delete/i }).click(); }) .then(() => { - cy.findByText(/add your hosts to fleet/i).should("exist"); - cy.findByText(/add new host/i).should("exist"); + cy.findByText(/add your devices to fleet/i).should("exist"); + cy.findByText(/generate installer/i).should("exist"); cy.findByText(/about this host/i).should("not.exist"); cy.findByText(hostname).should("not.exist"); }); diff --git a/cypress/integration/free/admin.spec.ts b/cypress/integration/free/admin.spec.ts index 64e0dde02e..838cdc9ef3 100644 --- a/cypress/integration/free/admin.spec.ts +++ b/cypress/integration/free/admin.spec.ts @@ -37,13 +37,13 @@ describe( cy.findByText(/settings/i).should("exist"); }); - // See and select "add new host" - cy.findByRole("button", { name: /add new host/i }).click(); + // See and select "generate installer" + cy.findByRole("button", { name: /generate installer/i }).click(); cy.contains(/team/i).should("not.exist"); cy.contains("button", /done/i).click(); - // See the “Show enroll secret” button. A modal appears after the user selects the button - cy.contains("button", /show enroll secret/i).click(); + // See the "Manage" enroll secret” button. A modal appears after the user selects the button + cy.contains("button", /manage enroll secret/i).click(); cy.contains("button", /done/i).click(); // See and select "add label" diff --git a/cypress/integration/free/maintainer.spec.ts b/cypress/integration/free/maintainer.spec.ts index 845430ee40..b4c5b4104e 100644 --- a/cypress/integration/free/maintainer.spec.ts +++ b/cypress/integration/free/maintainer.spec.ts @@ -36,12 +36,11 @@ describe( cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting cy.findByText(/teams/i).should("not.exist"); - cy.contains("button", /add new host/i).click(); - cy.findByText("select a team").should("not.exist"); + cy.contains("button", /generate installer/i).click(); cy.contains("button", /done/i).click(); - // See the “Show enroll secret” button. A modal appears after the user selects the button - cy.contains("button", /show enroll secret/i).click(); + // See the Manage enroll secret” button. A modal appears after the user selects the button + cy.contains("button", /manage enroll secret/i).click(); cy.contains("button", /done/i).click(); cy.contains("button", /add label/i).click(); diff --git a/cypress/integration/free/observer.spec.ts b/cypress/integration/free/observer.spec.ts index 2cb5817917..107679173a 100644 --- a/cypress/integration/free/observer.spec.ts +++ b/cypress/integration/free/observer.spec.ts @@ -39,9 +39,9 @@ describe("Free tier - Observer user", () => { // Host manage page: No team UI, cannot add host, add label, nor enroll secret cy.visit("/hosts/manage"); cy.findByText(/teams/i).should("not.exist"); - cy.contains("button", /add new host/i).should("not.exist"); + cy.contains("button", /generate installer/i).should("not.exist"); cy.contains("button", /add label/i).should("not.exist"); - cy.contains("button", /show enroll secret/i).should("not.exist"); + cy.contains("button", /manage enroll secret/i).should("not.exist"); // Host details page: No team UI, cannot delete or query cy.get("tbody").within(() => { diff --git a/cypress/integration/premium/admin.spec.ts b/cypress/integration/premium/admin.spec.ts index a03e40db9a..6f554a3322 100644 --- a/cypress/integration/premium/admin.spec.ts +++ b/cypress/integration/premium/admin.spec.ts @@ -36,19 +36,9 @@ describe( // See the “Teams” column in the Hosts table cy.get("thead").contains(/team/i).should("exist"); - // See and select the “Add new host” button - cy.contains("button", /add new host/i).click(); - - // See the “Select a team for this new host” in the Add new host modal. This modal appears after the user selects the “Add new host” button - cy.get( - ".add-host-modal__team-dropdown-wrapper .Select-control" - ).click(); - - cy.get(".Select-menu-outer").within(() => { - cy.findByText(/no team/i).should("exist"); - cy.findByText(/apples/i).should("exist"); - cy.findByText(/oranges/i).should("exist"); - }); + // See and select the “Generate installer” button + cy.contains("button", /generate installer/i).click(); + cy.contains("button", /done/i).click(); // On the Host details page, they should… // See the “Team” information below the hostname diff --git a/cypress/integration/premium/maintainer.spec.ts b/cypress/integration/premium/maintainer.spec.ts index 005e3ac9f4..2f5f720280 100644 --- a/cypress/integration/premium/maintainer.spec.ts +++ b/cypress/integration/premium/maintainer.spec.ts @@ -29,9 +29,9 @@ describe( cy.wait(3000); // eslint-disable-line cypress/no-unnecessary-waiting - cy.findByText(/show enroll secret/i).should("exist"); + cy.findByText(/manage enroll secret/i).should("exist"); - cy.contains("button", /add new host/i).click(); + cy.contains("button", /generate installer/i).click(); // TODO: Check Team Apples is in Select a team dropdown cy.contains("button", /done/i).click(); diff --git a/cypress/integration/premium/observer.spec.ts b/cypress/integration/premium/observer.spec.ts index 995c1f414e..3292b5dcd7 100644 --- a/cypress/integration/premium/observer.spec.ts +++ b/cypress/integration/premium/observer.spec.ts @@ -38,8 +38,8 @@ describe("Premium tier - Observer user", () => { cy.contains("button", /query/i).click(); cy.contains("button", /create custom query/i).should("not.exist"); - // Not see the "Show enroll secret” button - cy.contains("button", /show enroll secret/i).should("not.exist"); + // Not see the "Manage enroll secret” button + cy.contains("button", /manage enroll secret/i).should("not.exist"); // TODO - Fix tests according to improved query experience - MP // Query pages: Can see team in select targets dropdown diff --git a/cypress/integration/premium/team_maintainer_observer.spec.ts b/cypress/integration/premium/team_maintainer_observer.spec.ts index 1488837eb1..dc91c12d23 100644 --- a/cypress/integration/premium/team_maintainer_observer.spec.ts +++ b/cypress/integration/premium/team_maintainer_observer.spec.ts @@ -27,7 +27,7 @@ describe( // On the Hosts page, they should… // See hosts - // cy.findByText(/kinda empty in here/i).should("not.exist"); + // cy.findByText(/generate installer/i).should("not.exist"); // ^^TODO hosts table is not rendering because we need new forEach script/command for admin to assign team after the host is added // See the “Teams” column in the Hosts table @@ -140,16 +140,8 @@ describe( // cy.get("thead").contains(/team/i).should("exist"); // ^^TODO hosts table is not rendering because we need new forEach script/command for admin to assign team after the host is added - // See and select the “Add new host” button - cy.findByRole("button", { name: /add new host/i }).click(); - - // See the “Select a team for this new host” in the Add new host modal. This modal appears after the user selects the “Add new host” button - cy.get(".add-host-modal__team-dropdown-wrapper .Select-control").click(); - cy.get(".Select-menu-outer").within(() => { - cy.findByText(/no team/i).should("not.exist"); - cy.findByText(/apples/i).should("not.exist"); - cy.findByText(/oranges/i).should("exist"); - }); + // See and select the “Generate installer” button + cy.findByRole("button", { name: /generate installer/i }).click(); cy.findByRole("button", { name: /done/i }).click(); // On the Host details page, they should… diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx index 334a206a46..a125f45e4b 100644 --- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx +++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx @@ -73,7 +73,7 @@ import EditColumnsModal from "./components/EditColumnsModal/EditColumnsModal"; import TransferHostModal from "./components/TransferHostModal"; import DeleteHostModal from "./components/DeleteHostModal"; import SoftwareVulnerabilities from "./components/SoftwareVulnerabilities"; // @ts-ignore -import AddHostModal from "./components/AddHostModal"; +import GenerateInstallerModal from "./components/GenerateInstallerModal"; import EditColumnsIcon from "../../../../assets/images/icon-edit-columns-16x16@2x.png"; import PencilIcon from "../../../../assets/images/icon-pencil-14x14@2x.png"; import TrashIcon from "../../../../assets/images/icon-trash-14x14@2x.png"; @@ -157,7 +157,6 @@ const ManageHostsPage = ({ // ========= states const [selectedLabel, setSelectedLabel] = useState(); const [statusLabels, setStatusLabels] = useState(); - const [showAddHostModal, setShowAddHostModal] = useState(false); const [showEnrollSecretModal, setShowEnrollSecretModal] = useState( false ); @@ -167,6 +166,10 @@ const ManageHostsPage = ({ const [showEditColumnsModal, setShowEditColumnsModal] = useState( false ); + const [ + showGenerateInstallerModal, + setShowGenerateInstallerModal, + ] = useState(false); const [showTransferHostModal, setShowTransferHostModal] = useState( false ); @@ -220,6 +223,11 @@ const ManageHostsPage = ({ isGlobalAdmin || isGlobalMaintainer || isTeamAdmin || isTeamMaintainer; const canAddNewLabels = isGlobalAdmin || isGlobalMaintainer; + const generateInstallerTeam = currentTeam || { + name: "No team", + secrets: globalSecret, + }; + const { isLoading: isLabelsLoading, data: labels, @@ -282,6 +290,10 @@ const ManageHostsPage = ({ setShowDeleteHostModal(!showDeleteHostModal); }; + const toggleGenerateInstallerModal = () => { + setShowGenerateInstallerModal(!showGenerateInstallerModal); + }; + const toggleAllMatchingHosts = (shouldSelect: boolean) => { if (typeof shouldSelect !== "undefined") { setIsAllMatchingHostsSelected(shouldSelect); @@ -290,27 +302,6 @@ const ManageHostsPage = ({ } }; - const renderAddHostModal = () => { - if (!canAddNewHosts || !showAddHostModal) { - return null; - } - - return ( - setShowAddHostModal(false)} - className={`${baseClass}__invite-modal`} - > - setShowAddHostModal(false)} - config={config} - currentUser={currentUser} - /> - - ); - }; - const getLabelSelected = () => { return selectedFilters.find((f) => f.includes(LABEL_SLUG_PREFIX)); }; @@ -1041,6 +1032,19 @@ const ManageHostsPage = ({ ); }; + const renderGenerateInstallerModal = () => { + if (!showGenerateInstallerModal) { + return null; + } + + return ( + + ); + }; + const renderTransferHostModal = () => { if (!showTransferHostModal || !teams) { return null; @@ -1221,7 +1225,7 @@ const ManageHostsPage = ({ ); }; - const renderTable = () => { + const renderTable = (selectedTeam: number) => { if ( !config || !currentUser || @@ -1237,8 +1241,15 @@ const ManageHostsPage = ({ } // Hosts have not been set up for this instance yet. - if (getStatusSelected() === ALL_HOSTS_LABEL && selectedLabel.count === 0) { - return ; + if ( + (getStatusSelected() === ALL_HOSTS_LABEL && selectedLabel.count === 0) || + (getStatusSelected() === ALL_HOSTS_LABEL && + filteredHostCount === 0 && + searchQuery === "") + ) { + return ( + + ); } const secondarySelectActions: IActionButtonProps[] = [ @@ -1294,6 +1305,8 @@ const ManageHostsPage = ({ ); }; + const selectedTeam = currentTeam?.id || 0; + return (
{renderForm()} @@ -1308,7 +1321,7 @@ const ManageHostsPage = ({ className={`${baseClass}__enroll-hosts button`} variant="inverse" > - Show enroll secret + Manage enroll secret )} {canAddNewHosts && @@ -1321,24 +1334,24 @@ const ManageHostsPage = ({ filteredHostCount === 0 ) && ( )}
{renderActiveFilterBlock()} {renderSoftwareVulnerabilities()} - {config && (!isPremiumTier || teams) && renderTable()} + {config && (!isPremiumTier || teams) && renderTable(selectedTeam)} )} {!isLabelsLoading && renderSidePanel()} - {renderAddHostModal()} {renderEnrollSecretModal()} {renderEditColumnsModal()} {renderDeleteLabelModal()} + {renderGenerateInstallerModal()} {renderTransferHostModal()} {renderDeleteHostModal()} diff --git a/frontend/pages/hosts/ManageHostsPage/components/AddHostModal/AddHostModal.jsx b/frontend/pages/hosts/ManageHostsPage/components/AddHostModal/AddHostModal.jsx deleted file mode 100644 index 97a3e19e30..0000000000 --- a/frontend/pages/hosts/ManageHostsPage/components/AddHostModal/AddHostModal.jsx +++ /dev/null @@ -1,373 +0,0 @@ -import React, { Component } from "react"; -import PropTypes from "prop-types"; -import FileSaver from "file-saver"; - -import Fleet from "fleet"; -import Button from "components/buttons/Button"; -import configInterface from "interfaces/config"; -import teamInterface from "interfaces/team"; -import userInterface from "interfaces/user"; -import permissionUtils from "utilities/permissions"; -import EnrollSecretTable from "components/EnrollSecretTable"; -import FleetIcon from "components/icons/FleetIcon"; -import Dropdown from "components/forms/fields/Dropdown"; -import InputField from "components/forms/fields/InputField"; -import { stringToClipboard } from "utilities/copy_text"; -import DownloadIcon from "../../../../../../assets/images/icon-download-12x12@2x.png"; - -const baseClass = "add-host-modal"; - -const NO_TEAM_OPTION = { - value: "no-team", - label: "No team", -}; - -class AddHostModal extends Component { - static propTypes = { - teams: PropTypes.arrayOf(teamInterface), - onReturnToApp: PropTypes.func, - config: configInterface, - currentUser: userInterface, - }; - - constructor(props) { - super(props); - - this.userRole = { - isAnyTeamMaintainer: permissionUtils.isAnyTeamMaintainer( - this.props.currentUser - ), - isGlobalAdmin: permissionUtils.isGlobalAdmin(this.props.currentUser), - isGlobalMaintainer: permissionUtils.isGlobalMaintainer( - this.props.currentUser - ), - }; - this.currentUserTeams = this.userRole.isAnyTeamMaintainer - ? Object.values(this.props.currentUser.teams).filter( - (team) => team.role === "maintainer" - ) - : this.props.teams; - - this.teamSecrets = this.props.teams - ? Object.values(this.props.teams).map((team) => { - return { id: team.id, name: team.name, secrets: team.secrets }; - }) - : []; - - this.state = { - fetchCertificateError: undefined, - selectedTeam: null, - globalSecrets: [], - selectedEnrollSecrets: [], - copyMessage: "", - }; - } - - componentDidMount() { - const { isGlobalAdmin, isGlobalMaintainer } = this.userRole; - - (() => { - if (isGlobalAdmin || isGlobalMaintainer) { - Fleet.config - .loadEnrollSecret() - .then((response) => { - this.setState({ - globalSecrets: response.spec.secrets, - selectedTeam: { id: NO_TEAM_OPTION.value }, // Reset initial selectedTeam value to "no-team" in the case of global users - }); - }) - .catch((err) => { - console.log(err); - }); - } else { - this.setState({ selectedTeam: this.currentUserTeams[0] }); - } - })(); - - Fleet.config - .loadCertificate() - .then((certificate) => { - this.setState({ certificate }); - }) - .catch(() => { - this.setState({ - fetchCertificateError: - "Failed to load certificate. Is Fleet app URL configured properly?", - }); - }); - } - - onFetchCertificate = (evt) => { - evt.preventDefault(); - - const { certificate } = this.state; - - const filename = "fleet.pem"; - const file = new global.window.File([certificate], filename, { - type: "application/x-pem-file", - }); - - FileSaver.saveAs(file); - - return false; - }; - - onCopyRunCommand = (evt) => { - evt.preventDefault(); - - stringToClipboard("osqueryd --flagfile=flagfile.txt --verbose") - .then(() => this.setState({ copyMessage: "Copied!" })) - .catch(() => this.setState({ copyMessage: "Copy failed" })); - - // Clear message after 1 second - setTimeout(() => this.setState({ copyMessage: "" }), 1000); - - return false; - }; - - // if isGlobalAdmin or isGlobalMaintainer, we include a "No team" option and reveal globalSecrets - // if not, we pull secrets for the user's teams from the teamsSecrets - onChangeSelectTeam = (teamId) => { - const { globalSecrets } = this.state; - const { currentUserTeams, teamSecrets } = this; - if (teamId === "no-team") { - this.setState({ selectedTeam: { id: NO_TEAM_OPTION.value } }); - this.setState({ selectedEnrollSecrets: globalSecrets || [] }); - } else { - const selectedTeam = currentUserTeams.find((team) => team.id === teamId); - const selectedEnrollSecrets = - teamSecrets.find((e) => e.id === selectedTeam.id)?.secrets || ""; - this.setState({ selectedTeam }); - this.setState({ - selectedEnrollSecrets, - }); - } - }; - - getSelectedEnrollSecrets = (selectedTeam) => { - if (selectedTeam.id === NO_TEAM_OPTION.value) { - return this.state.globalSecrets; - } - return ( - this.teamSecrets.find((e) => e.id === selectedTeam.id)?.secrets || "" - ); - }; - - createTeamDropdownOptions = (currentUserTeams = []) => { - const teamOptions = currentUserTeams.map((team) => { - return { - value: team.id, - label: team.name, - }; - }); - return this.userRole.isAnyTeamMaintainer - ? teamOptions - : [NO_TEAM_OPTION, ...teamOptions]; - }; - - renderRunCommandLabel = () => { - const { copyMessage } = this.state; - const { onCopyRunCommand } = this; - - return ( - - - {copyMessage && {`${copyMessage} `}} - - - - ); - }; - - render() { - const { config, onReturnToApp } = this.props; - const { fetchCertificateError, selectedTeam, globalSecrets } = this.state; - const { - createTeamDropdownOptions, - currentUserTeams, - getSelectedEnrollSecrets, - onChangeSelectTeam, - renderRunCommandLabel, - } = this; - - const isPremiumTier = permissionUtils.isPremiumTier(config); - - let tlsHostname = config.server_url; - try { - const serverUrl = new URL(config.server_url); - tlsHostname = serverUrl.hostname; - if (serverUrl.port) { - tlsHostname += `:${serverUrl.port}`; - } - } catch (e) { - if (!(e instanceof TypeError)) { - throw e; - } - } - - const flagfileContent = `# Server ---tls_hostname=${tlsHostname} ---tls_server_certs=fleet.pem -# Enrollment ---host_identifier=instance ---enroll_secret_path=secret.txt ---enroll_tls_endpoint=/api/v1/osquery/enroll -# Configuration ---config_plugin=tls ---config_tls_endpoint=/api/v1/osquery/config ---config_refresh=10 -# Live query ---disable_distributed=false ---distributed_plugin=tls ---distributed_interval=10 ---distributed_tls_max_attempts=3 ---distributed_tls_read_endpoint=/api/v1/osquery/distributed/read ---distributed_tls_write_endpoint=/api/v1/osquery/distributed/write -# Logging ---logger_plugin=tls ---logger_tls_endpoint=/api/v1/osquery/log ---logger_tls_period=10 -# File carving ---disable_carver=false ---carver_start_endpoint=/api/v1/osquery/carve/begin ---carver_continue_endpoint=/api/v1/osquery/carve/block ---carver_block_size=2000000`; - - const onDownloadFlagfile = (evt) => { - evt.preventDefault(); - - const filename = "flagfile.txt"; - const file = new global.window.File([flagfileContent], filename); - - FileSaver.saveAs(file); - - return false; - }; - - return ( -
-
- -
    -
  1. -

    - 1Enroll - secret -

    -

    - Osquery uses an enroll secret to authenticate with the Fleet - server. -

    -
    - {isPremiumTier && currentUserTeams ? ( - - ) : null} - {isPremiumTier && selectedTeam && ( - - )} - {!isPremiumTier && ( - - )} -
    -
  2. -
  3. -

    - 2Server - certificate -

    -

    - Provide the TLS certificate used by the Fleet server to enable - secure connections from osquery: -

    -

    - {fetchCertificateError ? ( - - {fetchCertificateError} - - ) : ( - - Download - download icon - - )} -

    -
  4. -
  5. -

    - 3Flagfile -

    -

    - If using the enroll secret and server certificate downloaded - above, use the generated flagfile. In some configurations, - modifications may need to be made: -

    -

    - - Download - download icon - -

    -
  6. -
  7. -

    - 4Run - osquery -

    -

    - Run osquery from the directory containing the above files (may - require sudo or Run as Administrator privileges): -

    -
    - -
    -
  8. -
-
- -
- -
-
- ); - } -} - -export default AddHostModal; diff --git a/frontend/pages/hosts/ManageHostsPage/components/AddHostModal/_styles.scss b/frontend/pages/hosts/ManageHostsPage/components/AddHostModal/_styles.scss deleted file mode 100644 index 8b9312ecf7..0000000000 --- a/frontend/pages/hosts/ManageHostsPage/components/AddHostModal/_styles.scss +++ /dev/null @@ -1,171 +0,0 @@ -.add-host-modal { - &__manual-install-header { - position: relative; - z-index: 2; - - h2 { - font-size: 18px; - font-weight: $bold; - line-height: 1.33; - letter-spacing: -0.6px; - color: #48c586; - margin: 0; - } - - h3 { - font-size: 15px; - font-weight: $regular; - line-height: 1.6; - letter-spacing: normal; - color: rgba(32, 37, 50, 0.48); - margin: 0; - } - - .fleeticon { - float: left; - font-size: 44px; - color: #48c586; - margin-right: 15px; - } - } - - &__manual-install-content { - h4 { - font-size: $small; - font-weight: $bold; - margin: $pad-xlarge 0 0; - - .fleeticon { - margin-left: 5px; - font-size: 18px; - } - } - - p { - font-size: $x-small; - font-weight: $regular; - line-height: 20px; - letter-spacing: normal; - color: $core-fleet-black; - margin: 8px 0 0 44px; - } - - a { - font-size: $x-small; - color: $core-vibrant-blue; - font-weight: $bold; - text-decoration: none; - - img { - width: 12px; - height: 12px; - margin-left: 7px; - } - } - } - - &__documentation-link { - h4 { - margin: 0; - } - } - - &__install-steps { - margin: 0; - padding: 0; - list-style: none; - } - - &__step-number { - display: inline-flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - border-radius: 100px; - border: 2px solid $core-vibrant-blue; - color: $core-vibrant-blue; - font-weight: $bold; - text-align: center; - margin-right: $pad-medium; - } - - &__button-wrap { - display: flex; - justify-content: flex-end; - margin: $pad-large 0 0; - } - - &__download-cert { - text-align: center; - padding-top: 15px; - - span { - display: block; - } - } - - &__reveal-secret { - float: right; - text-decoration: none; - } - - &__secret-wrapper { - position: relative; - margin: $pad-medium 0 0 44px; - } - - &__team-dropdown-wrapper { - .form-field__label { - font-weight: $bold; - } - } - - &__team-dropdown-wrapper { - .form-field__label { - font-weight: $bold; - } - } - - pre, - code { - background-color: $ui-off-white; - color: $core-fleet-blue; - border: 1px solid $ui-fleet-blue-15; - border-radius: 4px; - padding: 7px $pad-medium; - margin: $pad-large 0 0 44px; - } - - &__error { - color: $ui-error; - } - - &__run-osquery-input { - margin-left: 44px; - } - - &__run-osquery-copy-icon { - color: $core-vibrant-blue; - margin-left: $pad-small; - margin-right: $pad-xxlarge; - } - - .buttons { - display: flex; - align-items: center; - position: absolute; - right: 16px; - transform: translateY(80%); - height: 16px; - - span { - font-weight: $regular; - } - - img { - width: 16px; - height: 16px; - } - } -} diff --git a/frontend/pages/hosts/ManageHostsPage/components/AddHostModal/index.js b/frontend/pages/hosts/ManageHostsPage/components/AddHostModal/index.js deleted file mode 100644 index 999c5a522d..0000000000 --- a/frontend/pages/hosts/ManageHostsPage/components/AddHostModal/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./AddHostModal"; diff --git a/frontend/pages/hosts/ManageHostsPage/components/NoHosts/NoHosts.tsx b/frontend/pages/hosts/ManageHostsPage/components/NoHosts/NoHosts.tsx index d5ab59f5bd..1a125d3b92 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/NoHosts/NoHosts.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/NoHosts/NoHosts.tsx @@ -8,26 +8,28 @@ import { ITeam } from "interfaces/team"; import RoboDogImage from "../../../../../../assets/images/robo-dog-176x144@2x.png"; interface INoHostsProps { - setShowAddHostModal: React.Dispatch>; + toggleGenerateInstallerModal: () => void; } const baseClass = "no-hosts"; -const NoHosts = ({ setShowAddHostModal }: INoHostsProps): JSX.Element => { +const NoHosts = ({ + toggleGenerateInstallerModal, +}: INoHostsProps): JSX.Element => { return (
No Hosts
-

Add your hosts to Fleet

-

Add your laptops and servers to securely monitor them.

+

Add your devices to Fleet

+

Generate an installer to add your own devices.