diff --git a/cypress/integration/all/app/settingsflow.spec.ts b/cypress/integration/all/app/settingsflow.spec.ts index 419358cbb0..2295b11994 100644 --- a/cypress/integration/all/app/settingsflow.spec.ts +++ b/cypress/integration/all/app/settingsflow.spec.ts @@ -49,8 +49,10 @@ describe("App settings flow", () => { .click() .type("https://http.cat/100"); - // only allowed to fill in either metadata || metadata url - cy.findByLabelText(/metadata url/i) + // specifically targeting this one to avoid conflict + // with cypress seeing multiple "metadata url" - one + // in a tooltip, the other as the actual label + cy.getAttached("[for='metadataURL']") .click() .type("http://github.com/fleetdm/fleet"); @@ -62,9 +64,10 @@ describe("App settings flow", () => { .click() .type("rachel@example.com"); - cy.findByLabelText(/smtp server/i) - .click() - .type("localhost"); + // specifically targeting this one to avoid conflict + // with cypress seeing multiple "metadata" - one + // in a tooltip, the other as the actual label + cy.getAttached("[for='smtpServer']").click().type("localhost"); cy.getAttached("#smtpPort").clear().type("1025"); @@ -86,15 +89,13 @@ describe("App settings flow", () => { .click() .type("http://server.com/example"); - cy.getAttached(".app-config-form__host-percentage").click(); + cy.getAttached( + ".app-config-form__host-percentage .Select-control" + ).click(); + cy.getAttached(".Select-menu-outer").contains(/5%/i).click(); - cy.getAttached(".app-config-form__host-percentage") - .contains(/5%/i) - .click(); - - cy.getAttached(".app-config-form__days-count").click(); - - cy.getAttached(".app-config-form__days-count") + cy.getAttached(".app-config-form__days-count .Select-control").click(); + cy.getAttached(".Select-menu-outer") .contains(/7 days/i) .click(); @@ -104,15 +105,20 @@ describe("App settings flow", () => { cy.findByLabelText(/verify ssl certs/i).check({ force: true }); cy.findByLabelText(/enable starttls/i).check({ force: true }); - cy.findByLabelText(/^host expiry$/i).check({ force: true }); + cy.getAttached("[for='enableHostExpiry']").within(() => { + cy.getAttached("[type='checkbox']").check({ force: true }); + }); - cy.findByLabelText(/host expiry window/i) - .clear() - .type("5"); + // specifically targeting this one to avoid conflict + // with cypress seeing multiple "host expiry" - one + // in the checkbox above, the other as this label + cy.getAttached("[name='hostExpiryWindow']").clear().type("5"); cy.findByLabelText(/disable live queries/i).check({ force: true }); - cy.findByRole("button", { name: /update settings/i }).click(); + cy.findByRole("button", { name: /update settings/i }) + .invoke("attr", "disabled", false) + .click(); cy.findByText(/updated settings/i).should("exist"); @@ -150,7 +156,7 @@ describe("App settings flow", () => { "https://http.cat/100" ); - cy.findByLabelText(/metadata url/i).should( + cy.getAttached("#metadataURL").should( "have.value", "http://github.com/fleetdm/fleet" ); @@ -160,7 +166,7 @@ describe("App settings flow", () => { "rachel@example.com" ); - cy.findByLabelText(/smtp server/i).should("have.value", "localhost"); + cy.getAttached("#smtpServer").should("have.value", "localhost"); cy.getAttached("#smtpPort").should("have.value", "1025"); diff --git a/frontend/components/LastUpdatedText/LastUpdatedText.tsx b/frontend/components/LastUpdatedText/LastUpdatedText.tsx new file mode 100644 index 0000000000..93aadc739d --- /dev/null +++ b/frontend/components/LastUpdatedText/LastUpdatedText.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict"; + +import TooltipWrapper from "components/TooltipWrapper"; + +const baseClass = "component__last-updated-text"; + +const renderLastUpdatedText = ( + lastUpdatedAt: string, + whatToRetrieve: string +): JSX.Element => { + if (!lastUpdatedAt || lastUpdatedAt === "0001-01-01T00:00:00Z") { + lastUpdatedAt = "never"; + } else { + lastUpdatedAt = formatDistanceToNowStrict(new Date(lastUpdatedAt), { + addSuffix: true, + }); + } + + return ( + + + {`Last updated ${lastUpdatedAt}`} + + + ); +}; + +export default renderLastUpdatedText; diff --git a/frontend/components/LastUpdatedText/_styles.scss b/frontend/components/LastUpdatedText/_styles.scss new file mode 100644 index 0000000000..91654058ea --- /dev/null +++ b/frontend/components/LastUpdatedText/_styles.scss @@ -0,0 +1,19 @@ +.component__last-updated-text { + font-size: $xx-small; + color: $ui-fleet-black-75; + padding-right: $pad-small; + + .tooltip { + padding-left: $pad-xsmall; + } + + .tooltip__tooltip-text { + display: flex; + text-align: center; + } + img { + height: 16px; + width: 16px; + vertical-align: text-top; + } + } \ No newline at end of file diff --git a/frontend/pages/Homepage/components/LastUpdatedText/index.ts b/frontend/components/LastUpdatedText/index.ts similarity index 100% rename from frontend/pages/Homepage/components/LastUpdatedText/index.ts rename to frontend/components/LastUpdatedText/index.ts diff --git a/frontend/components/TooltipWrapper/TooltipWrapper.stories.tsx b/frontend/components/TooltipWrapper/TooltipWrapper.stories.tsx new file mode 100644 index 0000000000..e7555311a8 --- /dev/null +++ b/frontend/components/TooltipWrapper/TooltipWrapper.stories.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import { Meta, Story } from "@storybook/react"; + +import TooltipWrapper from "."; + +import "../../index.scss"; + +interface ITooltipWrapperProps { + children: string; + tipContent: string; +} + +export default { + component: TooltipWrapper, + title: "Components/Tooltip", + args: { + tipContent: "This is an example tooltip.", + }, + argTypes: { + position: { + options: ["top", "bottom"], + control: "radio", + }, + }, +} as Meta; + +// using line breaks to create space for top position +const Template: Story = (props) => ( + <> +
+
+
+
+ Example text + +); + +export const Default = Template.bind({}); diff --git a/frontend/components/TooltipWrapper/TooltipWrapper.tsx b/frontend/components/TooltipWrapper/TooltipWrapper.tsx new file mode 100644 index 0000000000..485e8900fd --- /dev/null +++ b/frontend/components/TooltipWrapper/TooltipWrapper.tsx @@ -0,0 +1,30 @@ +import React from "react"; + +interface ITooltipWrapperProps { + children: string; + tipContent: string; + position?: "top" | "bottom"; +} + +const baseClass = "component__tooltip-wrapper"; + +const TooltipWrapper = ({ + children, + tipContent, + position = "bottom", +}: ITooltipWrapperProps): JSX.Element => { + return ( +
+
+ {children} +
+
+
+
+ ); +}; + +export default TooltipWrapper; diff --git a/frontend/components/TooltipWrapper/_styles.scss b/frontend/components/TooltipWrapper/_styles.scss new file mode 100644 index 0000000000..382a08aa6a --- /dev/null +++ b/frontend/components/TooltipWrapper/_styles.scss @@ -0,0 +1,78 @@ +.component__tooltip-wrapper { + position: relative; + cursor: help; + + &:hover { + .component__tooltip-wrapper__tip-text { + visibility: visible; + opacity: 1; + } + } + &__element { + position: static; + display: inline; // treat like a span but allow other tags as children + } + &__underline { + position: absolute; + top: 0; + left: 0; + + &::before { + content: attr(data-text); + opacity: 0; + visibility: hidden; + } + &::after { + content: ""; + width: 100%; + height: 100%; + position: absolute; + bottom: -2px; + left: 0; + border-bottom: 1px dashed $ui-fleet-black-50; + } + } + &__tip-text { + width: max-content; + max-width: 296px; + padding: 12px; + color: $core-white; + background-color: $core-fleet-blue; + font-weight: $regular; + font-size: $xx-small; + border-radius: 4px; + position: absolute; + top: calc(100% + 6px); + left: 0; + box-sizing: border-box; + z-index: 99; // not more than the site nav + visibility: hidden; + opacity: 0; + transition: opacity .3s ease; + line-height: 1.375; + + // invisible block to cover space so + // hover state can continue from text to bubble + &::before { + content: ""; + width: 100%; + height: 6px; + position: absolute; + top: -6px; + left: 0; + } + p { + margin: 0; + } + } + &[data-position="top"] { + .component__tooltip-wrapper__tip-text { + top: auto; + bottom: 100%; + + &::before { + display: none; + } + } + } +} \ No newline at end of file diff --git a/frontend/components/TooltipWrapper/index.tsx b/frontend/components/TooltipWrapper/index.tsx new file mode 100644 index 0000000000..c89e32ce2f --- /dev/null +++ b/frontend/components/TooltipWrapper/index.tsx @@ -0,0 +1 @@ +export { default } from "./TooltipWrapper"; diff --git a/frontend/components/forms/FormField/FormField.tsx b/frontend/components/forms/FormField/FormField.tsx index d26da67f12..392ac516de 100644 --- a/frontend/components/forms/FormField/FormField.tsx +++ b/frontend/components/forms/FormField/FormField.tsx @@ -2,6 +2,8 @@ import React from "react"; import classnames from "classnames"; import { isEmpty } from "lodash"; +import TooltipWrapper from "components/TooltipWrapper"; + const baseClass = "form-field"; export interface IFormFieldProps { @@ -12,6 +14,7 @@ export interface IFormFieldProps { label: Array | JSX.Element | string; name: string; type: string; + tooltip?: string; } const FormField = ({ @@ -22,6 +25,7 @@ const FormField = ({ label, name, type, + tooltip, }: IFormFieldProps): JSX.Element => { const renderLabel = () => { const labelWrapperClasses = classnames(`${baseClass}__label`, { @@ -33,8 +37,19 @@ const FormField = ({ } return ( -
- - - Editing your email address requires that SMTP is
- configured in order to send a validation email.
-
- Users with Admin role can configure SMTP in -
- Settings > Organization settings. -
-
-
-
-
@@ -419,13 +413,7 @@ const AppConfigFormFunctional = ({ parseTarget onBlur={validateForm} error={formErrors.idp_name} - /> - -
-
@@ -443,13 +431,7 @@ const AppConfigFormFunctional = ({ parseTarget onBlur={validateForm} error={formErrors.entity_id} - /> -
-
-
@@ -459,11 +441,7 @@ const AppConfigFormFunctional = ({ name="issuerURI" value={issuerURI} parseTarget - /> -
-
-
@@ -475,13 +453,7 @@ const AppConfigFormFunctional = ({ parseTarget onBlur={validateForm} error={formErrors.idp_image_url} - /> -
-
-
@@ -493,13 +465,7 @@ const AppConfigFormFunctional = ({ value={metadata} parseTarget onBlur={validateForm} - /> -
-
-
@@ -517,14 +483,9 @@ const AppConfigFormFunctional = ({ parseTarget onBlur={validateForm} error={formErrors.metadata_url} + tooltip="A URL that references the identity provider metadata." />
-
- -
-
-
- -
-
- -
- {renderSmtpSection()} -
-
- If your mail server requires authentication, you need to specify the authentication type here.

\ -

No Authentication - Select this if your SMTP is open.

\ -

Username & Password - Select this if your SMTP server requires authentication with a username and password.

\ - " +

If your mail server requires authentication, you need to specify the authentication type here.

\ +

No Authentication - Select this if your SMTP is open.

\ +

Username & Password - Select this if your SMTP server requires authentication with a username and password.

\ + " } /> + {renderSmtpSection()}
); @@ -794,14 +742,9 @@ const AppConfigFormFunctional = ({ parseTarget onBlur={validateForm} error={formErrors.destination_url} - /> - -
-

Provide a URL to deliver
the webhook request to.

\ +

Provide a URL to deliver
the webhook request to.

\ " } /> @@ -814,14 +757,9 @@ const AppConfigFormFunctional = ({ name="hostStatusWebhookHostPercentage" value={hostStatusWebhookHostPercentage} parseTarget - /> -
-
-

Select the minimum percentage of hosts that
must fail to check into Fleet in order to trigger
the webhook request.

\ +

Select the minimum percentage of hosts that
must fail to check into Fleet in order to trigger
the webhook request.

\ " } /> @@ -834,14 +772,9 @@ const AppConfigFormFunctional = ({ name="hostStatusWebhookDaysCount" value={hostStatusWebhookDaysCount} parseTarget - /> -
-
-

Select the minimum number of days that the
configured Percentage of hosts must fail to
check into Fleet in order to trigger the
webhook request.

\ +

Select the minimum number of days that the
configured Percentage of hosts must fail to
check into Fleet in order to trigger the
webhook request.

\ " } /> @@ -917,10 +850,7 @@ const AppConfigFormFunctional = ({ name="domain" value={domain} parseTarget - /> - If you need to specify a HELO domain,
you can do it here (Default: Blank)

' } /> @@ -931,15 +861,12 @@ const AppConfigFormFunctional = ({ name="verifySSLCerts" value={verifySSLCerts} parseTarget + tooltip={ + '

Turn this off (not recommended)
if you use a self-signed certificate
(Default: On)

' + } > Verify SSL certs - Turn this off (not recommended)
if you use a self-signed certificate
(Default: On)

' - } - />
Detects if STARTTLS is enabled
in your SMTP server and starts
to use it. (Default: On)

' + } > Enable STARTTLS
- Detects if STARTTLS is enabled
in your SMTP server and starts
to use it. (Default: On)

' - } - />
When enabled, allows automatic cleanup
of hosts that have not communicated with Fleet
in some number of days. (Default: Off)

' + } > Host expiry
- When enabled, allows automatic cleanup
of hosts that have not communicated with Fleet
in some number of days. (Default: Off)

' - } - />
- If a host has not communicated with Fleet
in the specified number of days, it will be removed.

" + tooltip={ + "

If a host has not communicated with Fleet in the specified number of days, it will be removed.

" } />
@@ -998,15 +916,12 @@ const AppConfigFormFunctional = ({ name="disableLiveQuery" value={disableLiveQuery} parseTarget + tooltip={ + '

When enabled, disables the ability to run live queries
(ad hoc queries executed via the UI or fleetctl). (Default: Off)

' + } > Disable live queries - When enabled, disables the ability to run live queries
(ad hoc queries executed via the UI or fleetctl). (Default: Off)

' - } - /> diff --git a/frontend/components/forms/fields/Checkbox/Checkbox.tsx b/frontend/components/forms/fields/Checkbox/Checkbox.tsx index 89bfb0db59..25a026a25f 100644 --- a/frontend/components/forms/fields/Checkbox/Checkbox.tsx +++ b/frontend/components/forms/fields/Checkbox/Checkbox.tsx @@ -4,6 +4,7 @@ import { noop, pick } from "lodash"; import FormField from "components/forms/FormField"; import { IFormFieldProps } from "components/forms/FormField/FormField"; +import TooltipWrapper from "components/TooltipWrapper"; const baseClass = "fleet-checkbox"; @@ -18,6 +19,7 @@ export interface ICheckboxProps { wrapperClassName?: string; indeterminate?: boolean; parseTarget?: boolean; + tooltip?: string; } const Checkbox = (props: ICheckboxProps) => { @@ -32,6 +34,7 @@ const Checkbox = (props: ICheckboxProps) => { wrapperClassName, indeterminate, parseTarget, + tooltip, } = props; const handleChange = () => { @@ -72,7 +75,15 @@ const Checkbox = (props: ICheckboxProps) => { }} /> - {children} + + {tooltip ? ( + + {children as string} + + ) : ( + <>{children} + )} + ); diff --git a/frontend/components/forms/fields/Checkbox/_styles.scss b/frontend/components/forms/fields/Checkbox/_styles.scss index 0a206a4610..d569ce8218 100644 --- a/frontend/components/forms/fields/Checkbox/_styles.scss +++ b/frontend/components/forms/fields/Checkbox/_styles.scss @@ -85,5 +85,6 @@ &__label { font-size: $x-small; padding-left: $pad-small; + display: inherit; } } diff --git a/frontend/components/forms/fields/Dropdown/Dropdown.jsx b/frontend/components/forms/fields/Dropdown/Dropdown.jsx index 75a3fba15c..455ee6a9ec 100644 --- a/frontend/components/forms/fields/Dropdown/Dropdown.jsx +++ b/frontend/components/forms/fields/Dropdown/Dropdown.jsx @@ -32,6 +32,7 @@ class Dropdown extends Component { ]), wrapperClassName: PropTypes.string, parseTarget: PropTypes.bool, + tooltip: PropTypes.string, }; static defaultProps = { @@ -45,6 +46,7 @@ class Dropdown extends Component { name: "targets", placeholder: "Select One...", parseTarget: false, + tooltip: "", }; onMenuOpen = () => { @@ -124,7 +126,13 @@ class Dropdown extends Component { searchable, } = this.props; - const formFieldProps = pick(this.props, ["hint", "label", "error", "name"]); + const formFieldProps = pick(this.props, [ + "hint", + "label", + "error", + "name", + "tooltip", + ]); const selectClasses = classnames(className, `${baseClass}__select`, { [`${baseClass}__select--error`]: error, }); diff --git a/frontend/components/forms/fields/InputField/InputField.jsx b/frontend/components/forms/fields/InputField/InputField.jsx index 9212dfb948..f24b927005 100644 --- a/frontend/components/forms/fields/InputField/InputField.jsx +++ b/frontend/components/forms/fields/InputField/InputField.jsx @@ -28,6 +28,7 @@ class InputField extends Component { PropTypes.number, ]).isRequired, parseTarget: PropTypes.bool, + tooltip: PropTypes.string, }; static defaultProps = { @@ -42,6 +43,7 @@ class InputField extends Component { blockAutoComplete: false, value: "", parseTarget: false, + tooltip: "", }; componentDidMount() { @@ -93,7 +95,13 @@ class InputField extends Component { [`${baseClass}__textarea`]: type === "textarea", }); - const formFieldProps = pick(this.props, ["hint", "label", "error", "name"]); + const formFieldProps = pick(this.props, [ + "hint", + "label", + "error", + "name", + "tooltip", + ]); if (type === "textarea") { return ( diff --git a/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.jsx b/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.jsx index 7a14973442..b5b0fe8c43 100644 --- a/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.jsx +++ b/frontend/components/forms/fields/InputFieldWithIcon/InputFieldWithIcon.jsx @@ -3,6 +3,7 @@ import PropTypes from "prop-types"; import classnames from "classnames"; import FleetIcon from "components/icons/FleetIcon"; +import TooltipWrapper from "components/TooltipWrapper"; import InputField from "../InputField"; const baseClass = "input-icon-field"; @@ -23,10 +24,11 @@ class InputFieldWithIcon extends InputField { disabled: PropTypes.bool, iconPosition: PropTypes.oneOf(["start", "end"]), inputOptions: PropTypes.object, // eslint-disable-line react/forbid-prop-types + tooltip: PropTypes.string, }; renderHeading = () => { - const { error, placeholder, name, label } = this.props; + const { error, placeholder, name, label, tooltip } = this.props; const labelClasses = classnames(`${baseClass}__label`); if (error) { @@ -34,8 +36,16 @@ class InputFieldWithIcon extends InputField { } return ( - ); }; diff --git a/frontend/components/queries/PackQueriesListWrapper/PackQueriesTable/PackQueriesTableConfig.tsx b/frontend/components/queries/PackQueriesListWrapper/PackQueriesTable/PackQueriesTableConfig.tsx index 8208e80205..6f2d19423f 100644 --- a/frontend/components/queries/PackQueriesListWrapper/PackQueriesTable/PackQueriesTableConfig.tsx +++ b/frontend/components/queries/PackQueriesListWrapper/PackQueriesTable/PackQueriesTableConfig.tsx @@ -2,18 +2,18 @@ // disable this rule as it was throwing an error in Header and Cell component // definitions for the selection row for some reason when we dont really need it. import React from "react"; -import ReactTooltip from "react-tooltip"; import { find } from "lodash"; import { performanceIndicator } from "fleet/helpers"; +import { IScheduledQuery } from "interfaces/scheduled_query"; +import { IDropdownOption } from "interfaces/dropdownOption"; + import Checkbox from "components/forms/fields/Checkbox"; import DropdownCell from "components/TableContainer/DataTable/DropdownCell"; import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell"; import PillCell from "components/TableContainer/DataTable/PillCell"; import TextCell from "components/TableContainer/DataTable/TextCell"; -import { IScheduledQuery } from "interfaces/scheduled_query"; -import { IDropdownOption } from "interfaces/dropdownOption"; -import QuestionIcon from "../../../../../assets/images/icon-question-16x16@2x.png"; +import TooltipWrapper from "components/TooltipWrapper"; interface IGetToggleAllRowsSelectedProps { checked: boolean; @@ -123,33 +123,12 @@ const generateTableHeaders = ( title: "Performance impact", Header: () => { return ( -
+
- Performance impact + + Performance impact + - - question icon - - -
- This is the average
- performance impact
- across all hosts where this
- query was scheduled. -
-
); }, diff --git a/frontend/components/queries/PackQueriesListWrapper/_styles.scss b/frontend/components/queries/PackQueriesListWrapper/_styles.scss index fdffb18f15..7ebbcab548 100644 --- a/frontend/components/queries/PackQueriesListWrapper/_styles.scss +++ b/frontend/components/queries/PackQueriesListWrapper/_styles.scss @@ -48,4 +48,10 @@ .queries-table__performance-impact-tooltip { font-weight: 400; } + + .column-with-tooltip { + height: 0; + position: relative; + top: -10px; + } } diff --git a/frontend/components/side_panels/QuerySidePanel/QuerySidePanel.tsx b/frontend/components/side_panels/QuerySidePanel/QuerySidePanel.tsx index c678ff892c..3e7741afab 100644 --- a/frontend/components/side_panels/QuerySidePanel/QuerySidePanel.tsx +++ b/frontend/components/side_panels/QuerySidePanel/QuerySidePanel.tsx @@ -1,13 +1,12 @@ import React from "react"; import classnames from "classnames"; -import IconToolTip from "components/IconToolTip"; import { IOsqueryTable } from "interfaces/osquery_table"; // @ts-ignore import { osqueryTableNames } from "utilities/osquery_tables"; // @ts-ignore import Dropdown from "components/forms/fields/Dropdown"; // @ts-ignore -import FleetIcon from "components/icons/FleetIcon"; // @ts-ignore +import FleetIcon from "components/icons/FleetIcon"; +import TooltipWrapper from "components/TooltipWrapper"; // @ts-ignore import SecondarySidePanelContainer from "../SecondarySidePanelContainer"; - import AppleIcon from "../../../../assets/images/icon-apple-dark-20x20@2x.png"; import LinuxIcon from "../../../../assets/images/icon-linux-dark-20x20@2x.png"; import WindowsIcon from "../../../../assets/images/icon-windows-dark-20x20@2x.png"; @@ -49,8 +48,11 @@ const QuerySidePanel = ({ return columns?.map((column) => (
  • - {column.name} - + + + {column.name} + +
    {displayTypeForDataType(column.type)} diff --git a/frontend/index.scss b/frontend/index.scss index be5c47267d..4574dee79b 100644 --- a/frontend/index.scss +++ b/frontend/index.scss @@ -3,6 +3,6 @@ @import "styles/helpers.scss"; @import "styles/var/**/*.scss"; @import "styles/global/**/*.scss"; -@import "components/**/*.scss"; +@import "components/**/**/*.scss"; @import "layouts/**/*.scss"; @import "pages/**/**/*.scss"; diff --git a/frontend/pages/Homepage/cards/MDM/MDM.tsx b/frontend/pages/Homepage/cards/MDM/MDM.tsx index c3bab367db..6f700cd481 100644 --- a/frontend/pages/Homepage/cards/MDM/MDM.tsx +++ b/frontend/pages/Homepage/cards/MDM/MDM.tsx @@ -7,7 +7,7 @@ import { IMacadminAggregate, IDataTableMDMFormat } from "interfaces/macadmins"; import TableContainer from "components/TableContainer"; // @ts-ignore import Spinner from "components/Spinner"; -import renderLastUpdatedText from "../../components/LastUpdatedText"; +import renderLastUpdatedText from "components/LastUpdatedText"; import generateTableHeaders from "./MDMTableConfig"; interface IMDMCardProps { diff --git a/frontend/pages/Homepage/cards/MDM/_styles.scss b/frontend/pages/Homepage/cards/MDM/_styles.scss index d9baf69be6..7184e4af02 100644 --- a/frontend/pages/Homepage/cards/MDM/_styles.scss +++ b/frontend/pages/Homepage/cards/MDM/_styles.scss @@ -84,24 +84,3 @@ color: $ui-error; } } -.homepage-info-card__section-title-detail { - .last-updated { - font-size: $xx-small; - color: $ui-fleet-black-75; - padding-right: $pad-small; - - .tooltip { - padding-left: $pad-xsmall; - } - - .tooltip__tooltip-text { - display: flex; - text-align: center; - } - img { - height: 16px; - width: 16px; - vertical-align: text-top; - } - } -} diff --git a/frontend/pages/Homepage/cards/Munki/Munki.tsx b/frontend/pages/Homepage/cards/Munki/Munki.tsx index 4d187bdf32..8f7575f7c0 100644 --- a/frontend/pages/Homepage/cards/Munki/Munki.tsx +++ b/frontend/pages/Homepage/cards/Munki/Munki.tsx @@ -7,7 +7,7 @@ import { IMacadminAggregate, IMunkiAggregate } from "interfaces/macadmins"; import TableContainer from "components/TableContainer"; // @ts-ignore import Spinner from "components/Spinner"; -import renderLastUpdatedText from "../../components/LastUpdatedText"; +import renderLastUpdatedText from "components/LastUpdatedText"; import generateTableHeaders from "./MunkiTableConfig"; interface IMunkiCardProps { diff --git a/frontend/pages/Homepage/cards/Munki/_styles.scss b/frontend/pages/Homepage/cards/Munki/_styles.scss index e7597a1f08..80fdd435bc 100644 --- a/frontend/pages/Homepage/cards/Munki/_styles.scss +++ b/frontend/pages/Homepage/cards/Munki/_styles.scss @@ -84,24 +84,3 @@ color: $ui-error; } } -.homepage-info-card__section-title-detail { - .last-updated { - font-size: $xx-small; - color: $ui-fleet-black-75; - padding-right: $pad-small; - - .tooltip { - padding-left: $pad-xsmall; - } - - .tooltip__tooltip-text { - display: flex; - text-align: center; - } - img { - height: 16px; - width: 16px; - vertical-align: text-top; - } - } -} diff --git a/frontend/pages/Homepage/cards/Software/Software.tsx b/frontend/pages/Homepage/cards/Software/Software.tsx index e573f38eaa..366567a11d 100644 --- a/frontend/pages/Homepage/cards/Software/Software.tsx +++ b/frontend/pages/Homepage/cards/Software/Software.tsx @@ -10,7 +10,7 @@ import TableContainer, { ITableQueryData } from "components/TableContainer"; import TableDataError from "components/TableDataError"; // TODO how do we handle errors? UI just keeps spinning? // @ts-ignore import Spinner from "components/Spinner"; -import renderLastUpdatedText from "../../components/LastUpdatedText/LastUpdatedText"; +import renderLastUpdatedText from "components/LastUpdatedText/LastUpdatedText"; import generateTableHeaders from "./SoftwareTableConfig"; interface ISoftwareCardProps { diff --git a/frontend/pages/Homepage/cards/Software/_styles.scss b/frontend/pages/Homepage/cards/Software/_styles.scss index 6cb1c58b7b..93a224c464 100644 --- a/frontend/pages/Homepage/cards/Software/_styles.scss +++ b/frontend/pages/Homepage/cards/Software/_styles.scss @@ -136,24 +136,3 @@ color: $ui-error; } } -.homepage-info-card__section-title-detail { - .last-updated { - font-size: $xx-small; - color: $ui-fleet-black-75; - padding-right: $pad-small; - - .tooltip { - padding-left: $pad-xsmall; - } - - .tooltip__tooltip-text { - display: flex; - text-align: center; - } - img { - height: 16px; - width: 16px; - vertical-align: text-top; - } - } -} diff --git a/frontend/pages/Homepage/components/LastUpdatedText/LastUpdatedText.tsx b/frontend/pages/Homepage/components/LastUpdatedText/LastUpdatedText.tsx deleted file mode 100644 index d0b8f6aee8..0000000000 --- a/frontend/pages/Homepage/components/LastUpdatedText/LastUpdatedText.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from "react"; -import ReactTooltip from "react-tooltip"; -import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict"; -import { kebabCase } from "lodash"; - -import QuestionIcon from "../../../../../assets/images/icon-question-16x16@2x.png"; - -const renderLastUpdatedText = ( - lastUpdatedAt: string, - whatToRetrieve: string -): JSX.Element => { - if (!lastUpdatedAt || lastUpdatedAt === "0001-01-01T00:00:00Z") { - lastUpdatedAt = "never"; - } else { - lastUpdatedAt = formatDistanceToNowStrict(new Date(lastUpdatedAt), { - addSuffix: true, - }); - } - - return ( - - {`Last updated ${lastUpdatedAt}`} - - - question icon - - - - Fleet periodically -
    - queries all hosts -
    - to retrieve {whatToRetrieve} -
    -
    -
    -
    - ); -}; - -export default renderLastUpdatedText; diff --git a/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx b/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx index d33c1ff9f0..a95562b9df 100644 --- a/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx +++ b/frontend/pages/admin/UserManagementPage/components/UserForm/UserForm.tsx @@ -1,29 +1,20 @@ import React, { Component, FormEvent } from "react"; -import ReactTooltip from "react-tooltip"; import { Link } from "react-router"; import PATHS from "router/paths"; import { Dispatch } from "redux"; import { ITeam } from "interfaces/team"; -import { IUserFormErrors } from "interfaces/user"; -import Button from "components/buttons/Button"; -import validatePresence from "components/forms/validators/validate_presence"; -import validEmail from "components/forms/validators/valid_email"; +import { IUserFormErrors } from "interfaces/user"; // @ts-ignore +import { renderFlash } from "redux/nodes/notifications/actions"; // ignore TS error for now until these are rewritten in ts. -// @ts-ignore -import { renderFlash } from "redux/nodes/notifications/actions"; -// @ts-ignore -import validPassword from "components/forms/validators/valid_password"; -// @ts-ignore -import IconToolTip from "components/IconToolTip"; -// @ts-ignore -import InputField from "components/forms/fields/InputField"; -// @ts-ignore -import InputFieldWithIcon from "components/forms/fields/InputFieldWithIcon"; -// @ts-ignore -import Checkbox from "components/forms/fields/Checkbox"; -// @ts-ignore +import Button from "components/buttons/Button"; +import validatePresence from "components/forms/validators/validate_presence"; +import validEmail from "components/forms/validators/valid_email"; // @ts-ignore +import validPassword from "components/forms/validators/valid_password"; // @ts-ignore +import InputField from "components/forms/fields/InputField"; // @ts-ignore +import InputFieldWithIcon from "components/forms/fields/InputFieldWithIcon"; // @ts-ignore +import Checkbox from "components/forms/fields/Checkbox"; // @ts-ignore import Dropdown from "components/forms/fields/Dropdown"; import Radio from "components/forms/fields/Radio"; import InfoBanner from "components/InfoBanner/InfoBanner"; @@ -485,69 +476,40 @@ class UserForm extends Component { data-tip-disable={isNewUser || smtpConfigured} >
    \ + Users with Admin role can configure SMTP in Settings > Organization settings. \ + " + } />
    - - - Editing an email address requires that SMTP is
    - configured in order to send a validation email.
    -
    - Users with Admin role can configure SMTP in -
    - Settings > Organization settings. -
    -
    -

    + Users with Admin role can configure SSO in Settings > Organization settings. + `} > - - Enable single sign on - -

    - Password authentication will be disabled for this user. -

    -
    - - - Enabling single sign on for a user requires that SSO is
    - first enabled for the organization.
    -
    - Users with Admin role can configure SSO in -
    - Settings > Organization settings. -
    -
    + Enable single sign on + +

    + Password authentication will be disabled for this user. +

    {isNewUser && (
    @@ -563,45 +525,26 @@ class UserForm extends Component { name={"newUserType"} onChange={onRadioChange("newUserType")} /> -
    - - - - The "Invite user" feature requires that SMTP - is -
    - configured in order to send invitation emails.
    -
    - SMTP can be configured in{" "} - - Settings >
    - Organization settings -
    - . -
    -
    -
    +
    + SMTP can be configured in Settings > Organization settings. + ` + } + /> ) : ( { <>
    { "Must include 7 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)", ]} blockAutoComplete - /> -
    -
    - \ -

    This password is temporary. This user will be asked to set a new password after logging in to the Fleet UI.

    \ -

    This user will not be asked to set a new password after logging in to fleetctl or the Fleet API.

    \ -
    \ + tooltip={`\ + This password is temporary. This user will be asked to set a new password after logging in to the Fleet UI.

    \ + This user will not be asked to set a new password after logging in to fleetctl or the Fleet API.\ `} />
    diff --git a/frontend/pages/admin/UserManagementPage/components/UserForm/_styles.scss b/frontend/pages/admin/UserManagementPage/components/UserForm/_styles.scss index c897fb7c70..39491bbe9b 100644 --- a/frontend/pages/admin/UserManagementPage/components/UserForm/_styles.scss +++ b/frontend/pages/admin/UserManagementPage/components/UserForm/_styles.scss @@ -53,7 +53,7 @@ color: $core-fleet-black; font-size: $x-small; font-weight: $bold; - margin-bottom: $pad-xsmall; + margin-bottom: $pad-small; } &__user-permissions-info { diff --git a/frontend/pages/hosts/HostDetailsPage/HostDetailsPage.tsx b/frontend/pages/hosts/HostDetailsPage/HostDetailsPage.tsx index 8f2812dec2..cbccca72e0 100644 --- a/frontend/pages/hosts/HostDetailsPage/HostDetailsPage.tsx +++ b/frontend/pages/hosts/HostDetailsPage/HostDetailsPage.tsx @@ -43,6 +43,7 @@ import Modal from "components/Modal"; import TableContainer from "components/TableContainer"; import TabsWrapper from "components/TabsWrapper"; import InfoBanner from "components/InfoBanner"; +import TooltipWrapper from "components/TooltipWrapper"; import { Accordion, AccordionItem, @@ -82,7 +83,6 @@ import CopyIcon from "../../../../assets/images/icon-copy-clipboard-fleet-blue-2 import DeleteIcon from "../../../../assets/images/icon-action-delete-14x14@2x.png"; import IssueIcon from "../../../../assets/images/icon-issue-fleet-black-50-16x16@2x.png"; import QueryIcon from "../../../../assets/images/icon-action-query-16x16@2x.png"; -import QuestionIcon from "../../../../assets/images/icon-question-16x16@2x.png"; import TransferIcon from "../../../../assets/images/icon-action-transfer-16x16@2x.png"; const baseClass = "host-details"; @@ -611,29 +611,10 @@ const HostDetailsPage = ({

    - Example policy: - {" "} - - host issue + + Example policy: + - - - A policy is a yes or no question -
    you can ask all your devices. -
    -
    { title: "Last run", Header: () => { return ( - <> + Last run - since the last time osquery
    started on this host.`} - /> - +
    ); }, disableSortBy: true, @@ -82,13 +78,9 @@ const generatePackTableHeaders = (): IDataColumn[] => { title: "Performance impact", Header: () => { return ( - <> + Performance impact - impact on this host.`} - /> - + ); }, disableSortBy: true, diff --git a/frontend/pages/hosts/HostDetailsPage/SoftwareTab/SoftwareTableConfig.tsx b/frontend/pages/hosts/HostDetailsPage/SoftwareTab/SoftwareTableConfig.tsx index a6df65cbe6..078f6c9e48 100644 --- a/frontend/pages/hosts/HostDetailsPage/SoftwareTab/SoftwareTableConfig.tsx +++ b/frontend/pages/hosts/HostDetailsPage/SoftwareTab/SoftwareTableConfig.tsx @@ -2,14 +2,17 @@ import React from "react"; import { Link } from "react-router"; import ReactTooltip from "react-tooltip"; import { isEmpty } from "lodash"; -// import distanceInWordsToNow from "date-fns/distance_in_words_to_now"; // TODO: Enable after backend has been updated to provide last_opened_at + +// TODO: Enable after backend has been updated to provide last_opened_at +// import distanceInWordsToNow from "date-fns/distance_in_words_to_now"; + +import { ISoftware } from "interfaces/software"; import PATHS from "router/paths"; import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell"; import TextCell from "components/TableContainer/DataTable/TextCell"; -import { ISoftware } from "interfaces/software"; +import TooltipWrapper from "components/TooltipWrapper"; import IssueIcon from "../../../../../assets/images/icon-issue-fleet-black-50-16x16@2x.png"; -import QuestionIcon from "../../../../../assets/images/icon-question-16x16@2x.png"; import Chevron from "../../../../../assets/images/icon-chevron-right-9x6@2x.png"; interface IHeaderProps { @@ -127,29 +130,17 @@ const generateSoftwareTableHeaders = (): IDataColumn[] => { if (bundle_identifier) { return ( - {name} - - bundle identifier - - - + Bundle identifier:
    - {bundle_identifier} + ${bundle_identifier}
    -
    + `} + > + {name} +
    ); } diff --git a/frontend/pages/hosts/HostDetailsPage/UsersTable/UsersTableConfig.tsx b/frontend/pages/hosts/HostDetailsPage/UsersTable/UsersTableConfig.tsx index 855a160be6..de061e9b5e 100644 --- a/frontend/pages/hosts/HostDetailsPage/UsersTable/UsersTableConfig.tsx +++ b/frontend/pages/hosts/HostDetailsPage/UsersTable/UsersTableConfig.tsx @@ -1,9 +1,8 @@ import React from "react"; -import ReactTooltip from "react-tooltip"; import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell"; import TextCell from "components/TableContainer/DataTable/TextCell"; -import QuestionIcon from "../../../../../assets/images/icon-question-16x16@2x.png"; +import TooltipWrapper from "components/TooltipWrapper"; interface IHeaderProps { column: { @@ -51,33 +50,9 @@ const generateUsersTableHeaders = (): IDataColumn[] => { title: "Shell", Header: () => { return ( -
    - Shell - - question icon - - -
    - The command line shell, such as bash, -
    - that this user is equipped with by default -
    - when they log in to the system. -
    -
    -
    + + Shell + ); }, disableSortBy: true, diff --git a/frontend/pages/hosts/HostDetailsPage/_styles.scss b/frontend/pages/hosts/HostDetailsPage/_styles.scss index 157853115f..741c749da0 100644 --- a/frontend/pages/hosts/HostDetailsPage/_styles.scss +++ b/frontend/pages/hosts/HostDetailsPage/_styles.scss @@ -508,14 +508,6 @@ &:first-child { padding-right: 0px; } - .name-container { - .tooltip__tooltip-icon { - @include breakpoint(ltdesktop) { - position: absolute; - right: 0.5rem; - } - } - } } .software-link { @@ -546,6 +538,12 @@ text-overflow: unset; } } + + .name__cell, + .version__cell { + overflow: initial; + text-overflow: initial; + } } } diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx index 5ff178ff41..163276efff 100644 --- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx @@ -24,10 +24,10 @@ import { DEFAULT_POLICY } from "utilities/constants"; import Button from "components/buttons/Button"; import InfoBanner from "components/InfoBanner/InfoBanner"; -import IconToolTip from "components/IconToolTip"; import Spinner from "components/Spinner"; import TeamsDropdown from "components/TeamsDropdown"; import TableDataError from "components/TableDataError"; +import TooltipWrapper from "components/TooltipWrapper"; import PoliciesListWrapper from "./components/PoliciesListWrapper"; import ManageAutomationsModal from "./components/ManageAutomationsModal"; import AddPolicyModal from "./components/AddPolicyModal"; @@ -471,21 +471,17 @@ const ManagePolicyPage = ({ ${baseClass}__inherited-policies-button`} onClick={toggleShowInheritedPolicies} > - {inheritedPoliciesButtonText( - showInheritedPolicies, - globalPolicies.length - )} - -
    -

    “All teams” policies are checked
    for this team’s hosts.

    \ - " + for this team’s hosts.' } - /> -
    + > + {inheritedPoliciesButtonText( + showInheritedPolicies, + globalPolicies.length + )} + + )} {showInheritedPoliciesButton && showInheritedPolicies && ( diff --git a/frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/ManageAutomationsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/ManageAutomationsModal.tsx index a6cfb227c8..ef09412bc2 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/ManageAutomationsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/ManageAutomationsModal/ManageAutomationsModal.tsx @@ -1,19 +1,17 @@ import React, { useState } from "react"; +import { IPolicy } from "interfaces/policy"; +import { IWebhookFailingPolicies } from "interfaces/webhook"; +import { useDeepEffect } from "utilities/hooks"; +import { size } from "lodash"; + import Modal from "components/Modal"; import Button from "components/buttons/Button"; // @ts-ignore import Checkbox from "components/forms/fields/Checkbox"; // @ts-ignore import InputField from "components/forms/fields/InputField"; -import IconToolTip from "components/IconToolTip"; import validURL from "components/forms/validators/valid_url"; - -import { IPolicy } from "interfaces/policy"; -import { IWebhookFailingPolicies } from "interfaces/webhook"; -import { useDeepEffect } from "utilities/hooks"; -import { size } from "lodash"; - import PreviewPayloadModal from "../PreviewPayloadModal"; interface IManageAutomationsModalProps { @@ -192,10 +190,7 @@ const ManageAutomationsModal = ({ 'For each policy, Fleet will send a JSON payload to this URL with a list of the hosts that updated their answer to "No."' } placeholder={"https://server.com/example"} - /> - Provide a URL to deliver a
    webhook request to.

    "} + tooltip="Provide a URL to deliver a webhook request to." />
  • -
    -
    -

    Queries from the “All teams”
    schedule run on this team’s hosts.

    \ - " - } - /> -
    - + + + ) : null} {showInheritedQueries && inheritedScheduledQueriesList && diff --git a/frontend/pages/schedule/ManageSchedulePage/components/PreviewDataModal/PreviewDataModal.tsx b/frontend/pages/schedule/ManageSchedulePage/components/PreviewDataModal/PreviewDataModal.tsx index d9d2dbe35d..2defc43f3c 100644 --- a/frontend/pages/schedule/ManageSchedulePage/components/PreviewDataModal/PreviewDataModal.tsx +++ b/frontend/pages/schedule/ManageSchedulePage/components/PreviewDataModal/PreviewDataModal.tsx @@ -3,11 +3,9 @@ import React from "react"; import { syntaxHighlight } from "fleet/helpers"; -import ReactTooltip from "react-tooltip"; import Modal from "components/Modal"; import Button from "components/buttons/Button"; - -import QuestionIcon from "../../../../../../assets/images/icon-question-16x16@2x.png"; +import TooltipWrapper from "components/TooltipWrapper"; const baseClass = "preview-data-modal"; @@ -40,32 +38,12 @@ const PreviewDataModal = ({

    - The data sent to your configured log destination will look similar to - the following JSON:{" "} - - preview schedule - - - -

    - The "snapshot" key includes the query's -
    - results. These will be unique to your query. -

    - - + The data sent to your configured log destination will look similar + to the following JSON: +

    diff --git a/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/ScheduleTableConfig.tsx b/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/ScheduleTableConfig.tsx
    index c194de8e56..fddf21a00d 100644
    --- a/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/ScheduleTableConfig.tsx
    +++ b/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/ScheduleTableConfig.tsx
    @@ -2,7 +2,6 @@
     // disable this rule as it was throwing an error in Header and Cell component
     // definitions for the selection row for some reason when we dont really need it.
     import React from "react";
    -import ReactTooltip from "react-tooltip";
     import { performanceIndicator, secondsToDhms } from "fleet/helpers";
     
     // @ts-ignore
    @@ -13,7 +12,7 @@ import PillCell from "components/TableContainer/DataTable/PillCell";
     import { IDropdownOption } from "interfaces/dropdownOption";
     import { IGlobalScheduledQuery } from "interfaces/global_scheduled_query";
     import { ITeamScheduledQuery } from "interfaces/team_scheduled_query";
    -import QuestionIcon from "../../../../../../assets/images/icon-question-16x16@2x.png";
    +import TooltipWrapper from "components/TooltipWrapper";
     
     interface IGetToggleAllRowsSelectedProps {
       checked: boolean;
    @@ -111,33 +110,18 @@ const generateTableHeaders = (
           title: "Performance impact",
           Header: () => {
             return (
    -          
    +
    - Performance impact - - - question icon - - -
    + performance impact
    across all hosts where this
    - query was scheduled. -
    -
    + query was scheduled.`} + > + Performance impact + +
    ); }, diff --git a/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/_styles.scss b/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/_styles.scss index e377c8142c..b45582975b 100644 --- a/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/_styles.scss +++ b/frontend/pages/schedule/ManageSchedulePage/components/ScheduleListWrapper/_styles.scss @@ -77,6 +77,12 @@ .queries-table__performance-impact-tooltip { font-weight: 400; } + + .column-with-tooltip { + height: 0; + position: relative; + top: -10px; + } } .no-schedule { diff --git a/frontend/pages/software/ManageSoftwarePage/ManageSoftwarePage.tsx b/frontend/pages/software/ManageSoftwarePage/ManageSoftwarePage.tsx index e51cb45ecd..e55efe8d61 100644 --- a/frontend/pages/software/ManageSoftwarePage/ManageSoftwarePage.tsx +++ b/frontend/pages/software/ManageSoftwarePage/ManageSoftwarePage.tsx @@ -2,9 +2,7 @@ import React, { useCallback, useContext, useEffect, useState } from "react"; import { useQuery } from "react-query"; import { useDispatch } from "react-redux"; import { InjectedRouter } from "react-router/lib/Router"; -import ReactTooltip from "react-tooltip"; import { useDebouncedCallback } from "use-debounce/lib"; -import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict"; import { AppContext } from "context/app"; import { IConfig, IConfigNested } from "interfaces/config"; @@ -33,13 +31,12 @@ import TableDataError from "components/TableDataError"; import TeamsDropdownHeader, { ITeamsDropdownState, } from "components/PageHeader/TeamsDropdownHeader"; - -import ExternalLinkIcon from "../../../../assets/images/open-new-tab-12x12@2x.png"; -import QuestionIcon from "../../../../assets/images/icon-question-16x16@2x.png"; +import renderLastUpdatedText from "components/LastUpdatedText"; import softwareTableHeaders from "./SoftwareTableConfig"; import ManageAutomationsModal from "./components/ManageAutomationsModal"; import EmptySoftware from "../components/EmptySoftware"; +import ExternalLinkIcon from "../../../../assets/images/open-new-tab-12x12@2x.png"; interface IManageSoftwarePageProps { router: InjectedRouter; @@ -320,11 +317,7 @@ const ManageSoftwarePage = ({ const renderSoftwareCount = useCallback(() => { const count = softwareCount; - const lastUpdatedAt = software?.counts_updated_at - ? formatDistanceToNowStrict(new Date(software?.counts_updated_at), { - addSuffix: true, - }) - : software?.counts_updated_at; + const lastUpdatedAt = software?.counts_updated_at; if (!isSoftwareEnabled || !lastUpdatedAt) { return null; @@ -339,44 +332,20 @@ const ManageSoftwarePage = ({ } // TODO: Use setInterval to keep last updated time current? - return count !== undefined ? ( - - {`${count} software item${count === 1 ? "" : "s"}`} - - {`Last updated ${lastUpdatedAt}`}{" "} - - - question icon - - - - Fleet periodically -
    - queries all hosts -
    - to retrieve software -
    -
    -
    -
    -
    - ) : null; + if (count) { + return ( +
    + {`${count} software item${count === 1 ? "" : "s"}`} + {renderLastUpdatedText(lastUpdatedAt, "software")} +
    + ); + } + + return null; }, [isFetchingCount, software, softwareCountError, softwareCount]); // TODO: retool this with react-router location descriptor objects diff --git a/frontend/pages/software/ManageSoftwarePage/_styles.scss b/frontend/pages/software/ManageSoftwarePage/_styles.scss index 4cd220c1f3..c625d15a40 100644 --- a/frontend/pages/software/ManageSoftwarePage/_styles.scss +++ b/frontend/pages/software/ManageSoftwarePage/_styles.scss @@ -158,28 +158,17 @@ } } &__count { + display: flex; + + :first-child { + margin-right: $pad-small; + } .count-error { color: $ui-error; } .count-loading { color: $ui-fleet-black-50; } - .count-last-updated { - color: $ui-fleet-black-75; - font-size: $xx-small; - font-weight: normal; - padding-left: $pad-small; - - .tooltip__tooltip-text { - display: flex; - text-align: center; - } - img { - height: 16px; - width: 16px; - vertical-align: text-top; - } - } } &__status_dropdown { diff --git a/frontend/pages/software/ManageSoftwarePage/components/ManageAutomationsModal/ManageAutomationsModal.tsx b/frontend/pages/software/ManageSoftwarePage/components/ManageAutomationsModal/ManageAutomationsModal.tsx index f029de3745..e614115502 100644 --- a/frontend/pages/software/ManageSoftwarePage/components/ManageAutomationsModal/ManageAutomationsModal.tsx +++ b/frontend/pages/software/ManageSoftwarePage/components/ManageAutomationsModal/ManageAutomationsModal.tsx @@ -6,7 +6,6 @@ import Button from "components/buttons/Button"; import Checkbox from "components/forms/fields/Checkbox"; // @ts-ignore import InputField from "components/forms/fields/InputField"; -import IconToolTip from "components/IconToolTip"; import validURL from "components/forms/validators/valid_url"; import { IWebhookSoftwareVulnerabilities } from "interfaces/webhook"; @@ -210,10 +209,7 @@ const ManageAutomationsModal = ({ "For each new vulnerability detected, Fleet will send a JSON payload to this URL with a list of the affected hosts." } placeholder={"https://server.com/example"} - /> - Provide a URL to deliver a
    webhook request to.

    "} + tooltip="Provide a URL to deliver a webhook request to." />