diff --git a/playwright/tests/root_password.spec.ts b/playwright/tests/root_password.spec.ts index f53d237515..89d42e15f8 100644 --- a/playwright/tests/root_password.spec.ts +++ b/playwright/tests/root_password.spec.ts @@ -10,16 +10,13 @@ test.describe('The user section', () => { // See https://playwright.dev/docs/api/class-locator // initial expectation - the root password is not configured yet - await expect(page.getByText("None authentication method defined for root user")).toBeVisible(); + await expect(page.getByText("No root authentication method defined")).toBeVisible(); // click the "Users" header await page.locator("a[href='#/users']").click(); - // display the actions menu for the root password - await page.locator("#actions-for-root-password").click(); - - // click the "Set" item - await page.getByRole("menuitem", { name: "Set" }).click(); + // click on the "Set a password" button + await page.getByRole("button", { name: "Set a password" }).click(); // fill a new password await page.locator('#password').fill('agama'); diff --git a/playwright/tests/take_screenshots.spec.ts b/playwright/tests/take_screenshots.spec.ts index 8182cf0b10..fc38f3fd77 100644 --- a/playwright/tests/take_screenshots.spec.ts +++ b/playwright/tests/take_screenshots.spec.ts @@ -37,7 +37,7 @@ test.describe("The Installer", () => { // check for multiple texts in parallel, avoid waiting for timeouts let action = await Promise.any([ page.getByText("Product selection").waitFor().then(() => actions.setProduct), - page.getByText("None authentication method").waitFor().then(() => actions.setPassword), + page.getByText("No root authentication method").waitFor().then(() => actions.setPassword), page.getByText("Root authentication set").waitFor().then(() => actions.done), ]); @@ -52,7 +52,7 @@ test.describe("The Installer", () => { // update the action for the next step action = await Promise.any([ - page.getByText("None authentication method").waitFor().then(() => actions.setPassword), + page.getByText("No root authentication method").waitFor().then(() => actions.setPassword), page.getByText("Root authentication set").waitFor().then(() => actions.done), ]); } @@ -63,8 +63,8 @@ test.describe("The Installer", () => { await page.locator("a[href='#/users']").click(); // Create users page screenshot await page.screenshot({ path: "screenshots/users-page.png" }); - await page.locator("#actions-for-root-password").click(); - await page.getByRole("menuitem", { name: "Set" }).click(); + // click on the "Set a password" button + await page.getByRole("button", { name: "Set a password" }).click(); await page.locator("#password").fill("linux"); await page.locator("#passwordConfirmation").fill("linux"); await page.locator('button[type="submit"]').click(); diff --git a/web/package/cockpit-agama.changes b/web/package/cockpit-agama.changes index b3186b19fd..9e3490f088 100644 --- a/web/package/cockpit-agama.changes +++ b/web/package/cockpit-agama.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Mon Jun 12 11:13:41 UTC 2023 - David Diaz + +- UI: Makes more evident when there is none authentication method + defined for the root user (gh#openSUSE/agama#615). + ------------------------------------------------------------------- Fri Jun 9 09:38:05 UTC 2023 - David Diaz diff --git a/web/src/components/overview/UsersSection.jsx b/web/src/components/overview/UsersSection.jsx index b0b3f6b8d2..e438826cad 100644 --- a/web/src/components/overview/UsersSection.jsx +++ b/web/src/components/overview/UsersSection.jsx @@ -96,7 +96,7 @@ export default function UsersSection({ showErrors }) { return (
Root authentication set for using both, password and public SSH Key} /> - None authentication method defined for root user} /> + No root authentication method defined} /> Root authentication set for using password} /> Root authentication set for using public SSH Key} />
diff --git a/web/src/components/overview/UsersSection.test.jsx b/web/src/components/overview/UsersSection.test.jsx index e9da0f525d..6e0bca8c23 100644 --- a/web/src/components/overview/UsersSection.test.jsx +++ b/web/src/components/overview/UsersSection.test.jsx @@ -116,6 +116,6 @@ describe("when none root auth method is set", () => { it("renders information about it", async () => { installerRender(); - await screen.findByText(/none authentication method defined for root/i); + await screen.findByText("No root authentication method defined"); }); }); diff --git a/web/src/components/users/RootAuthMethods.jsx b/web/src/components/users/RootAuthMethods.jsx index 6060fcfd73..26cdc69bac 100644 --- a/web/src/components/users/RootAuthMethods.jsx +++ b/web/src/components/users/RootAuthMethods.jsx @@ -20,7 +20,7 @@ */ import React, { useState, useEffect } from "react"; -import { Skeleton, Truncate } from "@patternfly/react-core"; +import { Button, Skeleton, Truncate } from "@patternfly/react-core"; import { TableComposable, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table'; import { Em, RowActions } from '~/components/core'; import { RootPasswordPopup, RootSSHKeyPopup } from '~/components/users'; @@ -28,6 +28,18 @@ import { RootPasswordPopup, RootSSHKeyPopup } from '~/components/users'; import { useCancellablePromise } from "~/utils"; import { useInstallerClient } from "~/context/installer"; +const MethodsNotDefined = ({ setPassword, setSSHKey }) => { + return ( +
+
No root authentication method defined yet
+
Please, define at least one authentication method for logging into the system as root.
+
+ + +
+
+ ); +}; export default function RootAuthMethods() { const { users: client } = useInstallerClient(); const { cancellablePromise } = useCancellablePromise(); @@ -65,10 +77,15 @@ export default function RootAuthMethods() { const isSSHKeyDefined = sshKey !== ""; + const openPasswordForm = () => setIsPasswordFormOpen(true); + const openSSHKeyForm = () => setIsSSHKeyFormOpen(true); + const closePasswordForm = () => setIsPasswordFormOpen(false); + const closeSSHKeyForm = () => setIsSSHKeyFormOpen(false); + const passwordActions = [ { title: isPasswordDefined ? "Change" : "Set", - onClick: () => setIsPasswordFormOpen(true), + onClick: openPasswordForm }, isPasswordDefined && { @@ -81,7 +98,7 @@ export default function RootAuthMethods() { const sshKeyActions = [ { title: isSSHKeyDefined ? "Change" : "Set", - onClick: () => setIsSSHKeyFormOpen(true), + onClick: openSSHKeyForm }, sshKey && { title: "Discard", @@ -100,9 +117,6 @@ export default function RootAuthMethods() { ); } - const closePasswordForm = () => setIsPasswordFormOpen(false); - const closeSSHKeyForm = () => setIsSSHKeyFormOpen(false); - const PasswordLabel = () => { return isPasswordDefined ? "Already set" @@ -121,8 +135,12 @@ export default function RootAuthMethods() { ); }; - return ( - <> + const Content = () => { + if (!isPasswordDefined && !isSSHKeyDefined) { + return ; + } + + return ( @@ -148,7 +166,12 @@ export default function RootAuthMethods() { + ); + }; + return ( + <> + { isPasswordFormOpen && { }); describe("when ready", () => { - it("renders a table holding available methods", async () => { - installerRender(); + describe("and no method is defined", () => { + it("renders a text inviting the user to define at least one", async () => { + installerRender(); + + await screen.findByText("No root authentication method defined yet"); + screen.getByText(/at least one/); + }); + + it("renders buttons for setting either, a password or a SSH Public Key", async () => { + installerRender(); + + await screen.findByRole("button", { name: "Set a password" }); + screen.getByRole("button", { name: "Upload a SSH Public Key" }); + }); + + it("allows setting the password", async () => { + const { user } = installerRender(); + + const button = await screen.findByRole("button", { name: "Set a password" }); + await user.click(button); + + screen.getByRole("dialog", { name: "Set a root password" }); + }); + + it("allows setting the SSH Public Key", async () => { + const { user } = installerRender(); - const table = await screen.findByRole("grid"); - within(table).getByText("Password"); - within(table).getByText("SSH Key"); + const button = await screen.findByRole("button", { name: "Upload a SSH Public Key" }); + await user.click(button); + + screen.getByRole("dialog", { name: "Add a SSH Public Key for root" }); + }); + }); + + describe("and at least one method is already defined", () => { + beforeEach(() => isRootPasswordSetFn.mockResolvedValue(true)); + + it("renders a table with available methods", async () => { + installerRender(); + + const table = await screen.findByRole("grid"); + within(table).getByText("Password"); + within(table).getByText("SSH Key"); + }); }); describe("and the password has been set", () => { @@ -132,6 +170,9 @@ describe("when ready", () => { }); describe("but the password is not set yet", () => { + // Mock another auth method for reaching the table + beforeEach(() => getRootSSHKeyFn.mockResolvedValue("Fake")); + it("renders the 'Not set' status", async () => { installerRender(); @@ -226,6 +267,9 @@ describe("when ready", () => { }); describe("but the SSH Key is not set yet", () => { + // Mock another auth method for reaching the table + beforeEach(() => isRootPasswordSetFn.mockResolvedValue(true)); + it("renders the 'Not set' status", async () => { installerRender(); @@ -266,6 +310,9 @@ describe("when ready", () => { }); describe("and user settings changes", () => { + // Mock an auth method for reaching the table + beforeEach(() => isRootPasswordSetFn.mockResolvedValue(true)); + it("updates the UI accordingly", async () => { const [mockFunction, callbacks] = createCallbackMock(); onUsersChangeFn = mockFunction;