mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Add host modal: Select team dropdown (#1740)
* Fix Add host modal dropdown for team maintainer * Update Cypress tests * Set default values for add host team dropdown
This commit is contained in:
parent
bd38cc1fe9
commit
8b4c6a1dd7
2
changes/1694-team-maintainer-add-host
Normal file
2
changes/1694-team-maintainer-add-host
Normal file
@ -0,0 +1,2 @@
|
||||
Add user role validations to select team dropdown in "Add host" modal
|
||||
Remove Redux enrollSecret state from ManageHostsPage and instead handle as local state in "Add host" modal
|
@ -23,13 +23,14 @@ describe("Hosts page", () => {
|
||||
|
||||
cy.contains("button", /add new host/i).click();
|
||||
|
||||
cy.get('a[href*="showSecret"]').click();
|
||||
cy.contains("a", /download/i)
|
||||
.first()
|
||||
.click();
|
||||
|
||||
cy.get('a[href*="showSecret"]').click();
|
||||
|
||||
// Assert enroll secret downloaded matches the one displayed
|
||||
// NOTE: This test often fails when the Cypress downloads folder was not cleared properly
|
||||
// before each test run (seems to be related to issues with Cypress trashAssetsBeforeRun)
|
||||
cy.readFile(path.join(Cypress.config("downloadsFolder"), "secret.txt"), {
|
||||
timeout: 5000,
|
||||
}).then((contents) => {
|
||||
|
@ -30,7 +30,7 @@ describe("Basic tier - Admin user", () => {
|
||||
// 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(".add-host-modal__team-dropdown-wrapper").within(() => {
|
||||
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");
|
||||
|
@ -137,16 +137,13 @@ describe("Basic tier - Team observer/maintainer user", () => {
|
||||
cy.findByText(/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").within(() => {
|
||||
cy.findByText(/select a team for this new host/i).should("exist");
|
||||
cy.get(".Select").within(() => {
|
||||
cy.findByText(/select a team/i).click();
|
||||
cy.findByText(/no team/i).should("exist");
|
||||
// cy.findByText(/apples/i).should("exist");
|
||||
// cy.findByText(/oranges/i).should("not exist");
|
||||
// ^ TODO: Team maintainer has access to only their teams, team observer does not have access
|
||||
});
|
||||
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");
|
||||
});
|
||||
|
||||
cy.findByRole("button", { name: /done/i }).click();
|
||||
|
||||
// On the Host details page, they should…
|
||||
|
@ -17,7 +17,6 @@ import teamInterface from "interfaces/team";
|
||||
import userInterface from "interfaces/user";
|
||||
import osqueryTableInterface from "interfaces/osquery_table";
|
||||
import statusLabelsInterface from "interfaces/status_labels";
|
||||
import enrollSecretInterface from "interfaces/enroll_secret";
|
||||
import { selectOsqueryTable } from "redux/nodes/components/QueryPages/actions";
|
||||
import { renderFlash } from "redux/nodes/notifications/actions";
|
||||
import labelActions from "redux/nodes/entities/labels/actions";
|
||||
@ -103,7 +102,6 @@ export class ManageHostsPage extends PureComponent {
|
||||
routeParams: PropTypes.objectOf(
|
||||
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
||||
),
|
||||
enrollSecret: enrollSecretInterface,
|
||||
selectedFilters: PropTypes.arrayOf(PropTypes.string),
|
||||
selectedLabel: labelInterface,
|
||||
selectedTeam: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
@ -223,12 +221,6 @@ export class ManageHostsPage extends PureComponent {
|
||||
toggleAddHostModal();
|
||||
};
|
||||
|
||||
// The onChange method below is for the dropdown used in modals
|
||||
onChangeTeam = (team) => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(teamActions.getEnrollSecrets(team));
|
||||
};
|
||||
|
||||
// NOTE: this is called once on the initial rendering. The initial render of
|
||||
// the TableContainer child component will call this handler.
|
||||
onTableQueryChange = async ({
|
||||
@ -707,7 +699,7 @@ export class ManageHostsPage extends PureComponent {
|
||||
renderAddHostModal = () => {
|
||||
const { toggleAddHostModal, onChangeTeam } = this;
|
||||
const { showAddHostModal } = this.state;
|
||||
const { enrollSecret, config, canAddNewHosts, teams } = this.props;
|
||||
const { config, currentUser, canAddNewHosts, teams } = this.props;
|
||||
|
||||
if (!canAddNewHosts || !showAddHostModal) {
|
||||
return null;
|
||||
@ -723,8 +715,8 @@ export class ManageHostsPage extends PureComponent {
|
||||
teams={teams}
|
||||
onChangeTeam={onChangeTeam}
|
||||
onReturnToApp={toggleAddHostModal}
|
||||
enrollSecret={enrollSecret}
|
||||
config={config}
|
||||
currentUser={currentUser}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
@ -1056,7 +1048,6 @@ const mapStateToProps = (state, ownProps) => {
|
||||
|
||||
const { selectedOsqueryTable } = state.components.QueryPages;
|
||||
const { errors: labelErrors, loading: loadingLabels } = state.entities.labels;
|
||||
const enrollSecret = state.app.enrollSecret;
|
||||
const config = state.app.config;
|
||||
|
||||
const { loading: loadingHosts } = state.entities.hosts;
|
||||
@ -1090,7 +1081,6 @@ const mapStateToProps = (state, ownProps) => {
|
||||
labelErrors,
|
||||
labels,
|
||||
loadingLabels,
|
||||
enrollSecret,
|
||||
selectedLabel,
|
||||
selectedOsqueryTable,
|
||||
statusLabels,
|
||||
|
@ -6,7 +6,7 @@ import Fleet from "fleet";
|
||||
import Button from "components/buttons/Button";
|
||||
import configInterface from "interfaces/config";
|
||||
import teamInterface from "interfaces/team";
|
||||
import enrollSecretInterface from "interfaces/enroll_secret";
|
||||
import userInterface from "interfaces/user";
|
||||
import permissionUtils from "utilities/permissions";
|
||||
import EnrollSecretTable from "components/config/EnrollSecretTable";
|
||||
import FleetIcon from "components/icons/FleetIcon";
|
||||
@ -23,18 +23,62 @@ const NO_TEAM_OPTION = {
|
||||
class AddHostModal extends Component {
|
||||
static propTypes = {
|
||||
teams: PropTypes.arrayOf(teamInterface),
|
||||
onChangeTeam: PropTypes.func,
|
||||
onReturnToApp: PropTypes.func,
|
||||
enrollSecret: enrollSecretInterface,
|
||||
config: configInterface,
|
||||
currentUser: userInterface,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { fetchCertificateError: undefined, selectedTeam: null };
|
||||
|
||||
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 = 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: [],
|
||||
};
|
||||
}
|
||||
|
||||
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) => {
|
||||
@ -63,32 +107,57 @@ class AddHostModal extends Component {
|
||||
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 { teams, onChangeTeam } = this.props;
|
||||
const { globalSecrets } = this.state;
|
||||
const { currentUserTeams, teamSecrets } = this;
|
||||
if (teamId === "no-team") {
|
||||
onChangeTeam(null);
|
||||
this.setState({ selectedTeam: { id: NO_TEAM_OPTION.value } });
|
||||
this.setState({ selectedEnrollSecrets: globalSecrets || [] });
|
||||
} else {
|
||||
const selectedTeam = teams.find((team) => team.id === teamId);
|
||||
onChangeTeam(selectedTeam);
|
||||
const selectedTeam = currentUserTeams.find((team) => team.id === teamId);
|
||||
const selectedEnrollSecrets =
|
||||
teamSecrets.find((e) => e.id === selectedTeam.id)?.secrets || "";
|
||||
this.setState({ selectedTeam });
|
||||
this.setState({
|
||||
selectedEnrollSecrets,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
createTeamDropdownOptions = (teams) => {
|
||||
const teamOptions = teams.map((team) => {
|
||||
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 [NO_TEAM_OPTION, ...teamOptions];
|
||||
return this.userRole.isAnyTeamMaintainer
|
||||
? teamOptions
|
||||
: [NO_TEAM_OPTION, ...teamOptions];
|
||||
};
|
||||
|
||||
render() {
|
||||
const { config, onReturnToApp, enrollSecret, teams } = this.props;
|
||||
const { fetchCertificateError, selectedTeam } = this.state;
|
||||
const { createTeamDropdownOptions, onChangeSelectTeam } = this;
|
||||
const { config, onReturnToApp } = this.props;
|
||||
const { fetchCertificateError, selectedTeam, globalSecrets } = this.state;
|
||||
const {
|
||||
createTeamDropdownOptions,
|
||||
currentUserTeams,
|
||||
getSelectedEnrollSecrets,
|
||||
onChangeSelectTeam,
|
||||
} = this;
|
||||
|
||||
const isBasicTier = permissionUtils.isBasicTier(config);
|
||||
|
||||
let tlsHostname = config.server_url;
|
||||
try {
|
||||
@ -172,18 +241,23 @@ class AddHostModal extends Component {
|
||||
server.
|
||||
</p>
|
||||
<div className={`${baseClass}__secret-wrapper`}>
|
||||
{permissionUtils.isBasicTier(config) ? (
|
||||
{isBasicTier ? (
|
||||
<Dropdown
|
||||
wrapperClassName={`${baseClass}__team-dropdown-wrapper`}
|
||||
label={"Select a team for this new host:"}
|
||||
value={selectedTeam && selectedTeam.id}
|
||||
options={createTeamDropdownOptions(teams)}
|
||||
options={createTeamDropdownOptions(currentUserTeams)}
|
||||
onChange={onChangeSelectTeam}
|
||||
placeholder={"Select a team"}
|
||||
searchable={false}
|
||||
/>
|
||||
) : null}
|
||||
<EnrollSecretTable secrets={enrollSecret} />
|
||||
{isBasicTier && selectedTeam && (
|
||||
<EnrollSecretTable
|
||||
secrets={getSelectedEnrollSecrets(selectedTeam)}
|
||||
/>
|
||||
)}
|
||||
{!isBasicTier && <EnrollSecretTable secrets={globalSecrets} />}
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
|
Loading…
Reference in New Issue
Block a user