Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions frontend/components/AddHostsModal/AddHostsModal.tests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ describe("AddHostsModal", () => {
expect(text).toBeInTheDocument();
});
it("renders no enroll secret cta", async () => {
const onCancel = jest.fn();
const openEnrollSecretModal = jest.fn();

const render = createCustomRenderer({
withBackendMock: true,
context: {
Expand All @@ -181,23 +184,27 @@ describe("AddHostsModal", () => {
},
});

render(
const { user } = render(
<AddHostsModal
isAnyTeamSelected={false}
currentTeamName="Apples"
isLoading={false}
onCancel={noop}
openEnrollSecretModal={noop}
onCancel={onCancel}
openEnrollSecretModal={openEnrollSecretModal}
/>
);

const text = screen.getByText("Something's gone wrong.");
const ctaButton = screen.getByRole("button", {
name: "Manage enroll secrets",
});
expect(screen.getByText("Something's gone wrong.")).toBeInTheDocument();
expect(
screen.getByText(/you have no enroll secrets\./i)
).toBeInTheDocument();

expect(text).toBeInTheDocument();
expect(ctaButton).toBeEnabled();
const cta = screen.getByText(/manage enroll secrets/i);
expect(cta).toBeInTheDocument();

await user.click(cta);

expect(onCancel).toHaveBeenCalledTimes(1);
expect(openEnrollSecretModal).toHaveBeenCalledTimes(1);
});

it("excludes `--enable-scripts` flag if `config.server_settings.scripts-disabled` is `true`", async () => {
Expand Down
9 changes: 5 additions & 4 deletions frontend/components/AddHostsModal/AddHostsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useQuery } from "react-query";
import configAPI from "services/entities/config";
import { AppContext } from "context/app";

import Button from "components/buttons/Button";
import CustomLink from "components/CustomLink";
import DataError from "components/DataError";
import Modal from "components/Modal";
import Spinner from "components/Spinner";
Expand Down Expand Up @@ -60,9 +60,10 @@ const AddHostsModal = ({
<span className="info__data">
You have no enroll secrets.{" "}
{openEnrollSecretModal ? (
<Button onClick={onManageEnrollSecretsClick} variant="inverse">
Manage enroll secrets
</Button>
<CustomLink
customClickHandler={onManageEnrollSecretsClick}
text="Manage enroll secrets"
/>
Comment on lines 62 to +66
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CustomLink renders an <a href={url}>, but this usage doesn’t pass a url. Anchors without href are usually not treated as links by assistive tech, even if they’re clickable. Consider passing a real url (or adjusting the component/usage to render a semantic button when it’s an in-app action that opens a modal).

Copilot uses AI. Check for mistakes.
) : (
"Manage enroll secrets"
)}{" "}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,54 @@ const EnrollSecretModal = ({
title="Manage enroll secrets"
className={baseClass}
>
<div className={`${baseClass} form`}>
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just moved the form div into the ternary, that's what was styling the empty state to be 100% width + its padding made the empty state overflow

{teamInfo?.secrets?.length ? (
<>
<div className={`${baseClass}__header`}>
<div className={`${baseClass}__description`}>
Use these secret(s) to enroll hosts
{teamInfo?.secrets?.length ? (
<div className={`${baseClass} form`}>
<div className={`${baseClass}__header`}>
<div className={`${baseClass}__description`}>
Use these secret(s) to enroll hosts
{primoMode || teamInfo?.name === "Unassigned" ? (
""
) : (
<>
{" "}
to <b>{teamInfo?.name}</b>
</>
)}
.
</div>
<div className={`${baseClass}__add-secret`}>
<GitOpsModeTooltipWrapper
entityType="secrets"
position="right"
tipOffset={8}
renderChildren={(disableChildren) => (
<Button
disabled={disableChildren}
onClick={addNewSecretClick}
className={`${baseClass}__add-secret-btn`}
variant="brand-inverse-icon"
iconStroke
>
Add secret <Icon name="plus" color="core-fleet-green" />
</Button>
)}
/>
</div>
</div>
<EnrollSecretTable
secrets={teamInfo?.secrets}
toggleSecretEditorModal={toggleSecretEditorModal}
toggleDeleteSecretModal={toggleDeleteSecretModal}
setSelectedSecret={setSelectedSecret}
/>
</div>
) : (
<Card color="grey" paddingSize="small">
<EmptyTable
header="You have no enroll secrets."
info={
<>
Add secret(s) to enroll hosts
{primoMode || teamInfo?.name === "Unassigned" ? (
""
) : (
Expand All @@ -67,75 +109,31 @@ const EnrollSecretModal = ({
</>
)}
.
</div>
<div className={`${baseClass}__add-secret`}>
<GitOpsModeTooltipWrapper
entityType="secrets"
position="right"
tipOffset={8}
renderChildren={(disableChildren) => (
<Button
disabled={disableChildren}
onClick={addNewSecretClick}
className={`${baseClass}__add-secret-btn`}
variant="brand-inverse-icon"
iconStroke
>
Add secret <Icon name="plus" color="core-fleet-green" />
</Button>
)}
/>
</div>
</div>
<EnrollSecretTable
secrets={teamInfo?.secrets}
toggleSecretEditorModal={toggleSecretEditorModal}
toggleDeleteSecretModal={toggleDeleteSecretModal}
setSelectedSecret={setSelectedSecret}
/>
</>
) : (
<Card color="grey" paddingSize="small">
<EmptyTable
header="You have no enroll secrets."
info={
<>
Add secret(s) to enroll hosts
{primoMode || teamInfo?.name === "Unassigned" ? (
""
) : (
<>
{" "}
to <b>{teamInfo?.name}</b>
</>
)}
.
</>
}
primaryButton={
<GitOpsModeTooltipWrapper
entityType="secrets"
position="right"
tipOffset={8}
renderChildren={(disableChildren) => (
<Button
disabled={disableChildren}
onClick={addNewSecretClick}
className={`${baseClass}__add-secret-btn`}
variant="brand-inverse-icon"
iconStroke
>
Add secret <Icon name="plus" color="core-fleet-green" />
</Button>
)}
/>
}
/>
</Card>
)}
<div className="modal-cta-wrap">
<Button onClick={onReturnToApp}>Close</Button>
</div>
</>
}
primaryButton={
<GitOpsModeTooltipWrapper
entityType="secrets"
position="right"
tipOffset={8}
renderChildren={(disableChildren) => (
<Button
disabled={disableChildren}
onClick={addNewSecretClick}
className={`${baseClass}__add-secret-btn`}
variant="brand-inverse-icon"
iconStroke
>
Add secret <Icon name="plus" color="core-fleet-green" />
</Button>
)}
/>
}
/>
</Card>
)}
<div className="modal-cta-wrap">
<Button onClick={onReturnToApp}>Close</Button>
</div>
</Modal>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,6 @@
text-decoration: none;
}

pre,
code {
background-color: $ui-off-white;
color: $core-fleet-blue;
border: 1px solid $ui-fleet-black-10;
border-radius: $border-radius;
padding: 7px $pad-medium;
margin: $pad-large 0 0 44px;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused code

&__error {
color: $ui-error;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,6 @@
margin: $pad-medium 0 0 0;
}

pre,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused

code {
background-color: $ui-off-white;
color: $core-fleet-blue;
border: 1px solid $ui-fleet-black-10;
border-radius: $border-radius;
padding: 7px $pad-medium;
margin: $pad-large 0 0 44px;
}

&__error {
color: $ui-error;
}
Expand Down
10 changes: 8 additions & 2 deletions frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import { strToBool } from "utilities/strings/stringUtils";

import Button from "components/buttons/Button";
import Icon from "components/Icon/Icon";
import CustomLink from "components/CustomLink";
import { SingleValue } from "react-select-5";
import DropdownWrapper from "components/forms/fields/DropdownWrapper";
import { CustomOptionType } from "components/forms/fields/DropdownWrapper/DropdownWrapper";
Expand Down Expand Up @@ -1934,8 +1935,13 @@ const ManageHostsPage = ({
>
<div>
<span>
You have no enroll secrets. Manage enroll secrets to enroll hosts
to <b>{isAnyTeamSelected ? currentTeamName : "Fleet"}</b>.
You have no enroll secrets.{" "}
<CustomLink
customClickHandler={() => setShowEnrollSecretModal(true)}
text="Manage enroll secrets"
/>{" "}
Comment on lines +1939 to +1942
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CustomLink renders an <a href={url}>. In this new usage no url is provided, so the anchor can be rendered without an href, which is typically not announced as a link by screen readers and can behave inconsistently for keyboard users. Consider either providing a real url (and navigate to enroll secret management), or switching to a semantic button styled as a link / updating CustomLink usage to include appropriate href/role when it only opens a modal.

Suggested change
<CustomLink
customClickHandler={() => setShowEnrollSecretModal(true)}
text="Manage enroll secrets"
/>{" "}
<button
type="button"
onClick={() => setShowEnrollSecretModal(true)}
style={{
background: "none",
border: 0,
padding: 0,
margin: 0,
color: "inherit",
textDecoration: "underline",
cursor: "pointer",
font: "inherit",
}}
>
Manage enroll secrets
</button>{" "}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CustomLink with no href is the only way we currently can get that underline like a link styling with a custom onclick. Maybe we can revisit with agentic programming to get a button to do the underline transition on hover in a future iteration.

to enroll hosts to{" "}
<b>{isAnyTeamSelected ? currentTeamName : "Fleet"}</b>.
</span>
</div>
</InfoBanner>
Expand Down
Loading