mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 17:08:53 +00:00
Schedule Page: Team maintainer can modify their team's schedule (#2243)
* Change permissions to schedule UI to include team maintainers * Update e2e tests
This commit is contained in:
parent
e3ff9e4274
commit
f397b77c1a
11 changed files with 276 additions and 145 deletions
1
changes/issue-2058-team-maintainer-can-schedule-team
Normal file
1
changes/issue-2058-team-maintainer-can-schedule-team
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Team maintainer now has permissions to modify their team's schedule
|
||||
|
|
@ -38,6 +38,7 @@ describe(
|
|||
|
||||
cy.findByText(/query all/i).click();
|
||||
|
||||
cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
cy.findByText(/run query/i).should("exist");
|
||||
|
||||
cy.get(".ace_scroller")
|
||||
|
|
@ -48,79 +49,7 @@ describe(
|
|||
|
||||
cy.findByText(/query updated/i).should("be.visible");
|
||||
|
||||
// // Start e2e test for schedules
|
||||
// cy.visit("/schedule/manage");
|
||||
|
||||
// cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
|
||||
// cy.findByRole("button", { name: /schedule a query/i }).click();
|
||||
|
||||
// cy.findByText(/select query/i).click();
|
||||
|
||||
// cy.findByText(/query all window crashes/i).click();
|
||||
|
||||
// cy.get(
|
||||
// ".schedule-editor-modal__form-field--frequency > .dropdown__select"
|
||||
// ).click();
|
||||
|
||||
// cy.findByText(/every week/i).click();
|
||||
|
||||
// cy.findByText(/show advanced options/i).click();
|
||||
|
||||
// cy.get(
|
||||
// ".schedule-editor-modal__form-field--logging > .dropdown__select"
|
||||
// ).click();
|
||||
|
||||
// cy.findByText(/ignore removals/i).click();
|
||||
|
||||
// cy.get(".schedule-editor-modal__form-field--shard > .input-field")
|
||||
// .click()
|
||||
// .type("50");
|
||||
|
||||
// cy.get(".schedule-editor-modal__btn-wrap")
|
||||
// .contains("button", /schedule/i)
|
||||
// .click();
|
||||
|
||||
// cy.visit("/schedule/manage");
|
||||
|
||||
// cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
// cy.findByText(/query all window crashes/i).should("exist");
|
||||
|
||||
// cy.findByText(/actions/i).click();
|
||||
// cy.findByText(/edit/i).click();
|
||||
|
||||
// cy.get(
|
||||
// ".schedule-editor-modal__form-field--frequency > .dropdown__select"
|
||||
// ).click();
|
||||
|
||||
// cy.findByText(/every 6 hours/i).click();
|
||||
|
||||
// cy.findByText(/show advanced options/i).click();
|
||||
|
||||
// cy.findByText(/ignore removals/i).click();
|
||||
// cy.findByText(/snapshot/i).click();
|
||||
|
||||
// cy.get(".schedule-editor-modal__form-field--shard > .input-field")
|
||||
// .click()
|
||||
// .type("{selectall}{backspace}10");
|
||||
|
||||
// cy.get(".schedule-editor-modal__btn-wrap")
|
||||
// .contains("button", /schedule/i)
|
||||
// .click();
|
||||
|
||||
// cy.visit("/schedule/manage");
|
||||
|
||||
// cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
// cy.findByText(/actions/i).click();
|
||||
// cy.findByText(/remove/i).click();
|
||||
|
||||
// cy.get(".remove-scheduled-query-modal__btn-wrap")
|
||||
// .contains("button", /remove/i)
|
||||
// .click();
|
||||
|
||||
// cy.findByText(/query all window crashes/i).should("not.exist");
|
||||
|
||||
// // End e2e test for schedules
|
||||
// E2e Test for schedules moved to premium/admin
|
||||
|
||||
cy.visit("/queries/manage");
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ describe(
|
|||
cy.visit("/");
|
||||
|
||||
// Ensure page is loaded
|
||||
cy.wait(3000); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
// cy.wait(3000); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
cy.contains("All hosts");
|
||||
|
||||
// Settings restrictions
|
||||
|
|
|
|||
|
|
@ -78,12 +78,143 @@ describe(
|
|||
// cy.findByText(/teams/i).should("exist");
|
||||
// });
|
||||
|
||||
cy.visit("/queries/manage");
|
||||
|
||||
cy.findByRole("button", { name: /create new query/i }).click();
|
||||
|
||||
// Using class selector because third party element doesn't work with Cypress Testing Selector Library
|
||||
cy.get(".ace_scroller")
|
||||
.click({ force: true })
|
||||
.type("{selectall}SELECT * FROM windows_crashes;");
|
||||
|
||||
cy.findByRole("button", { name: /save/i }).click();
|
||||
|
||||
// save modal
|
||||
cy.get(".query-form__query-save-modal-name")
|
||||
.click()
|
||||
.type("Query all window crashes");
|
||||
|
||||
cy.get(".query-form__query-save-modal-description")
|
||||
.click()
|
||||
.type("See all window crashes");
|
||||
|
||||
cy.findByRole("button", { name: /save query/i }).click();
|
||||
|
||||
cy.findByText(/query created/i).should("exist");
|
||||
cy.findByText(/back to queries/i).should("exist");
|
||||
cy.visit("/queries/manage");
|
||||
|
||||
cy.findByText(/query all/i).click();
|
||||
|
||||
cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
cy.findByText(/run query/i).should("exist");
|
||||
|
||||
cy.get(".ace_scroller")
|
||||
.click({ force: true })
|
||||
.type("{selectall}SELECT datetime, username FROM windows_crashes;");
|
||||
|
||||
cy.findByRole("button", { name: /^Save$/ }).click();
|
||||
|
||||
cy.findByText(/query updated/i).should("be.visible");
|
||||
|
||||
// Start e2e test for schedules
|
||||
cy.visit("/schedule/manage");
|
||||
|
||||
cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
|
||||
cy.findByRole("button", { name: /schedule a query/i }).click();
|
||||
|
||||
cy.findByText(/select query/i).click();
|
||||
|
||||
cy.findByText(/query all window crashes/i).click();
|
||||
|
||||
cy.get(
|
||||
".schedule-editor-modal__form-field--frequency > .dropdown__select"
|
||||
).click();
|
||||
|
||||
cy.findByText(/every week/i).click();
|
||||
|
||||
cy.findByText(/show advanced options/i).click();
|
||||
|
||||
cy.get(
|
||||
".schedule-editor-modal__form-field--logging > .dropdown__select"
|
||||
).click();
|
||||
|
||||
cy.findByText(/ignore removals/i).click();
|
||||
|
||||
cy.get(".schedule-editor-modal__form-field--shard > .input-field")
|
||||
.click()
|
||||
.type("50");
|
||||
|
||||
cy.get(".schedule-editor-modal__btn-wrap")
|
||||
.contains("button", /schedule/i)
|
||||
.click();
|
||||
|
||||
cy.visit("/schedule/manage");
|
||||
|
||||
cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
cy.findByText(/query all window crashes/i).should("exist");
|
||||
|
||||
cy.findByText(/actions/i).click();
|
||||
cy.findByText(/edit/i).click();
|
||||
|
||||
cy.get(
|
||||
".schedule-editor-modal__form-field--frequency > .dropdown__select"
|
||||
).click();
|
||||
|
||||
cy.findByText(/every 6 hours/i).click();
|
||||
|
||||
cy.findByText(/show advanced options/i).click();
|
||||
|
||||
cy.findByText(/ignore removals/i).click();
|
||||
cy.findByText(/snapshot/i).click();
|
||||
|
||||
cy.get(".schedule-editor-modal__form-field--shard > .input-field")
|
||||
.click()
|
||||
.type("{selectall}{backspace}10");
|
||||
|
||||
cy.get(".schedule-editor-modal__btn-wrap")
|
||||
.contains("button", /schedule/i)
|
||||
.click();
|
||||
|
||||
cy.visit("/schedule/manage");
|
||||
|
||||
cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting
|
||||
cy.findByText(/actions/i).click();
|
||||
cy.findByText(/remove/i).click();
|
||||
|
||||
cy.get(".remove-scheduled-query-modal__btn-wrap")
|
||||
.contains("button", /remove/i)
|
||||
.click();
|
||||
|
||||
cy.findByText(/query all window crashes/i).should("not.exist");
|
||||
|
||||
// End e2e test for schedules
|
||||
|
||||
cy.visit("/queries/manage");
|
||||
|
||||
cy.findByText(/query all window crashes/i)
|
||||
.parent()
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.get(".fleet-checkbox__input").check({ force: true });
|
||||
});
|
||||
|
||||
cy.findByRole("button", { name: /delete/i }).click();
|
||||
|
||||
// Can't figure out how attach findByRole onto modal button
|
||||
// Can't use findByText because delete button under modal
|
||||
cy.get(".remove-query-modal")
|
||||
.contains("button", /delete/i)
|
||||
.click();
|
||||
|
||||
cy.findByText(/successfully removed query/i).should("be.visible");
|
||||
|
||||
cy.findByText(/query all/i).should("not.exist");
|
||||
|
||||
// On the Packs pages (manage, new, and edit), they should…
|
||||
// ^^General admin functionality for packs page is being tested in app/packflow.spec.ts
|
||||
|
||||
// On the Schedule pages (manage, new, and edit), they should…
|
||||
// ^^General admin functionality for packs page is being tested in app/queryflow.spec.ts
|
||||
|
||||
// On the Settings pages, they should…
|
||||
// See the “Teams” navigation item and access the Settings - Teams page
|
||||
cy.visit("/settings/organization");
|
||||
|
|
|
|||
|
|
@ -34,13 +34,11 @@ describe(
|
|||
|
||||
// Nav restrictions
|
||||
cy.findByText(/settings/i).should("not.exist");
|
||||
cy.findByText(/schedule/i).should("not.exist");
|
||||
cy.findByText(/schedule/i).should("exist");
|
||||
cy.visit("/settings/organization");
|
||||
cy.findByText(/you do not have permissions/i).should("exist");
|
||||
cy.visit("/packs/manage");
|
||||
cy.findByText(/you do not have permissions/i).should("exist");
|
||||
cy.visit("/schedule/manage");
|
||||
cy.findByText(/you do not have permissions/i).should("exist");
|
||||
|
||||
// NOT see and select "add label"
|
||||
cy.findByRole("button", { name: /new label/i }).should("not.exist");
|
||||
|
|
@ -120,7 +118,7 @@ describe(
|
|||
cy.get("nav").within(() => {
|
||||
cy.findByText(/hosts/i).should("exist");
|
||||
cy.findByText(/queries/i).should("exist");
|
||||
cy.findByText(/schedule/i).should("not.exist");
|
||||
cy.findByText(/schedule/i).should("exist");
|
||||
cy.findByText(/settings/i).should("not.exist");
|
||||
});
|
||||
|
||||
|
|
@ -140,7 +138,7 @@ describe(
|
|||
// ^^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.findByText(/add new host/i).click();
|
||||
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();
|
||||
|
|
@ -191,6 +189,15 @@ describe(
|
|||
// });
|
||||
// });
|
||||
|
||||
// On the Schedule page, they should
|
||||
// See Oranges (team they maintain) only, not able to reach packs, able to schedule a query
|
||||
cy.visit("/schedule/manage");
|
||||
cy.findByText(/oranges/i).click();
|
||||
cy.findByText(/apples/i).should("not.exist");
|
||||
cy.findByText(/advanced/i).should("not.exist");
|
||||
cy.findByRole("button", { name: /schedule a query/i }).click();
|
||||
// TODO: Write e2e test for team maintainer to schedule a query
|
||||
|
||||
// On the Profile page, they should…
|
||||
// See 2 Teams in the Team section and Various in the Role section
|
||||
cy.visit("/profile");
|
||||
|
|
|
|||
|
|
@ -75,12 +75,15 @@ The following table depicts various permissions levels in a team.
|
|||
| Filter hosts assigned to team using labels | ✅ | ✅ |
|
||||
| Target hosts assigned to team using labels | ✅ | ✅ |
|
||||
| Run saved queries as live queries on hosts assigned to team | ✅ | ✅ |
|
||||
| Create new team policies | | ✅ |
|
||||
| Delete team policies | | ✅ |
|
||||
| Run custom queries as live queries on hosts assigned to team | | ✅ |
|
||||
| Enroll hosts to member team | | ✅ |
|
||||
| Delete hosts belonging to member team | | ✅ |
|
||||
| Edit queries they authored | | ✅ |
|
||||
| Delete queries they authored | | ✅ |
|
||||
| Browse global policies | | ✅ |
|
||||
| Create new team schedules | | ✅ |
|
||||
| Delete team schedules | | ✅ |
|
||||
| Browse global schedules | | ✅ |
|
||||
| Create new team policies | | ✅ |
|
||||
| Delete team policies | | ✅ |
|
||||
| Browse global policies | | ✅ |
|
||||
|
||||
|
|
|
|||
|
|
@ -48,34 +48,6 @@ class SiteTopNav extends Component {
|
|||
|
||||
const iconClasses = classnames([`${navItemBaseClass}__icon`]);
|
||||
|
||||
let icon = (
|
||||
<img src={HostsIcon} alt={`${iconName} icon`} className={iconClasses} />
|
||||
);
|
||||
if (iconName === "queries")
|
||||
icon = (
|
||||
<img
|
||||
src={QueriesIcon}
|
||||
alt={`${iconName} icon`}
|
||||
className={iconClasses}
|
||||
/>
|
||||
);
|
||||
else if (iconName === "packs")
|
||||
icon = (
|
||||
<img src={PacksIcon} alt={`${iconName} icon`} className={iconClasses} />
|
||||
);
|
||||
else if (iconName === "policies")
|
||||
icon = (
|
||||
<img
|
||||
src={PoliciesIcon}
|
||||
alt={`${iconName} icon`}
|
||||
className={iconClasses}
|
||||
/>
|
||||
);
|
||||
else if (iconName === "settings")
|
||||
icon = (
|
||||
<img src={AdminIcon} alt={`${iconName} icon`} className={iconClasses} />
|
||||
);
|
||||
|
||||
if (iconName === "logo") {
|
||||
return (
|
||||
<li className={navItemClasses} key={`nav-item-${name}`}>
|
||||
|
|
@ -88,6 +60,26 @@ class SiteTopNav extends Component {
|
|||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
const iconImage = () => {
|
||||
switch (iconName) {
|
||||
case "hosts":
|
||||
return HostsIcon;
|
||||
case "queries":
|
||||
return QueriesIcon;
|
||||
case "packs":
|
||||
return PacksIcon;
|
||||
case "policies":
|
||||
return PoliciesIcon;
|
||||
default:
|
||||
return AdminIcon;
|
||||
}
|
||||
};
|
||||
|
||||
const icon = (
|
||||
<img src={iconImage()} alt={`${iconName} icon`} className={iconClasses} />
|
||||
);
|
||||
|
||||
return (
|
||||
<li className={navItemClasses} key={`nav-item-${name}`}>
|
||||
<a
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export default (currentUser) => {
|
|||
},
|
||||
];
|
||||
|
||||
const globalMaintainerNavItems = [
|
||||
const teamMaintainerNavItems = [
|
||||
{
|
||||
icon: "packs",
|
||||
name: "Schedule",
|
||||
|
|
@ -55,6 +55,9 @@ export default (currentUser) => {
|
|||
pathname: PATHS.MANAGE_SCHEDULE,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const globalMaintainerNavItems = [
|
||||
{
|
||||
icon: "policies",
|
||||
name: "Policies",
|
||||
|
|
@ -67,11 +70,24 @@ export default (currentUser) => {
|
|||
];
|
||||
|
||||
if (permissionUtils.isGlobalAdmin(currentUser)) {
|
||||
return [...userNavItems, ...globalMaintainerNavItems, ...adminNavItems];
|
||||
return [
|
||||
...userNavItems,
|
||||
...teamMaintainerNavItems,
|
||||
...globalMaintainerNavItems,
|
||||
...adminNavItems,
|
||||
];
|
||||
}
|
||||
|
||||
if (permissionUtils.isGlobalMaintainer(currentUser)) {
|
||||
return [...userNavItems, ...globalMaintainerNavItems];
|
||||
return [
|
||||
...userNavItems,
|
||||
...teamMaintainerNavItems,
|
||||
...globalMaintainerNavItems,
|
||||
];
|
||||
}
|
||||
|
||||
if (permissionUtils.isAnyTeamMaintainer(currentUser)) {
|
||||
return [...userNavItems, ...teamMaintainerNavItems];
|
||||
}
|
||||
|
||||
return userNavItems;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { push } from "react-router-redux";
|
|||
import deepDifference from "utilities/deep_difference";
|
||||
import { IConfig } from "interfaces/config";
|
||||
import { IQuery } from "interfaces/query";
|
||||
import { IUser } from "interfaces/user";
|
||||
import { ITeam } from "interfaces/team";
|
||||
import { IGlobalScheduledQuery } from "interfaces/global_scheduled_query";
|
||||
import { ITeamScheduledQuery } from "interfaces/team_scheduled_query";
|
||||
|
|
@ -20,6 +21,7 @@ import queryActions from "redux/nodes/entities/queries/actions";
|
|||
import teamActions from "redux/nodes/entities/teams/actions";
|
||||
// @ts-ignore
|
||||
import { renderFlash } from "redux/nodes/notifications/actions";
|
||||
import permissionUtils from "utilities/permissions";
|
||||
|
||||
import paths from "router/paths";
|
||||
import Button from "components/buttons/Button";
|
||||
|
|
@ -41,7 +43,8 @@ const renderTable = (
|
|||
allScheduledQueriesList: IGlobalScheduledQuery[] | ITeamScheduledQuery[],
|
||||
allScheduledQueriesError: { name: string; reason: string }[],
|
||||
toggleScheduleEditorModal: () => void,
|
||||
teamId: number
|
||||
teamId: number,
|
||||
isTeamMaintainer: boolean
|
||||
): JSX.Element => {
|
||||
if (Object.keys(allScheduledQueriesError).length !== 0) {
|
||||
return <TableDataError />;
|
||||
|
|
@ -54,12 +57,14 @@ const renderTable = (
|
|||
allScheduledQueriesList={allScheduledQueriesList}
|
||||
toggleScheduleEditorModal={toggleScheduleEditorModal}
|
||||
teamId={teamId}
|
||||
isTeamMaintainer={isTeamMaintainer}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderAllTeamsTable = (
|
||||
teamId: number,
|
||||
isTeamMaintainer: boolean,
|
||||
allTeamsScheduledQueriesList: IGlobalScheduledQuery[],
|
||||
allTeamsScheduledQueriesError: { name: string; reason: string }[]
|
||||
): JSX.Element => {
|
||||
|
|
@ -73,6 +78,7 @@ const renderAllTeamsTable = (
|
|||
inheritedQueries
|
||||
allScheduledQueriesList={allTeamsScheduledQueriesList}
|
||||
teamId={teamId}
|
||||
isTeamMaintainer={isTeamMaintainer}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -87,6 +93,9 @@ interface IRootState {
|
|||
app: {
|
||||
config: IConfig;
|
||||
};
|
||||
auth: {
|
||||
user: IUser;
|
||||
};
|
||||
entities: {
|
||||
global_scheduled_queries: {
|
||||
isLoading: boolean;
|
||||
|
|
@ -149,6 +158,16 @@ const ManageSchedulePage = (props: ITeamSchedulesPageProps): JSX.Element => {
|
|||
return state.app.config.tier === "premium";
|
||||
});
|
||||
|
||||
const user = useSelector(
|
||||
(state: IRootState): IUser => {
|
||||
return state.auth.user;
|
||||
}
|
||||
);
|
||||
|
||||
const isTeamMaintainer = useSelector((state: IRootState): boolean => {
|
||||
return permissionUtils.isAnyTeamMaintainer(state.auth.user);
|
||||
});
|
||||
|
||||
const allQueries = useSelector((state: IRootState) => state.entities.queries);
|
||||
const allQueriesList = Object.values(allQueries.data);
|
||||
|
||||
|
|
@ -180,21 +199,34 @@ const ManageSchedulePage = (props: ITeamSchedulesPageProps): JSX.Element => {
|
|||
const selectedTeam = isNaN(teamId) ? "global" : teamId;
|
||||
|
||||
const generateTeamOptionsDropdownItems = (): ITeamOptions[] => {
|
||||
const teamOptions: ITeamOptions[] = [
|
||||
{
|
||||
const teamOptions: ITeamOptions[] = [];
|
||||
|
||||
if (isTeamMaintainer) {
|
||||
user.teams.forEach((team) => {
|
||||
if (team.role === "maintainer") {
|
||||
teamOptions.push({
|
||||
disabled: false,
|
||||
label: team.name,
|
||||
value: team.id,
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
teamOptions.push({
|
||||
disabled: false,
|
||||
label: "All teams",
|
||||
value: "global",
|
||||
},
|
||||
];
|
||||
|
||||
allTeamsList.forEach((team) => {
|
||||
teamOptions.push({
|
||||
disabled: false,
|
||||
label: team.name,
|
||||
value: team.id,
|
||||
});
|
||||
});
|
||||
|
||||
allTeamsList.forEach((team) => {
|
||||
teamOptions.push({
|
||||
disabled: false,
|
||||
label: team.name,
|
||||
value: team.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return teamOptions;
|
||||
};
|
||||
|
||||
|
|
@ -349,6 +381,15 @@ const ManageSchedulePage = (props: ITeamSchedulesPageProps): JSX.Element => {
|
|||
}
|
||||
};
|
||||
|
||||
if (selectedTeam === "global" && isTeamMaintainer) {
|
||||
const teamMaintainerTeams = generateTeamOptionsDropdownItems();
|
||||
dispatch(
|
||||
push(
|
||||
`${paths.MANAGE_TEAM_SCHEDULE(Number(teamMaintainerTeams[0].value))}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__wrapper body-wrap`}>
|
||||
|
|
@ -398,13 +439,15 @@ const ManageSchedulePage = (props: ITeamSchedulesPageProps): JSX.Element => {
|
|||
{allScheduledQueriesList.length !== 0 &&
|
||||
allScheduledQueriesError.length !== 0 && (
|
||||
<div className={`${baseClass}__action-button-container`}>
|
||||
<Button
|
||||
variant="inverse"
|
||||
onClick={handleAdvanced}
|
||||
className={`${baseClass}__advanced-button`}
|
||||
>
|
||||
Advanced
|
||||
</Button>
|
||||
{!isTeamMaintainer && (
|
||||
<Button
|
||||
variant="inverse"
|
||||
onClick={handleAdvanced}
|
||||
className={`${baseClass}__advanced-button`}
|
||||
>
|
||||
Advanced
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="brand"
|
||||
className={`${baseClass}__schedule-button`}
|
||||
|
|
@ -422,7 +465,8 @@ const ManageSchedulePage = (props: ITeamSchedulesPageProps): JSX.Element => {
|
|||
allScheduledQueriesList,
|
||||
allScheduledQueriesError,
|
||||
toggleScheduleEditorModal,
|
||||
teamId
|
||||
teamId,
|
||||
isTeamMaintainer
|
||||
)}
|
||||
</div>
|
||||
{/* must use ternary for NaN */}
|
||||
|
|
@ -455,6 +499,7 @@ const ManageSchedulePage = (props: ITeamSchedulesPageProps): JSX.Element => {
|
|||
{showInheritedQueries &&
|
||||
renderAllTeamsTable(
|
||||
teamId,
|
||||
isTeamMaintainer,
|
||||
allTeamsScheduledQueriesList,
|
||||
allTeamsScheduledQueriesError
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ interface IScheduleListWrapperProps {
|
|||
toggleScheduleEditorModal?: () => void;
|
||||
teamId: number;
|
||||
inheritedQueries?: boolean;
|
||||
isTeamMaintainer: boolean;
|
||||
}
|
||||
interface IRootState {
|
||||
entities: {
|
||||
|
|
@ -55,6 +56,7 @@ const ScheduleListWrapper = (props: IScheduleListWrapperProps): JSX.Element => {
|
|||
onEditScheduledQueryClick,
|
||||
teamId,
|
||||
inheritedQueries,
|
||||
isTeamMaintainer,
|
||||
} = props;
|
||||
const dispatch = useDispatch();
|
||||
const { MANAGE_PACKS } = paths;
|
||||
|
|
@ -69,8 +71,9 @@ const ScheduleListWrapper = (props: IScheduleListWrapperProps): JSX.Element => {
|
|||
<div className={`${noScheduleClass}__inner-text`}>
|
||||
<h2>You don't have any queries scheduled.</h2>
|
||||
<p>
|
||||
Schedule a query, or go to your osquery packs via the
|
||||
‘Advanced’ button.
|
||||
{!isTeamMaintainer
|
||||
? "Schedule a query, or go to your osquery packs via the ‘Advanced’ button."
|
||||
: "Schedule a query to run on hosts assigned to this team."}
|
||||
</p>
|
||||
<div className={`${noScheduleClass}__-cta-buttons`}>
|
||||
<Button
|
||||
|
|
@ -80,13 +83,15 @@ const ScheduleListWrapper = (props: IScheduleListWrapperProps): JSX.Element => {
|
|||
>
|
||||
Schedule a query
|
||||
</Button>
|
||||
<Button
|
||||
variant="inverse"
|
||||
onClick={handleAdvanced}
|
||||
className={`${baseClass}__advanced-button`}
|
||||
>
|
||||
Advanced
|
||||
</Button>
|
||||
{!isTeamMaintainer && (
|
||||
<Button
|
||||
variant="inverse"
|
||||
onClick={handleAdvanced}
|
||||
className={`${baseClass}__advanced-button`}
|
||||
>
|
||||
Advanced
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -129,6 +129,8 @@ const routes = (
|
|||
<Route path="policies" component={PoliciesPageWrapper}>
|
||||
<Route path="manage" component={ManagePoliciesPage} />
|
||||
</Route>
|
||||
</Route>
|
||||
<Route component={AuthAnyMaintainerGlobalAdminRoutes}>
|
||||
<Route path="schedule" component={SchedulePageWrapper}>
|
||||
<Route path="manage" component={ManageSchedulePage} />
|
||||
<Route
|
||||
|
|
|
|||
Loading…
Reference in a new issue