diff --git a/changes/issue-6666-loading-spinners b/changes/issue-6666-loading-spinners new file mode 100644 index 0000000000..c069890716 --- /dev/null +++ b/changes/issue-6666-loading-spinners @@ -0,0 +1 @@ +* Adds loading spinners to all buttons that post to the API \ No newline at end of file diff --git a/cypress/integration/all/app/fleetdesktop.spec.ts b/cypress/integration/all/app/fleetdesktop.spec.ts index 08920e9405..3fd6967996 100644 --- a/cypress/integration/all/app/fleetdesktop.spec.ts +++ b/cypress/integration/all/app/fleetdesktop.spec.ts @@ -35,7 +35,7 @@ describe("Fleet Desktop", () => { cy.getAttached('img[alt="Host info icon"]').click(); }); cy.getAttached(".device-user-info__modal").within(() => { - cy.getAttached(".device-user-info__btn").click(); + cy.findByRole("button", { name: /ok/i }).click(); }); }); it("renders and searches the host's software", () => { diff --git a/cypress/integration/all/app/manageUsers.spec.ts b/cypress/integration/all/app/manageUsers.spec.ts index bd0e3c0f5a..0a063cba48 100644 --- a/cypress/integration/all/app/manageUsers.spec.ts +++ b/cypress/integration/all/app/manageUsers.spec.ts @@ -50,28 +50,28 @@ describe("Manage users flow", () => { } ); cy.findByPlaceholderText("Password").clear().type(BAD_PASSWORD_LENGTH); - cy.getAttached(".create-user-form__btn-wrap") + cy.getAttached(".modal-cta-wrap") .contains("button", /create/i) .click(); cy.findByText(/password must meet the criteria below/i).should("exist"); cy.findByLabelText(/password must meet the criteria below/i) .clear() .type(BAD_PASSWORD_NO_NUMBER); - cy.getAttached(".create-user-form__btn-wrap") + cy.getAttached(".modal-cta-wrap") .contains("button", /create/i) .click(); cy.findByText(/password must meet the criteria below/i).should("exist"); cy.findByLabelText(/password must meet the criteria below/i) .clear() .type(BAD_PASSWORD_NO_NUMBER); - cy.getAttached(".create-user-form__btn-wrap") + cy.getAttached(".modal-cta-wrap") .contains("button", /create/i) .click(); cy.findByText(/password must meet the criteria below/i).should("exist"); cy.findByLabelText(/password must meet the criteria below/i) .clear() .type(GOOD_PASSWORD); - cy.getAttached(".create-user-form__btn-wrap") + cy.getAttached(".modal-cta-wrap") .contains("button", /create/i) .click(); cy.findByText(/new name/i).should("exist"); @@ -95,30 +95,22 @@ describe("Manage users flow", () => { } ); cy.findByLabelText("Password").clear().type(BAD_PASSWORD_LENGTH); - cy.getAttached(".create-user-form__btn-wrap") - .contains("button", /save/i) - .click(); + cy.getAttached(".modal-cta-wrap").contains("button", /save/i).click(); cy.findByText(/password must meet the criteria below/i).should("exist"); cy.findByLabelText(/password must meet the criteria below/i) .clear() .type(BAD_PASSWORD_NO_NUMBER); - cy.getAttached(".create-user-form__btn-wrap") - .contains("button", /save/i) - .click(); + cy.getAttached(".modal-cta-wrap").contains("button", /save/i).click(); cy.findByText(/password must meet the criteria below/i).should("exist"); cy.findByLabelText(/password must meet the criteria below/i) .clear() .type(BAD_PASSWORD_NO_SYMBOL); - cy.getAttached(".create-user-form__btn-wrap") - .contains("button", /save/i) - .click(); + cy.getAttached(".modal-cta-wrap").contains("button", /save/i).click(); cy.findByText(/password must meet the criteria below/i).should("exist"); cy.findByLabelText(/password must meet the criteria below/i) .clear() .type(GOOD_PASSWORD); - cy.getAttached(".create-user-form__btn-wrap") - .contains("button", /save/i) - .click(); + cy.getAttached(".modal-cta-wrap").contains("button", /save/i).click(); cy.findByText(/successfully edited/i).should("exist"); }); it("deletes an existing user", () => { @@ -129,7 +121,7 @@ describe("Manage users flow", () => { cy.findByText(/action/i).click(); cy.findByText(/delete/i).click(); }); - cy.getAttached(".delete-user-form__btn-wrap") + cy.getAttached(".modal-cta-wrap") .contains("button", /delete/i) .click(); cy.findByText(/successfully deleted/i).should("exist"); diff --git a/cypress/integration/all/app/policiesflow.spec.ts b/cypress/integration/all/app/policiesflow.spec.ts index cfa7a0a72d..559f1b0b23 100644 --- a/cypress/integration/all/app/policiesflow.spec.ts +++ b/cypress/integration/all/app/policiesflow.spec.ts @@ -151,7 +151,7 @@ describe("Policies flow (empty)", () => { .type( "{selectall}SELECT 1 FROM users WHERE username = 'backup' LIMIT 1;" ); - cy.getAttached(".policy-form__save").click(); + cy.findByRole("button", { name: /save/i }).click(); cy.getAttached(".policy-form__policy-save-modal-name") .click() .type("Does the device have a user named 'backup'?"); @@ -161,7 +161,7 @@ describe("Policies flow (empty)", () => { cy.getAttached(".policy-form__policy-save-modal-resolution") .click() .type("Create a user named 'backup'"); - cy.getAttached(".policy-form__button--modal-save").click(); + cy.findByRole("button", { name: /save policy/i }).click(); cy.findByText(/policy created/i).should("exist"); }); @@ -170,10 +170,8 @@ describe("Policies flow (empty)", () => { cy.findByText(/add a policy/i).click(); }); cy.findByText(/gatekeeper enabled/i).click(); - cy.getAttached(".policy-form__save").click(); - cy.getAttached(".policy-form__button-wrap--modal").within(() => { - cy.getAttached(".policy-form__button--modal-save").click(); - }); + cy.findByRole("button", { name: /save/i }).click(); + cy.findByRole("button", { name: /save policy/i }).click(); cy.findByText(/policy created/i).should("exist"); }); }); @@ -215,7 +213,7 @@ describe("Policies flow (empty)", () => { cy.getAttached(".manage-policies-page__header-wrap").within(() => { cy.findByText(/add a policy/i).click(); }); - cy.getAttached(".add-policy-modal__modal").within(() => { + cy.getAttached(".add-policy-modal").within(() => { cy.findByRole("button", { name: /create your own policy/i }).click(); }); @@ -304,7 +302,7 @@ describe("Policies flow (empty)", () => { cy.getAttached(".manage-policies-page__header-wrap").within(() => { cy.findByText(/add a policy/i).click(); }); - cy.getAttached(".add-policy-modal__modal").within(() => { + cy.getAttached(".add-policy-modal").within(() => { cy.findByText("Automatic login disabled (macOS)").click(); }); @@ -313,7 +311,7 @@ describe("Policies flow (empty)", () => { testCompatibility(el, i, [true, false, false]); }); }); - cy.getAttached(".policy-form__save").click(); + cy.findByRole("button", { name: /save/i }).click(); cy.getAttached(".platform-selector").within(() => { cy.getAttached(".fleet-checkbox__input").each((el, i) => { @@ -326,10 +324,10 @@ describe("Policies flow (empty)", () => { cy.getAttached(".manage-policies-page__header-wrap").within(() => { cy.findByText(/add a policy/i).click(); }); - cy.getAttached(".add-policy-modal__modal").within(() => { + cy.getAttached(".add-policy-modal").within(() => { cy.findByText("Automatic login disabled (macOS)").click(); }); - cy.getAttached(".policy-form__save").click(); + cy.findByRole("button", { name: /save/i }).click(); cy.getAttached(".platform-selector").within(() => { cy.getAttached(".fleet-checkbox__input").each((el, i) => { @@ -340,14 +338,16 @@ describe("Policies flow (empty)", () => { testSelections(el, i, [false, false, false]); }); }); - cy.getAttached(".policy-form__button--modal-save").should("be.disabled"); + cy.getAttached(".modal-cta-wrap").within(() => { + cy.findByRole("button", { name: /save policy/i }).should("be.disabled"); + }); }); it("allows user to overide preselected platforms when saving new policy", () => { cy.getAttached(".manage-policies-page__header-wrap").within(() => { cy.findByText(/add a policy/i).click(); }); - cy.getAttached(".add-policy-modal__modal").within(() => { + cy.getAttached(".add-policy-modal").within(() => { cy.findByText("Automatic login disabled (macOS)").click(); }); @@ -356,7 +356,7 @@ describe("Policies flow (empty)", () => { testCompatibility(el, i, [true, false, false]); }); }); - cy.getAttached(".policy-form__save").click(); + cy.findByRole("button", { name: /save/i }).click(); cy.getAttached(".platform-selector").within(() => { cy.getAttached(".fleet-checkbox__input").each((el, i) => { @@ -368,7 +368,7 @@ describe("Policies flow (empty)", () => { testSelections(el, i, [false, false, true]); }); }); - cy.getAttached(".policy-form__button--modal-save").click(); + cy.findByRole("button", { name: /save policy/i }).click(); cy.findByText(/policy created/i).should("exist"); // confirm that new policy was saved with user-selected platforms @@ -390,11 +390,11 @@ describe("Policies flow (empty)", () => { cy.getAttached(".manage-policies-page__header-wrap").within(() => { cy.findByText(/add a policy/i).click(); }); - cy.getAttached(".add-policy-modal__modal").within(() => { + cy.getAttached(".add-policy-modal").within(() => { cy.findByText("Antivirus healthy (macOS)").click(); }); - cy.getAttached(".policy-form__save").click(); - cy.getAttached(".policy-form__button--modal-save").click(); + cy.findByRole("button", { name: /save/i }).click(); + cy.findByRole("button", { name: /save policy/i }).click(); cy.findByText(/policy created/i).should("exist"); // edit platform selections for policy @@ -485,7 +485,7 @@ describe("Policies flow (seeded)", () => { "{selectall}SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;" ); cy.getAttached(".fleet-checkbox__label").first().click(); - cy.getAttached(".policy-form__save").click(); + cy.findByRole("button", { name: /save/i }).click(); cy.findByText(/policy updated/i).should("exist"); cy.visit("policies/1"); cy.getAttached(".fleet-checkbox__input").first().should("not.be.checked"); @@ -500,7 +500,7 @@ describe("Policies flow (seeded)", () => { }); }); cy.findByRole("button", { name: /delete/i }).click(); - cy.getAttached(".delete-policies-modal").within(() => { + cy.getAttached(".delete-policy-modal").within(() => { cy.findByRole("button", { name: /cancel/i }).should("exist"); cy.findByRole("button", { name: /delete/i }).click(); }); @@ -530,6 +530,8 @@ describe("Policies flow (seeded)", () => { cy.getAttached(".fleet-slider").click(); }); cy.findByRole("button", { name: /^Save$/ }).click(); + // Confirm failing policies webhook was updated successfully + cy.findByText(/updated policy automations/i).should("exist"); }); it("empty automation state prompts to create an integration", () => { cy.getAttached(".button-wrap").within(() => { diff --git a/cypress/integration/all/app/queryflow.spec.ts b/cypress/integration/all/app/queryflow.spec.ts index 9aec747b47..b16ca541f5 100644 --- a/cypress/integration/all/app/queryflow.spec.ts +++ b/cypress/integration/all/app/queryflow.spec.ts @@ -84,7 +84,7 @@ describe("Query flow (seeded)", () => { cy.getAttached(".ace_scroller") .click() .type("{selectall}SELECT datetime, username FROM windows_crashes;"); - cy.getAttached(".query-form__save").click(); + cy.findByRole("button", { name: "Save" }).click(); cy.findByText(/query updated/i).should("be.visible"); }); it("saves an existing query as new query", () => { @@ -97,11 +97,12 @@ describe("Query flow (seeded)", () => { cy.getAttached(".ace_scroller") .click() .type("{selectall}SELECT datetime, username FROM windows_crashes;"); - cy.getAttached(".query-form__save-as-new").click(); + cy.findByRole("button", { name: /save as new/i }).click(); cy.findByText(/copy of/i).should("be.visible"); }); it("deletes an existing query", () => { cy.findByText(/detect presence of authorized ssh keys/i) + .parent() .parent() .parent() .within(() => { @@ -151,9 +152,7 @@ describe("Query flow (seeded)", () => { cy.getAttached(".input-field").click().type("50"); } ); - cy.getAttached( - ".schedule-editor-modal__btn-wrap .modal-cta-wrap" - ).within(() => { + cy.getAttached(".modal-cta-wrap").within(() => { cy.findByRole("button", { name: /schedule/i }).click(); }); }); @@ -171,7 +170,7 @@ describe("Query flow (seeded)", () => { cy.findByText(/every 6 hours/i).click(); cy.findByText(/every day/i).click(); - cy.getAttached(".schedule-editor-modal__btn-wrap").within(() => { + cy.getAttached(".modal-cta-wrap").within(() => { cy.findByRole("button", { name: /schedule/i }).click(); }); }); diff --git a/cypress/integration/free/admin.spec.ts b/cypress/integration/free/admin.spec.ts index 36d07eaafe..ff234abf40 100644 --- a/cypress/integration/free/admin.spec.ts +++ b/cypress/integration/free/admin.spec.ts @@ -279,7 +279,7 @@ describe( }); }); cy.findByRole("button", { name: /delete/i }).click(); - cy.getAttached(".delete-policies-modal").within(() => { + cy.getAttached(".delete-policy-modal").within(() => { cy.findByRole("button", { name: /delete/i }).should("exist"); cy.findByRole("button", { name: /cancel/i }).click(); }); diff --git a/cypress/integration/free/maintainer.spec.ts b/cypress/integration/free/maintainer.spec.ts index 4b77500d44..9b5dae075b 100644 --- a/cypress/integration/free/maintainer.spec.ts +++ b/cypress/integration/free/maintainer.spec.ts @@ -275,7 +275,7 @@ describe( }); }); cy.findByRole("button", { name: /delete/i }).click(); - cy.getAttached(".delete-policies-modal").within(() => { + cy.getAttached(".delete-policy-modal").within(() => { cy.findByRole("button", { name: /delete/i }).should("exist"); cy.findByRole("button", { name: /cancel/i }).click(); }); diff --git a/cypress/integration/premium/admin.spec.ts b/cypress/integration/premium/admin.spec.ts index bbcd7faaf2..133048c2d8 100644 --- a/cypress/integration/premium/admin.spec.ts +++ b/cypress/integration/premium/admin.spec.ts @@ -479,9 +479,11 @@ describe("Premium tier - Global Admin user", () => { cy.findByText(/gatekeeper enabled/i).click(); cy.getAttached(".policy-form__button-wrap").within(() => { cy.findByRole("button", { name: /run/i }).should("exist"); - cy.getAttached(".policy-form__save").click(); + cy.findByRole("button", { name: /save/i }).click(); + }); + cy.getAttached(".modal-cta-wrap").within(() => { + cy.findByRole("button", { name: /save policy/i }).click(); }); - cy.getAttached(".policy-form__button--modal-save").click(); cy.findByText(/policy created/i).should("exist"); cy.findByText(/gatekeeper enabled/i).should("exist"); }); @@ -539,7 +541,7 @@ describe("Premium tier - Global Admin user", () => { }); }); cy.findByRole("button", { name: /delete/i }).click(); - cy.getAttached(".delete-policies-modal").within(() => { + cy.getAttached(".delete-policy-modal").within(() => { cy.findByRole("button", { name: /delete/i }).should("exist"); cy.findByRole("button", { name: /cancel/i }).click(); }); @@ -593,7 +595,7 @@ describe("Premium tier - Global Admin user", () => { }); }); cy.findByRole("button", { name: /delete/i }).click(); - cy.getAttached(".delete-policies-modal").within(() => { + cy.getAttached(".delete-policy-modal").within(() => { cy.findByRole("button", { name: /delete/i }).should("exist"); cy.findByRole("button", { name: /cancel/i }).click(); }); diff --git a/cypress/integration/premium/maintainer.spec.ts b/cypress/integration/premium/maintainer.spec.ts index 32e0985716..a68354d2b9 100644 --- a/cypress/integration/premium/maintainer.spec.ts +++ b/cypress/integration/premium/maintainer.spec.ts @@ -237,9 +237,11 @@ describe("Premium tier - Maintainer user", () => { cy.findByText(/gatekeeper enabled/i).click(); cy.getAttached(".policy-form__button-wrap").within(() => { cy.findByRole("button", { name: /run/i }).should("exist"); - cy.getAttached(".policy-form__save").click(); + cy.findByRole("button", { name: /save/i }).click(); + }); + cy.getAttached(".modal-cta-wrap").within(() => { + cy.findByRole("button", { name: /save policy/i }).click(); }); - cy.getAttached(".policy-form__button--modal-save").click(); cy.findByText(/policy created/i).should("exist"); }); it("allows global maintainer to delete a team policy", () => { @@ -260,7 +262,7 @@ describe("Premium tier - Maintainer user", () => { }); }); cy.findByRole("button", { name: /delete/i }).click(); - cy.getAttached(".delete-policies-modal").within(() => { + cy.getAttached(".delete-policy-modal").within(() => { cy.findByRole("button", { name: /delete/i }).should("exist"); cy.findByRole("button", { name: /cancel/i }).click(); }); diff --git a/cypress/integration/premium/team_admin.spec.ts b/cypress/integration/premium/team_admin.spec.ts index f0d1342077..0424a191f5 100644 --- a/cypress/integration/premium/team_admin.spec.ts +++ b/cypress/integration/premium/team_admin.spec.ts @@ -223,7 +223,7 @@ describe("Premium tier - Team Admin user", () => { }); cy.findAllByText(/detect presence/i).click(); }); - cy.getAttached(".query-form__save").should("be.disabled"); + cy.findByRole("button", { name: "Save" }).should("be.disabled"); }); }); describe("Manage schedules page", () => { @@ -242,7 +242,7 @@ describe("Premium tier - Team Admin user", () => { cy.getAttached(".schedule-editor-modal__form").within(() => { cy.findByText(/select query/i).click(); cy.findByText(/detect presence/i).click(); - cy.getAttached(".schedule-editor-modal__btn-wrap").within(() => { + cy.getAttached(".modal-cta-wrap").within(() => { cy.findByRole("button", { name: /schedule/i }).click(); }); }); @@ -260,7 +260,7 @@ describe("Premium tier - Team Admin user", () => { cy.findByText(/every day/i).click(); cy.findByText(/every 6 hours/i).click(); - cy.getAttached(".schedule-editor-modal__btn-wrap").within(() => { + cy.getAttached(".modal-cta-wrap").within(() => { cy.findByRole("button", { name: /schedule/i }).click(); }); }); @@ -297,9 +297,11 @@ describe("Premium tier - Team Admin user", () => { cy.findByText(/gatekeeper enabled/i).click(); cy.getAttached(".policy-form__button-wrap").within(() => { cy.findByRole("button", { name: /run/i }).should("exist"); - cy.getAttached(".policy-form__save").click(); + cy.findByRole("button", { name: /save/i }).click(); + }); + cy.getAttached(".modal-cta-wrap").within(() => { + cy.findByRole("button", { name: /save policy/i }).click(); }); - cy.getAttached(".policy-form__button--modal-save").click(); cy.findByText(/policy created/i).should("exist"); }); it("allows team admin to edit a team policy", () => { @@ -345,7 +347,7 @@ describe("Premium tier - Team Admin user", () => { }); }); cy.findByRole("button", { name: /delete/i }).click(); - cy.getAttached(".delete-policies-modal").within(() => { + cy.getAttached(".delete-policy-modal").within(() => { cy.findByRole("button", { name: /delete/i }).should("exist"); cy.findByRole("button", { name: /cancel/i }).click(); }); diff --git a/cypress/integration/premium/team_maintainer_observer.spec.ts b/cypress/integration/premium/team_maintainer_observer.spec.ts index 0f1e72c871..7e5c351c6e 100644 --- a/cypress/integration/premium/team_maintainer_observer.spec.ts +++ b/cypress/integration/premium/team_maintainer_observer.spec.ts @@ -263,7 +263,7 @@ describe("Premium tier - Team observer/maintainer user", () => { cy.findByText(/detect presence/i).click(); cy.findByText(/every day/i).click(); cy.findByText(/every 6 hours/i).click(); - cy.getAttached(".schedule-editor-modal__btn-wrap").within(() => { + cy.getAttached(".modal-cta-wrap").within(() => { cy.findByRole("button", { name: /schedule/i }).click(); }); }); @@ -285,8 +285,10 @@ describe("Premium tier - Team observer/maintainer user", () => { // Add a default policy cy.findByText(/gatekeeper enabled/i).click(); - cy.getAttached(".policy-form__save").click(); - cy.getAttached(".policy-form__button--modal-save").click(); + cy.findByRole("button", { name: /save/i }).click(); + cy.getAttached(".modal-cta-wrap").within(() => { + cy.findByRole("button", { name: /save policy/i }).click(); + }); cy.findByText(/policy created/i).should("exist"); // On maintaining team, should see "save" and "run" for a new policy diff --git a/cypress/integration/premium/teamflow.spec.ts b/cypress/integration/premium/teamflow.spec.ts index fa37bc8b83..903948896c 100644 --- a/cypress/integration/premium/teamflow.spec.ts +++ b/cypress/integration/premium/teamflow.spec.ts @@ -120,7 +120,7 @@ describe("Teams flow (seeded)", () => { cy.getAttached(".input-field").click().type("50"); } ); - cy.getAttached(".schedule-editor-modal__btn-wrap").within(() => { + cy.getAttached(".modal-cta-wrap").within(() => { cy.findByRole("button", { name: /schedule/i }).click(); }); }); diff --git a/frontend/components/AddHostsModal/DownloadInstallers/_styles.scss b/frontend/components/AddHostsModal/DownloadInstallers/_styles.scss index 48aff6b09b..241b0347d7 100644 --- a/frontend/components/AddHostsModal/DownloadInstallers/_styles.scss +++ b/frontend/components/AddHostsModal/DownloadInstallers/_styles.scss @@ -71,11 +71,6 @@ } } - &__button--download { - width: 154px; - height: 38px; - } - &__success { display: flex; flex-direction: column; diff --git a/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx b/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx index 63e2e8230f..cbfce304b0 100644 --- a/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx +++ b/frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx @@ -456,7 +456,7 @@ const PlatformWrapper = ({
-
diff --git a/frontend/components/AddHostsModal/_styles.scss b/frontend/components/AddHostsModal/_styles.scss deleted file mode 100644 index 61f05cd9c5..0000000000 --- a/frontend/components/AddHostsModal/_styles.scss +++ /dev/null @@ -1,11 +0,0 @@ -.add-hosts-modal { - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - margin-top: $pad-xxlarge; - } - - &__btn { - margin-left: 12px; - } -} diff --git a/frontend/components/DeleteSecretModal/DeleteSecretModal.tsx b/frontend/components/DeleteSecretModal/DeleteSecretModal.tsx index bf9fb62f5e..8ade21920a 100644 --- a/frontend/components/DeleteSecretModal/DeleteSecretModal.tsx +++ b/frontend/components/DeleteSecretModal/DeleteSecretModal.tsx @@ -8,6 +8,7 @@ interface IDeleteSecretModal { teams: ITeam[]; onDeleteSecret: () => void; toggleDeleteSecretModal: () => void; + isUpdatingSecret: boolean; } const baseClass = "delete-secret-modal"; @@ -17,6 +18,7 @@ const DeleteSecretModal = ({ teams, onDeleteSecret, toggleDeleteSecretModal, + isUpdatingSecret, }: IDeleteSecretModal): JSX.Element => { const renderTeam = () => { if (typeof selectedTeam === "string") { @@ -50,20 +52,17 @@ const DeleteSecretModal = ({
- +
diff --git a/frontend/components/DeleteSecretModal/_styles.scss b/frontend/components/DeleteSecretModal/_styles.scss index 13b86a3b8a..5a18c1622d 100644 --- a/frontend/components/DeleteSecretModal/_styles.scss +++ b/frontend/components/DeleteSecretModal/_styles.scss @@ -1,8 +1,4 @@ .delete-secret-modal { - &__btn { - margin-left: $pad-large; - } - &__error { color: $ui-error; } diff --git a/frontend/components/EnrollSecretModal/EnrollSecretModal.tsx b/frontend/components/EnrollSecretModal/EnrollSecretModal.tsx index 95aeab1fde..ef738f8be2 100644 --- a/frontend/components/EnrollSecretModal/EnrollSecretModal.tsx +++ b/frontend/components/EnrollSecretModal/EnrollSecretModal.tsx @@ -95,7 +95,7 @@ const EnrollSecretModal = ({
-
diff --git a/frontend/components/EnrollSecretTable/EnrollSecretRow/_styles.scss b/frontend/components/EnrollSecretTable/EnrollSecretRow/_styles.scss index 3495365c9b..bb992c5de5 100644 --- a/frontend/components/EnrollSecretTable/EnrollSecretRow/_styles.scss +++ b/frontend/components/EnrollSecretTable/EnrollSecretRow/_styles.scss @@ -35,7 +35,7 @@ align-items: center; position: absolute; right: 16px; - top: 13px; + top: 16px; height: 16px; span { @@ -81,11 +81,8 @@ } } - &__edit-secret-btn { - margin: $pad-small $pad-small $pad-xsmall $pad-large; - } - + &__edit-secret-btn, &__delete-secret-btn { - margin: $pad-small 0 $pad-xsmall; + margin: $pad-xsmall $pad-small $pad-large; } } diff --git a/frontend/components/Modal/_styles.scss b/frontend/components/Modal/_styles.scss index c13e59b39b..f9b7d059da 100644 --- a/frontend/components/Modal/_styles.scss +++ b/frontend/components/Modal/_styles.scss @@ -23,12 +23,9 @@ .modal-cta-wrap { display: flex; - justify-content: flex-end; + flex-direction: row-reverse; margin-top: $pad-large; - - :nth-child(2) { - margin-left: $pad-medium; - } + gap: $pad-medium; } } diff --git a/frontend/components/SecretEditorModal/SecretEditorModal.tsx b/frontend/components/SecretEditorModal/SecretEditorModal.tsx index efee863b0f..624730cd18 100644 --- a/frontend/components/SecretEditorModal/SecretEditorModal.tsx +++ b/frontend/components/SecretEditorModal/SecretEditorModal.tsx @@ -8,12 +8,13 @@ import Button from "components/buttons/Button"; // @ts-ignore import InputField from "components/forms/fields/InputField"; -interface IAddSecretModal { +interface ISecretEditorModalProps { selectedTeam: number; onSaveSecret: (newEnrollSecret: string) => void; teams: ITeam[]; toggleSecretEditorModal: () => void; selectedSecret: IEnrollSecret | undefined; + isUpdatingSecret: boolean; } const baseClass = "secret-editor-modal"; @@ -35,7 +36,8 @@ const SecretEditorModal = ({ teams, toggleSecretEditorModal, selectedSecret, -}: IAddSecretModal): JSX.Element => { + isUpdatingSecret, +}: ISecretEditorModalProps): JSX.Element => { const [enrollSecretString, setEnrollSecretString] = useState( selectedSecret ? selectedSecret.secret : randomSecretGenerator() ); @@ -99,7 +101,12 @@ const SecretEditorModal = ({ />
-
diff --git a/frontend/components/SecretField/_styles.scss b/frontend/components/SecretField/_styles.scss index 8c6166fa98..0293fbf25a 100644 --- a/frontend/components/SecretField/_styles.scss +++ b/frontend/components/SecretField/_styles.scss @@ -87,12 +87,4 @@ margin-bottom: $pad-medium; } } - - &__edit-secret-btn { - margin: $pad-small $pad-small $pad-xsmall $pad-large; - } - - &__delete-secret-btn { - margin: $pad-small 0 $pad-xsmall; - } } diff --git a/frontend/components/Spinner/Spinner.tsx b/frontend/components/Spinner/Spinner.tsx index bcdf8bcd3c..1fd14e0f36 100644 --- a/frontend/components/Spinner/Spinner.tsx +++ b/frontend/components/Spinner/Spinner.tsx @@ -1,19 +1,41 @@ import React from "react"; +import classnames from "classnames"; interface ISpinnerProps { small?: boolean; + button?: boolean; + white?: boolean; } -const baseClass = "loading-spinner"; - -const Spinner = ({ small }: ISpinnerProps): JSX.Element => { +const Spinner = ({ small, button, white }: ISpinnerProps): JSX.Element => { + const classOptions = classnames(`loading-spinner`, { + small, + button, + white, + }); return ( -
-
-
-
-
-
+
+
+ + + +
); diff --git a/frontend/components/Spinner/_styles.scss b/frontend/components/Spinner/_styles.scss index b056f7963a..0a582700a4 100644 --- a/frontend/components/Spinner/_styles.scss +++ b/frontend/components/Spinner/_styles.scss @@ -9,79 +9,86 @@ box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.1); border-radius: 8px; - &__ring { - box-sizing: border-box; - display: inline-block; + .loader { position: relative; - width: 32px; - height: 32px; - border-radius: 100%; - border: 4px solid $core-vibrant-blue; - - div { - box-sizing: border-box; + margin: 0 auto; + width: 34px; + + &:before { + content: ""; display: block; - position: absolute; - width: 32px; - height: 32px; - margin: -4px; - border: 4px solid; - border-radius: 50%; - animation: ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; - border-color: $ui-vibrant-blue-25 transparent transparent transparent; + padding-top: 100%; } } - - div:nth-child(1) { - animation-delay: -0.45s; - } - div:nth-child(2) { - animation-delay: -0.3s; - } - - div:nth-child(3) { - animation-delay: -0.15s; - } -} - -.loading-spinner.small { - background: none; - box-shadow: none; - height: auto; - margin: 0; - transform: scale(0.7); -} - -button { - position: relative; - .loading-spinner { + &.button { background: none; - box-shadow: none; - margin: 0; - transform: scale(0.7); position: absolute; - &__ring { - border-color: $core-white; - - div { - border-color: $core-vibrant-blue transparent transparent transparent; - width: 32px; - height: 32px; + width: auto; + height: auto; + top: auto; + box-shadow: none; + z-index: 9; + margin: 0; + padding: 0; + &.white { + .path { + stroke: $core-white; } } + .loader { + width: 23px; + } + .background { + display: none; + } } -} -.spinner-wrap { - display: flex; -} + .circular { + animation: rotate 2s linear infinite; + height: 100%; + transform-origin: center center; + width: 100%; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + } -@keyframes ring { - 0% { - transform: rotate(0deg); + .path { + stroke-dasharray: 1, 200; + stroke-dashoffset: 0; + stroke: $core-vibrant-blue; + animation: dash 1.5s ease-in-out infinite; + stroke-linecap: round; } - 100% { - transform: rotate(360deg); + + .background { + stroke: $ui-vibrant-blue-25; } -} + + @keyframes rotate { + 100% { + transform: rotate(360deg); + } + } + + @keyframes dash { + 0% { + stroke-dasharray: 1, 200; + stroke-dashoffset: 0; + } + + 50% { + stroke-dasharray: 89, 200; + stroke-dashoffset: -35px; + } + + 100% { + stroke-dasharray: 89, 200; + stroke-dashoffset: -124px; + } + } +} \ No newline at end of file diff --git a/frontend/components/buttons/Button/Button.tsx b/frontend/components/buttons/Button/Button.tsx index 24c6586cfd..e3bb5996cb 100644 --- a/frontend/components/buttons/Button/Button.tsx +++ b/frontend/components/buttons/Button/Button.tsx @@ -1,5 +1,6 @@ import React from "react"; import classnames from "classnames"; +import Spinner from "components/Spinner"; const baseClass = "button"; @@ -35,6 +36,7 @@ export interface IButtonProps { onClick?: | ((value?: any) => void) | ((evt: React.MouseEvent) => void); + isLoading?: boolean; } // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -95,6 +97,7 @@ class Button extends React.Component { type, title, variant, + isLoading, } = this.props; const fullClassName = classnames( baseClass, @@ -105,6 +108,11 @@ class Button extends React.Component { [`${baseClass}--${size}`]: size !== undefined, } ); + const onWhite = + variant === "text-link" || + variant === "inverse" || + variant === "text-icon" || + variant === "label"; return ( ); } diff --git a/frontend/components/buttons/Button/_styles.scss b/frontend/components/buttons/Button/_styles.scss index 85f2bf84bc..c4ba344a9d 100644 --- a/frontend/components/buttons/Button/_styles.scss +++ b/frontend/components/buttons/Button/_styles.scss @@ -45,6 +45,7 @@ $base-class: "button"; height: 38px; top: 0; border: 0; + position: relative; cursor: pointer; &:focus { @@ -57,6 +58,7 @@ $base-class: "button"; $core-vibrant-blue-over, $core-vibrant-blue-down ); + display: flex; } &--success { @@ -69,6 +71,15 @@ $base-class: "button"; $core-vibrant-red-over, $core-vibrant-red-down ); + display: flex; + + .loading-spinner { + &__ring { + div { + border-color: $ui-error transparent transparent transparent; + } + } + } } &--blue-green { @@ -320,4 +331,14 @@ $base-class: "button"; display: flex; justify-content: space-between; } + + .transparent-text { + opacity: 0; + } + + .children-wrapper { + display: flex; + flex-direction: row; + align-items: center; + } } diff --git a/frontend/components/forms/ChangeEmailForm/ChangeEmailForm.jsx b/frontend/components/forms/ChangeEmailForm/ChangeEmailForm.jsx index e3cb33469b..da840134dd 100644 --- a/frontend/components/forms/ChangeEmailForm/ChangeEmailForm.jsx +++ b/frontend/components/forms/ChangeEmailForm/ChangeEmailForm.jsx @@ -28,15 +28,11 @@ class ChangeEmailForm extends Component { label="Password" type="password" /> -
- -
diff --git a/frontend/components/forms/ChangeEmailForm/_styles.scss b/frontend/components/forms/ChangeEmailForm/_styles.scss deleted file mode 100644 index f61dfe8e96..0000000000 --- a/frontend/components/forms/ChangeEmailForm/_styles.scss +++ /dev/null @@ -1,10 +0,0 @@ -.change-email-form { - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - } - - &__btn { - margin-left: 12px; - } -} diff --git a/frontend/components/forms/ChangePasswordForm/ChangePasswordForm.jsx b/frontend/components/forms/ChangePasswordForm/ChangePasswordForm.jsx index 180ccdeafa..68082bb40b 100644 --- a/frontend/components/forms/ChangePasswordForm/ChangePasswordForm.jsx +++ b/frontend/components/forms/ChangePasswordForm/ChangePasswordForm.jsx @@ -49,15 +49,11 @@ class ChangePasswordForm extends Component { label="New password confirmation" type="password" /> -
- -
diff --git a/frontend/components/forms/ChangePasswordForm/_styles.scss b/frontend/components/forms/ChangePasswordForm/_styles.scss deleted file mode 100644 index 92a2a90f99..0000000000 --- a/frontend/components/forms/ChangePasswordForm/_styles.scss +++ /dev/null @@ -1,12 +0,0 @@ -.change-password-form { - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - } - - &__btn { - &:last-child { - margin-right: $pad-medium; - } - } -} diff --git a/frontend/components/forms/ConfigurePackQueryForm/ConfigurePackQueryForm.jsx b/frontend/components/forms/ConfigurePackQueryForm/ConfigurePackQueryForm.jsx index 72417f2240..253ccf9d74 100644 --- a/frontend/components/forms/ConfigurePackQueryForm/ConfigurePackQueryForm.jsx +++ b/frontend/components/forms/ConfigurePackQueryForm/ConfigurePackQueryForm.jsx @@ -144,13 +144,9 @@ export class ConfigurePackQueryForm extends Component { label="Shard" type="number" /> -
+
{renderCancelButton()} -
diff --git a/frontend/components/forms/ConfigurePackQueryForm/_styles.scss b/frontend/components/forms/ConfigurePackQueryForm/_styles.scss index 4ef798750f..48500906c5 100644 --- a/frontend/components/forms/ConfigurePackQueryForm/_styles.scss +++ b/frontend/components/forms/ConfigurePackQueryForm/_styles.scss @@ -33,14 +33,4 @@ margin-left: 6px; } } - - &__btn-wrapper { - padding-top: 20px; - - .button { - display: block; - width: 100%; - margin-bottom: 20px; - } - } } diff --git a/frontend/components/forms/ConfirmInviteForm/ConfirmInviteForm.jsx b/frontend/components/forms/ConfirmInviteForm/ConfirmInviteForm.jsx index 24bc734b04..653fb10ef9 100644 --- a/frontend/components/forms/ConfirmInviteForm/ConfirmInviteForm.jsx +++ b/frontend/components/forms/ConfirmInviteForm/ConfirmInviteForm.jsx @@ -52,11 +52,7 @@ class ConfirmInviteForm extends Component { />
-
diff --git a/frontend/components/forms/ForgotPasswordForm/ForgotPasswordForm.jsx b/frontend/components/forms/ForgotPasswordForm/ForgotPasswordForm.jsx index 958629b74c..863bd5fdf8 100644 --- a/frontend/components/forms/ForgotPasswordForm/ForgotPasswordForm.jsx +++ b/frontend/components/forms/ForgotPasswordForm/ForgotPasswordForm.jsx @@ -29,7 +29,8 @@ class ForgotPasswordForm extends Component {
diff --git a/frontend/components/forms/RegistrationForm/ConfirmationPage/ConfirmationPage.tsx b/frontend/components/forms/RegistrationForm/ConfirmationPage/ConfirmationPage.tsx index 34553a314c..a9a91e9239 100644 --- a/frontend/components/forms/RegistrationForm/ConfirmationPage/ConfirmationPage.tsx +++ b/frontend/components/forms/RegistrationForm/ConfirmationPage/ConfirmationPage.tsx @@ -106,7 +106,7 @@ const ConfirmationPage = ({ type="submit" tabIndex={tabIndex} disabled={!currentPage} - className="button button--brand" + variant="brand" > Confirm diff --git a/frontend/components/forms/RegistrationForm/FleetDetails/FleetDetails.jsx b/frontend/components/forms/RegistrationForm/FleetDetails/FleetDetails.jsx index 8bd35d2472..208bb16064 100644 --- a/frontend/components/forms/RegistrationForm/FleetDetails/FleetDetails.jsx +++ b/frontend/components/forms/RegistrationForm/FleetDetails/FleetDetails.jsx @@ -59,7 +59,7 @@ class FleetDetails extends Component { type="submit" tabIndex={tabIndex} disabled={!currentPage} - className="button button--brand" + variant="brand" > Next diff --git a/frontend/components/forms/RegistrationForm/OrgDetails/OrgDetails.jsx b/frontend/components/forms/RegistrationForm/OrgDetails/OrgDetails.jsx index 7d783e9e0e..dbeee1e11e 100644 --- a/frontend/components/forms/RegistrationForm/OrgDetails/OrgDetails.jsx +++ b/frontend/components/forms/RegistrationForm/OrgDetails/OrgDetails.jsx @@ -61,7 +61,7 @@ class OrgDetails extends Component { type="submit" tabIndex={tabIndex} disabled={!currentPage} - className="button button--brand" + variant="brand" > Next diff --git a/frontend/components/forms/ResetPasswordForm/ResetPasswordForm.jsx b/frontend/components/forms/ResetPasswordForm/ResetPasswordForm.jsx index e4a86c34c8..a34e7f912b 100644 --- a/frontend/components/forms/ResetPasswordForm/ResetPasswordForm.jsx +++ b/frontend/components/forms/ResetPasswordForm/ResetPasswordForm.jsx @@ -42,8 +42,9 @@ class ResetPasswordForm extends Component { />
-
diff --git a/frontend/components/forms/packs/EditPackForm/_styles.scss b/frontend/components/forms/packs/EditPackForm/_styles.scss index 571e28af19..72de07dd60 100644 --- a/frontend/components/forms/packs/EditPackForm/_styles.scss +++ b/frontend/components/forms/packs/EditPackForm/_styles.scss @@ -24,11 +24,9 @@ } &__pack-buttons { - margin: $pad-large 0; - text-align: right; - - .button { - margin-left: 15px; - } + display: flex; + justify-content: flex-end; + margin-top: $pad-large; + gap: $pad-medium; } } diff --git a/frontend/components/forms/packs/PackForm/PackForm.tsx b/frontend/components/forms/packs/PackForm/PackForm.tsx index 5c41c21a5e..20fc8bf26a 100644 --- a/frontend/components/forms/packs/PackForm/PackForm.tsx +++ b/frontend/components/forms/packs/PackForm/PackForm.tsx @@ -25,6 +25,7 @@ interface IPackForm { ) => boolean; selectedTargetsCount?: number; isPremiumTier?: boolean; + isUpdatingPack: boolean; } const EditPackForm = ({ @@ -33,6 +34,7 @@ const EditPackForm = ({ onFetchTargets, selectedTargetsCount, isPremiumTier, + isUpdatingPack, }: IPackForm): JSX.Element => { const [errors, setErrors] = useState<{ [key: string]: string }>({}); const [packName, setPackName] = useState(""); @@ -111,7 +113,11 @@ const EditPackForm = ({ />
-
diff --git a/frontend/pages/Homepage/cards/WelcomeHost/WelcomeHost.tsx b/frontend/pages/Homepage/cards/WelcomeHost/WelcomeHost.tsx index 292c109fa1..67f74600b0 100644 --- a/frontend/pages/Homepage/cards/WelcomeHost/WelcomeHost.tsx +++ b/frontend/pages/Homepage/cards/WelcomeHost/WelcomeHost.tsx @@ -155,7 +155,8 @@ const WelcomeHost = ({

diff --git a/frontend/pages/UserSettingsPage/UserSettingsPage.tsx b/frontend/pages/UserSettingsPage/UserSettingsPage.tsx index 5e2b591695..8287516d1e 100644 --- a/frontend/pages/UserSettingsPage/UserSettingsPage.tsx +++ b/frontend/pages/UserSettingsPage/UserSettingsPage.tsx @@ -1,4 +1,4 @@ -import React, { useState, useContext, useEffect } from "react"; +import React, { useState, useContext } from "react"; import { InjectedRouter } from "react-router"; import { AppContext } from "context/app"; @@ -222,11 +222,8 @@ const UserSettingsPage = ({ src={ExternalURLIcon} />

-
-
diff --git a/frontend/pages/admin/AppSettingsPage/cards/Advanced/Advanced.tsx b/frontend/pages/admin/AppSettingsPage/cards/Advanced/Advanced.tsx index ba83e32fce..61da4d5b8d 100644 --- a/frontend/pages/admin/AppSettingsPage/cards/Advanced/Advanced.tsx +++ b/frontend/pages/admin/AppSettingsPage/cards/Advanced/Advanced.tsx @@ -16,6 +16,7 @@ const baseClass = "app-config-form"; const Advanced = ({ appConfig, handleSubmit, + isUpdatingSettings, }: IAppConfigFormProps): JSX.Element => { const [formData, setFormData] = useState({ domain: appConfig.smtp_settings.domain || "", @@ -179,6 +180,8 @@ const Advanced = ({ type="submit" variant="brand" disabled={Object.keys(formErrors).length > 0} + className="save-loading" + isLoading={isUpdatingSettings} > Save diff --git a/frontend/pages/admin/AppSettingsPage/cards/Agents/Agents.tsx b/frontend/pages/admin/AppSettingsPage/cards/Agents/Agents.tsx index c486f341e3..d492c5ee8c 100644 --- a/frontend/pages/admin/AppSettingsPage/cards/Agents/Agents.tsx +++ b/frontend/pages/admin/AppSettingsPage/cards/Agents/Agents.tsx @@ -21,6 +21,7 @@ const Agents = ({ appConfig, handleSubmit, isPremiumTier, + isUpdatingSettings, }: IAppConfigFormProps): JSX.Element => { const { ADMIN_TEAMS } = paths; @@ -122,6 +123,8 @@ const Agents = ({ type="submit" variant="brand" disabled={Object.keys(formErrors).length > 0} + className="save-loading" + isLoading={isUpdatingSettings} > Save diff --git a/frontend/pages/admin/AppSettingsPage/cards/FleetDesktop/FleetDesktop.tsx b/frontend/pages/admin/AppSettingsPage/cards/FleetDesktop/FleetDesktop.tsx index 5737009bc7..d02f923c45 100644 --- a/frontend/pages/admin/AppSettingsPage/cards/FleetDesktop/FleetDesktop.tsx +++ b/frontend/pages/admin/AppSettingsPage/cards/FleetDesktop/FleetDesktop.tsx @@ -22,6 +22,7 @@ const FleetDesktop = ({ appConfig, handleSubmit, isPremiumTier, + isUpdatingSettings, }: IAppConfigFormProps): JSX.Element => { const [formData, setFormData] = useState< Pick @@ -102,6 +103,8 @@ const FleetDesktop = ({ type="submit" variant="brand" disabled={Object.keys(formErrors).length > 0} + className="save-loading" + isLoading={isUpdatingSettings} > Save diff --git a/frontend/pages/admin/AppSettingsPage/cards/HostStatusWebhook/HostStatusWebhook.tsx b/frontend/pages/admin/AppSettingsPage/cards/HostStatusWebhook/HostStatusWebhook.tsx index 98ac423818..978f2dda2b 100644 --- a/frontend/pages/admin/AppSettingsPage/cards/HostStatusWebhook/HostStatusWebhook.tsx +++ b/frontend/pages/admin/AppSettingsPage/cards/HostStatusWebhook/HostStatusWebhook.tsx @@ -22,6 +22,7 @@ const baseClass = "app-config-form"; const HostStatusWebhook = ({ appConfig, handleSubmit, + isUpdatingSettings, }: IAppConfigFormProps): JSX.Element => { const [ showHostStatusWebhookPreviewModal, @@ -206,6 +207,8 @@ const HostStatusWebhook = ({ type="submit" variant="brand" disabled={Object.keys(formErrors).length > 0} + className="save-loading" + isLoading={isUpdatingSettings} > Save diff --git a/frontend/pages/admin/AppSettingsPage/cards/Info/Info.tsx b/frontend/pages/admin/AppSettingsPage/cards/Info/Info.tsx index 2a6de707d9..d7be5ad36f 100644 --- a/frontend/pages/admin/AppSettingsPage/cards/Info/Info.tsx +++ b/frontend/pages/admin/AppSettingsPage/cards/Info/Info.tsx @@ -18,6 +18,7 @@ const baseClass = "app-config-form"; const Info = ({ appConfig, handleSubmit, + isUpdatingSettings, }: IAppConfigFormProps): JSX.Element => { const [formData, setFormData] = useState({ orgName: appConfig.org_info.org_name || "", @@ -92,6 +93,8 @@ const Info = ({ type="submit" variant="brand" disabled={Object.keys(formErrors).length > 0} + className="save-loading" + isLoading={isUpdatingSettings} > Save diff --git a/frontend/pages/admin/AppSettingsPage/cards/Smtp/Smtp.tsx b/frontend/pages/admin/AppSettingsPage/cards/Smtp/Smtp.tsx index 8d9ca13c37..7d350a0cc6 100644 --- a/frontend/pages/admin/AppSettingsPage/cards/Smtp/Smtp.tsx +++ b/frontend/pages/admin/AppSettingsPage/cards/Smtp/Smtp.tsx @@ -22,6 +22,7 @@ const baseClass = "app-config-form"; const Smtp = ({ appConfig, handleSubmit, + isUpdatingSettings, }: IAppConfigFormProps): JSX.Element => { const [formData, setFormData] = useState({ enableSMTP: appConfig.smtp_settings.enable_smtp || false, @@ -252,6 +253,8 @@ const Smtp = ({ type="submit" variant="brand" disabled={Object.keys(formErrors).length > 0} + className="save-loading" + isLoading={isUpdatingSettings} > Save diff --git a/frontend/pages/admin/AppSettingsPage/cards/Sso/Sso.tsx b/frontend/pages/admin/AppSettingsPage/cards/Sso/Sso.tsx index 2184bf790f..8c2926c5d0 100644 --- a/frontend/pages/admin/AppSettingsPage/cards/Sso/Sso.tsx +++ b/frontend/pages/admin/AppSettingsPage/cards/Sso/Sso.tsx @@ -20,6 +20,7 @@ const Sso = ({ appConfig, handleSubmit, isPremiumTier, + isUpdatingSettings, }: IAppConfigFormProps): JSX.Element => { const [formData, setFormData] = useState({ enableSSO: appConfig.sso_settings.enable_sso || false, @@ -242,6 +243,8 @@ const Sso = ({ type="submit" variant="brand" disabled={Object.keys(formErrors).length > 0} + className="save-loading" + isLoading={isUpdatingSettings} > Save diff --git a/frontend/pages/admin/AppSettingsPage/cards/Statistics/Statistics.tsx b/frontend/pages/admin/AppSettingsPage/cards/Statistics/Statistics.tsx index 1c279df129..3da9ad716b 100644 --- a/frontend/pages/admin/AppSettingsPage/cards/Statistics/Statistics.tsx +++ b/frontend/pages/admin/AppSettingsPage/cards/Statistics/Statistics.tsx @@ -17,6 +17,7 @@ const baseClass = "app-config-form"; const Statistics = ({ appConfig, handleSubmit, + isUpdatingSettings, }: IAppConfigFormProps): JSX.Element => { const [ showUsageStatsPreviewModal, @@ -125,7 +126,12 @@ const Statistics = ({
- diff --git a/frontend/pages/admin/AppSettingsPage/cards/WebAddress/WebAddress.tsx b/frontend/pages/admin/AppSettingsPage/cards/WebAddress/WebAddress.tsx index c5ab794830..d5d2eaf38c 100644 --- a/frontend/pages/admin/AppSettingsPage/cards/WebAddress/WebAddress.tsx +++ b/frontend/pages/admin/AppSettingsPage/cards/WebAddress/WebAddress.tsx @@ -14,6 +14,7 @@ const baseClass = "app-config-form"; const WebAddress = ({ appConfig, handleSubmit, + isUpdatingSettings, }: IAppConfigFormProps): JSX.Element => { const [formData, setFormData] = useState({ serverURL: appConfig.server_settings.server_url || "", @@ -78,6 +79,8 @@ const WebAddress = ({ type="submit" variant="brand" disabled={Object.keys(formErrors).length > 0} + className="save-loading" + isLoading={isUpdatingSettings} > Save diff --git a/frontend/pages/admin/AppSettingsPage/cards/constants.ts b/frontend/pages/admin/AppSettingsPage/cards/constants.ts index 37dc1bbc55..7be2f5f7b1 100644 --- a/frontend/pages/admin/AppSettingsPage/cards/constants.ts +++ b/frontend/pages/admin/AppSettingsPage/cards/constants.ts @@ -5,6 +5,7 @@ export const DEFAULT_TRANSPARENCY_URL = "https://fleetdm.com/transparency"; export interface IAppConfigFormProps { appConfig: IConfig; isPremiumTier?: boolean; + isUpdatingSettings?: boolean; handleSubmit: any; } diff --git a/frontend/pages/admin/AppSettingsPage/components/OrgSettingsForm/OrgSettingsForm.tsx b/frontend/pages/admin/AppSettingsPage/components/OrgSettingsForm/OrgSettingsForm.tsx index 0117fa79b8..cd4e4ee916 100644 --- a/frontend/pages/admin/AppSettingsPage/components/OrgSettingsForm/OrgSettingsForm.tsx +++ b/frontend/pages/admin/AppSettingsPage/components/OrgSettingsForm/OrgSettingsForm.tsx @@ -36,6 +36,7 @@ const OrgSettingsForm = ({ const handlePageError = useErrorHandler(); const [activeSection, setActiveSection] = useState("info"); + const [isUpdatingSettings, setIsUpdatingSettings] = useState(false); const { data: appConfig, isLoading, refetch: refetchConfig } = useQuery< IConfig, @@ -58,6 +59,8 @@ const OrgSettingsForm = ({ return false; } + setIsUpdatingSettings(true); + const diff = deepDifference(formData, appConfig); // send all formData.agent_options because diff overrides all agent options diff.agent_options = formData.agent_options; @@ -84,6 +87,7 @@ const OrgSettingsForm = ({ }) .finally(() => { refetchConfig(); + setIsUpdatingSettings(false); }); }, [appConfig] @@ -103,36 +107,55 @@ const OrgSettingsForm = ({ return ( <> {activeSection === "info" && ( - + )} {activeSection === "webaddress" && ( - + )} {activeSection === "sso" && ( )} {activeSection === "smtp" && ( - + )} {activeSection === "agents" && ( )} {activeSection === "host-status-webhook" && ( )} {activeSection === "statistics" && ( - + )} {activeSection === "advanced" && ( @@ -142,6 +165,7 @@ const OrgSettingsForm = ({ appConfig={appConfig} isPremiumTier={isPremiumTier} handleSubmit={onFormSubmit} + isUpdatingSettings={isUpdatingSettings} /> )} diff --git a/frontend/pages/admin/IntegrationsPage/IntegrationsPage.tsx b/frontend/pages/admin/IntegrationsPage/IntegrationsPage.tsx index b3a9d28d35..3dfceafdfc 100644 --- a/frontend/pages/admin/IntegrationsPage/IntegrationsPage.tsx +++ b/frontend/pages/admin/IntegrationsPage/IntegrationsPage.tsx @@ -54,6 +54,9 @@ const IntegrationsPage = (): JSX.Element => { integrationEditing, setIntegrationEditing, ] = useState(); + const [isUpdatingIntegration, setIsUpdatingIntegration] = useState( + false + ); const [jiraIntegrations, setJiraIntegrations] = useState< IJiraIntegration[] >(); @@ -233,7 +236,7 @@ const IntegrationsPage = (): JSX.Element => { }, }); }; - + setIsUpdatingIntegration(true); deleteIntegrationDestination() .then(() => { renderFlash( @@ -264,6 +267,7 @@ const IntegrationsPage = (): JSX.Element => { ); }) .finally(() => { + setIsUpdatingIntegration(false); toggleDeleteIntegrationModal(); }); } @@ -442,6 +446,7 @@ const IntegrationsPage = (): JSX.Element => { integrationEditing?.groupId?.toString() || "" } + isUpdatingIntegration={isUpdatingIntegration} /> )} {showEditIntegrationModal && integrations && ( diff --git a/frontend/pages/admin/IntegrationsPage/components/CreateIntegrationModal/_styles.scss b/frontend/pages/admin/IntegrationsPage/components/CreateIntegrationModal/_styles.scss index 91e8836b30..e43dbdebe8 100644 --- a/frontend/pages/admin/IntegrationsPage/components/CreateIntegrationModal/_styles.scss +++ b/frontend/pages/admin/IntegrationsPage/components/CreateIntegrationModal/_styles.scss @@ -15,14 +15,4 @@ &__info-header { margin-bottom: $pad-xlarge; } - - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - margin-top: $pad-xxlarge; - } - - &__btn { - margin-left: 12px; - } } diff --git a/frontend/pages/admin/IntegrationsPage/components/DeleteIntegrationModal/DeleteIntegrationModal.tsx b/frontend/pages/admin/IntegrationsPage/components/DeleteIntegrationModal/DeleteIntegrationModal.tsx index 5d38fca8b9..9d230e8138 100644 --- a/frontend/pages/admin/IntegrationsPage/components/DeleteIntegrationModal/DeleteIntegrationModal.tsx +++ b/frontend/pages/admin/IntegrationsPage/components/DeleteIntegrationModal/DeleteIntegrationModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React from "react"; import Modal from "components/Modal"; import Button from "components/buttons/Button"; @@ -10,6 +10,7 @@ interface IDeleteIntegrationModalProps { projectKey: string; onSubmit: () => void; onCancel: () => void; + isUpdatingIntegration: boolean; } const DeleteIntegrationModal = ({ @@ -17,6 +18,7 @@ const DeleteIntegrationModal = ({ projectKey, onSubmit, onCancel, + isUpdatingIntegration, }: IDeleteIntegrationModalProps): JSX.Element => { return (

The automations that use this integration will be turned off.

+ -
diff --git a/frontend/pages/admin/IntegrationsPage/components/EditIntegrationModal/_styles.scss b/frontend/pages/admin/IntegrationsPage/components/EditIntegrationModal/_styles.scss deleted file mode 100644 index 60adcf5f04..0000000000 --- a/frontend/pages/admin/IntegrationsPage/components/EditIntegrationModal/_styles.scss +++ /dev/null @@ -1,11 +0,0 @@ -.edit-team-modal { - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - margin-top: $pad-xxlarge; - } - - &__btn { - margin-left: 12px; - } -} diff --git a/frontend/pages/admin/IntegrationsPage/components/IntegrationForm/IntegrationForm.tsx b/frontend/pages/admin/IntegrationsPage/components/IntegrationForm/IntegrationForm.tsx index 8be3992555..de9e41ba61 100644 --- a/frontend/pages/admin/IntegrationsPage/components/IntegrationForm/IntegrationForm.tsx +++ b/frontend/pages/admin/IntegrationsPage/components/IntegrationForm/IntegrationForm.tsx @@ -239,7 +239,7 @@ const IntegrationForm = ({ } /> )} -
+
-
{" "} +
the integration. -
diff --git a/frontend/pages/admin/IntegrationsPage/components/IntegrationForm/_styles.scss b/frontend/pages/admin/IntegrationsPage/components/IntegrationForm/_styles.scss index 42cb58b9a4..01de0f74b7 100644 --- a/frontend/pages/admin/IntegrationsPage/components/IntegrationForm/_styles.scss +++ b/frontend/pages/admin/IntegrationsPage/components/IntegrationForm/_styles.scss @@ -84,15 +84,6 @@ } } - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - } - - &__btn { - margin-left: 12px; - } - &__invite-admin { margin-bottom: 8px; } diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/MembersPage.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/MembersPage.tsx index 149100d656..32e931c97c 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/MembersPage.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/MembersPage.tsx @@ -67,6 +67,7 @@ const MembersPage = ({ false ); const [isLoading, setIsLoading] = useState(false); + const [isUpdatingMembers, setIsUpdatingMembers] = useState(false); const [userEditing, setUserEditing] = useState(); const [searchString, setSearchString] = useState(""); const [createUserErrors, setCreateUserErrors] = useState( @@ -143,7 +144,7 @@ const MembersPage = ({ const onRemoveMemberSubmit = useCallback(() => { const removedUsers = { users: [{ id: userEditing?.id }] }; - setIsLoading(true); + setIsUpdatingMembers(true); teamsAPI .removeMembers(teamId, removedUsers) .then(() => { @@ -160,7 +161,7 @@ const MembersPage = ({ renderFlash("error", "Unable to remove members. Please try again.") ) .finally(() => { - setIsLoading(false); + setIsUpdatingMembers(false); toggleRemoveMemberModal(); refetchUsers(); }); @@ -197,7 +198,7 @@ const MembersPage = ({ ); const onCreateMemberSubmit = (formData: IFormData) => { - setIsLoading(true); + setIsUpdatingMembers(true); if (formData.newUserType === NewUserType.AdminInvited) { const requestData = { @@ -238,7 +239,7 @@ const MembersPage = ({ } }) .finally(() => { - setIsLoading(false); + setIsUpdatingMembers(false); }); } else { const requestData = { @@ -269,7 +270,7 @@ const MembersPage = ({ } }) .finally(() => { - setIsLoading(false); + setIsUpdatingMembers(false); }); } }; @@ -281,7 +282,7 @@ const MembersPage = ({ formData ); - setIsLoading(true); + setIsUpdatingMembers(true); const userName = userEditing?.name; @@ -310,7 +311,6 @@ const MembersPage = ({ } else { refetchUsers(); } - setIsLoading(false); toggleEditMemberModal(); }) .catch((userErrors: { data: IApiError }) => { @@ -324,6 +324,9 @@ const MembersPage = ({ `Could not edit ${userName || "member"}. Please try again.` ); } + }) + .finally(() => { + setIsUpdatingMembers(false); }); }, [toggleEditMemberModal, userEditing, refetchUsers] @@ -451,7 +454,7 @@ const MembersPage = ({ isSsoEnabled={userEditing?.sso_enabled} isModifiedByGlobalAdmin={isGlobalAdmin} currentTeam={currentTeam} - isLoading={isLoading} + isUpdatingUsers={isUpdatingMembers} /> )} {showCreateUserModal && ( @@ -468,14 +471,14 @@ const MembersPage = ({ canUseSso={canUseSso} currentTeam={currentTeam} isModifiedByGlobalAdmin={isGlobalAdmin} - isLoading={isLoading} + isUpdatingUsers={isUpdatingMembers} /> )} {showRemoveMemberModal && currentTeam && ( diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/AddMemberModal/AddMemberModal.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/AddMemberModal/AddMemberModal.tsx index 73e30002db..0a5fb0e1a4 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/AddMemberModal/AddMemberModal.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/AddMemberModal/AddMemberModal.tsx @@ -65,21 +65,16 @@ const AddMemberModal = ({

-
+
-
diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/AddMemberModal/_styles.scss b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/AddMemberModal/_styles.scss index fe39582dac..08174655e3 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/AddMemberModal/_styles.scss +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/AddMemberModal/_styles.scss @@ -3,14 +3,4 @@ font-weight: $bold; margin-bottom: 4px; } - - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - margin-top: $pad-xxlarge; - } - - &__btn { - margin-left: 12px; - } } diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/RemoveMemberModal/RemoveMemberModal.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/RemoveMemberModal/RemoveMemberModal.tsx index 37aac54683..564d9384b4 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/RemoveMemberModal/RemoveMemberModal.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/RemoveMemberModal/RemoveMemberModal.tsx @@ -1,15 +1,14 @@ -import React, { useEffect } from "react"; +import React from "react"; import Modal from "components/Modal"; import Button from "components/buttons/Button"; -import Spinner from "components/Spinner"; const baseClass = "remove-member-modal"; interface IDeleteTeamModalProps { memberName: string; teamName: string; - isLoading: boolean; + isUpdatingMembers: boolean; onSubmit: () => void; onCancel: () => void; } @@ -17,7 +16,7 @@ interface IDeleteTeamModalProps { const RemoveMemberModal = ({ memberName, teamName, - isLoading, + isUpdatingMembers, onSubmit, onCancel, }: IDeleteTeamModalProps): JSX.Element => { @@ -28,38 +27,31 @@ const RemoveMemberModal = ({ onEnter={onSubmit} className={baseClass} > - {isLoading ? ( - - ) : ( -
-

- You are about to remove{" "} - {memberName} from{" "} - {teamName}. -

-

- If {memberName} is not a member of any other team, they will lose - access to Fleet. -

-
- - -
-
- )} +
+

+ You are about to remove{" "} + {memberName} from{" "} + {teamName}. +

+

+ If {memberName} is not a member of any other team, they will lose + access to Fleet. +

+
+ + +
+
); }; diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/RemoveMemberModal/_styles.scss b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/RemoveMemberModal/_styles.scss index 411cd785fb..6541dc2212 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/RemoveMemberModal/_styles.scss +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/MembersPage/components/RemoveMemberModal/_styles.scss @@ -7,14 +7,4 @@ &__warning { color: $ui-error; } - - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - margin-top: $pad-xxlarge; - } - - &__btn { - margin-left: 12px; - } } diff --git a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx index 92b4367a63..d981de7e76 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamDetailsWrapper/TeamDetailsWrapper.tsx @@ -127,9 +127,8 @@ const TeamDetailsWrapper = ({ const [backendValidators, setBackendValidators] = useState<{ [key: string]: string; }>({}); - const [teamIsRemoving, setTeamIsRemoving] = useState(false); - const [teamIsEditing, setTeamIsEditing] = useState(false); - + const [isUpdatingTeams, setIsUpdatingTeams] = useState(false); + const [isUpdatingSecret, setIsUpdatingSecret] = useState(false); const { refetch: refetchMe } = useQuery(["me"], () => usersAPI.me(), { enabled: false, onSuccess: ({ user, available_teams }: IGetMeResponse) => { @@ -235,7 +234,7 @@ const TeamDetailsWrapper = ({ if (enrollSecretString) { newSecrets.push({ secret: enrollSecretString }); } - + setIsUpdatingSecret(true); try { await enrollSecretsAPI.modifyTeamEnrollSecrets(teamIdFromURL, newSecrets); refetchTeamSecrets(); @@ -254,6 +253,8 @@ const TeamDetailsWrapper = ({ selectedSecret ? "edit" : "add" } enroll secret. Please try again.` ); + } finally { + setIsUpdatingSecret(false); } }; @@ -264,7 +265,7 @@ const TeamDetailsWrapper = ({ const newSecrets = currentSecrets.filter( (s) => s.secret !== selectedSecret?.secret ); - + setIsUpdatingSecret(true); try { await enrollSecretsAPI.modifyTeamEnrollSecrets(teamIdFromURL, newSecrets); refetchTeamSecrets(); @@ -274,6 +275,8 @@ const TeamDetailsWrapper = ({ } catch (error) { console.error(error); renderFlash("error", "Could not delete enroll secret. Please try again."); + } finally { + setIsUpdatingSecret(false); } }; @@ -282,7 +285,7 @@ const TeamDetailsWrapper = ({ return false; } - setTeamIsRemoving(true); + setIsUpdatingTeams(true); try { await teamsAPI.destroy(currentTeam.id); @@ -294,7 +297,7 @@ const TeamDetailsWrapper = ({ return false; } finally { toggleDeleteTeamModal(); - setTeamIsRemoving(false); + setIsUpdatingTeams(false); } }, [toggleDeleteTeamModal, currentTeam?.id]); @@ -313,7 +316,7 @@ const TeamDetailsWrapper = ({ return; } - setTeamIsEditing(true); + setIsUpdatingTeams(true); try { await teamsAPI.update(updatedAttrs, currentTeam.id); @@ -342,7 +345,7 @@ const TeamDetailsWrapper = ({ return false; } finally { - setTeamIsEditing(false); + setIsUpdatingTeams(false); } }, [toggleEditTeamModal, currentTeam, setBackendValidators] @@ -503,6 +506,7 @@ const TeamDetailsWrapper = ({ onSaveSecret={onSaveSecret} toggleSecretEditorModal={toggleSecretEditorModal} selectedSecret={selectedSecret} + isUpdatingSecret={isUpdatingSecret} /> )} {showDeleteSecretModal && ( @@ -511,6 +515,7 @@ const TeamDetailsWrapper = ({ selectedTeam={teamIdFromURL} teams={teams || []} toggleDeleteSecretModal={toggleDeleteSecretModal} + isUpdatingSecret={isUpdatingSecret} /> )} {showDeleteTeamModal && ( @@ -518,7 +523,7 @@ const TeamDetailsWrapper = ({ onCancel={toggleDeleteTeamModal} onSubmit={onDeleteSubmit} name={currentTeam.name} - isLoading={teamIsRemoving} + isUpdatingTeams={isUpdatingTeams} /> )} {showEditTeamModal && ( @@ -527,7 +532,7 @@ const TeamDetailsWrapper = ({ onSubmit={onEditSubmit} defaultName={currentTeam.name} backendValidators={backendValidators} - isLoading={teamIsEditing} + isUpdatingTeams={isUpdatingTeams} /> )} {children} diff --git a/frontend/pages/admin/TeamManagementPage/TeamManagementPage.tsx b/frontend/pages/admin/TeamManagementPage/TeamManagementPage.tsx index 06e325ded1..237b4cd126 100644 --- a/frontend/pages/admin/TeamManagementPage/TeamManagementPage.tsx +++ b/frontend/pages/admin/TeamManagementPage/TeamManagementPage.tsx @@ -25,9 +25,7 @@ const noTeamsClass = "no-teams"; const TeamManagementPage = (): JSX.Element => { const { renderFlash } = useContext(NotificationContext); - const [teamIsLoading, setTeamIsLoading] = useState(false); - const [teamIsEditing, setTeamIsEditing] = useState(false); - const [teamIsRemoving, setTeamIsRemoving] = useState(false); + const [isUpdatingTeams, setIsUpdatingTeams] = useState(false); const [showCreateTeamModal, setShowCreateTeamModal] = useState(false); const [showDeleteTeamModal, setShowDeleteTeamModal] = useState(false); const [showEditTeamModal, setShowEditTeamModal] = useState(false); @@ -96,7 +94,7 @@ const TeamManagementPage = (): JSX.Element => { const onCreateSubmit = useCallback( (formData: ITeamFormData) => { - setTeamIsLoading(true); + setIsUpdatingTeams(true); teamsAPI .create(formData) .then(() => { @@ -116,7 +114,7 @@ const TeamManagementPage = (): JSX.Element => { } }) .finally(() => { - setTeamIsLoading(false); + setIsUpdatingTeams(false); }); }, [toggleCreateTeamModal] @@ -124,7 +122,7 @@ const TeamManagementPage = (): JSX.Element => { const onDeleteSubmit = useCallback(() => { if (teamEditing) { - setTeamIsRemoving(true); + setIsUpdatingTeams(true); teamsAPI .destroy(teamEditing.id) .then(() => { @@ -137,7 +135,7 @@ const TeamManagementPage = (): JSX.Element => { ); }) .finally(() => { - setTeamIsRemoving(true); + setIsUpdatingTeams(false); refetchTeams(); toggleDeleteTeamModal(); }); @@ -149,7 +147,7 @@ const TeamManagementPage = (): JSX.Element => { if (formData.name === teamEditing?.name) { toggleEditTeamModal(); } else if (teamEditing) { - setTeamIsEditing(true); + setIsUpdatingTeams(true); teamsAPI .update(formData, teamEditing.id) .then(() => { @@ -175,7 +173,7 @@ const TeamManagementPage = (): JSX.Element => { } }) .finally(() => { - setTeamIsEditing(false); + setIsUpdatingTeams(false); }); } }, @@ -264,7 +262,7 @@ const TeamManagementPage = (): JSX.Element => { onCancel={toggleCreateTeamModal} onSubmit={onCreateSubmit} backendValidators={backendValidators} - isLoading={teamIsLoading} + isUpdatingTeams={isUpdatingTeams} /> )} {showDeleteTeamModal && ( @@ -272,7 +270,7 @@ const TeamManagementPage = (): JSX.Element => { onCancel={toggleDeleteTeamModal} onSubmit={onDeleteSubmit} name={teamEditing?.name || ""} - isLoading={teamIsRemoving} + isUpdatingTeams={isUpdatingTeams} /> )} {showEditTeamModal && ( @@ -281,7 +279,7 @@ const TeamManagementPage = (): JSX.Element => { onSubmit={onEditSubmit} defaultName={teamEditing?.name || ""} backendValidators={backendValidators} - isLoading={teamIsEditing} + isUpdatingTeams={isUpdatingTeams} /> )}
diff --git a/frontend/pages/admin/TeamManagementPage/components/CreateTeamModal/CreateTeamModal.tsx b/frontend/pages/admin/TeamManagementPage/components/CreateTeamModal/CreateTeamModal.tsx index 06556245fa..35fe247739 100644 --- a/frontend/pages/admin/TeamManagementPage/components/CreateTeamModal/CreateTeamModal.tsx +++ b/frontend/pages/admin/TeamManagementPage/components/CreateTeamModal/CreateTeamModal.tsx @@ -7,7 +7,6 @@ import Button from "components/buttons/Button"; import InfoBanner from "components/InfoBanner/InfoBanner"; // @ts-ignore import InputField from "components/forms/fields/InputField"; -import Spinner from "components/Spinner"; const baseClass = "create-team-modal"; @@ -15,14 +14,14 @@ interface ICreateTeamModalProps { onCancel: () => void; onSubmit: (formData: ITeamFormData) => void; backendValidators: { [key: string]: string }; - isLoading: boolean; + isUpdatingTeams: boolean; } const CreateTeamModal = ({ onCancel, onSubmit, backendValidators, - isLoading, + isUpdatingTeams, }: ICreateTeamModalProps): JSX.Element => { const [name, setName] = useState(""); const [errors, setErrors] = useState<{ [key: string]: string }>( @@ -53,43 +52,45 @@ const CreateTeamModal = ({ return ( - {isLoading ? ( - - ) : ( -
- - -

- Need to test queries and configurations before deploying? -

-

- A popular pattern is to end a team’s name with “- Sandbox”, then - you can use this to test new queries and configuration with - staging hosts or volunteers acting as canaries. -

-
-
- - -
- - )} +
+ + +

+ Need to test queries and configurations before deploying? +

+

+ A popular pattern is to end a team’s name with “- Sandbox”, then you + can use this to test new queries and configuration with staging + hosts or volunteers acting as canaries. +

+
+
+ + +
+
); }; diff --git a/frontend/pages/admin/TeamManagementPage/components/DeleteTeamModal/DeleteTeamModal.tsx b/frontend/pages/admin/TeamManagementPage/components/DeleteTeamModal/DeleteTeamModal.tsx index fa3ad467f0..c94b041e70 100644 --- a/frontend/pages/admin/TeamManagementPage/components/DeleteTeamModal/DeleteTeamModal.tsx +++ b/frontend/pages/admin/TeamManagementPage/components/DeleteTeamModal/DeleteTeamModal.tsx @@ -1,21 +1,20 @@ -import React, { useEffect } from "react"; +import React from "react"; import Modal from "components/Modal"; import Button from "components/buttons/Button"; -import Spinner from "components/Spinner"; const baseClass = "delete-team-modal"; interface IDeleteTeamModalProps { name: string; - isLoading: boolean; + isUpdatingTeams: boolean; onSubmit: () => void; onCancel: () => void; } const DeleteTeamModal = ({ name, - isLoading, + isUpdatingTeams, onSubmit, onCancel, }: IDeleteTeamModalProps): JSX.Element => { @@ -26,31 +25,31 @@ const DeleteTeamModal = ({ onEnter={onSubmit} className={baseClass} > - {isLoading ? ( - - ) : ( -
-

- You are about to delete{" "} - {name} from Fleet. -

-

- Members of this team who are not members of other teams will lose - access to Fleet. -

-

- This action cannot be undone. -

-
- - -
-
- )} +
+

+ You are about to delete{" "} + {name} from Fleet. +

+

+ Members of this team who are not members of other teams will lose + access to Fleet. +

+

This action cannot be undone.

+
+ + +
+
); }; diff --git a/frontend/pages/admin/TeamManagementPage/components/EditTeamModal/EditTeamModal.tsx b/frontend/pages/admin/TeamManagementPage/components/EditTeamModal/EditTeamModal.tsx index c6f8a4effe..69bcf56118 100644 --- a/frontend/pages/admin/TeamManagementPage/components/EditTeamModal/EditTeamModal.tsx +++ b/frontend/pages/admin/TeamManagementPage/components/EditTeamModal/EditTeamModal.tsx @@ -6,7 +6,6 @@ import Modal from "components/Modal"; // @ts-ignore import InputField from "components/forms/fields/InputField"; import Button from "components/buttons/Button"; -import Spinner from "components/Spinner"; const baseClass = "edit-team-modal"; @@ -15,7 +14,7 @@ interface IEditTeamModalProps { onSubmit: (formData: ITeamFormData) => void; defaultName: string; backendValidators: { [key: string]: string }; - isLoading: boolean; + isUpdatingTeams: boolean; } const EditTeamModal = ({ @@ -23,7 +22,7 @@ const EditTeamModal = ({ onSubmit, defaultName, backendValidators, - isLoading, + isUpdatingTeams, }: IEditTeamModalProps): JSX.Element => { const [name, setName] = useState(defaultName); const [errors, setErrors] = useState<{ [key: string]: string }>( @@ -49,42 +48,35 @@ const EditTeamModal = ({ return ( - {isLoading ? ( - - ) : ( -
- -
- - -
- - )} +
+ +
+ + +
+
); }; diff --git a/frontend/pages/admin/TeamManagementPage/components/EditTeamModal/_styles.scss b/frontend/pages/admin/TeamManagementPage/components/EditTeamModal/_styles.scss deleted file mode 100644 index 60adcf5f04..0000000000 --- a/frontend/pages/admin/TeamManagementPage/components/EditTeamModal/_styles.scss +++ /dev/null @@ -1,11 +0,0 @@ -.edit-team-modal { - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - margin-top: $pad-xxlarge; - } - - &__btn { - margin-left: 12px; - } -} diff --git a/frontend/pages/admin/UserManagementPage/components/CreateUserModal/CreateUserModal.tsx b/frontend/pages/admin/UserManagementPage/components/CreateUserModal/CreateUserModal.tsx index e880e11688..c917ee31bb 100644 --- a/frontend/pages/admin/UserManagementPage/components/CreateUserModal/CreateUserModal.tsx +++ b/frontend/pages/admin/UserManagementPage/components/CreateUserModal/CreateUserModal.tsx @@ -19,7 +19,7 @@ interface ICreateUserModalProps { currentTeam?: ITeam; canUseSso: boolean; // corresponds to whether SSO is enabled for the organization isModifiedByGlobalAdmin?: boolean | false; - isLoading?: boolean | false; + isUpdatingUsers?: boolean | false; serverErrors?: { base: string; email: string }; createUserErrors?: IUserFormErrors; } @@ -38,36 +38,30 @@ const CreateUserModal = ({ smtpConfigured, canUseSso, isModifiedByGlobalAdmin, - isLoading, + isUpdatingUsers, serverErrors, createUserErrors, }: ICreateUserModalProps): JSX.Element => { return ( - <> - {isLoading && ( -
- -
- )} - - +
); }; diff --git a/frontend/pages/admin/UserManagementPage/components/CreateUserModal/_styles.scss b/frontend/pages/admin/UserManagementPage/components/CreateUserModal/_styles.scss deleted file mode 100644 index b9e2d3b058..0000000000 --- a/frontend/pages/admin/UserManagementPage/components/CreateUserModal/_styles.scss +++ /dev/null @@ -1,15 +0,0 @@ -.create-user-modal { - .loading-spinner { - width: 100%; - height: 100%; - position: absolute; - top: 0; - left: 0; - display: flex; - align-items: center; - background: rgba(255, 255, 255, 0.75); - z-index: 1; - border-radius: 8px; - margin: 0; - } -} diff --git a/frontend/pages/admin/UserManagementPage/components/DeleteUserForm/DeleteUserForm.tsx b/frontend/pages/admin/UserManagementPage/components/DeleteUserForm/DeleteUserForm.tsx deleted file mode 100644 index 30de3ec5da..0000000000 --- a/frontend/pages/admin/UserManagementPage/components/DeleteUserForm/DeleteUserForm.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { useEffect } from "react"; -import Button from "components/buttons/Button"; - -const baseClass = "delete-user-form"; - -interface IDeleteUserForm { - name: string; - onDelete: () => void; - onCancel: () => void; -} - -const DeleteUserForm = ({ - name, - onDelete, - onCancel, -}: IDeleteUserForm): JSX.Element => { - useEffect(() => { - const listener = (event: KeyboardEvent) => { - if (event.code === "Enter" || event.code === "NumpadEnter") { - event.preventDefault(); - onDelete(); - } - }; - - document.addEventListener("keydown", listener); - return () => { - document.removeEventListener("keydown", listener); - }; - }, []); - - return ( -
-

- You are about to delete{" "} - {name} from Fleet. -

-

This action cannot be undone.

-
- - -
-
- ); -}; - -export default DeleteUserForm; diff --git a/frontend/pages/admin/UserManagementPage/components/DeleteUserForm/_styles.scss b/frontend/pages/admin/UserManagementPage/components/DeleteUserForm/_styles.scss deleted file mode 100644 index 5d0c8abbe7..0000000000 --- a/frontend/pages/admin/UserManagementPage/components/DeleteUserForm/_styles.scss +++ /dev/null @@ -1,22 +0,0 @@ -.delete-user-form { - margin-top: $pad-large; - - &__name { - font-weight: $bold; - } - - &__warning { - color: $ui-error; - } - - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - } - - &__btn { - height: 38px; - margin-bottom: 5px; - margin-left: 15px; - } -} diff --git a/frontend/pages/admin/UserManagementPage/components/DeleteUserForm/index.ts b/frontend/pages/admin/UserManagementPage/components/DeleteUserForm/index.ts deleted file mode 100644 index 7ccf11612d..0000000000 --- a/frontend/pages/admin/UserManagementPage/components/DeleteUserForm/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./DeleteUserForm"; diff --git a/frontend/pages/admin/UserManagementPage/components/DeleteUserModal/DeleteUserModal.tsx b/frontend/pages/admin/UserManagementPage/components/DeleteUserModal/DeleteUserModal.tsx new file mode 100644 index 0000000000..9f581a1461 --- /dev/null +++ b/frontend/pages/admin/UserManagementPage/components/DeleteUserModal/DeleteUserModal.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import Button from "components/buttons/Button"; +import Modal from "components/Modal"; + +const baseClass = "delete-user-form"; + +interface IDeleteUserModal { + name: string; + onDelete: () => void; + onCancel: () => void; + isUpdatingUsers: boolean; +} + +const DeleteUserModal = ({ + name, + onDelete, + onCancel, + isUpdatingUsers, +}: IDeleteUserModal): JSX.Element => { + return ( + +
+

+ You are about to delete{" "} + {name} from Fleet. +

+

This action cannot be undone.

+
+ + +
+
+
+ ); +}; + +export default DeleteUserModal; diff --git a/frontend/pages/admin/UserManagementPage/components/DeleteUserModal/_styles.scss b/frontend/pages/admin/UserManagementPage/components/DeleteUserModal/_styles.scss new file mode 100644 index 0000000000..5c32d9b554 --- /dev/null +++ b/frontend/pages/admin/UserManagementPage/components/DeleteUserModal/_styles.scss @@ -0,0 +1,9 @@ +.delete-user-modal { + &__name { + font-weight: $bold; + } + + &__warning { + color: $ui-error; + } +} diff --git a/frontend/pages/admin/UserManagementPage/components/DeleteUserModal/index.ts b/frontend/pages/admin/UserManagementPage/components/DeleteUserModal/index.ts new file mode 100644 index 0000000000..6eecec5929 --- /dev/null +++ b/frontend/pages/admin/UserManagementPage/components/DeleteUserModal/index.ts @@ -0,0 +1 @@ +export { default } from "./DeleteUserModal"; diff --git a/frontend/pages/admin/UserManagementPage/components/EditUserModal/EditUserModal.tsx b/frontend/pages/admin/UserManagementPage/components/EditUserModal/EditUserModal.tsx index b70ac1d269..0d04785381 100644 --- a/frontend/pages/admin/UserManagementPage/components/EditUserModal/EditUserModal.tsx +++ b/frontend/pages/admin/UserManagementPage/components/EditUserModal/EditUserModal.tsx @@ -24,7 +24,7 @@ interface IEditUserModalProps { editUserErrors?: IUserFormErrors; isModifiedByGlobalAdmin?: boolean | false; isInvitePending?: boolean; - isLoading: boolean; + isUpdatingUsers: boolean; } const baseClass = "edit-user-modal"; @@ -46,7 +46,7 @@ const EditUserModal = ({ editUserErrors, isModifiedByGlobalAdmin, isInvitePending, - isLoading, + isUpdatingUsers, }: IEditUserModalProps): JSX.Element => { return ( - {isLoading ? ( - - ) : ( - - )} + ); }; diff --git a/frontend/pages/admin/UserManagementPage/components/EditUserModal/_styles.scss b/frontend/pages/admin/UserManagementPage/components/EditUserModal/_styles.scss deleted file mode 100644 index 460f09a6f3..0000000000 --- a/frontend/pages/admin/UserManagementPage/components/EditUserModal/_styles.scss +++ /dev/null @@ -1,16 +0,0 @@ -.edit-user-modal { - position: relative; - .loading-spinner { - width: 100%; - height: 100%; - position: absolute; - top: 0; - left: 0; - display: flex; - align-items: center; - background: rgba(255, 255, 255, 0.75); - z-index: 1; - border-radius: 8px; - margin: 0; - } -} diff --git a/frontend/pages/admin/UserManagementPage/components/ResetPasswordModal/ResetPasswordModal.tsx b/frontend/pages/admin/UserManagementPage/components/ResetPasswordModal/ResetPasswordModal.tsx index 8e9095d052..d2f665a104 100644 --- a/frontend/pages/admin/UserManagementPage/components/ResetPasswordModal/ResetPasswordModal.tsx +++ b/frontend/pages/admin/UserManagementPage/components/ResetPasswordModal/ResetPasswordModal.tsx @@ -29,20 +29,15 @@ const ResetPasswordModal = ({
This will revoke all active Fleet API tokens for this user.

-
+
-
diff --git a/frontend/pages/admin/UserManagementPage/components/ResetPasswordModal/_styles.scss b/frontend/pages/admin/UserManagementPage/components/ResetPasswordModal/_styles.scss deleted file mode 100644 index 77003afbba..0000000000 --- a/frontend/pages/admin/UserManagementPage/components/ResetPasswordModal/_styles.scss +++ /dev/null @@ -1,19 +0,0 @@ -.reset-password-modal { - margin-top: $pad-large; - - p { - margin-bottom: 40px; - font-size: $small; - } - - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - } - - &__btn { - height: 38px; - margin-bottom: 5px; - margin-left: 15px; - } -} diff --git a/frontend/pages/admin/UserManagementPage/components/ResetSessionsModal/ResetSessionsModal.tsx b/frontend/pages/admin/UserManagementPage/components/ResetSessionsModal/ResetSessionsModal.tsx index fff919785a..6af237b636 100644 --- a/frontend/pages/admin/UserManagementPage/components/ResetSessionsModal/ResetSessionsModal.tsx +++ b/frontend/pages/admin/UserManagementPage/components/ResetSessionsModal/ResetSessionsModal.tsx @@ -28,20 +28,15 @@ const ResetSessionsModal = ({
This will revoke all active Fleet API tokens for this user.

-
+
-
diff --git a/frontend/pages/admin/UserManagementPage/components/ResetSessionsModal/_styles.scss b/frontend/pages/admin/UserManagementPage/components/ResetSessionsModal/_styles.scss deleted file mode 100644 index ef9495c3ca..0000000000 --- a/frontend/pages/admin/UserManagementPage/components/ResetSessionsModal/_styles.scss +++ /dev/null @@ -1,19 +0,0 @@ -.reset-sessions-modal { - margin-top: $pad-large; - - p { - margin-bottom: 40px; - font-size: $small; - } - - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - } - - &__btn { - height: 38px; - margin-bottom: 5px; - margin-left: 15px; - } -} diff --git a/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx b/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx index fe09c7d363..f57750fa85 100644 --- a/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx +++ b/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx @@ -85,6 +85,7 @@ interface ICreateUserFormProps { isInvitePending?: boolean; serverErrors?: { base: string; email: string }; // "server" because this form does its own client validation createOrEditUserErrors?: IUserFormErrors; + isUpdatingUsers?: boolean; } const UserForm = ({ @@ -108,6 +109,7 @@ const UserForm = ({ isInvitePending, serverErrors, createOrEditUserErrors, + isUpdatingUsers, }: ICreateUserFormProps): JSX.Element => { const { renderFlash } = useContext(NotificationContext); @@ -567,20 +569,18 @@ const UserForm = ({ )} {!isPremiumTier && renderGlobalRoleForm()} -
+
-
diff --git a/frontend/pages/admin/UserManagementPage/components/UserForm/_styles.scss b/frontend/pages/admin/UserManagementPage/components/UserForm/_styles.scss index 39491bbe9b..efa0d58b6e 100644 --- a/frontend/pages/admin/UserManagementPage/components/UserForm/_styles.scss +++ b/frontend/pages/admin/UserManagementPage/components/UserForm/_styles.scss @@ -84,15 +84,6 @@ } } - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - } - - &__btn { - margin-left: 12px; - } - &__invite-admin { margin-bottom: 8px; } diff --git a/frontend/pages/admin/UserManagementPage/components/UsersTable/UsersTable.tsx b/frontend/pages/admin/UserManagementPage/components/UsersTable/UsersTable.tsx index df656f4229..22df1a4c53 100644 --- a/frontend/pages/admin/UserManagementPage/components/UsersTable/UsersTable.tsx +++ b/frontend/pages/admin/UserManagementPage/components/UsersTable/UsersTable.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback, useContext, useEffect } from "react"; +import React, { useState, useCallback, useContext } from "react"; import { InjectedRouter } from "react-router"; import { useQuery } from "react-query"; import memoize from "memoize-one"; @@ -22,7 +22,7 @@ import Modal from "components/Modal"; import { DEFAULT_CREATE_USER_ERRORS } from "utilities/constants"; import EmptyUsers from "../EmptyUsers"; import { generateTableHeaders, combineDataSets } from "./UsersTableConfig"; -import DeleteUserForm from "../DeleteUserForm"; +import DeleteUserModal from "../DeleteUserModal"; import ResetPasswordModal from "../ResetPasswordModal"; import ResetSessionsModal from "../ResetSessionsModal"; import { NewUserType } from "../UserForm/UserForm"; @@ -51,8 +51,7 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { const [showResetSessionsModal, setShowResetSessionsModal] = useState( false ); - const [isLoading, setIsLoading] = useState(false); - const [isEditingUser, setIsEditingUser] = useState(false); + const [isUpdatingUsers, setIsUpdatingUsers] = useState(false); const [userEditing, setUserEditing] = useState(null); const [createUserErrors, setCreateUserErrors] = useState( DEFAULT_CREATE_USER_ERRORS @@ -210,7 +209,7 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { }; const onCreateUserSubmit = (formData: any) => { - setIsLoading(true); + setIsUpdatingUsers(true); if (formData.newUserType === NewUserType.AdminInvited) { // Do some data formatting adding `invited_by` for the request to be correct and deleteing uncessary fields @@ -247,7 +246,7 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { } }) .finally(() => { - setIsLoading(false); + setIsUpdatingUsers(false); }); } else { // Do some data formatting deleting unnecessary fields @@ -279,7 +278,7 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { } }) .finally(() => { - setIsLoading(false); + setIsUpdatingUsers(false); }); } }; @@ -296,7 +295,7 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { const userUpdatedPasswordError = "Password must meet the criteria below"; const userUpdatedError = `Could not edit ${userEditing?.name}. Please try again.`; - setIsEditingUser(true); + setIsUpdatingUsers(true); if (userEditing.type === "invite") { return ( userData && @@ -323,7 +322,7 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { } }) .finally(() => { - setIsEditingUser(false); + setIsUpdatingUsers(false); }) ); } @@ -353,12 +352,13 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { } }) .finally(() => { - setIsEditingUser(false); + setIsUpdatingUsers(false); }) ); }; const onDeleteUser = () => { + setIsUpdatingUsers(true); if (userEditing.type === "invite") { invitesAPI .destroy(userEditing.id) @@ -374,6 +374,7 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { .finally(() => { toggleDeleteUserModal(); refetchInvites(); + setIsUpdatingUsers(false); }); } else { usersAPI @@ -390,6 +391,7 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { .finally(() => { toggleDeleteUserModal(); refetchUsers(); + setIsUpdatingUsers(false); }); } }; @@ -455,7 +457,7 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { isModifiedByGlobalAdmin isInvitePending={userEditing.type === "invite"} editUserErrors={editUserErrors} - isLoading={isEditingUser} + isUpdatingUsers={isUpdatingUsers} /> @@ -474,7 +476,7 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { isPremiumTier={isPremiumTier || false} smtpConfigured={config?.smtp_settings.configured || false} canUseSso={config?.sso_settings.enable_sso || false} - isLoading={isLoading} + isUpdatingUsers={isUpdatingUsers} isModifiedByGlobalAdmin /> ); @@ -482,13 +484,12 @@ const UsersTable = ({ router }: IUsersTableProps): JSX.Element => { const renderDeleteUserModal = () => { return ( - - - + ); }; diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx index 2101256122..64377c2fd1 100644 --- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx +++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx @@ -262,6 +262,9 @@ const ManageHostsPage = ({ [key: string]: string; }>(DEFAULT_CREATE_LABEL_ERRORS); const [resetPageIndex, setResetPageIndex] = useState(false); + const [isUpdatingLabel, setIsUpdatingLabel] = useState(false); + const [isUpdatingSecret, setIsUpdatingSecret] = useState(false); + const [isUpdatingHosts, setIsUpdatingHosts] = useState(false); // ======== end states @@ -294,13 +297,18 @@ const ManageHostsPage = ({ const canEnrollGlobalHosts = isGlobalAdmin || isGlobalMaintainer; const canAddNewLabels = (isGlobalAdmin || isGlobalMaintainer) ?? false; - const { data: labels, error: labelsError, refetch: refetchLabels } = useQuery< - ILabelsResponse, - Error, - ILabel[] - >(["labels"], () => labelsAPI.loadAll(), { - select: (data: ILabelsResponse) => data.labels, - }); + const { + isLoading: isLoadingLabels, + data: labels, + error: labelsError, + refetch: refetchLabels, + } = useQuery( + ["labels"], + () => labelsAPI.loadAll(), + { + select: (data: ILabelsResponse) => data.labels, + } + ); const { isLoading: isGlobalSecretsLoading, @@ -884,6 +892,8 @@ const ManageHostsPage = ({ newSecrets.push({ secret: enrollSecretString }); } + setIsUpdatingSecret(true); + try { if (currentTeam?.id) { await enrollSecretsAPI.modifyTeamEnrollSecrets( @@ -918,6 +928,8 @@ const ManageHostsPage = ({ selectedSecret ? "edit" : "add" } enroll secret. Please try again.` ); + } finally { + setIsUpdatingSecret(false); } }; @@ -933,6 +945,8 @@ const ManageHostsPage = ({ (s) => s.secret !== selectedSecret?.secret ); + setIsUpdatingSecret(true); + try { if (currentTeam?.id) { await enrollSecretsAPI.modifyTeamEnrollSecrets( @@ -958,6 +972,8 @@ const ManageHostsPage = ({ } catch (error) { console.error(error); renderFlash("error", "Could not delete enroll secret. Please try again."); + } finally { + setIsUpdatingSecret(false); } }; @@ -968,6 +984,7 @@ const ManageHostsPage = ({ } const updateAttrs = deepDifference(formData, selectedLabel); + setIsUpdatingLabel(true); labelsAPI .update(selectedLabel, updateAttrs) @@ -1003,6 +1020,9 @@ const ManageHostsPage = ({ } else { renderFlash("error", "Could not create label. Please try again."); } + }) + .finally(() => { + setIsUpdatingLabel(false); }); }; @@ -1011,6 +1031,7 @@ const ManageHostsPage = ({ }; const onSaveAddLabel = (formData: ILabelFormData) => { + setIsUpdatingLabel(true); labelsAPI .create(formData) .then(() => { @@ -1046,6 +1067,9 @@ const ManageHostsPage = ({ } else { renderFlash("error", "Could not create label. Please try again."); } + }) + .finally(() => { + setIsUpdatingLabel(false); }); }; @@ -1061,6 +1085,7 @@ const ManageHostsPage = ({ console.error("Label isn't available. This should not happen."); return false; } + setIsUpdatingLabel(true); const { MANAGE_HOSTS } = PATHS; try { @@ -1076,9 +1101,12 @@ const ManageHostsPage = ({ queryParams, }) ); + renderFlash("success", "Successfully deleted label."); } catch (error) { console.error(error); renderFlash("error", "Could not delete label. Please try again."); + } finally { + setIsUpdatingLabel(false); } }; @@ -1093,6 +1121,8 @@ const ManageHostsPage = ({ }; const onTransferHostSubmit = async (team: ITeam) => { + setIsUpdatingHosts(true); + const teamId = typeof team.id === "number" ? team.id : null; let action = hostsAPI.transferToTeam(teamId, selectedHostIds); @@ -1145,10 +1175,14 @@ const ManageHostsPage = ({ setIsAllMatchingHostsSelected(false); } catch (error) { renderFlash("error", "Could not transfer hosts. Please try again."); + } finally { + setIsUpdatingHosts(false); } }; const onDeleteHostSubmit = async () => { + setIsUpdatingHosts(true); + let action = hostsAPI.destroyBulk(selectedHostIds); if (isAllMatchingHostsSelected) { @@ -1201,6 +1235,8 @@ const ManageHostsPage = ({ selectedHostIds.length === 1 ? "host" : "hosts" }. Please try again.` ); + } finally { + setIsUpdatingHosts(false); } }; @@ -1427,6 +1463,7 @@ const ManageHostsPage = ({ onSaveSecret={onSaveSecret} toggleSecretEditorModal={toggleSecretEditorModal} selectedSecret={selectedSecret} + isUpdatingSecret={isUpdatingSecret} /> ); @@ -1436,6 +1473,7 @@ const ManageHostsPage = ({ selectedTeam={currentTeam?.id || 0} teams={teams || []} toggleDeleteSecretModal={toggleDeleteSecretModal} + isUpdatingSecret={isUpdatingSecret} /> ); @@ -1455,6 +1493,7 @@ const ManageHostsPage = ({ ); @@ -1489,6 +1528,7 @@ const ManageHostsPage = ({ teams={teams} onSubmit={onTransferHostSubmit} onCancel={toggleTransferHostModal} + isUpdatingHosts={isUpdatingHosts} /> ); }; @@ -1499,6 +1539,7 @@ const ManageHostsPage = ({ onSubmit={onDeleteHostSubmit} onCancel={toggleDeleteHostModal} isAllMatchingHostsSelected={isAllMatchingHostsSelected} + isUpdatingHosts={isUpdatingHosts} /> ); @@ -1684,6 +1725,7 @@ const ManageHostsPage = ({ handleSubmit={onSaveAddLabel} baseError={labelsError?.message || ""} backendValidators={labelValidator} + isUpdatingLabel={isUpdatingLabel} /> ); } @@ -1697,6 +1739,7 @@ const ManageHostsPage = ({ handleSubmit={onEditLabel} baseError={labelsError?.message || ""} backendValidators={labelValidator} + isUpdatingLabel={isUpdatingLabel} isEdit /> ); @@ -1860,14 +1903,14 @@ const ManageHostsPage = ({
- +
) @@ -1912,8 +1955,9 @@ const ManageHostsPage = ({ filteredHostCount === 0 ) && ( diff --git a/frontend/pages/hosts/ManageHostsPage/_styles.scss b/frontend/pages/hosts/ManageHostsPage/_styles.scss index 9fcbac6a4a..ee4d847cac 100644 --- a/frontend/pages/hosts/ManageHostsPage/_styles.scss +++ b/frontend/pages/hosts/ManageHostsPage/_styles.scss @@ -197,16 +197,6 @@ } } - &__modal-buttons { - width: 100%; - display: flex; - justify-content: flex-end; - - .button:first-child { - margin-right: $pad-medium; - } - } - .form-field--dropdown { margin: 0; } diff --git a/frontend/pages/hosts/ManageHostsPage/components/DeleteHostModal/DeleteHostModal.tsx b/frontend/pages/hosts/ManageHostsPage/components/DeleteHostModal/DeleteHostModal.tsx index 54b117036d..f2a9758d3f 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/DeleteHostModal/DeleteHostModal.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/DeleteHostModal/DeleteHostModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React from "react"; import Modal from "components/Modal"; import Button from "components/buttons/Button"; @@ -10,6 +10,7 @@ interface IDeleteHostModalProps { onSubmit: () => void; onCancel: () => void; isAllMatchingHostsSelected: boolean; + isUpdatingHosts: boolean; } const DeleteHostModal = ({ @@ -17,6 +18,7 @@ const DeleteHostModal = ({ onSubmit, onCancel, isAllMatchingHostsSelected, + isUpdatingHosts, }: IDeleteHostModalProps): JSX.Element => { return ( -
+
-
diff --git a/frontend/pages/hosts/ManageHostsPage/components/DeleteHostModal/_styles.scss b/frontend/pages/hosts/ManageHostsPage/components/DeleteHostModal/_styles.scss deleted file mode 100644 index ca38d2d780..0000000000 --- a/frontend/pages/hosts/ManageHostsPage/components/DeleteHostModal/_styles.scss +++ /dev/null @@ -1,11 +0,0 @@ -.delete-host-modal { - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - margin-top: $pad-xxlarge; - } - - &__btn { - margin-left: 12px; - } -} diff --git a/frontend/pages/hosts/ManageHostsPage/components/DeleteLabelModal/DeleteLabelModal.tsx b/frontend/pages/hosts/ManageHostsPage/components/DeleteLabelModal/DeleteLabelModal.tsx index 61fcf59048..29086ee6e4 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/DeleteLabelModal/DeleteLabelModal.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/DeleteLabelModal/DeleteLabelModal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React from "react"; import Modal from "components/Modal"; import Button from "components/buttons/Button"; @@ -8,11 +8,13 @@ const baseClass = "delete-label-modal"; interface IDeleteLabelModalProps { onSubmit: () => void; onCancel: () => void; + isUpdatingLabel: boolean; } const DeleteLabelModal = ({ onSubmit, onCancel, + isUpdatingLabel, }: IDeleteLabelModalProps): JSX.Element => { return (

Are you sure you wish to delete this label?

+ -
diff --git a/frontend/pages/hosts/ManageHostsPage/components/EditColumnsModal/EditColumnsModal.jsx b/frontend/pages/hosts/ManageHostsPage/components/EditColumnsModal/EditColumnsModal.jsx index be1fff5b33..e3bcade400 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/EditColumnsModal/EditColumnsModal.jsx +++ b/frontend/pages/hosts/ManageHostsPage/components/EditColumnsModal/EditColumnsModal.jsx @@ -82,16 +82,15 @@ const EditColumnsModal = ({ })}
- +
diff --git a/frontend/pages/hosts/ManageHostsPage/components/LabelForm/LabelForm.tsx b/frontend/pages/hosts/ManageHostsPage/components/LabelForm/LabelForm.tsx index ab4fd12533..9ea4020b33 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/LabelForm/LabelForm.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/LabelForm/LabelForm.tsx @@ -21,6 +21,7 @@ interface ILabelFormProps { handleSubmit: (formData: ILabelFormData) => void; onOsqueryTableSelect?: (tableName: string) => void; backendValidators: { [key: string]: string }; + isUpdatingLabel?: boolean; } const baseClass = "label-form"; @@ -60,6 +61,7 @@ const LabelForm = ({ handleSubmit, onOsqueryTableSelect, backendValidators, + isUpdatingLabel, }: ILabelFormProps): JSX.Element => { const [name, setName] = useState(selectedLabel?.name || ""); const [nameError, setNameError] = useState(""); @@ -163,6 +165,7 @@ const LabelForm = ({ selectedLabel && selectedLabel.label_membership_type === "manual"; const headerText = isEdit ? "Edit label" : "New label"; const saveBtnText = isEdit ? "Update label" : "Save label"; + const saveBtnClass = isEdit ? "update-label-loading" : "save-label-loading"; const aceHintText = isEdit ? "Label queries are immutable. To change the query, delete this label and create a new one." : ""; @@ -240,18 +243,15 @@ const LabelForm = ({

)} -
- diff --git a/frontend/pages/hosts/ManageHostsPage/components/LabelForm/_styles.scss b/frontend/pages/hosts/ManageHostsPage/components/LabelForm/_styles.scss index 4841cd4ed6..23d688f4fd 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/LabelForm/_styles.scss +++ b/frontend/pages/hosts/ManageHostsPage/components/LabelForm/_styles.scss @@ -26,13 +26,11 @@ } } - .query-form__run-query-btn, - .query-form__stop-query-btn { - margin-left: $pad-xsmall; - } - - &__cancel-btn { - margin-right: $pad-medium; + &__button-wrap { + margin: 0; + margin-top: $pad-large; + display: flex; + gap: $pad-medium; } &__text-editor-wrapper { diff --git a/frontend/pages/hosts/ManageHostsPage/components/NoHosts/NoHosts.tsx b/frontend/pages/hosts/ManageHostsPage/components/NoHosts/NoHosts.tsx index 47c97d409f..690f7fc457 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/NoHosts/NoHosts.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/NoHosts/NoHosts.tsx @@ -38,11 +38,7 @@ const NoHosts = ({

Add your devices to Fleet

Generate an installer to add your own devices.

-
diff --git a/frontend/pages/hosts/ManageHostsPage/components/TransferHostModal/TransferHostModal.tsx b/frontend/pages/hosts/ManageHostsPage/components/TransferHostModal/TransferHostModal.tsx index 065605a961..beb23206ff 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/TransferHostModal/TransferHostModal.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/TransferHostModal/TransferHostModal.tsx @@ -15,6 +15,7 @@ interface ITransferHostModal { teams: ITeam[]; onSubmit: (team: ITeam) => void; onCancel: () => void; + isUpdatingHosts: boolean; } interface INoTeamOption { @@ -33,6 +34,7 @@ const TransferHostModal = ({ onSubmit, teams, isGlobalAdmin, + isUpdatingHosts, }: ITransferHostModal): JSX.Element => { const [selectedTeam, setSelectedTeam] = useState(); @@ -83,17 +85,19 @@ const TransferHostModal = ({

) : null}
- +
diff --git a/frontend/pages/hosts/details/DeviceUserPage/InfoModal/InfoModal.tsx b/frontend/pages/hosts/details/DeviceUserPage/InfoModal/InfoModal.tsx index d1442b3853..47fe6c92f2 100644 --- a/frontend/pages/hosts/details/DeviceUserPage/InfoModal/InfoModal.tsx +++ b/frontend/pages/hosts/details/DeviceUserPage/InfoModal/InfoModal.tsx @@ -36,13 +36,8 @@ const InfoModal = ({ onCancel }: IInfoModalProps): JSX.Element => { open new tab

-
-
diff --git a/frontend/pages/hosts/details/DeviceUserPage/InfoModal/_styles.scss b/frontend/pages/hosts/details/DeviceUserPage/InfoModal/_styles.scss index a5b77cfb6a..46d7844223 100644 --- a/frontend/pages/hosts/details/DeviceUserPage/InfoModal/_styles.scss +++ b/frontend/pages/hosts/details/DeviceUserPage/InfoModal/_styles.scss @@ -19,13 +19,4 @@ margin: 0; } } - - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - } - - &__btn { - width: 120px; - } } diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx index b67b4b8529..714fc98f58 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx +++ b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx @@ -140,6 +140,7 @@ const HostDetailsPage = ({ const [selectedPolicy, setSelectedPolicy] = useState( null ); + const [isUpdatingHost, setIsUpdatingHost] = useState(false); const [refetchStartTime, setRefetchStartTime] = useState(null); const [showRefetchSpinner, setShowRefetchSpinner] = useState(false); @@ -389,6 +390,7 @@ const HostDetailsPage = ({ const onDestroyHost = async () => { if (host) { + setIsUpdatingHost(true); try { await hostAPI.destroy(host); renderFlash( @@ -401,6 +403,7 @@ const HostDetailsPage = ({ renderFlash("error", `Host "${host.hostname}" could not be deleted.`); } finally { setShowDeleteHostModal(false); + setIsUpdatingHost(false); } } }; @@ -447,6 +450,8 @@ const HostDetailsPage = ({ }; const onTransferHostSubmit = async (team: ITeam) => { + setIsUpdatingHost(true); + const teamId = typeof team.id === "number" ? team.id : null; try { @@ -463,6 +468,8 @@ const HostDetailsPage = ({ } catch (error) { console.log(error); renderFlash("error", "Could not transfer host. Please try again."); + } finally { + setIsUpdatingHost(false); } }; @@ -633,6 +640,7 @@ const HostDetailsPage = ({ onCancel={() => setShowDeleteHostModal(false)} onSubmit={onDestroyHost} hostName={host?.hostname} + isUpdatingHost={isUpdatingHost} /> )} {showQueryHostModal && host && ( @@ -651,6 +659,7 @@ const HostDetailsPage = ({ onSubmit={onTransferHostSubmit} teams={teams || []} isGlobalAdmin={isGlobalAdmin as boolean} + isUpdatingHost={isUpdatingHost} /> )} {!!host && showPolicyDetailsModal && ( diff --git a/frontend/pages/hosts/details/HostDetailsPage/_styles.scss b/frontend/pages/hosts/details/HostDetailsPage/_styles.scss index 2e48bcd5c3..39afb2768e 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/_styles.scss +++ b/frontend/pages/hosts/details/HostDetailsPage/_styles.scss @@ -344,11 +344,7 @@ &__action-button-container { display: flex; align-items: center; - } - - &__query-button, - &__transfer-button { - margin-right: $pad-large; + gap: $pad-large; } &__back-link { diff --git a/frontend/pages/hosts/details/HostDetailsPage/modals/DeleteHostModal/DeleteHostModal.tsx b/frontend/pages/hosts/details/HostDetailsPage/modals/DeleteHostModal/DeleteHostModal.tsx index f5851e2882..20e5dd5175 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/modals/DeleteHostModal/DeleteHostModal.tsx +++ b/frontend/pages/hosts/details/HostDetailsPage/modals/DeleteHostModal/DeleteHostModal.tsx @@ -8,6 +8,7 @@ interface IDeleteHostModal { onSubmit: (team: ITeam) => void; onCancel: () => void; hostName?: string; + isUpdatingHost: boolean; } const baseClass = "delete-host-modal"; @@ -16,6 +17,7 @@ const DeleteHostModal = ({ onCancel, onSubmit, hostName, + isUpdatingHost, }: IDeleteHostModal): JSX.Element => { return (
+ -
diff --git a/frontend/pages/hosts/details/HostDetailsPage/modals/TransferHostModal/TransferHostModal.tsx b/frontend/pages/hosts/details/HostDetailsPage/modals/TransferHostModal/TransferHostModal.tsx index bae21a91e6..aeda0d8cab 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/modals/TransferHostModal/TransferHostModal.tsx +++ b/frontend/pages/hosts/details/HostDetailsPage/modals/TransferHostModal/TransferHostModal.tsx @@ -13,6 +13,7 @@ interface ITransferHostModal { teams: ITeam[]; onSubmit: (team: ITeam) => void; onCancel: () => void; + isUpdatingHost: boolean; } interface INoTeamOption { @@ -31,6 +32,7 @@ const TransferHostModal = ({ onSubmit, teams, isGlobalAdmin, + isUpdatingHost, }: ITransferHostModal): JSX.Element => { const [selectedTeam, setSelectedTeam] = useState(); @@ -81,17 +83,19 @@ const TransferHostModal = ({

)}
- +
diff --git a/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/PolicyDetailsModal.tsx b/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/PolicyDetailsModal.tsx index a50b19332c..b230751c28 100644 --- a/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/PolicyDetailsModal.tsx +++ b/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/PolicyDetailsModal.tsx @@ -29,12 +29,8 @@ const PolicyDetailsModal = ({

{policy?.resolution}

)} -
-
diff --git a/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/_styles.scss b/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/_styles.scss index bec932df7b..3c48165687 100644 --- a/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/_styles.scss +++ b/frontend/pages/hosts/details/cards/Policies/HostPoliciesTable/PolicyDetailsModal/_styles.scss @@ -9,16 +9,6 @@ font-weight: $bold; } - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - margin-top: $pad-large; - } - - &__btn { - margin-left: 12px; - } - .policy-details-modal__resolution { overflow: hidden; } diff --git a/frontend/pages/packs/EditPackPage/EditPackPage.tsx b/frontend/pages/packs/EditPackPage/EditPackPage.tsx index 4e15e592c3..8d07ffadf7 100644 --- a/frontend/pages/packs/EditPackPage/EditPackPage.tsx +++ b/frontend/pages/packs/EditPackPage/EditPackPage.tsx @@ -102,6 +102,7 @@ const EditPacksPage = ({ const [selectedPackQueryIds, setSelectedPackQueryIds] = useState< number[] | never[] >([]); + const [isUpdatingPack, setIsUpdatingPack] = useState(false); const packTargets = storedPack ? [ @@ -154,6 +155,7 @@ const EditPacksPage = ({ }; const handlePackFormSubmit = (formData: IFormData) => { + setIsUpdatingPack(true); const updatedPack = deepDifference(formData, storedPack); packsAPI .update(packId, updatedPack) @@ -173,6 +175,9 @@ const EditPacksPage = ({ } else { renderFlash("error", `Could not update pack. Please try again.`); } + }) + .finally(() => { + setIsUpdatingPack(false); }); }; @@ -180,6 +185,7 @@ const EditPacksPage = ({ formData: IPackQueryFormData, editQuery: IScheduledQuery | undefined ) => { + setIsUpdatingPack(true); const request = editQuery ? scheduledQueriesAPI.update(editQuery, formData) : scheduledQueriesAPI.create(formData); @@ -193,11 +199,13 @@ const EditPacksPage = ({ .finally(() => { togglePackQueryEditorModal(); refetchStoredPackQueries(); + setIsUpdatingPack(false); }); return false; }; const onRemovePackQuerySubmit = () => { + setIsUpdatingPack(true); const queryOrQueries = selectedPackQueryIds.length === 1 ? "query" : "queries"; @@ -221,6 +229,7 @@ const EditPacksPage = ({ .finally(() => { toggleRemovePackQueryModal(); refetchStoredPackQueries(); + setIsUpdatingPack(false); }); }; @@ -245,6 +254,7 @@ const EditPacksPage = ({ onRemovePackQueries={onRemovePackQueriesClick} scheduledQueries={storedPackQueries} isLoadingPackQueries={isStoredPackQueriesLoading} + isUpdatingPack={isUpdatingPack} /> )} {showPackQueryEditorModal && fleetQueries && ( @@ -254,6 +264,7 @@ const EditPacksPage = ({ allQueries={fleetQueries} editQuery={selectedPackQuery} packId={packId} + isUpdatingPack={isUpdatingPack} /> )} {showRemovePackQueryModal && fleetQueries && ( @@ -262,6 +273,7 @@ const EditPacksPage = ({ onSubmit={onRemovePackQuerySubmit} selectedQuery={selectedPackQuery} selectedQueryIds={selectedPackQueryIds} + isUpdatingPack={isUpdatingPack} /> )}
diff --git a/frontend/pages/packs/EditPackPage/components/PackQueryEditorModal/PackQueryEditorModal.tsx b/frontend/pages/packs/EditPackPage/components/PackQueryEditorModal/PackQueryEditorModal.tsx index 625423048c..9995b6cee5 100644 --- a/frontend/pages/packs/EditPackPage/components/PackQueryEditorModal/PackQueryEditorModal.tsx +++ b/frontend/pages/packs/EditPackPage/components/PackQueryEditorModal/PackQueryEditorModal.tsx @@ -41,6 +41,7 @@ interface IPackQueryEditorModalProps { ) => void; editQuery?: IScheduledQuery; packId: number; + isUpdatingPack: boolean; } interface INoQueryOption { id: number; @@ -63,6 +64,7 @@ const PackQueryEditorModal = ({ allQueries, editQuery, packId, + isUpdatingPack, }: IPackQueryEditorModalProps): JSX.Element => { const [selectedQuery, setSelectedQuery] = useState< IScheduledQuery | INoQueryOption @@ -242,17 +244,19 @@ const PackQueryEditorModal = ({ />
- +
diff --git a/frontend/pages/packs/EditPackPage/components/RemovePackQueryModal/RemovePackQueryModal.tsx b/frontend/pages/packs/EditPackPage/components/RemovePackQueryModal/RemovePackQueryModal.tsx index cded026e7c..b90e90e064 100644 --- a/frontend/pages/packs/EditPackPage/components/RemovePackQueryModal/RemovePackQueryModal.tsx +++ b/frontend/pages/packs/EditPackPage/components/RemovePackQueryModal/RemovePackQueryModal.tsx @@ -11,6 +11,7 @@ interface IRemovePackQueryModalProps { onSubmit: () => void; selectedQuery?: IScheduledQuery; selectedQueryIds: number[]; + isUpdatingPack: boolean; } const RemovePackQueryModal = ({ @@ -18,6 +19,7 @@ const RemovePackQueryModal = ({ onSubmit, selectedQuery, selectedQueryIds, + isUpdatingPack, }: IRemovePackQueryModalProps): JSX.Element => { const queryOrQueries = selectedQuery || selectedQueryIds?.length === 1 ? "query" : "queries"; @@ -32,12 +34,18 @@ const RemovePackQueryModal = ({ Are you sure you want to remove the selected {queryOrQueries} from your pack?
+ -
diff --git a/frontend/pages/packs/ManagePacksPage/ManagePacksPage.tsx b/frontend/pages/packs/ManagePacksPage/ManagePacksPage.tsx index feac5e715b..d66005e220 100644 --- a/frontend/pages/packs/ManagePacksPage/ManagePacksPage.tsx +++ b/frontend/pages/packs/ManagePacksPage/ManagePacksPage.tsx @@ -64,6 +64,7 @@ const ManagePacksPage = ({ router }: IManagePacksPageProps): JSX.Element => { const [showDeletePackModal, setShowDeletePackModal] = useState( false ); + const [isUpdatingPack, setIsUpdatingPack] = useState(false); const { data: packs, @@ -91,6 +92,7 @@ const ManagePacksPage = ({ router }: IManagePacksPageProps): JSX.Element => { }; const onDeletePackSubmit = useCallback(() => { + setIsUpdatingPack(true); const packOrPacks = selectedPackIds.length === 1 ? "pack" : "packs"; const promises = selectedPackIds.map((id: number) => { @@ -110,6 +112,7 @@ const ManagePacksPage = ({ router }: IManagePacksPageProps): JSX.Element => { .finally(() => { refetchPacks(); toggleDeletePackModal(); + setIsUpdatingPack(false); }); }, [refetchPacks, selectedPackIds, toggleDeletePackModal]); @@ -200,6 +203,7 @@ const ManagePacksPage = ({ router }: IManagePacksPageProps): JSX.Element => { )}
diff --git a/frontend/pages/packs/ManagePacksPage/components/DeletePackModal/DeletePackModal.tsx b/frontend/pages/packs/ManagePacksPage/components/DeletePackModal/DeletePackModal.tsx index b9e622366d..f65fffe9f5 100644 --- a/frontend/pages/packs/ManagePacksPage/components/DeletePackModal/DeletePackModal.tsx +++ b/frontend/pages/packs/ManagePacksPage/components/DeletePackModal/DeletePackModal.tsx @@ -8,11 +8,13 @@ const baseClass = "remove-pack-modal"; interface IDeletePackModalProps { onCancel: () => void; onSubmit: () => void; + isUpdatingPack: boolean; } const DeletePackModal = ({ onCancel, onSubmit, + isUpdatingPack, }: IDeletePackModalProps): JSX.Element => { return ( - +
diff --git a/frontend/pages/packs/PackComposerPage/PackComposerPage.tsx b/frontend/pages/packs/PackComposerPage/PackComposerPage.tsx index 5555735803..6c53f2d77a 100644 --- a/frontend/pages/packs/PackComposerPage/PackComposerPage.tsx +++ b/frontend/pages/packs/PackComposerPage/PackComposerPage.tsx @@ -29,6 +29,7 @@ const PackComposerPage = ({ router }: IPackComposerPageProps): JSX.Element => { const { renderFlash } = useContext(NotificationContext); const [selectedTargetsCount, setSelectedTargetsCount] = useState(0); + const [isUpdatingPack, setIsUpdatingPack] = useState(false); const onFetchTargets = ( query: IQuery, @@ -42,6 +43,8 @@ const PackComposerPage = ({ router }: IPackComposerPageProps): JSX.Element => { const handleSubmit = async (formData: IEditPackFormData) => { const { create } = packsAPI; + setIsUpdatingPack(true); + try { const { pack: { id: packID }, @@ -62,6 +65,8 @@ const PackComposerPage = ({ router }: IPackComposerPageProps): JSX.Element => { } else { renderFlash("error", "Unable to create pack."); } + } finally { + setIsUpdatingPack(false); } }; @@ -74,6 +79,7 @@ const PackComposerPage = ({ router }: IPackComposerPageProps): JSX.Element => { onFetchTargets={onFetchTargets} selectedTargetsCount={selectedTargetsCount} isPremiumTier={isPremiumTier} + isUpdatingPack={isUpdatingPack} /> diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index ec129d919c..ad3fd57c3b 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -28,7 +28,7 @@ import MainContent from "components/MainContent"; import PoliciesTable from "./components/PoliciesTable"; import ManageAutomationsModal from "./components/ManageAutomationsModal"; import AddPolicyModal from "./components/AddPolicyModal"; -import DeletePoliciesModal from "./components/DeletePoliciesModal"; +import DeletePolicyModal from "./components/DeletePolicyModal"; interface IManagePoliciesPageProps { router: InjectedRouter; @@ -75,18 +75,17 @@ const ManagePolicyPage = ({ } = useContext(PolicyContext); const { setResetSelectedRows } = useContext(TableContext); - - const [isAutomationsLoading, setIsAutomationsLoading] = useState( + const [isUpdatingAutomations, setIsUpdatingAutomations] = useState( false ); - const [isDeletingPolicy, setIsDeletingPolicy] = useState(false); + const [isUpdatingPolicies, setIsUpdatingPolicies] = useState(false); const [selectedPolicyIds, setSelectedPolicyIds] = useState([]); const [showManageAutomationsModal, setShowManageAutomationsModal] = useState( false ); const [showPreviewPayloadModal, setShowPreviewPayloadModal] = useState(false); const [showAddPolicyModal, setShowAddPolicyModal] = useState(false); - const [showDeletePoliciesModal, setShowDeletePoliciesModal] = useState(false); + const [showDeletePolicyModal, setShowDeletePolicyModal] = useState(false); const [showInheritedPolicies, setShowInheritedPolicies] = useState(false); useEffect(() => { @@ -197,8 +196,8 @@ const ManagePolicyPage = ({ const toggleAddPolicyModal = () => setShowAddPolicyModal(!showAddPolicyModal); - const toggleDeletePoliciesModal = () => - setShowDeletePoliciesModal(!showDeletePoliciesModal); + const toggleDeletePolicyModal = () => + setShowDeletePolicyModal(!showDeletePolicyModal); const toggleShowInheritedPolicies = () => setShowInheritedPolicies(!showInheritedPolicies); @@ -206,7 +205,7 @@ const ManagePolicyPage = ({ const handleUpdateAutomations = async ( requestBody: IAutomationsConfig | ITeamAutomationsConfig ) => { - setIsAutomationsLoading(true); + setIsUpdatingAutomations(true); try { await (teamId ? teamsAPI.update(requestBody, teamId) @@ -219,7 +218,7 @@ const ManagePolicyPage = ({ ); } finally { toggleManageAutomationsModal(); - setIsAutomationsLoading(false); + setIsUpdatingAutomations(false); refetchConfig(); teamId && refetchTeamConfig(); } @@ -232,14 +231,14 @@ const ManagePolicyPage = ({ toggleAddPolicyModal(); }; - const onDeletePoliciesClick = (selectedTableIds: number[]): void => { - toggleDeletePoliciesModal(); + const onDeletePolicyClick = (selectedTableIds: number[]): void => { + toggleDeletePolicyModal(); setSelectedPolicyIds(selectedTableIds); }; - const onDeletePoliciesSubmit = async () => { + const onDeletePolicySubmit = async () => { const id = currentTeam?.id; - setIsDeletingPolicy(true); + setIsUpdatingPolicies(true); try { const request = id ? teamPoliciesAPI.destroy(id, selectedPolicyIds) @@ -263,8 +262,8 @@ const ManagePolicyPage = ({ }. Please try again.` ); } finally { - toggleDeletePoliciesModal(); - setIsDeletingPolicy(false); + toggleDeletePolicyModal(); + setIsUpdatingPolicies(false); } }; @@ -426,7 +425,7 @@ const ManagePolicyPage = ({ isFetchingConfig } onAddPolicyClick={onAddPolicyClick} - onDeletePoliciesClick={onDeletePoliciesClick} + onDeletePolicyClick={onDeletePolicyClick} canAddOrDeletePolicy={canAddOrDeletePolicy} currentTeam={currentTeam} currentAutomatedPolicies={currentAutomatedPolicies} @@ -442,7 +441,7 @@ const ManagePolicyPage = ({ policiesList={globalPolicies || []} isLoading={isFetchingGlobalPolicies || isFetchingConfig} onAddPolicyClick={onAddPolicyClick} - onDeletePoliciesClick={onDeletePoliciesClick} + onDeletePolicyClick={onDeletePolicyClick} canAddOrDeletePolicy={canAddOrDeletePolicy} currentTeam={currentTeam} currentAutomatedPolicies={currentAutomatedPolicies} @@ -478,7 +477,7 @@ const ManagePolicyPage = ({ )} - {showDeletePoliciesModal && ( - )} diff --git a/frontend/pages/policies/ManagePoliciesPage/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/_styles.scss index 8bd9426c88..6339b46856 100644 --- a/frontend/pages/policies/ManagePoliciesPage/_styles.scss +++ b/frontend/pages/policies/ManagePoliciesPage/_styles.scss @@ -107,16 +107,6 @@ } } - &__modal-buttons { - width: 100%; - display: flex; - justify-content: flex-end; - - .button:first-child { - margin-right: $pad-medium; - } - } - &__add-policy-link { transition: color 150ms ease-in-out, background 150ms ease-in-out, top 50ms ease-in-out, box-shadow 50ms ease-in-out, border 50ms ease-in-out; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/AddPolicyModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/AddPolicyModal.tsx index e5024e20f6..cd612531ca 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/AddPolicyModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/AddPolicyModal.tsx @@ -72,18 +72,10 @@ const AddPolicyModal = ({ }); return ( - + <> Choose a policy template to get started or{" "} - . diff --git a/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/_styles.scss index 1a7bb3b8fe..95245cd125 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/_styles.scss +++ b/frontend/pages/policies/ManagePoliciesPage/components/AddPolicyModal/_styles.scss @@ -1,62 +1,27 @@ .add-policy-modal { - #error-icon { - height: 12px; - width: 12px; - margin-right: 8px; - } + @include position(absolute, 22px null null null); + background-color: $core-white; + width: 658px; + padding: $pad-xxlarge; + border-radius: $pad-small; - #new-tab-icon { - height: 12px; - width: 12px; - margin-left: 6px; - } + .info { + display: flex; - a { - font-size: $x-small; - color: $core-vibrant-blue; - font-weight: $bold; - text-decoration: none; - } - - &__modal { - @include position(absolute, 22px null null null); - background-color: $core-white; - width: 658px; - padding: $pad-xxlarge; - border-radius: $pad-small; - - a { - font-size: $x-small; - color: $core-vibrant-blue; + &__header { + display: block; + color: $core-fleet-black; font-weight: $bold; - text-decoration: none; - } - - .button--unstyled { font-size: $x-small; - color: $core-vibrant-blue; - font-weight: $bold; - text-decoration: none; + text-align: left; } - - .info { - display: flex; - - &__header { - display: block; - color: $core-fleet-black; - font-weight: $bold; - font-size: $x-small; - text-align: left; - } - &__data { - display: block; - color: $core-fleet-black; - font-weight: normal; - font-size: $x-small; - text-align: left; - margin-top: 10px; - } + &__data { + display: block; + color: $core-fleet-black; + font-weight: normal; + font-size: $x-small; + text-align: left; + margin-top: 10px; } } diff --git a/frontend/pages/policies/ManagePoliciesPage/components/DeletePoliciesModal/DeletePoliciesModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/DeletePoliciesModal/DeletePoliciesModal.tsx deleted file mode 100644 index 6a25606892..0000000000 --- a/frontend/pages/policies/ManagePoliciesPage/components/DeletePoliciesModal/DeletePoliciesModal.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from "react"; - -import Modal from "components/Modal"; -import Button from "components/buttons/Button"; -import Spinner from "components/Spinner"; - -const baseClass = "delete-policies-modal"; - -interface IDeletePoliciesModalProps { - isLoading: boolean; - onCancel: () => void; - onSubmit: () => void; -} - -const DeletePoliciesModal = ({ - isLoading, - onCancel, - onSubmit, -}: IDeletePoliciesModalProps): JSX.Element => { - return ( - - <> - {isLoading ? ( - - ) : ( -
- Are you sure you want to delete the selected policies? -
- - -
-
- )} - -
- ); -}; - -export default DeletePoliciesModal; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/DeletePoliciesModal/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/components/DeletePoliciesModal/_styles.scss deleted file mode 100644 index 3cefbc0f8f..0000000000 --- a/frontend/pages/policies/ManagePoliciesPage/components/DeletePoliciesModal/_styles.scss +++ /dev/null @@ -1,13 +0,0 @@ -.delete-policies-modal { - font-size: $x-small; - - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - margin-top: $pad-xxlarge; - } - - &__btn { - margin-left: 12px; - } -} diff --git a/frontend/pages/policies/ManagePoliciesPage/components/DeletePoliciesModal/index.ts b/frontend/pages/policies/ManagePoliciesPage/components/DeletePoliciesModal/index.ts deleted file mode 100644 index 71188244f5..0000000000 --- a/frontend/pages/policies/ManagePoliciesPage/components/DeletePoliciesModal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./DeletePoliciesModal"; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/DeletePolicyModal/DeletePolicyModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/DeletePolicyModal/DeletePolicyModal.tsx new file mode 100644 index 0000000000..bc6f8c568e --- /dev/null +++ b/frontend/pages/policies/ManagePoliciesPage/components/DeletePolicyModal/DeletePolicyModal.tsx @@ -0,0 +1,48 @@ +import React from "react"; + +import Modal from "components/Modal"; +import Button from "components/buttons/Button"; +import Spinner from "components/Spinner"; + +const baseClass = "delete-policy-modal"; + +interface IDeletePolicyModalProps { + isUpdatingPolicies: boolean; + onCancel: () => void; + onSubmit: () => void; +} + +const DeletePolicyModal = ({ + isUpdatingPolicies, + onCancel, + onSubmit, +}: IDeletePolicyModalProps): JSX.Element => { + return ( + +
+ Are you sure you want to delete the selected policies? +
+ + +
+
+
+ ); +}; + +export default DeletePolicyModal; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/DeletePolicyModal/index.ts b/frontend/pages/policies/ManagePoliciesPage/components/DeletePolicyModal/index.ts new file mode 100644 index 0000000000..83b12fbb30 --- /dev/null +++ b/frontend/pages/policies/ManagePoliciesPage/components/DeletePolicyModal/index.ts @@ -0,0 +1 @@ +export { default } from "./DeletePolicyModal"; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/ManageAutomationsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/ManageAutomationsModal.tsx index 54f68ca48b..1ca83e23c2 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/ManageAutomationsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/ManageAutomationsModal.tsx @@ -27,7 +27,7 @@ interface IManageAutomationsModalProps { automationsConfig: IAutomationsConfig | ITeamAutomationsConfig; availableIntegrations: IIntegrations; availablePolicies: IPolicy[]; - isAutomationsLoading: boolean; + isUpdatingAutomations: boolean; showPreviewPayloadModal: boolean; onExit: () => void; handleSubmit: (formData: IAutomationsConfig | ITeamAutomationsConfig) => void; @@ -86,8 +86,8 @@ const ManageAutomationsModal = ({ automationsConfig, availableIntegrations, availablePolicies, - isAutomationsLoading, - showPreviewPayloadModal: showPreviewModal, + isUpdatingAutomations, + showPreviewPayloadModal, onExit, handleSubmit, togglePreviewPayloadModal: togglePreviewModal, @@ -337,33 +337,29 @@ const ManageAutomationsModal = ({ ); - return showPreviewModal ? ( + return showPreviewPayloadModal ? ( renderPreview() ) : ( - <> - {isAutomationsLoading ? ( - - ) : ( -
-
- { - setIsPolicyAutomationsEnabled(!isPolicyAutomationsEnabled); - setErrors({}); - }} - inactiveText={"Policy automations disabled"} - activeText={"Policy automations enabled"} - /> -
-
-
-
- {availablePolicies?.length ? ( -
-

- {/* {errors.policyItems ? ( +

+
+ { + setIsPolicyAutomationsEnabled(!isPolicyAutomationsEnabled); + setErrors({}); + }} + inactiveText={"Policy automations disabled"} + activeText={"Policy automations enabled"} + /> +
+
+
+
+ {availablePolicies?.length ? ( +
+

+ {/* {errors.policyItems ? ( {errors.policyItems} @@ -372,86 +368,79 @@ const ManageAutomationsModal = ({ Choose which policies you would like to listen to: )} */} - - Choose which policies you would like to listen to: - -

- {policyItems && - policyItems.map((policyItem) => { - const { isChecked, name, id } = policyItem; - return ( -
- { - updatePolicyItems(policyItem.id); - !isChecked && - setErrors((errs) => - omit(errs, "policyItems") - ); - }} - > - {name} - -
- ); - })} -
- ) : ( -
- You have no policies. -

Add a policy to turn on automations.

-
- )} + + Choose which policies you would like to listen to: + +

+ {policyItems && + policyItems.map((policyItem) => { + const { isChecked, name, id } = policyItem; + return ( +
+ { + updatePolicyItems(policyItem.id); + !isChecked && + setErrors((errs) => omit(errs, "policyItems")); + }} + > + {name} + +
+ ); + })}
-
- Workflow - - + ) : ( +
+ You have no policies. +

Add a policy to turn on automations.

- {isWebhookEnabled ? renderWebhook() : renderIntegrations()} -
- {!isPolicyAutomationsEnabled && ( -
)}
-
- - +
+ Workflow + +
+ {isWebhookEnabled ? renderWebhook() : renderIntegrations()}
- )} - + {!isPolicyAutomationsEnabled && ( +
+ )} +
+
+ + +
+
); }; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx index 672cb78574..5a24f19ac9 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx @@ -25,7 +25,7 @@ interface IPoliciesTableProps { policiesList: IPolicyStats[]; isLoading: boolean; onAddPolicyClick?: () => void; - onDeletePoliciesClick: (selectedTableIds: number[]) => void; + onDeletePolicyClick: (selectedTableIds: number[]) => void; canAddOrDeletePolicy?: boolean; tableType?: string; currentTeam: ITeamSummary | undefined; @@ -36,7 +36,7 @@ const PoliciesTable = ({ policiesList, isLoading, onAddPolicyClick, - onDeletePoliciesClick, + onDeletePolicyClick, canAddOrDeletePolicy, tableType, currentTeam, @@ -131,7 +131,7 @@ const PoliciesTable = ({ manualSortBy showMarkAllPages={false} isAllPagesSelected={false} - onPrimarySelectActionClick={onDeletePoliciesClick} + onPrimarySelectActionClick={onDeletePolicyClick} primarySelectActionButtonVariant="text-icon" primarySelectActionButtonIcon="delete" primarySelectActionButtonText={"Delete"} diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss index 52969b1f1d..d3d2ec49ba 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss +++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss @@ -31,6 +31,11 @@ font-size: $x-small; color: $core-fleet-black; } + + &__action-button-container { + display: flex; + justify-content: center; + } } .has-not-run { diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PreviewPayloadModal/PreviewPayloadModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PreviewPayloadModal/PreviewPayloadModal.tsx index 1cbbdca2c4..14c3e0b29b 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PreviewPayloadModal/PreviewPayloadModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/PreviewPayloadModal/PreviewPayloadModal.tsx @@ -63,12 +63,8 @@ const PreviewPayloadModal = ({
         
-
-
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PreviewPayloadModal/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/components/PreviewPayloadModal/_styles.scss index 77fee85287..1a81c10993 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PreviewPayloadModal/_styles.scss +++ b/frontend/pages/policies/ManagePoliciesPage/components/PreviewPayloadModal/_styles.scss @@ -23,15 +23,6 @@ font-weight: $bold; } - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - } - - &__btn { - margin-left: 12px; - } - &__advanced-options-button { margin: $pad-medium 0; color: $core-vibrant-blue; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PreviewTicketModal/PreviewTicketModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PreviewTicketModal/PreviewTicketModal.tsx index a5fbba7346..02cd840494 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PreviewTicketModal/PreviewTicketModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/PreviewTicketModal/PreviewTicketModal.tsx @@ -40,12 +40,8 @@ const PreviewTicketModal = ({ src={type === "zendesk" ? ZendeskTicket : JiraTicket} />
-
-
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PreviewTicketModal/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/components/PreviewTicketModal/_styles.scss index 6130ec3f25..536b47b622 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PreviewTicketModal/_styles.scss +++ b/frontend/pages/policies/ManagePoliciesPage/components/PreviewTicketModal/_styles.scss @@ -22,13 +22,4 @@ &__info-header { font-weight: $bold; } - - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - } - - &__btn { - margin-left: 12px; - } } diff --git a/frontend/pages/policies/PolicyPage/components/NewPolicyModal/NewPolicyModal.tsx b/frontend/pages/policies/PolicyPage/components/NewPolicyModal/NewPolicyModal.tsx index b434716dc6..ea565eb57a 100644 --- a/frontend/pages/policies/PolicyPage/components/NewPolicyModal/NewPolicyModal.tsx +++ b/frontend/pages/policies/PolicyPage/components/NewPolicyModal/NewPolicyModal.tsx @@ -21,7 +21,7 @@ export interface INewPolicyModalProps { setIsNewPolicyModalOpen: (isOpen: boolean) => void; backendValidators: { [key: string]: string }; platformSelector: IPlatformSelector; - policyIsLoading: boolean; + isUpdatingPolicy: boolean; } const validatePolicyName = (name: string) => { @@ -42,7 +42,7 @@ const NewPolicyModal = ({ setIsNewPolicyModalOpen, backendValidators, platformSelector, - policyIsLoading, + isUpdatingPolicy, }: INewPolicyModalProps): JSX.Element => { const { lastEditedQueryName, @@ -134,16 +134,7 @@ const NewPolicyModal = ({ placeholder="What steps should a device owner take to resolve a host that fails this policy? (optional)" /> {platformSelector.render()} -
- +
+
diff --git a/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx b/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx index 5f4de5a618..2480a51226 100644 --- a/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx +++ b/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx @@ -36,7 +36,6 @@ interface IPolicyFormProps { showOpenSchemaActionText: boolean; storedPolicy: IPolicy | undefined; isStoredPolicyLoading: boolean; - isCreatingNewPolicy: boolean; isUpdatingPolicy: boolean; onCreatePolicy: (formData: IPolicyFormData) => void; onOsqueryTableSelect: (tableName: string) => void; @@ -64,7 +63,6 @@ const PolicyForm = ({ showOpenSchemaActionText, storedPolicy, isStoredPolicyLoading, - isCreatingNewPolicy, isUpdatingPolicy, onCreatePolicy, onOsqueryTableSelect, @@ -484,14 +482,35 @@ const PolicyForm = ({ > {hasSavePermissions && ( )} + + + Select the platform(s) this +
+ policy will be checked on +
+ to save or run the policy. +
+
{showDeleteQueryModal && ( diff --git a/frontend/pages/queries/ManageQueriesPage/_styles.scss b/frontend/pages/queries/ManageQueriesPage/_styles.scss index 3526bdd33d..aec551a5cc 100644 --- a/frontend/pages/queries/ManageQueriesPage/_styles.scss +++ b/frontend/pages/queries/ManageQueriesPage/_styles.scss @@ -67,16 +67,6 @@ } } - &__modal-buttons { - width: 100%; - display: flex; - justify-content: flex-end; - - .button:first-child { - margin-right: $pad-medium; - } - } - .form-field--dropdown { margin: 0; } @@ -262,7 +252,11 @@ } &__inner-text { + display: flex; + flex-direction: column; + align-items: center; width: 485px; + img { width: 12px; position: relative; diff --git a/frontend/pages/queries/ManageQueriesPage/components/DeleteQueryModal/DeleteQueryModal.tsx b/frontend/pages/queries/ManageQueriesPage/components/DeleteQueryModal/DeleteQueryModal.tsx index f0e98b3f24..e2a2525e6c 100644 --- a/frontend/pages/queries/ManageQueriesPage/components/DeleteQueryModal/DeleteQueryModal.tsx +++ b/frontend/pages/queries/ManageQueriesPage/components/DeleteQueryModal/DeleteQueryModal.tsx @@ -7,13 +7,13 @@ import Spinner from "components/Spinner"; const baseClass = "delete-query-modal"; interface IDeleteQueryModalProps { - isLoading: boolean; + isUpdatingQueries: boolean; onCancel: () => void; onSubmit: () => void; } const DeleteQueryModal = ({ - isLoading, + isUpdatingQueries, onCancel, onSubmit, }: IDeleteQueryModalProps): JSX.Element => { @@ -24,23 +24,23 @@ const DeleteQueryModal = ({ onEnter={onSubmit} className={baseClass} > - <> - {isLoading ? ( - - ) : ( -
- Are you sure you want to delete the selected queries? -
- - -
-
- )} - +
+ Are you sure you want to delete the selected queries? +
+ + +
+
); }; diff --git a/frontend/pages/queries/QueryPage/components/NewQueryModal/NewQueryModal.tsx b/frontend/pages/queries/QueryPage/components/NewQueryModal/NewQueryModal.tsx index c12431519f..b9349a2e1d 100644 --- a/frontend/pages/queries/QueryPage/components/NewQueryModal/NewQueryModal.tsx +++ b/frontend/pages/queries/QueryPage/components/NewQueryModal/NewQueryModal.tsx @@ -114,22 +114,17 @@ const NewQueryModal = ({ where they have access.


-
+
- +
diff --git a/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx b/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx index 2096ef9ef5..ac1f3d87fc 100644 --- a/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx +++ b/frontend/pages/queries/QueryPage/components/QueryForm/QueryForm.tsx @@ -488,12 +488,13 @@ const QueryForm = ({ <> {isEditMode && ( )}
@@ -508,15 +509,16 @@ const QueryForm = ({ } >
{" "} { wrapperClassName={`${baseClass}__text-editor-wrapper`} readOnly /> -
-
diff --git a/frontend/pages/queries/QueryPage/components/QueryResults/ShowQueryModal/_styles.scss b/frontend/pages/queries/QueryPage/components/QueryResults/ShowQueryModal/_styles.scss index 1c737fcb16..7c87bbae15 100644 --- a/frontend/pages/queries/QueryPage/components/QueryResults/ShowQueryModal/_styles.scss +++ b/frontend/pages/queries/QueryPage/components/QueryResults/ShowQueryModal/_styles.scss @@ -1,14 +1,4 @@ .show-query-modal { - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - margin: $pad-large 0 0; - } - - &__btn { - margin-left: 12px; - } - .yaml-ace { min-height: 0; } diff --git a/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx b/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx index 950fd58537..048bdc05e5 100644 --- a/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx +++ b/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx @@ -291,8 +291,10 @@ const ManageSchedulePage = ({ const selectedTeamData = teams?.find((team: ITeam) => selectedTeam === team.id) || undefined; - const [scheduleIsLoading, setScheduleIsLoading] = useState(false); - const [scheduleIsRemoving, setScheduleIsRemoving] = useState(false); + const [ + isUpdatingScheduledQuery, + setIsUpdatingScheduledQuery, + ] = useState(false); const [showInheritedQueries, setShowInheritedQueries] = useState( false ); @@ -342,7 +344,7 @@ const ManageSchedulePage = ({ }; const onRemoveScheduledQuerySubmit = useCallback(() => { - setScheduleIsRemoving(true); + setIsUpdatingScheduledQuery(true); const promises = selectedQueryIds.map((id: number) => { return selectedTeamId ? teamScheduledQueriesAPI.destroy(selectedTeamId, id) @@ -367,7 +369,7 @@ const ManageSchedulePage = ({ }) .finally(() => { refetchGlobalScheduledQueries(); - setScheduleIsRemoving(false); + setIsUpdatingScheduledQuery(false); }); }, [ selectedTeamId, @@ -378,7 +380,7 @@ const ManageSchedulePage = ({ const onAddScheduledQuerySubmit = useCallback( (formData: IFormData, editQuery: IEditScheduledQuery | undefined) => { - setScheduleIsLoading(true); + setIsUpdatingScheduledQuery(true); if (editQuery) { const updatedAttributes = deepDifference(formData, editQuery); @@ -403,7 +405,7 @@ const ManageSchedulePage = ({ ); }) .finally(() => { - setScheduleIsLoading(false); + setIsUpdatingScheduledQuery(false); refetchGlobalScheduledQueries(); }); } else { @@ -424,7 +426,7 @@ const ManageSchedulePage = ({ renderFlash("error", "Could not schedule query. Please try again."); }) .finally(() => { - setScheduleIsLoading(false); + setIsUpdatingScheduledQuery(false); refetchGlobalScheduledQueries(); }); } @@ -554,14 +556,14 @@ const ManageSchedulePage = ({ teamId={selectedTeamId} togglePreviewDataModal={togglePreviewDataModal} showPreviewDataModal={showPreviewDataModal} - isLoading={scheduleIsLoading} + isUpdatingScheduledQuery={isUpdatingScheduledQuery} /> )} {showRemoveScheduledQueryModal && ( )}
diff --git a/frontend/pages/schedule/ManageSchedulePage/_styles.scss b/frontend/pages/schedule/ManageSchedulePage/_styles.scss index 76e943cceb..b031bab4e8 100644 --- a/frontend/pages/schedule/ManageSchedulePage/_styles.scss +++ b/frontend/pages/schedule/ManageSchedulePage/_styles.scss @@ -72,16 +72,6 @@ margin-right: $pad-medium; } - &__modal-buttons { - width: 100%; - display: flex; - justify-content: flex-end; - - .button:first-child { - margin-right: $pad-medium; - } - } - .Select.is-open { .Select-value-label { color: $core-vibrant-blue !important; @@ -231,6 +221,11 @@ margin: 0; } } + + &__cta-buttons { + display: flex; + justify-content: center; + } } .no-team-schedule { diff --git a/frontend/pages/schedule/ManageSchedulePage/components/PreviewDataModal/PreviewDataModal.tsx b/frontend/pages/schedule/ManageSchedulePage/components/PreviewDataModal/PreviewDataModal.tsx index e224aa1465..3156c73288 100644 --- a/frontend/pages/schedule/ManageSchedulePage/components/PreviewDataModal/PreviewDataModal.tsx +++ b/frontend/pages/schedule/ManageSchedulePage/components/PreviewDataModal/PreviewDataModal.tsx @@ -48,12 +48,8 @@ const PreviewDataModal = ({
         
-
-
diff --git a/frontend/pages/schedule/ManageSchedulePage/components/PreviewDataModal/_styles.scss b/frontend/pages/schedule/ManageSchedulePage/components/PreviewDataModal/_styles.scss index b1409b90a6..3e35092aa5 100644 --- a/frontend/pages/schedule/ManageSchedulePage/components/PreviewDataModal/_styles.scss +++ b/frontend/pages/schedule/ManageSchedulePage/components/PreviewDataModal/_styles.scss @@ -18,13 +18,4 @@ font-size: $x-small; text-decoration: none; } - - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - } - - &__btn { - margin-left: 12px; - } } diff --git a/frontend/pages/schedule/ManageSchedulePage/components/RemoveScheduledQueryModal/RemoveScheduledQueryModal.tsx b/frontend/pages/schedule/ManageSchedulePage/components/RemoveScheduledQueryModal/RemoveScheduledQueryModal.tsx index 1e10813892..711f9b58eb 100644 --- a/frontend/pages/schedule/ManageSchedulePage/components/RemoveScheduledQueryModal/RemoveScheduledQueryModal.tsx +++ b/frontend/pages/schedule/ManageSchedulePage/components/RemoveScheduledQueryModal/RemoveScheduledQueryModal.tsx @@ -7,13 +7,13 @@ import Spinner from "components/Spinner"; const baseClass = "remove-scheduled-query-modal"; interface IRemoveScheduledQueryModalProps { - isLoading: boolean; + isUpdatingScheduledQuery: boolean; onCancel: () => void; onSubmit: () => void; } const RemoveScheduledQueryModal = ({ - isLoading, + isUpdatingScheduledQuery, onCancel, onSubmit, }: IRemoveScheduledQueryModalProps): JSX.Element => { @@ -24,22 +24,23 @@ const RemoveScheduledQueryModal = ({ onEnter={onSubmit} className={baseClass} > - {isLoading ? ( - - ) : ( -
- Are you sure you want to remove the selected queries from the - schedule? -
- - -
+
+ Are you sure you want to remove the selected queries from the schedule? +
+ +
- )} +
); }; diff --git a/frontend/pages/schedule/ManageSchedulePage/components/ScheduleEditorModal/ScheduleEditorModal.tsx b/frontend/pages/schedule/ManageSchedulePage/components/ScheduleEditorModal/ScheduleEditorModal.tsx index 0b04c8cec4..5d3e1d186d 100644 --- a/frontend/pages/schedule/ManageSchedulePage/components/ScheduleEditorModal/ScheduleEditorModal.tsx +++ b/frontend/pages/schedule/ManageSchedulePage/components/ScheduleEditorModal/ScheduleEditorModal.tsx @@ -50,7 +50,7 @@ interface IScheduleEditorModalProps { teamId?: number; togglePreviewDataModal: () => void; showPreviewDataModal: boolean; - isLoading: boolean; + isUpdatingScheduledQuery: boolean; } interface INoQueryOption { id: number; @@ -94,7 +94,7 @@ const ScheduleEditorModal = ({ teamId, togglePreviewDataModal, showPreviewDataModal, - isLoading, + isUpdatingScheduledQuery, }: IScheduleEditorModalProps): JSX.Element => { const { config } = useContext(AppContext); @@ -234,124 +234,122 @@ const ScheduleEditorModal = ({ onEnter={onFormSubmit} className={baseClass} > - {isLoading ? ( - - ) : ( -
- {!editQuery && ( - - )} + + {!editQuery && ( - -

- Your configured log destination is {loggingConfig}. -

-

- {loggingConfig === "unknown" - ? "" - : `This means that when this query is run on your hosts, the data will + )} + + +

+ Your configured log destination is {loggingConfig}. +

+

+ {loggingConfig === "unknown" + ? "" + : `This means that when this query is run on your hosts, the data will be sent to ${generateLoggingDestination(loggingConfig)}.`} -

-

- Check out the Fleet documentation on  - - how to configure a different log destination - - . -

-
-
- - {showAdvancedOptions && ( -
- - - - -
- )} -
-
-
- -
-
- - +

+

+ Check out the Fleet documentation on  + + how to configure a different log destination + + . +

+ +
+ + {showAdvancedOptions && ( +
+ + + +
+ )} +
+
+
+
- - )} +
+ + +
+
+ ); }; diff --git a/frontend/pages/schedule/ManageSchedulePage/components/ScheduleEditorModal/_styles.scss b/frontend/pages/schedule/ManageSchedulePage/components/ScheduleEditorModal/_styles.scss index c5c2382908..a8b0f2a203 100644 --- a/frontend/pages/schedule/ManageSchedulePage/components/ScheduleEditorModal/_styles.scss +++ b/frontend/pages/schedule/ManageSchedulePage/components/ScheduleEditorModal/_styles.scss @@ -23,16 +23,6 @@ font-weight: $bold; } - &__btn-wrap { - display: flex; - justify-content: space-between; - align-items: center; - } - - .modal-cta-wrap { - margin-top: 0; - } - .Select-value-label { font-size: $small; } diff --git a/frontend/pages/software/ManageSoftwarePage/components/ManageAutomationsModal/ManageAutomationsModal.tsx b/frontend/pages/software/ManageSoftwarePage/components/ManageAutomationsModal/ManageAutomationsModal.tsx index cceda8167a..8a014aaf12 100644 --- a/frontend/pages/software/ManageSoftwarePage/components/ManageAutomationsModal/ManageAutomationsModal.tsx +++ b/frontend/pages/software/ManageSoftwarePage/components/ManageAutomationsModal/ManageAutomationsModal.tsx @@ -441,13 +441,6 @@ const ManageAutomationsModal = ({ )}
-
diff --git a/frontend/pages/software/ManageSoftwarePage/components/PreviewPayloadModal/PreviewPayloadModal.tsx b/frontend/pages/software/ManageSoftwarePage/components/PreviewPayloadModal/PreviewPayloadModal.tsx index de77b20010..d8bdfd095c 100644 --- a/frontend/pages/software/ManageSoftwarePage/components/PreviewPayloadModal/PreviewPayloadModal.tsx +++ b/frontend/pages/software/ManageSoftwarePage/components/PreviewPayloadModal/PreviewPayloadModal.tsx @@ -55,12 +55,8 @@ const PreviewPayloadModal = ({
         
-
-
diff --git a/frontend/pages/software/ManageSoftwarePage/components/PreviewPayloadModal/_styles.scss b/frontend/pages/software/ManageSoftwarePage/components/PreviewPayloadModal/_styles.scss index c7f83e9269..5424338604 100644 --- a/frontend/pages/software/ManageSoftwarePage/components/PreviewPayloadModal/_styles.scss +++ b/frontend/pages/software/ManageSoftwarePage/components/PreviewPayloadModal/_styles.scss @@ -18,13 +18,4 @@ font-size: $x-small; text-decoration: none; } - - &__btn-wrap { - display: flex; - flex-direction: row-reverse; - } - - &__btn { - margin-left: 12px; - } }