mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
New tooltips! (#4326)
* Allow sort by more than one key * created custom tooltip component * remove unused code * fixed style for more layouts * added tooltip to query side panel * tooltips added to setting form * finished settings form * added tooltip for perf impact table headers * tooltip for pack table and user form * tooltip on manage policies page * tooltip for manage schedules * tooltip for automations; spacing for form input * tooltip for automations modal * user form; fixed input with icon component * more user form tooltips * tooltip for homepage; style fixes * replaced many more tooltips with new version * added story for tooltip * added position prop * fixed tests * re-work how we click react-select dropdowns * forcing the update button click * trying a blur * fixed typo * trying blur on another element * temp check-in * replaced tooltip from host details software * more consolidation of tooltip use for software * fixed settings flow test Co-authored-by: Tomas Touceda <chiiph@gmail.com>
This commit is contained in:
parent
455033476f
commit
33c5f0651c
50 changed files with 572 additions and 797 deletions
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
31
frontend/components/LastUpdatedText/LastUpdatedText.tsx
Normal file
31
frontend/components/LastUpdatedText/LastUpdatedText.tsx
Normal file
|
|
@ -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 (
|
||||
<span className={baseClass}>
|
||||
<TooltipWrapper
|
||||
tipContent={`Fleet periodically queries all hosts to retrieve ${whatToRetrieve}`}
|
||||
>
|
||||
{`Last updated ${lastUpdatedAt}`}
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default renderLastUpdatedText;
|
||||
19
frontend/components/LastUpdatedText/_styles.scss
Normal file
19
frontend/components/LastUpdatedText/_styles.scss
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ITooltipWrapperProps> = (props) => (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<TooltipWrapper {...props}>Example text</TooltipWrapper>
|
||||
</>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
30
frontend/components/TooltipWrapper/TooltipWrapper.tsx
Normal file
30
frontend/components/TooltipWrapper/TooltipWrapper.tsx
Normal file
|
|
@ -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 (
|
||||
<div className={baseClass} data-position={position}>
|
||||
<div className={`${baseClass}__element`}>
|
||||
{children}
|
||||
<div className={`${baseClass}__underline`} data-text={children} />
|
||||
</div>
|
||||
<div
|
||||
className={`${baseClass}__tip-text`}
|
||||
dangerouslySetInnerHTML={{ __html: tipContent }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TooltipWrapper;
|
||||
78
frontend/components/TooltipWrapper/_styles.scss
Normal file
78
frontend/components/TooltipWrapper/_styles.scss
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
frontend/components/TooltipWrapper/index.tsx
Normal file
1
frontend/components/TooltipWrapper/index.tsx
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./TooltipWrapper";
|
||||
|
|
@ -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<any> | 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 (
|
||||
<label className={labelWrapperClasses} htmlFor={name}>
|
||||
{error || label}
|
||||
<label
|
||||
className={labelWrapperClasses}
|
||||
htmlFor={name}
|
||||
data-has-tooltip={!!tooltip}
|
||||
>
|
||||
{error ||
|
||||
(tooltip ? (
|
||||
<TooltipWrapper tipContent={tooltip}>
|
||||
{label as string}
|
||||
</TooltipWrapper>
|
||||
) : (
|
||||
<>{label}</>
|
||||
))}
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@
|
|||
font-weight: $bold;
|
||||
color: $core-vibrant-red;
|
||||
}
|
||||
|
||||
&[data-has-tooltip="true"] {
|
||||
margin-bottom: $pad-small;
|
||||
}
|
||||
}
|
||||
|
||||
&__hint {
|
||||
|
|
|
|||
|
|
@ -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.\
|
||||
<br /><br /> \
|
||||
Users with Admin role can configure SMTP in <strong>Settings > Organization settings</strong>.\
|
||||
"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<ReactTooltip
|
||||
place="bottom"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
id="smtp-tooltip"
|
||||
backgroundColor="#3e4771"
|
||||
data-html
|
||||
>
|
||||
<span className={`${baseClass}__tooltip-text`}>
|
||||
Editing your email address requires that SMTP is <br />
|
||||
configured in order to send a validation email. <br />
|
||||
<br />
|
||||
Users with Admin role can configure SMTP in
|
||||
<br />
|
||||
<strong>Settings > Organization settings</strong>.
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
<InputField
|
||||
{...fields.name}
|
||||
label="Full name (required)"
|
||||
|
|
|
|||
|
|
@ -21,12 +21,10 @@ import validateYaml from "components/forms/validators/validate_yaml";
|
|||
import validEmail from "components/forms/validators/valid_email";
|
||||
import validUrl from "components/forms/validators/valid_url";
|
||||
|
||||
import IconToolTip from "components/IconToolTip";
|
||||
import InfoBanner from "components/InfoBanner/InfoBanner";
|
||||
// @ts-ignore
|
||||
import YamlAce from "components/YamlAce";
|
||||
import Modal from "components/Modal";
|
||||
import SelectTargetsDropdownStories from "components/forms/fields/SelectTargetsDropdown/SelectTargetsDropdown.stories";
|
||||
import OpenNewTabIcon from "../../../../../assets/images/open-new-tab-12x12@2x.png";
|
||||
import {
|
||||
IAppConfigFormProps,
|
||||
|
|
@ -383,11 +381,7 @@ const AppConfigFormFunctional = ({
|
|||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.server_url}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
text={"The base URL of this instance for use in Fleet links."}
|
||||
tooltip="The base URL of this instance for use in Fleet links."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -419,13 +413,7 @@ const AppConfigFormFunctional = ({
|
|||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.idp_name}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
text={
|
||||
"A required human friendly name for the identity provider that will provide single sign on authentication."
|
||||
}
|
||||
tooltip="A required human friendly name for the identity provider that will provide single sign on authentication."
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__inputs`}>
|
||||
|
|
@ -443,13 +431,7 @@ const AppConfigFormFunctional = ({
|
|||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.entity_id}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
text={
|
||||
"The required entity ID is a URI that you use to identify Fleet when configuring the identity provider."
|
||||
}
|
||||
tooltip="The required entity ID is a URI that you use to identify Fleet when configuring the identity provider."
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__inputs`}>
|
||||
|
|
@ -459,11 +441,7 @@ const AppConfigFormFunctional = ({
|
|||
name="issuerURI"
|
||||
value={issuerURI}
|
||||
parseTarget
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
text={"The issuer URI supplied by the identity provider."}
|
||||
tooltip="The issuer URI supplied by the identity provider."
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__inputs`}>
|
||||
|
|
@ -475,13 +453,7 @@ const AppConfigFormFunctional = ({
|
|||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.idp_image_url}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
text={
|
||||
"An optional link to an image such as a logo for the identity provider."
|
||||
}
|
||||
tooltip="An optional link to an image such as a logo for the identity provider."
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__inputs`}>
|
||||
|
|
@ -493,13 +465,7 @@ const AppConfigFormFunctional = ({
|
|||
value={metadata}
|
||||
parseTarget
|
||||
onBlur={validateForm}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
text={
|
||||
"Metadata provided by the identity provider. Either metadata or a metadata url must be provided."
|
||||
}
|
||||
tooltip="Metadata provided by the identity provider. Either metadata or a metadata url must be provided."
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__inputs`}>
|
||||
|
|
@ -517,14 +483,9 @@ const AppConfigFormFunctional = ({
|
|||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.metadata_url}
|
||||
tooltip="A URL that references the identity provider metadata."
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
text={"A URL that references the identity provider metadata."}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={`${baseClass}__inputs`}>
|
||||
<Checkbox
|
||||
onChange={handleInputChange}
|
||||
|
|
@ -621,11 +582,9 @@ const AppConfigFormFunctional = ({
|
|||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.sender_address}
|
||||
tooltip="The sender address for emails from Fleet."
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip text={"The sender address for emails from Fleet."} />
|
||||
</div>
|
||||
<div className={`${baseClass}__inputs ${baseClass}__inputs--smtp`}>
|
||||
<InputField
|
||||
label="SMTP server"
|
||||
|
|
@ -635,6 +594,7 @@ const AppConfigFormFunctional = ({
|
|||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.server}
|
||||
tooltip="The hostname / IP address and corresponding port of your organization's SMTP server."
|
||||
/>
|
||||
<InputField
|
||||
label=" "
|
||||
|
|
@ -655,13 +615,6 @@ const AppConfigFormFunctional = ({
|
|||
Use SSL/TLS to connect (recommended)
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
text={
|
||||
"The hostname / IP address and corresponding port of your organization's SMTP server."
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__inputs`}>
|
||||
<Dropdown
|
||||
label="Authentication type"
|
||||
|
|
@ -670,20 +623,15 @@ const AppConfigFormFunctional = ({
|
|||
name="smtpAuthenticationType"
|
||||
value={smtpAuthenticationType}
|
||||
parseTarget
|
||||
/>
|
||||
{renderSmtpSection()}
|
||||
</div>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={
|
||||
tooltip={
|
||||
"\
|
||||
<p>If your mail server requires authentication, you need to specify the authentication type here.</p> \
|
||||
<p><strong>No Authentication</strong> - Select this if your SMTP is open.</p> \
|
||||
<p><strong>Username & Password</strong> - Select this if your SMTP server requires authentication with a username and password.</p>\
|
||||
"
|
||||
<p>If your mail server requires authentication, you need to specify the authentication type here.</p> \
|
||||
<p><strong>No Authentication</strong> - Select this if your SMTP is open.</p> \
|
||||
<p><strong>Username & Password</strong> - Select this if your SMTP server requires authentication with a username and password.</p>\
|
||||
"
|
||||
}
|
||||
/>
|
||||
{renderSmtpSection()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -794,14 +742,9 @@ const AppConfigFormFunctional = ({
|
|||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.destination_url}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={
|
||||
tooltip={
|
||||
"\
|
||||
<center><p>Provide a URL to deliver <br/>the webhook request to.</p></center>\
|
||||
<p>Provide a URL to deliver <br/>the webhook request to.</p>\
|
||||
"
|
||||
}
|
||||
/>
|
||||
|
|
@ -814,14 +757,9 @@ const AppConfigFormFunctional = ({
|
|||
name="hostStatusWebhookHostPercentage"
|
||||
value={hostStatusWebhookHostPercentage}
|
||||
parseTarget
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={
|
||||
tooltip={
|
||||
"\
|
||||
<center><p>Select the minimum percentage of hosts that<br/>must fail to check into Fleet in order to trigger<br/>the webhook request.</p></center>\
|
||||
<p>Select the minimum percentage of hosts that<br/>must fail to check into Fleet in order to trigger<br/>the webhook request.</p>\
|
||||
"
|
||||
}
|
||||
/>
|
||||
|
|
@ -834,14 +772,9 @@ const AppConfigFormFunctional = ({
|
|||
name="hostStatusWebhookDaysCount"
|
||||
value={hostStatusWebhookDaysCount}
|
||||
parseTarget
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={
|
||||
tooltip={
|
||||
"\
|
||||
<center><p>Select the minimum number of days that the<br/>configured <b>Percentage of hosts</b> must fail to<br/>check into Fleet in order to trigger the<br/>webhook request.</p></center>\
|
||||
<p>Select the minimum number of days that the<br/>configured <b>Percentage of hosts</b> must fail to<br/>check into Fleet in order to trigger the<br/>webhook request.</p>\
|
||||
"
|
||||
}
|
||||
/>
|
||||
|
|
@ -917,10 +850,7 @@ const AppConfigFormFunctional = ({
|
|||
name="domain"
|
||||
value={domain}
|
||||
parseTarget
|
||||
/>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={
|
||||
tooltip={
|
||||
'<p>If you need to specify a HELO domain, <br />you can do it here <em className="hint hint--brand">(Default: <strong>Blank</strong>)</em></p>'
|
||||
}
|
||||
/>
|
||||
|
|
@ -931,15 +861,12 @@ const AppConfigFormFunctional = ({
|
|||
name="verifySSLCerts"
|
||||
value={verifySSLCerts}
|
||||
parseTarget
|
||||
tooltip={
|
||||
'<p>Turn this off (not recommended) <br />if you use a self-signed certificate <em className="hint hint--brand"><br />(Default: <strong>On</strong>)</em></p>'
|
||||
}
|
||||
>
|
||||
Verify SSL certs
|
||||
</Checkbox>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={
|
||||
'<p>Turn this off (not recommended) <br />if you use a self-signed certificate <em className="hint hint--brand"><br />(Default: <strong>On</strong>)</em></p>'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="tooltip-wrap">
|
||||
<Checkbox
|
||||
|
|
@ -947,15 +874,12 @@ const AppConfigFormFunctional = ({
|
|||
name="enableStartTLS"
|
||||
value={enableStartTLS}
|
||||
parseTarget
|
||||
tooltip={
|
||||
'<p>Detects if STARTTLS is enabled <br />in your SMTP server and starts <br />to use it. <em className="hint hint--brand">(Default: <strong>On</strong>)</em></p>'
|
||||
}
|
||||
>
|
||||
Enable STARTTLS
|
||||
</Checkbox>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={
|
||||
'<p>Detects if STARTTLS is enabled <br />in your SMTP server and starts <br />to use it. <em className="hint hint--brand">(Default: <strong>On</strong>)</em></p>'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="tooltip-wrap">
|
||||
<Checkbox
|
||||
|
|
@ -963,15 +887,12 @@ const AppConfigFormFunctional = ({
|
|||
name="enableHostExpiry"
|
||||
value={enableHostExpiry}
|
||||
parseTarget
|
||||
tooltip={
|
||||
'<p>When enabled, allows automatic cleanup <br />of hosts that have not communicated with Fleet <br />in some number of days. <em className="hint hint--brand">(Default: <strong>Off</strong>)</em></p>'
|
||||
}
|
||||
>
|
||||
Host expiry
|
||||
</Checkbox>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={
|
||||
'<p>When enabled, allows automatic cleanup <br />of hosts that have not communicated with Fleet <br />in some number of days. <em className="hint hint--brand">(Default: <strong>Off</strong>)</em></p>'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="tooltip-wrap tooltip-wrap--input">
|
||||
<InputField
|
||||
|
|
@ -984,11 +905,8 @@ const AppConfigFormFunctional = ({
|
|||
parseTarget
|
||||
onBlur={validateForm}
|
||||
error={formErrors.host_expiry_window}
|
||||
/>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={
|
||||
"<p>If a host has not communicated with Fleet <br />in the specified number of days, it will be removed.</p>"
|
||||
tooltip={
|
||||
"<p>If a host has not communicated with Fleet in the specified number of days, it will be removed.</p>"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -998,15 +916,12 @@ const AppConfigFormFunctional = ({
|
|||
name="disableLiveQuery"
|
||||
value={disableLiveQuery}
|
||||
parseTarget
|
||||
tooltip={
|
||||
'<p>When enabled, disables the ability to run live queries <br />(ad hoc queries executed via the UI or fleetctl). <em className="hint hint--brand">(Default: <strong>Off</strong>)</em></p>'
|
||||
}
|
||||
>
|
||||
Disable live queries
|
||||
</Checkbox>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={
|
||||
'<p>When enabled, disables the ability to run live queries <br />(ad hoc queries executed via the UI or fleetctl). <em className="hint hint--brand">(Default: <strong>Off</strong>)</em></p>'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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) => {
|
|||
}}
|
||||
/>
|
||||
<span className={checkBoxTickClass} />
|
||||
<span className={`${checkBoxClass}__label`}>{children}</span>
|
||||
<span className={`${checkBoxClass}__label`}>
|
||||
{tooltip ? (
|
||||
<TooltipWrapper tipContent={tooltip}>
|
||||
{children as string}
|
||||
</TooltipWrapper>
|
||||
) : (
|
||||
<>{children}</>
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
</FormField>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -85,5 +85,6 @@
|
|||
&__label {
|
||||
font-size: $x-small;
|
||||
padding-left: $pad-small;
|
||||
display: inherit;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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 htmlFor={name} className={labelClasses}>
|
||||
{label || placeholder}
|
||||
<label
|
||||
htmlFor={name}
|
||||
className={labelClasses}
|
||||
data-has-tooltip={!!tooltip}
|
||||
>
|
||||
{tooltip ? (
|
||||
<TooltipWrapper tipContent={tooltip}>{label}</TooltipWrapper>
|
||||
) : (
|
||||
<>{label || placeholder}</>
|
||||
)}
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 = ({
|
|||
/>
|
||||
<span className={`${baseClass}__control`} />
|
||||
</span>
|
||||
<span className={`${baseClass}__label`}>{label}</span>
|
||||
<span className={`${baseClass}__label`}>
|
||||
{tooltip ? (
|
||||
<TooltipWrapper tipContent={tooltip}>{label}</TooltipWrapper>
|
||||
) : (
|
||||
<>{label}</>
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div>
|
||||
<div className="column-with-tooltip">
|
||||
<span className="queries-table__performance-impact-header">
|
||||
Performance impact
|
||||
<TooltipWrapper tipContent="This is the average performance impact across all hosts where this query was scheduled.">
|
||||
Performance impact
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
<span
|
||||
data-tip
|
||||
data-for="queries-table__performance-impact-tooltip"
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<img alt="question icon" src={QuestionIcon} />
|
||||
</span>
|
||||
<ReactTooltip
|
||||
className="queries-table__performance-impact-tooltip"
|
||||
place="bottom"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id="queries-table__performance-impact-tooltip"
|
||||
data-html
|
||||
>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
This is the average <br />
|
||||
performance impact <br />
|
||||
across all hosts where this <br />
|
||||
query was scheduled.
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -48,4 +48,10 @@
|
|||
.queries-table__performance-impact-tooltip {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.column-with-tooltip {
|
||||
height: 0;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) => (
|
||||
<li key={column.name} className={`${columnBaseClass}__item`}>
|
||||
<span className={`${columnBaseClass}__name`}>{column.name}</span>
|
||||
<IconToolTip text={column.description} />
|
||||
<span className={`${columnBaseClass}__name`}>
|
||||
<TooltipWrapper tipContent={column.description}>
|
||||
{column.name}
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
<div className={`${columnBaseClass}__description`}>
|
||||
<span className={`${columnBaseClass}__type`}>
|
||||
{displayTypeForDataType(column.type)}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<span className="last-updated">
|
||||
{`Last updated ${lastUpdatedAt}`}
|
||||
<span className={`tooltip`}>
|
||||
<span
|
||||
className={`tooltip__tooltip-icon`}
|
||||
data-tip
|
||||
data-for={`last-updated-tooltip-${kebabCase(whatToRetrieve)}`}
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<img alt="question icon" src={QuestionIcon} />
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id={`last-updated-tooltip-${kebabCase(whatToRetrieve)}`}
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip__tooltip-text`}>
|
||||
Fleet periodically
|
||||
<br />
|
||||
queries all hosts
|
||||
<br />
|
||||
to retrieve {whatToRetrieve}
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default renderLastUpdatedText;
|
||||
|
|
@ -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<ICreateUserFormProps, ICreateUserFormState> {
|
|||
data-tip-disable={isNewUser || smtpConfigured}
|
||||
>
|
||||
<InputFieldWithIcon
|
||||
label="Email"
|
||||
error={errors.email || serverErrors?.email}
|
||||
name="email"
|
||||
onChange={onInputChange("email")}
|
||||
placeholder="Email"
|
||||
value={email || ""}
|
||||
disabled={!isNewUser && !smtpConfigured}
|
||||
tooltip={
|
||||
"\
|
||||
Editing an email address requires that SMTP is configured in order to send a validation email. \
|
||||
<br /><br /> \
|
||||
Users with Admin role can configure SMTP in <strong>Settings > Organization settings</strong>. \
|
||||
"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<ReactTooltip
|
||||
place="bottom"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
id="email-disabled-tooltip"
|
||||
backgroundColor="#3e4771"
|
||||
data-html
|
||||
>
|
||||
<span className={`${baseClass}__tooltip-text`}>
|
||||
Editing an email address requires that SMTP is <br />
|
||||
configured in order to send a validation email. <br />
|
||||
<br />
|
||||
Users with Admin role can configure SMTP in
|
||||
<br />
|
||||
<strong>Settings > Organization settings</strong>.
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
<div className={`${baseClass}__sso-input`}>
|
||||
<div
|
||||
className="sso-disabled"
|
||||
data-tip
|
||||
data-for="sso-disabled-tooltip"
|
||||
data-tip-disable={canUseSso}
|
||||
data-offset="{'top': 25, 'left': 100}"
|
||||
<Checkbox
|
||||
name="sso_enabled"
|
||||
onChange={onCheckboxChange("sso_enabled")}
|
||||
value={canUseSso && sso_enabled}
|
||||
disabled={!canUseSso}
|
||||
wrapperClassName={`${baseClass}__invite-admin`}
|
||||
tooltip={`
|
||||
Enabling single sign on for a user requires that SSO is first enabled for the organization.
|
||||
<br /><br />
|
||||
Users with Admin role can configure SSO in <strong>Settings > Organization settings</strong>.
|
||||
`}
|
||||
>
|
||||
<Checkbox
|
||||
name="sso_enabled"
|
||||
onChange={onCheckboxChange("sso_enabled")}
|
||||
value={canUseSso && sso_enabled}
|
||||
disabled={!canUseSso}
|
||||
wrapperClassName={`${baseClass}__invite-admin`}
|
||||
>
|
||||
Enable single sign on
|
||||
</Checkbox>
|
||||
<p className={`${baseClass}__sso-input sublabel`}>
|
||||
Password authentication will be disabled for this user.
|
||||
</p>
|
||||
</div>
|
||||
<ReactTooltip
|
||||
place="bottom"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
id="sso-disabled-tooltip"
|
||||
backgroundColor="#3e4771"
|
||||
data-html
|
||||
>
|
||||
<span className={`${baseClass}__tooltip-text`}>
|
||||
Enabling single sign on for a user requires that SSO is <br />
|
||||
first enabled for the organization. <br />
|
||||
<br />
|
||||
Users with Admin role can configure SSO in
|
||||
<br />
|
||||
<strong>Settings > Organization settings</strong>.
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
Enable single sign on
|
||||
</Checkbox>
|
||||
<p className={`${baseClass}__sso-input sublabel`}>
|
||||
Password authentication will be disabled for this user.
|
||||
</p>
|
||||
</div>
|
||||
{isNewUser && (
|
||||
<div className={`${baseClass}__new-user-container`}>
|
||||
|
|
@ -563,45 +525,26 @@ class UserForm extends Component<ICreateUserFormProps, ICreateUserFormState> {
|
|||
name={"newUserType"}
|
||||
onChange={onRadioChange("newUserType")}
|
||||
/>
|
||||
<div
|
||||
className="invite-disabled"
|
||||
data-tip
|
||||
data-for="invite-disabled-tooltip"
|
||||
data-tip-disable={smtpConfigured}
|
||||
>
|
||||
<Radio
|
||||
className={`${baseClass}__radio-input`}
|
||||
label={"Invite user"}
|
||||
id={"invite-user"}
|
||||
disabled={!smtpConfigured}
|
||||
checked={newUserType === NewUserType.AdminInvited}
|
||||
value={NewUserType.AdminInvited}
|
||||
name={"newUserType"}
|
||||
onChange={onRadioChange("newUserType")}
|
||||
/>
|
||||
<ReactTooltip
|
||||
place="bottom"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
id="invite-disabled-tooltip"
|
||||
backgroundColor="#3e4771"
|
||||
data-html
|
||||
>
|
||||
<span className={`${baseClass}__tooltip-text`}>
|
||||
The "Invite user" feature requires that SMTP
|
||||
is
|
||||
<br />
|
||||
configured in order to send invitation emails. <br />
|
||||
<br />
|
||||
SMTP can be configured in{" "}
|
||||
<strong>
|
||||
Settings > <br />
|
||||
Organization settings
|
||||
</strong>
|
||||
.
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
</div>
|
||||
<Radio
|
||||
className={`${baseClass}__radio-input`}
|
||||
label={"Invite user"}
|
||||
id={"invite-user"}
|
||||
disabled={!smtpConfigured}
|
||||
checked={newUserType === NewUserType.AdminInvited}
|
||||
value={NewUserType.AdminInvited}
|
||||
name={"newUserType"}
|
||||
onChange={onRadioChange("newUserType")}
|
||||
tooltip={
|
||||
smtpConfigured
|
||||
? ""
|
||||
: `
|
||||
The "Invite user" feature requires that SMTP
|
||||
is configured in order to send invitation emails.
|
||||
<br /><br />
|
||||
SMTP can be configured in Settings > Organization settings.
|
||||
`
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<input
|
||||
|
|
@ -616,6 +559,7 @@ class UserForm extends Component<ICreateUserFormProps, ICreateUserFormState> {
|
|||
<>
|
||||
<div className={`${baseClass}__password`}>
|
||||
<InputField
|
||||
label="Password"
|
||||
error={errors.password}
|
||||
name="password"
|
||||
onChange={onInputChange("password")}
|
||||
|
|
@ -626,16 +570,9 @@ class UserForm extends Component<ICreateUserFormProps, ICreateUserFormState> {
|
|||
"Must include 7 characters, at least 1 number (e.g. 0 - 9), and at least 1 symbol (e.g. &*#)",
|
||||
]}
|
||||
blockAutoComplete
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={`\
|
||||
<div class="password-tooltip-text">\
|
||||
<p>This password is temporary. This user will be asked to set a new password after logging in to the Fleet UI.</p>\
|
||||
<p>This user will not be asked to set a new password after logging in to fleetctl or the Fleet API.</p>\
|
||||
</div>\
|
||||
tooltip={`\
|
||||
This password is temporary. This user will be asked to set a new password after logging in to the Fleet UI.<br /><br />\
|
||||
This user will not be asked to set a new password after logging in to fleetctl or the Fleet API.\
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 = ({
|
|||
</span>
|
||||
</p>
|
||||
<span className={`${baseClass}__os-modal-example-title`}>
|
||||
Example policy:
|
||||
</span>{" "}
|
||||
<span
|
||||
className="policy-isexamplesue tooltip__tooltip-icon"
|
||||
data-tip
|
||||
data-for="policy-example"
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<img alt="host issue" src={QuestionIcon} />
|
||||
<TooltipWrapper tipContent="A policy is a yes or no question you can ask all your devices.">
|
||||
Example policy:
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="bottom"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id="policy-example"
|
||||
data-html
|
||||
>
|
||||
<span className={`${baseClass}__tooltip-text`}>
|
||||
A policy is a yes or no question
|
||||
<br /> you can ask all your devices.
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
<InputField
|
||||
disabled
|
||||
inputWrapperClass={`${baseClass}__os-policy`}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import React from "react";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import { uniqueId } from "lodash";
|
||||
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import PillCell from "components/TableContainer/DataTable/PillCell";
|
||||
import { IQueryStats } from "interfaces/query_stats";
|
||||
import {
|
||||
humanQueryLastRun,
|
||||
performanceIndicator,
|
||||
secondsToHms,
|
||||
} from "fleet/helpers";
|
||||
import IconToolTip from "components/IconToolTip";
|
||||
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import PillCell from "components/TableContainer/DataTable/PillCell";
|
||||
import TooltipWrapper from "components/TooltipWrapper";
|
||||
|
||||
interface IHeaderProps {
|
||||
column: {
|
||||
|
|
@ -65,13 +65,9 @@ const generatePackTableHeaders = (): IDataColumn[] => {
|
|||
title: "Last run",
|
||||
Header: () => {
|
||||
return (
|
||||
<>
|
||||
<TooltipWrapper tipContent="The last time the query ran<br/>since the last time osquery <br/>started on this host.">
|
||||
Last run
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={`The last time the query ran<br/>since the last time osquery <br/>started on this host.`}
|
||||
/>
|
||||
</>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
},
|
||||
disableSortBy: true,
|
||||
|
|
@ -82,13 +78,9 @@ const generatePackTableHeaders = (): IDataColumn[] => {
|
|||
title: "Performance impact",
|
||||
Header: () => {
|
||||
return (
|
||||
<>
|
||||
<TooltipWrapper tipContent="This is the performance <br />impact on this host.">
|
||||
Performance impact
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={`This is the performance <br />impact on this host.`}
|
||||
/>
|
||||
</>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
},
|
||||
disableSortBy: true,
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<span className="name-container">
|
||||
{name}
|
||||
<span
|
||||
className={`software-name tooltip__tooltip-icon`}
|
||||
data-tip
|
||||
data-for={`software-name__${cellProps.row.original.id.toString()}`}
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<img alt="bundle identifier" src={QuestionIcon} />
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="bottom"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id={`software-name__${cellProps.row.original.id.toString()}`}
|
||||
data-html
|
||||
>
|
||||
<span className={`software-name tooltip__tooltip-text`}>
|
||||
<TooltipWrapper
|
||||
tipContent={`
|
||||
<span>
|
||||
<b>Bundle identifier: </b>
|
||||
<br />
|
||||
{bundle_identifier}
|
||||
${bundle_identifier}
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
`}
|
||||
>
|
||||
{name}
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div>
|
||||
<span>Shell</span>
|
||||
<span
|
||||
data-tip
|
||||
data-for="host-users-table__shell-tooltip"
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<img alt="question icon" src={QuestionIcon} />
|
||||
</span>
|
||||
<ReactTooltip
|
||||
className="host-users-table__shell-tooltip"
|
||||
place="bottom"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id="host-users-table__shell-tooltip"
|
||||
data-html
|
||||
>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
The command line shell, such as bash,
|
||||
<br />
|
||||
that this user is equipped with by default
|
||||
<br />
|
||||
when they log in to the system.
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
</div>
|
||||
<TooltipWrapper tipContent="The command line shell, such as bash,<br />that this user is equipped with by<br />default when they log in to the system.">
|
||||
Shell
|
||||
</TooltipWrapper>
|
||||
);
|
||||
},
|
||||
disableSortBy: true,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)}
|
||||
</Button>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={
|
||||
"\
|
||||
<center><p>“All teams” policies are checked <br/> for this team’s hosts.</p></center>\
|
||||
"
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
'"All teams" policies are checked <br/> for this team’s hosts.'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
>
|
||||
{inheritedPoliciesButtonText(
|
||||
showInheritedPolicies,
|
||||
globalPolicies.length
|
||||
)}
|
||||
</TooltipWrapper>
|
||||
</Button>
|
||||
</span>
|
||||
)}
|
||||
{showInheritedPoliciesButton && showInheritedPolicies && (
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
/>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={"<p>Provide a URL to deliver a<br />webhook request to.</p>"}
|
||||
tooltip="Provide a URL to deliver a webhook request to."
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
/* eslint-disable jsx-a11y/interactive-supports-focus */
|
||||
import React, { useState, useContext, KeyboardEvent } from "react";
|
||||
import { IAceEditor } from "react-ace/lib/types";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import { isUndefined } from "lodash";
|
||||
import classnames from "classnames";
|
||||
|
||||
|
|
@ -20,9 +19,9 @@ import Button from "components/buttons/Button";
|
|||
import Checkbox from "components/forms/fields/Checkbox";
|
||||
import Spinner from "components/Spinner";
|
||||
import AutoSizeInputField from "components/forms/fields/AutoSizeInputField";
|
||||
import TooltipWrapper from "components/TooltipWrapper";
|
||||
import NewPolicyModal from "../NewPolicyModal";
|
||||
import InfoIcon from "../../../../../../assets/images/icon-info-purple-14x14@2x.png";
|
||||
import QuestionIcon from "../../../../../../assets/images/icon-question-16x16@2x.png";
|
||||
import PencilIcon from "../../../../../../assets/images/icon-pencil-14x14@2x.png";
|
||||
|
||||
const baseClass = "policy-form";
|
||||
|
|
@ -372,31 +371,9 @@ const PolicyForm = ({
|
|||
<>
|
||||
<b>Checks on:</b>
|
||||
<span className="platforms-text">
|
||||
{displayPlatforms.join(", ")}
|
||||
</span>
|
||||
<span className={`tooltip`}>
|
||||
<span
|
||||
className={`tooltip__tooltip-icon`}
|
||||
data-tip
|
||||
data-for="query-compatibility-tooltip"
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<img alt="question icon" src={QuestionIcon} />
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="bottom"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id="query-compatibility-tooltip"
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip__tooltip-text`}>
|
||||
To choose new platforms,
|
||||
<br />
|
||||
please create a new policy.
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
<TooltipWrapper tipContent="To choose new platforms, please create a new policy.">
|
||||
{displayPlatforms.join(", ")}
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div>
|
||||
<div className="column-with-tooltip">
|
||||
<span className="queries-table__performance-impact-header">
|
||||
Performance impact
|
||||
</span>
|
||||
<span
|
||||
data-tip
|
||||
data-for="queries-table__performance-impact-tooltip"
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<img alt="question icon" src={QuestionIcon} />
|
||||
</span>
|
||||
<ReactTooltip
|
||||
className="queries-table__performance-impact-tooltip"
|
||||
place="bottom"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id="queries-table__performance-impact-tooltip"
|
||||
data-html
|
||||
>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<TooltipWrapper
|
||||
tipContent={`
|
||||
This is the average <br />
|
||||
performance impact <br />
|
||||
across all hosts where this <br />
|
||||
query was scheduled.
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
query was scheduled.`}
|
||||
>
|
||||
Performance impact
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -41,6 +41,12 @@
|
|||
.queries-table__performance-impact-tooltip {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.column-with-tooltip {
|
||||
height: 0;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.no-queries {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<span className={baseClass}>
|
||||
<b>Compatible with:</b>
|
||||
<span className={`tooltip`}>
|
||||
<span
|
||||
className={`tooltip__tooltip-icon`}
|
||||
data-tip
|
||||
data-for="query-compatibility-tooltip"
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<img alt="question icon" src={QuestionIcon} />
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="bottom"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id="query-compatibility-tooltip"
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip__tooltip-text`}>
|
||||
Estimated compatiblity
|
||||
<br />
|
||||
based on the tables used
|
||||
<br />
|
||||
in the query
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
</span>
|
||||
<b>
|
||||
<TooltipWrapper tipContent="Estimated compatiblity based on the tables used in the query">
|
||||
Compatible with:
|
||||
</TooltipWrapper>
|
||||
</b>
|
||||
{displayIncompatibilityText(compatiblePlatforms) ||
|
||||
(!!compatiblePlatforms.length &&
|
||||
DISPLAY_ORDER.map((platform) => {
|
||||
|
|
|
|||
|
|
@ -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 ? (
|
||||
<>
|
||||
<span>
|
||||
<Button
|
||||
variant="unstyled"
|
||||
className={`${showInheritedQueries ? "upcarat" : "rightcarat"}
|
||||
${baseClass}__inherited-queries-button`}
|
||||
onClick={toggleInheritedQueries}
|
||||
<span>
|
||||
<Button
|
||||
variant="unstyled"
|
||||
className={`${showInheritedQueries ? "upcarat" : "rightcarat"}
|
||||
${baseClass}__inherited-queries-button`}
|
||||
onClick={toggleInheritedQueries}
|
||||
>
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
'Queries from the "All teams"<br/>schedule run on this team’s hosts.'
|
||||
}
|
||||
>
|
||||
{showInheritedQueries
|
||||
? `Hide ${inheritedScheduledQueriesList.length} inherited ${inheritedQueryOrQueries}`
|
||||
: `Show ${inheritedScheduledQueriesList.length} inherited ${inheritedQueryOrQueries}`}
|
||||
</Button>
|
||||
</span>
|
||||
<div className={`${baseClass}__details`}>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={
|
||||
"\
|
||||
<center><p>Queries from the “All teams”<br/>schedule run on this team’s hosts.</p></center>\
|
||||
"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</TooltipWrapper>
|
||||
</Button>
|
||||
</span>
|
||||
) : null}
|
||||
{showInheritedQueries &&
|
||||
inheritedScheduledQueriesList &&
|
||||
|
|
|
|||
|
|
@ -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 = ({
|
|||
<Modal title={"Example data"} onExit={onCancel} className={baseClass}>
|
||||
<div className={`${baseClass}__preview-modal`}>
|
||||
<p>
|
||||
The data sent to your configured log destination will look similar to
|
||||
the following JSON:{" "}
|
||||
<span
|
||||
className={`tooltip__tooltip-icon`}
|
||||
data-tip
|
||||
data-for={"preview-tooltip"}
|
||||
data-tip-disable={false}
|
||||
<TooltipWrapper
|
||||
tipContent={`The "snapshot" key includes the query's results. These will be unique to your query.`}
|
||||
>
|
||||
<img alt="preview schedule" src={QuestionIcon} />
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="bottom"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id={"preview-tooltip"}
|
||||
data-html
|
||||
>
|
||||
<span className={`software-name tooltip__tooltip-text`}>
|
||||
<p>
|
||||
The "snapshot" key includes the query's
|
||||
<br />
|
||||
results. These will be unique to your query.
|
||||
</p>
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
The data sent to your configured log destination will look similar
|
||||
to the following JSON:
|
||||
</TooltipWrapper>
|
||||
</p>
|
||||
<div className={`${baseClass}__host-status-webhook-preview`}>
|
||||
<pre dangerouslySetInnerHTML={{ __html: syntaxHighlight(json) }} />
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div>
|
||||
<div className="column-with-tooltip">
|
||||
<span className="queries-table__performance-impact-header">
|
||||
Performance impact
|
||||
</span>
|
||||
<span
|
||||
data-tip
|
||||
data-for="queries-table__performance-impact-tooltip"
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<img alt="question icon" src={QuestionIcon} />
|
||||
</span>
|
||||
<ReactTooltip
|
||||
className="queries-table__performance-impact-tooltip"
|
||||
place="bottom"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id="queries-table__performance-impact-tooltip"
|
||||
data-html
|
||||
>
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<TooltipWrapper
|
||||
tipContent={`
|
||||
This is the average <br />
|
||||
performance impact <br />
|
||||
across all hosts where this <br />
|
||||
query was scheduled.
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
query was scheduled.`}
|
||||
>
|
||||
Performance impact
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -77,6 +77,12 @@
|
|||
.queries-table__performance-impact-tooltip {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.column-with-tooltip {
|
||||
height: 0;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.no-schedule {
|
||||
|
|
|
|||
|
|
@ -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 ? (
|
||||
<span
|
||||
className={`${baseClass}__count ${
|
||||
isFetchingCount ? "count-loading" : ""
|
||||
}`}
|
||||
>
|
||||
{`${count} software item${count === 1 ? "" : "s"}`}
|
||||
<span className="count-last-updated">
|
||||
{`Last updated ${lastUpdatedAt}`}{" "}
|
||||
<span className={`tooltip`}>
|
||||
<span
|
||||
className={`tooltip__tooltip-icon`}
|
||||
data-tip
|
||||
data-for="last-updated-tooltip"
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<img alt="question icon" src={QuestionIcon} />
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id="last-updated-tooltip"
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip__tooltip-text`}>
|
||||
Fleet periodically
|
||||
<br />
|
||||
queries all hosts
|
||||
<br />
|
||||
to retrieve software
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
) : null;
|
||||
if (count) {
|
||||
return (
|
||||
<div
|
||||
className={`${baseClass}__count ${
|
||||
isFetchingCount ? "count-loading" : ""
|
||||
}`}
|
||||
>
|
||||
<span>{`${count} software item${count === 1 ? "" : "s"}`}</span>
|
||||
{renderLastUpdatedText(lastUpdatedAt, "software")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [isFetchingCount, software, softwareCountError, softwareCount]);
|
||||
|
||||
// TODO: retool this with react-router location descriptor objects
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
/>
|
||||
<IconToolTip
|
||||
isHtml
|
||||
text={"<p>Provide a URL to deliver a<br />webhook request to.</p>"}
|
||||
tooltip="Provide a URL to deliver a webhook request to."
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
|
|
|
|||
Loading…
Reference in a new issue