Fleet UI: Component fixes (styling bugs and code cleanup) (#26149)

This commit is contained in:
RachelElysia
2025-02-26 13:47:28 -05:00
committed by GitHub
parent 7a95f59f4a
commit 7b7fa2fcd1
38 changed files with 301 additions and 396 deletions
+1
View File
@@ -0,0 +1 @@
- Fleet UI: Constistent behavior for table overflow and not hiding badges when user names overflow table cell
@@ -1,5 +1,8 @@
import React from "react";
import { Meta, StoryObj } from "@storybook/react";
import InfoBanner from "components/InfoBanner";
import TooltipWrapper from "components/TooltipWrapper";
import CustomLink from ".";
const meta: Meta<typeof CustomLink> = {
@@ -24,3 +27,79 @@ export const ExternalLink: Story = {
newTab: true,
},
};
export const Multiline: Story = {
render: (args) => (
<div
style={{
width: "400px",
}}
>
Here&apos;s a CustomLink in a that might be split up across two lines{" "}
<CustomLink {...args} />
</div>
),
args: {
url: "https://www.google.com",
text:
"This is a custom link that has multiple words that might span multiple lines and the icon should stick with the last word onto the new line",
multiline: true,
newTab: true,
},
};
export const TooltipVariant: Story = {
render: (args) => (
<TooltipWrapper
tipContent={
<>
Tip content with a custom link <CustomLink {...args} />
</>
}
>
Hover to see custom link in tooltip{" "}
</TooltipWrapper>
),
args: {
url: "https://www.google.com",
text: "Tooltip link",
variant: "tooltip-link",
newTab: true,
},
};
export const BannerVariant: Story = {
render: (args) => (
<InfoBanner>
Here&apos;s a CustomLink in a banner <CustomLink {...args} />
</InfoBanner>
),
args: {
url: "https://www.google.com",
text: "Banner link",
variant: "banner-link",
newTab: true,
},
};
export const FlashMessageVariant: Story = {
args: {
url: "https://www.google.com",
text: "Flash message link",
variant: "flash-message-link",
},
};
export const DisabledKeyboardNav: Story = {
render: (args) => (
<>
Here, you can&apos;t tab to this link even if you wanted to which is
useful when within a disabled component. <CustomLink {...args} />
</>
),
args: {
url: "https://www.google.com",
text: "Disabled Keyboard Navigation",
disableKeyboardNavigation: true,
},
};
+5 -14
View File
@@ -14,22 +14,14 @@ interface ICustomLinkProps {
newTab?: boolean;
/** Icon wraps on new line with last word */
multiline?: boolean;
// TODO: Refactor to use variant
iconColor?: Colors;
// TODO: Refactor to use variant
color?: "core-fleet-blue" | "core-fleet-black" | "core-fleet-white";
/** Restricts access via keyboard when CustomLink is part of disabled UI */
disableKeyboardNavigation?: boolean;
/**
* Changes the appearance of the link.
*
* @default "default"
*
* TODO:
* Longterm: refactor 14 instances away from iconColor/color combo, which
* usually are identical and repetitive, toward variants e.g. "banner-link"
*/
variant?: "tooltip-link" | "default" | "flash-message-link";
variant?: "tooltip-link" | "banner-link" | "flash-message-link" | "default";
}
const baseClass = "custom-link";
@@ -40,8 +32,6 @@ const CustomLink = ({
className,
newTab = false,
multiline = false,
iconColor = "core-fleet-blue",
color = "core-fleet-blue",
disableKeyboardNavigation = false,
variant = "default",
}: ICustomLinkProps): JSX.Element => {
@@ -50,15 +40,16 @@ const CustomLink = ({
case "tooltip-link":
case "flash-message-link":
return "core-fleet-white";
case "banner-link":
return "core-fleet-black";
default:
return iconColor;
return "core-fleet-blue";
}
};
const customLinkClass = classnames(baseClass, className, {
[`${baseClass}--black`]: color === "core-fleet-black",
[`${baseClass}--white`]: color === "core-fleet-white",
[`${baseClass}--${variant}`]: variant !== "default",
[`${baseClass}--multiline`]: multiline,
});
const target = newTab ? "_blank" : "";
+13 -14
View File
@@ -1,7 +1,10 @@
.custom-link {
display: inline-flex;
align-items: center;
gap: $pad-xsmall;
// Changing display will break multiline links
&:not(.custom-link--multiline) {
display: inline-flex;
align-items: center;
gap: $pad-xsmall;
}
&__no-wrap {
white-space: nowrap;
@@ -10,18 +13,14 @@
}
}
// Legacy
&--black {
color: $core-fleet-black;
}
&--white {
color: $core-fleet-white;
}
// Variants
&--tooltip-link,
&--banner-link,
&--flash-messsage-link {
color: inherit; // Overrides fleet blue link color with parent color
}
&--tooltip-link {
color: $core-fleet-white;
font-size: $xx-small;
font-size: inherit; // Overrides link default font size with parent tooltip font size
}
}
@@ -15,8 +15,7 @@ const LicenseExpirationBanner = (): JSX.Element => {
url="https://fleetdm.com/learn-more-about/downgrading"
text="Downgrade or renew"
newTab
iconColor="core-fleet-black"
color="core-fleet-black"
variant="banner-link"
/>
}
>
@@ -19,8 +19,7 @@ const AppleBMRenewalMessage = ({ expired }: IAppleBMRenewalMessageProps) => {
url="/settings/integrations/mdm/abm"
text="Renew ABM"
className={`${baseClass}`}
color="core-fleet-black"
iconColor="core-fleet-black"
variant="banner-link"
/>
}
>
@@ -16,8 +16,7 @@ const AppleBMTermsMessage = () => {
text="Go to ABM"
className={`${baseClass}__new-tab`}
newTab
color="core-fleet-black"
iconColor="core-fleet-black"
variant="banner-link"
/>
}
>
@@ -20,8 +20,7 @@ const ApplePNCertRenewalMessage = ({ expired }: IApplePNCertRenewalMessage) => {
text="Renew APNs"
className={`${baseClass}__new-tab`}
newTab
color="core-fleet-black"
iconColor="core-fleet-black"
variant="banner-link"
/>
}
>
@@ -19,8 +19,7 @@ const VppRenewalMessage = ({ expired }: IVppRenewalMessageProps) => {
url="/settings/integrations/mdm/vpp"
text="Renew VPP"
className={`${baseClass}`}
color="core-fleet-black"
iconColor="core-fleet-black"
variant="banner-link"
/>
}
>
@@ -1,59 +0,0 @@
import CustomLink from "components/CustomLink";
import Icon from "components/Icon";
import { uniqueId } from "lodash";
import React from "react";
import ReactTooltip, { Place } from "react-tooltip";
import { COLORS } from "styles/var/colors";
interface IPremiumFeatureIconWithTooltip {
tooltipPlace?: Place;
tooltipDelayHide?: number;
tooltipPositionOverrides?: {
leftAdj?: number;
topAdj?: number;
};
}
const PremiumFeatureIconWithTooltip = ({
tooltipPlace,
tooltipDelayHide = 100,
tooltipPositionOverrides,
}: IPremiumFeatureIconWithTooltip) => {
const [leftAdj, topAdj] = [
tooltipPositionOverrides?.leftAdj ?? 0,
tooltipPositionOverrides?.topAdj ?? 0,
];
const tipId = uniqueId();
return (
<span className="premium-icon-tip">
<span data-tip data-for={tipId}>
<Icon name="premium-feature" className="premium-feature-icon" />
</span>
<ReactTooltip
place={tooltipPlace ?? "top"}
type="dark"
effect="solid"
id={tipId}
backgroundColor={COLORS["tooltip-bg"]}
delayHide={tooltipDelayHide}
delayUpdate={500}
overridePosition={(pos: { left: number; top: number }) => {
return {
left: pos.left + leftAdj,
top: pos.top + topAdj,
};
}}
>
{`This is a Fleet Premium feature. `}
<CustomLink
url="https://fleetdm.com/upgrade"
text="Learn more"
newTab
multiline={false}
iconColor="core-fleet-white"
/>
</ReactTooltip>
</span>
);
};
export default PremiumFeatureIconWithTooltip;
@@ -1,14 +0,0 @@
import { Meta, StoryObj } from "@storybook/react";
import PremiumFeatureIconWithTooltip from "./PremiumFeatureIconWithTooltip";
const meta: Meta<typeof PremiumFeatureIconWithTooltip> = {
title: "Components/PremiumFeatureIconWithTooltip",
component: PremiumFeatureIconWithTooltip,
};
export default meta;
type Story = StoryObj<typeof PremiumFeatureIconWithTooltip>;
export const Basic: Story = {};
@@ -1,5 +0,0 @@
.premium-icon-tip {
font-style: normal;
font-size: $x-small;
font-weight: normal;
}
@@ -1 +0,0 @@
export { default } from "./PremiumFeatureIconWithTooltip";
@@ -1,6 +1,5 @@
import React, { useCallback } from "react";
import { kebabCase, noop } from "lodash";
import PremiumFeatureIconWithTooltip from "components/PremiumFeatureIconWithTooltip";
import { ButtonVariant } from "components/buttons/Button/Button";
import Icon from "components/Icon/Icon";
@@ -17,7 +16,6 @@ export interface IActionButtonProps {
hideButton?: boolean | ((targetIds: number[]) => boolean);
iconSvg?: IconNames;
iconPosition?: string;
indicatePremiumFeature?: boolean;
}
function useActionCallback(
@@ -41,7 +39,6 @@ const ActionButton = (buttonProps: IActionButtonProps): JSX.Element | null => {
hideButton,
iconSvg,
iconPosition,
indicatePremiumFeature,
} = buttonProps;
const onButtonClick = useActionCallback(onActionButtonClick || noop);
@@ -62,14 +59,7 @@ const ActionButton = (buttonProps: IActionButtonProps): JSX.Element | null => {
return (
<div className={`${baseClass} ${baseClass}__${kebabCase(name)}`}>
{indicatePremiumFeature && (
<PremiumFeatureIconWithTooltip tooltipDelayHide={500} />
)}
<Button
disabled={indicatePremiumFeature}
onClick={() => onButtonClick(targetIds)}
variant={variant}
>
<Button onClick={() => onButtonClick(targetIds)} variant={variant}>
<>
{iconPosition === "left" && iconSvg && <Icon name={iconSvg} />}
{buttonText}
@@ -402,7 +402,6 @@ const DataTable = ({
hideButton,
iconSvg,
iconPosition,
indicatePremiumFeature,
} = actionButtonProps;
return (
<div className={`${baseClass}__${kebabCase(name)}`}>
@@ -414,7 +413,6 @@ const DataTable = ({
targetIds={targetIds}
variant={variant}
hideButton={hideButton}
indicatePremiumFeature={indicatePremiumFeature}
iconSvg={iconSvg}
iconPosition={iconPosition}
/>
@@ -17,6 +17,8 @@ interface ITooltipTruncatedTextCellProps {
/** @deprecated use the prop `className` in order to add custom classes to this component */
classes?: string;
className?: string;
/** Content does not get truncated */
suffix?: React.ReactNode;
}
const baseClass = "tooltip-truncated-cell";
@@ -27,13 +29,14 @@ const TooltipTruncatedTextCell = ({
tooltipBreakOnWord = false,
classes = "w250",
className,
suffix,
}: ITooltipTruncatedTextCellProps): JSX.Element => {
const classNames = classnames(baseClass, classes, className, {
"tooltip-break-on-word": tooltipBreakOnWord,
});
// Tooltip visibility logic: Enable only when text is truncated
const ref = useRef<HTMLInputElement>(null);
const ref = useRef<HTMLSpanElement>(null);
const [tooltipDisabled, setTooltipDisabled] = useState(true);
useLayoutEffect(() => {
@@ -51,14 +54,14 @@ const TooltipTruncatedTextCell = ({
return (
<div className={classNames}>
<div
className="data-table__tooltip-truncated-text"
className="data-table__tooltip-truncated-text-container"
data-tip
data-for={tooltipId}
data-tip-disable={isDefaultValue || tooltipDisabled}
>
<span
ref={ref}
className={`data-table__tooltip-truncated-text--cell ${
className={`data-table__tooltip-truncated-text ${
isDefaultValue ? "text-muted" : ""
} ${tooltipDisabled ? "" : "truncated"}`}
>
@@ -81,6 +84,7 @@ const TooltipTruncatedTextCell = ({
{/* Fixes triple click selecting next element in Safari */}
</>
</ReactTooltip>
{suffix && <span className="data-table__suffix">{suffix}</span>}
</div>
);
};
@@ -1,8 +1,22 @@
.tooltip-truncated-cell {
display: flex;
align-items: center; // For badges, etc
.text-muted {
color: $ui-fleet-black-50;
}
.data-table__tooltip-truncated-text-container {
display: flex; // Flex container for text and suffix
align-items: center; // Keep text and suffix aligned
min-width: 0; // Prevent flex child from growing beyond container
}
.data-table__tooltip-truncated-text {
white-space: nowrap; /* Prevent wrapping */
overflow: hidden; /* Hide overflowing text */
text-overflow: ellipsis; /* Add ellipsis for truncated text */
&--cell {
display: inline-block;
overflow: hidden;
@@ -35,4 +49,9 @@
word-break: normal;
}
}
.data-table__suffix {
margin-left: 8px; /* Add spacing between text and suffix */
flex-shrink: 0; /* Prevent suffix from shrinking */
}
}
@@ -1,26 +1,12 @@
import React, { KeyboardEvent } from "react";
import React from "react";
import { Meta, StoryFn } from "@storybook/react";
import { noop } from "lodash";
import AutoSizeInputField from ".";
import { IAutoSizeInputFieldProps } from "./AutoSizeInputField";
import "../../../../index.scss";
interface IAutoSizeInputFieldProps {
name: string;
placeholder: string;
value: string;
inputClassName?: string;
maxLength: number;
hasError?: boolean;
isDisabled?: boolean;
isFocused?: boolean;
onFocus: () => void;
onBlur: () => void;
onChange: (newSelectedValue: string) => void;
onKeyPress: (event: KeyboardEvent<HTMLTextAreaElement>) => void;
}
export default {
component: AutoSizeInputField,
title: "Components/FormFields/Input/AutoSizeInputField",
@@ -1,7 +1,7 @@
import React, { KeyboardEvent, useEffect, useRef } from "react";
import classnames from "classnames";
interface IAutoSizeInputFieldProps {
export interface IAutoSizeInputFieldProps {
name: string;
placeholder: string;
value: string;
@@ -14,16 +14,6 @@
.status__header {
width: 30%;
}
.hosts__header {
border-right: 0;
padding-right: 0;
width: 60px;
}
.linkToFilteredHosts__header {
width: 140px;
}
}
}
}
@@ -40,6 +30,5 @@
}
}
}
}
}
@@ -21,7 +21,6 @@
padding-right: 0;
}
.hosts_count__header {
border-right: 0;
padding-right: 0;
width: 9%;
}
@@ -30,9 +29,6 @@
border-left: 0;
width: 40px;
}
.linkToFilteredHosts__header {
width: 140px;
}
}
tbody {
@@ -52,7 +52,6 @@
padding-right: 0;
}
.hosts_count__header {
border-right: 0;
padding-right: 0;
width: 60px;
}
@@ -10,10 +10,6 @@
min-width: auto;
}
.data-table-block th:nth-last-child(2) {
border-right: 0;
}
@media (max-width: $break-lg) {
.view-hosts-link {
span {
@@ -51,8 +51,6 @@ const SetupAssistantProfileUploader = ({
text="Learn more"
className={`${baseClass}__new-tab`}
newTab
color="core-fleet-black"
iconColor="core-fleet-white"
/>
</>
);
@@ -129,6 +129,7 @@ const FleetMaintainedAppDetailsPage = ({
}
const { renderFlash } = useContext(NotificationContext);
const handlePageError = useErrorHandler();
const { isPremiumTier } = useContext(AppContext);
const { selectedOsqueryTable, setSelectedOsqueryTable } = useContext(
@@ -1,7 +0,0 @@
.software-title-details-table {
.data-table {
.hosts_count__header {
border-right: 0;
}
}
}
@@ -53,11 +53,6 @@
width: $col-md;
}
.hosts_count__header {
width: auto;
border-right: 0;
}
@media (min-width: $break-lg) {
// expand the width of version header at larger screen sizes
.versions__header {
@@ -4,10 +4,6 @@
&__wrapper {
overflow-x: auto;
}
th:nth-last-child(2) {
border-right: 0;
}
}
// used to position header text with premium icon correctly
@@ -1,17 +1,16 @@
import React from "react";
import ReactTooltip from "react-tooltip";
import stringUtils from "utilities/strings";
import { IUser, UserRole } from "interfaces/user";
import { ITeam } from "interfaces/team";
import { IDropdownOption } from "interfaces/dropdownOption";
import TextCell from "components/TableContainer/DataTable/TextCell/TextCell";
import TooltipTruncatedTextCell from "components/TableContainer/DataTable/TooltipTruncatedTextCell";
import ActionsDropdown from "components/ActionsDropdown";
import CustomLink from "components/CustomLink";
import TooltipWrapper from "components/TooltipWrapper";
import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper";
import stringUtils from "utilities/strings";
import { COLORS } from "styles/var/colors";
interface IHeaderProps {
column: {
@@ -59,6 +58,32 @@ export interface ITeamUsersTableData {
id: number;
}
export const renderApiUserIndicator = () => {
return (
<TooltipWrapper
className="api-only-tooltip"
tipContent={
<>
This user was created using fleetctl and
<br /> only has API access.{" "}
<CustomLink
text="Learn more"
newTab
url="https://fleetdm.com/docs/using-fleet/fleetctl-cli#using-fleetctl-with-an-api-only-user"
variant="tooltip-link"
/>
</>
}
tipOffset={14}
position="top"
showArrow
underline={false}
>
<span className="team-users__api-only-user">API</span>
</TooltipWrapper>
);
};
// NOTE: cellProps come from react-table
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
const generateColumnConfigs = (
@@ -72,52 +97,17 @@ const generateColumnConfigs = (
sortType: "caseInsensitive",
accessor: "name",
Cell: (cellProps: ICellProps) => {
const formatter = (val: string) => {
const apiOnlyUser =
"api_only" in cellProps.row.original
? cellProps.row.original.api_only
: false;
const apiOnlyUser =
"api_only" in cellProps.row.original
? cellProps.row.original.api_only
: false;
return (
<>
{val}
{apiOnlyUser && (
<>
<span
className="team-users__api-only-user"
data-tip
data-for={`api-only-tooltip-${cellProps.row.original.id}`}
>
API
</span>
<ReactTooltip
className="api-only-tooltip"
place="top"
type="dark"
effect="solid"
id={`api-only-tooltip-${cellProps.row.original.id}`}
backgroundColor={COLORS["tooltip-bg"]}
clickable
delayHide={200} // need delay set to hover using clickable
>
<>
This user was created using fleetctl and
<br /> only has API access.{" "}
<CustomLink
text="Learn more"
newTab
url="https://fleetdm.com/docs/using-fleet/fleetctl-cli#using-fleetctl-with-an-api-only-user"
iconColor="core-fleet-white"
/>
</>
</ReactTooltip>
</>
)}
</>
);
};
return <TextCell value={cellProps.cell.value} formatter={formatter} />;
return (
<TooltipTruncatedTextCell
value={cellProps.cell.value}
suffix={apiOnlyUser && renderApiUserIndicator()}
/>
);
},
},
{
@@ -13,25 +13,17 @@
.data-table__table {
thead {
.name__header {
width: $col-md;
width: initial; // Allows name to take up as much space on table as it can before truncating
}
.role__header {
width: $col-md;
}
.actions__header {
width: auto;
width: $col-sm;
}
}
tbody {
.name__cell {
max-width: $col-md;
}
.role__cell {
max-width: $col-md;
max-width: $col-sm;
}
.actions__cell {
@@ -26,6 +26,7 @@
border-radius: 30px;
margin-left: $pad-medium;
padding: 4px 12px;
white-space: nowrap;
}
.react-tabs__tab-list {
@@ -39,4 +40,9 @@
padding: 0;
}
}
.user-name-text {
text-overflow: ellipsis;
overflow: hidden;
}
}
@@ -17,8 +17,16 @@
}
tbody {
.name__cell {
width: auto;
// Override data-table td max-width at low widths to fit screen
@media (max-width: ($break-md - 1)) {
.name__cell {
max-width: 430px;
}
}
@media (max-width: ($break-sm - 1)) {
.name__cell {
max-width: 330px;
}
}
.host_count__cell,
@@ -3,104 +3,108 @@
@include grey-badge;
}
thead {
// need specificity to override datatable css
th {
&.actions__header {
padding-left: 0;
}
&.status__header,
&.role__header {
width: 86px; // set to prevent expanding
}
}
}
tbody {
// need specificity to override datatable css
td.name__cell,
td.role__cell,
td.teams__cell,
td.status__cell,
td.email__cell {
max-width: $col-sm;
white-space: nowrap;
}
td.status__cell,
td.role__cell {
white-space: nowrap; // Prevent No access from wrapping
}
td.actions__cell {
padding-left: 0;
}
}
@media (min-width: ($break-lg)) {
.name__header,
.name__cell {
max-width: $col-md;
}
}
@media (max-width: ($break-sm - 1)) {
.email__header,
.email__cell {
display: none;
width: 0;
}
}
@media (max-width: ($break-xs - 1)) {
.status__header,
.status__cell {
display: none;
width: 0;
}
}
@media (max-width: ($break-mobile-md - 1)) {
.teams__header,
.teams__cell {
display: none;
width: 0;
}
// Splits header to 2 lines with user count on the first line
.table-container__header {
align-items: end;
&-left {
flex-direction: column;
width: initial;
align-items: start;
.data-table-block {
.data-table__table {
thead {
// need specificity to override datatable css
th {
&.actions__header {
padding-left: 0;
}
&.status__header,
&.role__header {
width: 86px; // set to prevent expanding
}
}
}
.table-container__search {
width: 100%;
tbody {
// need specificity to override datatable css
td.name__cell,
td.role__cell,
td.teams__cell,
td.status__cell,
td.email__cell {
max-width: $col-sm;
white-space: nowrap;
}
td.status__cell,
td.role__cell {
white-space: nowrap; // Prevent No access from wrapping
}
td.actions__cell {
padding-left: 0;
}
}
}
}
@media (max-width: ($break-mobile-sm - 1)) {
.role__header,
.role__cell {
display: none;
width: 0;
}
@media (min-width: ($break-lg)) {
.name__header,
.name__cell {
max-width: $col-md;
}
}
// Splits header to 3 lines; user count, wide add user button, wide search
.table-container__header {
flex-direction: column;
align-items: start;
width: 100%;
@media (max-width: ($break-sm - 1)) {
.email__header,
.email__cell {
display: none;
width: 0;
}
}
&-left {
width: 100%;
@media (max-width: ($break-xs - 1)) {
.status__header,
.status__cell {
display: none;
width: 0;
}
}
.controls,
.button {
@media (max-width: ($break-mobile-md - 1)) {
.teams__header,
.teams__cell {
display: none;
width: 0;
}
// Splits header to 2 lines with user count on the first line
.table-container__header {
align-items: end;
&-left {
flex-direction: column;
width: initial;
align-items: start;
}
.table-container__search {
width: 100%;
}
}
}
@media (max-width: ($break-mobile-sm - 1)) {
.role__header,
.role__cell {
display: none;
width: 0;
}
// Splits header to 3 lines; user count, wide add user button, wide search
.table-container__header {
flex-direction: column;
align-items: start;
width: 100%;
&-left {
width: 100%;
.controls,
.button {
width: 100%;
}
}
}
}
}
@@ -1,17 +1,16 @@
import React from "react";
import ReactTooltip from "react-tooltip";
import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
import StatusIndicator from "components/StatusIndicator";
import TextCell from "components/TableContainer/DataTable/TextCell/TextCell";
import CustomLink from "components/CustomLink";
import TooltipTruncatedTextCell from "components/TableContainer/DataTable/TooltipTruncatedTextCell";
import TooltipWrapper from "components/TooltipWrapper";
import { IInvite } from "interfaces/invite";
import { IUser, UserRole } from "interfaces/user";
import { IDropdownOption } from "interfaces/dropdownOption";
import { generateRole, generateTeam, greyCell } from "utilities/helpers";
import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";
import { COLORS } from "styles/var/colors";
import { renderApiUserIndicator } from "pages/admin/TeamManagementPage/TeamDetailsWrapper/UsersPage/UsersPageTableConfig";
import ActionsDropdown from "../../../../../components/ActionsDropdown";
interface IHeaderProps {
@@ -75,52 +74,17 @@ const generateTableHeaders = (
disableSortBy: true,
accessor: "name",
Cell: (cellProps: ICellProps) => {
const formatter = (val: string) => {
const apiOnlyUser =
"api_only" in cellProps.row.original
? cellProps.row.original.api_only
: false;
const apiOnlyUser =
"api_only" in cellProps.row.original
? cellProps.row.original.api_only
: false;
return (
<>
{val}
{apiOnlyUser && (
<>
<span
className="user-management__api-only-user"
data-tip
data-for={`api-only-tooltip-${cellProps.row.original.id}`}
>
API
</span>
<ReactTooltip
className="api-only-tooltip"
place="top"
type="dark"
effect="solid"
id={`api-only-tooltip-${cellProps.row.original.id}`}
backgroundColor={COLORS["tooltip-bg"]}
clickable
delayHide={200} // need delay set to hover using clickable
>
<>
This user was created using fleetctl and
<br /> only has API access.{" "}
<CustomLink
text="Learn more"
newTab
url="https://fleetdm.com/docs/using-fleet/fleetctl-cli#using-fleetctl-with-an-api-only-user"
iconColor="core-fleet-white"
/>
</>
</ReactTooltip>
</>
)}
</>
);
};
return <TextCell value={cellProps.cell.value} formatter={formatter} />;
return (
<TooltipTruncatedTextCell
value={cellProps.cell.value}
suffix={apiOnlyUser && renderApiUserIndicator()}
/>
);
},
},
{
@@ -149,13 +149,11 @@ const ManageHostsPage = ({
isOnlyObserver,
isPremiumTier,
isFreeTier,
isSandboxMode,
userSettings,
setFilteredHostsPath,
setFilteredPoliciesPath,
setFilteredQueriesPath,
setFilteredSoftwarePath,
setUserSettings,
} = useContext(AppContext);
const { renderFlash } = useContext(NotificationContext);
@@ -1563,7 +1561,6 @@ const ManageHostsPage = ({
variant: "text-icon",
iconSvg: "transfer",
hideButton: !isPremiumTier || (!isGlobalAdmin && !isGlobalMaintainer),
indicatePremiumFeature: isPremiumTier && isSandboxMode,
},
];
@@ -1704,7 +1701,7 @@ const ManageHostsPage = ({
<div className={`${baseClass}__header-wrap`}>
{renderHeader()}
<div className={`${baseClass} button-wrap`}>
{!isSandboxMode && canEnrollHosts && !hasErrors && (
{canEnrollHosts && !hasErrors && (
<Button
onClick={() => setShowEnrollSecretModal(true)}
className={`${baseClass}__enroll-hosts button`}
@@ -88,8 +88,7 @@ const DeviceUserBanners = ({
<CustomLink
url="https://fleetdm.com/learn-more-about/encrypt-linux-device"
text="Guide"
color="core-fleet-black"
iconColor="core-fleet-black"
variant="banner-link"
/>
}
color="yellow"
@@ -124,8 +124,7 @@ const HostDetailsBanners = ({
<CustomLink
url={`${LEARN_MORE_ABOUT_BASE_LINK}/mdm-disk-encryption`}
text="Guide"
color="core-fleet-black"
iconColor="core-fleet-black"
variant="banner-link"
newTab
/>
}
@@ -104,7 +104,7 @@ const generateErrorTooltip = (
text="Learn more"
url="https://fleetdm.com/learn-more-about/idp-email"
newTab
iconColor="core-fleet-white"
variant="tooltip-link"
/>
</>
);