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 (
+
+ );
+};
+
+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 (
-
- {error || label}
+
+ {error ||
+ (tooltip ? (
+
+ {label as string}
+
+ ) : (
+ <>{label}>
+ ))}
);
};
diff --git a/frontend/components/forms/FormField/_styles.scss b/frontend/components/forms/FormField/_styles.scss
index be36437038..2cc938f3d2 100644
--- a/frontend/components/forms/FormField/_styles.scss
+++ b/frontend/components/forms/FormField/_styles.scss
@@ -12,6 +12,10 @@
font-weight: $bold;
color: $core-vibrant-red;
}
+
+ &[data-has-tooltip="true"] {
+ margin-bottom: $pad-small;
+ }
}
&__hint {
diff --git a/frontend/components/forms/UserSettingsForm/UserSettingsForm.jsx b/frontend/components/forms/UserSettingsForm/UserSettingsForm.jsx
index b95cafba93..de04b9e0a6 100644
--- a/frontend/components/forms/UserSettingsForm/UserSettingsForm.jsx
+++ b/frontend/components/forms/UserSettingsForm/UserSettingsForm.jsx
@@ -1,7 +1,6 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
-import ReactTooltip from "react-tooltip";
import Button from "components/buttons/Button";
import Form from "components/forms/Form";
import formFieldInterface from "interfaces/form_field";
@@ -57,25 +56,15 @@ class UserSettingsForm extends Component {
label="Email (required)"
hint={renderEmailHint()}
disabled={!smtpConfigured}
+ tooltip={
+ "\
+ 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 .\
+ "
+ }
/>
-
-
- 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 (
-
- {label || placeholder}
+
+ {tooltip ? (
+ {label}
+ ) : (
+ <>{label || placeholder}>
+ )}
);
};
diff --git a/frontend/components/forms/fields/InputFieldWithIcon/_styles.scss b/frontend/components/forms/fields/InputFieldWithIcon/_styles.scss
index 81336ef081..13d4ac15a6 100644
--- a/frontend/components/forms/fields/InputFieldWithIcon/_styles.scss
+++ b/frontend/components/forms/fields/InputFieldWithIcon/_styles.scss
@@ -56,9 +56,14 @@
}
&__label {
+ display: block;
font-size: $x-small;
font-weight: $bold;
margin-bottom: $pad-xsmall;
+
+ &[data-has-tooltip="true"] {
+ margin-bottom: $pad-small;
+ }
}
&__errors {
diff --git a/frontend/components/forms/fields/Radio/Radio.tsx b/frontend/components/forms/fields/Radio/Radio.tsx
index 07deb906ea..9aab17edfb 100644
--- a/frontend/components/forms/fields/Radio/Radio.tsx
+++ b/frontend/components/forms/fields/Radio/Radio.tsx
@@ -1,6 +1,8 @@
import React from "react";
import classnames from "classnames";
+import TooltipWrapper from "components/TooltipWrapper";
+
const baseClass = "radio";
export interface IRadioProps {
@@ -12,6 +14,7 @@ export interface IRadioProps {
name?: string;
className?: string;
disabled?: boolean;
+ tooltip?: string;
}
const Radio = ({
@@ -22,6 +25,7 @@ const Radio = ({
checked,
disabled,
label,
+ tooltip,
onChange,
}: IRadioProps): JSX.Element => {
const wrapperClasses = classnames(baseClass, className);
@@ -44,7 +48,13 @@ const Radio = ({
/>
- {label}
+
+ {tooltip ? (
+ {label}
+ ) : (
+ <>{label}>
+ )}
+
);
};
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
+
-
-
-
-
-
- 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}`}
-
-
-
-
-
-
- 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:
- {" "}
-
-
+
+ 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}
-
+ `}
+ >
+ {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
-
-
-
-
-
- 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."
/>
Checks on:
- {displayPlatforms.join(", ")}
-
-
-
-
-
-
-
- To choose new platforms,
-
- please create a new policy.
-
-
+
+ {displayPlatforms.join(", ")}
+
>
) : (
diff --git a/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/QueriesTableConfig.tsx b/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/QueriesTableConfig.tsx
index 4f1599d711..b0a81971ca 100644
--- a/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/QueriesTableConfig.tsx
+++ b/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/QueriesTableConfig.tsx
@@ -4,8 +4,12 @@
import React from "react";
import ReactTooltip from "react-tooltip";
import formatDistanceToNow from "date-fns/formatDistanceToNow";
+import PATHS from "router/paths";
import permissionsUtils from "utilities/permissions";
+import { IQuery } from "interfaces/query";
+import { IUser } from "interfaces/user";
+import { addGravatarUrlToResource } from "fleet/helpers";
// @ts-ignore
import Avatar from "components/Avatar";
@@ -15,13 +19,7 @@ import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCel
import PlatformCell from "components/TableContainer/DataTable/PlatformCell";
import TextCell from "components/TableContainer/DataTable/TextCell";
import PillCell from "components/TableContainer/DataTable/PillCell";
-
-import PATHS from "router/paths";
-
-import { IQuery } from "interfaces/query";
-import { IUser } from "interfaces/user";
-import { addGravatarUrlToResource } from "fleet/helpers";
-import QuestionIcon from "../../../../../../assets/images/icon-question-16x16@2x.png";
+import TooltipWrapper from "components/TooltipWrapper";
interface IQueryRow {
id: string;
@@ -109,33 +107,18 @@ const generateTableHeaders = (currentUser: IUser): IDataColumn[] => {
title: "Performance impact",
Header: () => {
return (
-
+
- Performance impact
-
-
-
-
-
-
+
performance impact
across all hosts where this
- query was scheduled.
-
-
+ query was scheduled.`}
+ >
+ Performance impact
+
+
);
},
diff --git a/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/_styles.scss b/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/_styles.scss
index 0ebabdce78..f801fdad2d 100644
--- a/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/_styles.scss
+++ b/frontend/pages/queries/ManageQueriesPage/components/QueriesListWrapper/_styles.scss
@@ -41,6 +41,12 @@
.queries-table__performance-impact-tooltip {
font-weight: 400;
}
+
+ .column-with-tooltip {
+ height: 0;
+ position: relative;
+ top: -10px;
+ }
}
.no-queries {
diff --git a/frontend/pages/queries/QueryPage/components/PlatformCompatibility/PlatformCompatibility.tsx b/frontend/pages/queries/QueryPage/components/PlatformCompatibility/PlatformCompatibility.tsx
index ed61741d0f..f25b7684b8 100644
--- a/frontend/pages/queries/QueryPage/components/PlatformCompatibility/PlatformCompatibility.tsx
+++ b/frontend/pages/queries/QueryPage/components/PlatformCompatibility/PlatformCompatibility.tsx
@@ -1,8 +1,8 @@
import React from "react";
-import ReactTooltip from "react-tooltip";
+
+import TooltipWrapper from "components/TooltipWrapper";
import CompatibleIcon from "../../../../../../assets/images/icon-compatible-green-16x16@2x.png";
import IncompatibleIcon from "../../../../../../assets/images/icon-incompatible-red-16x16@2x.png";
-import QuestionIcon from "../../../../../../assets/images/icon-question-16x16@2x.png";
const baseClass = "platform-compatibility";
@@ -40,33 +40,11 @@ const PlatformCompatibility = ({
compatiblePlatforms = formatPlatformsForDisplay(compatiblePlatforms);
return (
- Compatible with:
-
-
-
-
-
-
- Estimated compatiblity
-
- based on the tables used
-
- in the query
-
-
-
+
+
+ Compatible with:
+
+
{displayIncompatibilityText(compatiblePlatforms) ||
(!!compatiblePlatforms.length &&
DISPLAY_ORDER.map((platform) => {
diff --git a/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx b/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx
index 1d668a6c12..162c0e679e 100644
--- a/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx
+++ b/frontend/pages/schedule/ManageSchedulePage/ManageSchedulePage.tsx
@@ -31,8 +31,8 @@ import paths from "router/paths";
import Button from "components/buttons/Button";
import Spinner from "components/Spinner";
import TeamsDropdown from "components/TeamsDropdown";
-import IconToolTip from "components/IconToolTip";
import TableDataError from "components/TableDataError";
+import TooltipWrapper from "components/TooltipWrapper";
import ScheduleListWrapper from "./components/ScheduleListWrapper";
import ScheduleEditorModal from "./components/ScheduleEditorModal";
import RemoveScheduledQueryModal from "./components/RemoveScheduledQueryModal";
@@ -526,30 +526,24 @@ const ManageSchedulePage = ({
{selectedTeamId &&
inheritedScheduledQueriesList &&
inheritedScheduledQueriesList.length > 0 ? (
- <>
-
-
+
+ schedule run on this team’s hosts.'
+ }
>
{showInheritedQueries
? `Hide ${inheritedScheduledQueriesList.length} inherited ${inheritedQueryOrQueries}`
: `Show ${inheritedScheduledQueriesList.length} inherited ${inheritedQueryOrQueries}`}
-
-
-
-
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:{" "}
-
-
-
-
-
-
- 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
-
-
-
-
-
-
+
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}`}{" "}
-
-
-
-
-
-
- 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."
/>