Frontend of usage statistics (#1177)

* #454 added usage stats disclaimer to setup confirmation

* #454 added new section to settings for usage stats

* #454 fixed vulnerability for hrefs

* removed jsx file

* #454 added logic to checkbox

* #454 created modal to preview usage stats; cleanup

* fixed tests and linting
This commit is contained in:
Martavis Parker
2021-06-23 15:19:01 -07:00
committed by GitHub
parent 2b3f968478
commit 7af97579fe
15 changed files with 559 additions and 254 deletions
@@ -101,7 +101,11 @@ const ConfirmationPage = ({
{importOsqueryConfig()}
</div>
<p>
Fleet Device Management Inc. periodically collects anonymous information
about your instance. Sending usage statistics from your Fleet instance
is optional and can be disabled in settings.
</p>
<Button
type="submit"
tabIndex={tabIndex}
@@ -7,6 +7,13 @@
text-align: center;
}
&__wrapper {
& + p {
margin-top: 40px;
font-size: $x-small;
}
}
&__icon {
color: $ui-success;
font-size: 120px;
@@ -52,6 +59,7 @@
font-size: $small;
font-weight: $regular;
}
}
&__table-url {
@@ -148,7 +148,7 @@ class RegistrationForm extends Component {
/>
</div>
<div className={confirmationContainerClass}>
<h2>You&apos;re all set.</h2>
<h2>Success</h2>
<ConfirmationPage
formData={formData}
handleSubmit={onSubmitConfirmation}
@@ -29,6 +29,6 @@ describe("RegistrationForm - component", () => {
const form = mount(<RegistrationForm page={4} />);
expect(form.find("ConfirmationPage").length).toEqual(1);
expect(form.text()).toContain("You're all set");
expect(form.text()).toContain("Success");
});
});
@@ -1,5 +1,6 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { syntaxHighlight } from "fleet/helpers";
import Button from "components/buttons/Button";
import Checkbox from "components/forms/fields/Checkbox";
@@ -15,6 +16,7 @@ import validate from "components/forms/admin/AppConfigForm/validate";
import IconToolTip from "components/IconToolTip";
import InfoBanner from "components/InfoBanner/InfoBanner";
import YamlAce from "components/YamlAce";
import Modal from "components/modals/Modal";
import OpenNewTabIcon from "../../../../../assets/images/open-new-tab-12x12@2x.png";
const authMethodOptions = [
@@ -56,6 +58,7 @@ const formFields = [
"host_expiry_window",
"live_query_disabled",
"agent_options",
"enable_analytics",
];
const Header = ({ showAdvancedOptions }) => {
const CaratIcon = (
@@ -106,6 +109,7 @@ class AppConfigForm extends Component {
host_expiry_window: formFieldInterface.isRequired,
live_query_disabled: formFieldInterface.isRequired,
agent_options: formFieldInterface.isRequired,
enable_analytics: formFieldInterface.isRequired,
}).isRequired,
enrollSecret: enrollSecretInterface.isRequired,
handleSubmit: PropTypes.func.isRequired,
@@ -115,7 +119,10 @@ class AppConfigForm extends Component {
constructor(props) {
super(props);
this.state = { showAdvancedOptions: false };
this.state = {
showAdvancedOptions: false,
showUsageStatsPreviewModal: false,
};
}
onToggleAdvancedOptions = (evt) => {
@@ -128,6 +135,13 @@ class AppConfigForm extends Component {
return false;
};
toggleUsageStatsPreviewModal = () => {
const { showUsageStatsPreviewModal } = this.state;
this.setState({
showUsageStatsPreviewModal: !showUsageStatsPreviewModal,
});
};
renderAdvancedOptions = () => {
const { fields } = this.props;
const { showAdvancedOptions } = this.state;
@@ -200,277 +214,363 @@ class AppConfigForm extends Component {
);
};
renderUsageStatsPreviewModal = () => {
const { toggleUsageStatsPreviewModal } = this;
const { showUsageStatsPreviewModal } = this.state;
if (!showUsageStatsPreviewModal) {
return null;
}
const json = {
anonymous_identifier: 1,
fleet_version: "x.x.x",
hosts_enrolled_count: 12345,
};
return (
<Modal
title="Usage statistics"
onExit={toggleUsageStatsPreviewModal}
className={`${baseClass}__usage-stats-preview-modal`}
>
<p>An example JSON payload sent to Fleet Device Management Inc.</p>
<pre dangerouslySetInnerHTML={{ __html: syntaxHighlight(json) }} />
<div className="flex-end">
<Button type="button" onClick={toggleUsageStatsPreviewModal}>
Done
</Button>
</div>
</Modal>
);
};
render() {
const { fields, handleSubmit, smtpConfigured, enrollSecret } = this.props;
const {
onToggleAdvancedOptions,
renderAdvancedOptions,
renderSmtpSection,
toggleUsageStatsPreviewModal,
renderUsageStatsPreviewModal,
} = this;
const { showAdvancedOptions } = this.state;
return (
<form className={baseClass} onSubmit={handleSubmit}>
<div className={`${baseClass}__section`}>
<h2>
<a id="organization-info">Organization Info</a>
</h2>
<div className={`${baseClass}__inputs`}>
<InputField {...fields.org_name} label="Organization name" />
<InputField
{...fields.org_logo_url}
label="Organization avatar URL"
/>
<>
<form className={baseClass} onSubmit={handleSubmit}>
<div className={`${baseClass}__section`}>
<h2>
<a id="organization-info">Organization Info</a>
</h2>
<div className={`${baseClass}__inputs`}>
<InputField {...fields.org_name} label="Organization name" />
<InputField
{...fields.org_logo_url}
label="Organization avatar URL"
/>
</div>
<div
className={`${baseClass}__details ${baseClass}__avatar-preview`}
>
<OrgLogoIcon src={fields.org_logo_url.value} />
</div>
</div>
<div className={`${baseClass}__details ${baseClass}__avatar-preview`}>
<OrgLogoIcon src={fields.org_logo_url.value} />
</div>
</div>
<div className={`${baseClass}__section`}>
<h2>
<a id="fleet-web-address">Fleet web address</a>
</h2>
<div className={`${baseClass}__inputs`}>
<InputField
{...fields.server_url}
label="Fleet App URL"
hint={
<span>
Include base path only (eg. no <code>/v1</code>)
</span>
}
/>
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
text={"The base URL of this instance for use in Fleet links."}
/>
</div>
</div>
<div className={`${baseClass}__section`}>
<h2>
<a id="saml">SAML Single Sign On Options</a>
</h2>
<div className={`${baseClass}__inputs`}>
<Checkbox {...fields.enable_sso}>Enable Single Sign On</Checkbox>
<div className={`${baseClass}__section`}>
<h2>
<a id="fleet-web-address">Fleet web address</a>
</h2>
<div className={`${baseClass}__inputs`}>
<InputField
{...fields.server_url}
label="Fleet App URL"
hint={
<span>
Include base path only (eg. no <code>/v1</code>)
</span>
}
/>
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
text={"The base URL of this instance for use in Fleet links."}
/>
</div>
</div>
<div className={`${baseClass}__inputs`}>
<InputField {...fields.idp_name} label="Identity Provider Name" />
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
text={
"A required human friendly name for the identity provider that will provide single sign on authentication."
}
/>
<div className={`${baseClass}__section`}>
<h2>
<a id="saml">SAML Single Sign On Options</a>
</h2>
<div className={`${baseClass}__inputs`}>
<Checkbox {...fields.enable_sso}>Enable Single Sign On</Checkbox>
</div>
<div className={`${baseClass}__inputs`}>
<InputField {...fields.idp_name} label="Identity Provider Name" />
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
text={
"A required human friendly name for the identity provider that will provide single sign on authentication."
}
/>
</div>
<div className={`${baseClass}__inputs`}>
<InputField
{...fields.entity_id}
label="Entity ID"
hint={
<span>
The URI you provide here must exactly match the Entity ID
field used in identity provider configuration.
</span>
}
/>
</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."
}
/>
</div>
<div className={`${baseClass}__inputs`}>
<InputField {...fields.issuer_uri} label="Issuer URI" />
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
text={"The issuer URI supplied by the identity provider."}
/>
</div>
<div className={`${baseClass}__inputs`}>
<InputField {...fields.idp_image_url} label="IDP Image URL" />
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
text={
"An optional link to an image such as a logo for the identity provider."
}
/>
</div>
<div className={`${baseClass}__inputs`}>
<InputField
{...fields.metadata}
label="Metadata"
type="textarea"
/>
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
text={
"Metadata provided by the identity provider. Either metadata or a metadata url must be provided."
}
/>
</div>
<div className={`${baseClass}__inputs`}>
<InputField
{...fields.metadata_url}
label="Metadata URL"
hint={
<span>
If available from the identity provider, this is the
preferred means of providing metadata.
</span>
}
/>
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
text={"A URL that references the identity provider metadata."}
/>
</div>
<div className={`${baseClass}__inputs`}>
<Checkbox {...fields.enable_sso_idp_login}>
Allow SSO login initiated by Identity Provider
</Checkbox>
</div>
</div>
<div className={`${baseClass}__inputs`}>
<InputField
{...fields.entity_id}
label="Entity ID"
hint={
<span>
The URI you provide here must exactly match the Entity ID
field used in identity provider configuration.
</span>
}
/>
</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."
}
/>
<div className={`${baseClass}__section`}>
<h2>
<a id="smtp">
SMTP Options{" "}
<small
className={`smtp-options smtp-options--${
smtpConfigured ? "configured" : "notconfigured"
}`}
>
STATUS:{" "}
<em>{smtpConfigured ? "CONFIGURED" : "NOT CONFIGURED"}</em>
</small>
</a>
</h2>
<div className={`${baseClass}__inputs`}>
<Checkbox {...fields.enable_smtp}>Enable SMTP</Checkbox>
</div>
<div className={`${baseClass}__inputs`}>
<InputField {...fields.sender_address} label="Sender Address" />
</div>
<div className={`${baseClass}__details`}>
<IconToolTip text={"The sender address for emails from Fleet."} />
</div>
<div className={`${baseClass}__inputs ${baseClass}__inputs--smtp`}>
<InputField {...fields.server} label="SMTP Server" />
<InputField {...fields.port} label="&nbsp;" type="number" />
<Checkbox {...fields.enable_ssl_tls}>
Use SSL/TLS to connect (recommended)
</Checkbox>
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
text={
"The hostname / IP address and corresponding port of your organization&apos;s SMTP server."
}
/>
</div>
<div className={`${baseClass}__inputs`}>
<Dropdown
{...fields.authentication_type}
label="Authentication Type"
options={authTypeOptions}
/>
{renderSmtpSection()}
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
isHtml
text={
"\
<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>\
"
}
/>
</div>
</div>
<div className={`${baseClass}__inputs`}>
<InputField {...fields.issuer_uri} label="Issuer URI" />
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
text={"The issuer URI supplied by the identity provider."}
/>
<div className={`${baseClass}__section`}>
<h2>
<a id="osquery-enrollment-secrets">Osquery Enrollment Secrets</a>
</h2>
<div className={`${baseClass}__inputs`}>
<p className={`${baseClass}__enroll-secret-label`}>
Manage secrets with <code>fleetctl</code>. Active secrets:
</p>
<EnrollSecretTable secrets={enrollSecret} />
</div>
</div>
<div className={`${baseClass}__inputs`}>
<InputField {...fields.idp_image_url} label="IDP Image URL" />
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
text={
"An optional link to an image such as a logo for the identity provider."
}
/>
<div className={`${baseClass}__section`}>
<h2>
<a id="agent-options">Global agent options</a>
</h2>
<div className={`${baseClass}__yaml`}>
<p className={`${baseClass}__section-description`}>
This code will be used by osquery when it checks for
configuration options.
<br />
<b>
Changes to these configuration options will be applied to all
hosts in your organization that do not belong to any team.
</b>
</p>
<InfoBanner className={`${baseClass}__config-docs`}>
How do global agent options interact with team-level agent
options?&nbsp;
<a
href="https://github.com/fleetdm/fleet/blob/master/docs/1-Using-Fleet/2-fleetctl-CLI.md#osquery-configuration-options"
className={`${baseClass}__learn-more ${baseClass}__learn-more--inline`}
target="_blank"
rel="noopener noreferrer"
>
Learn more about agent options&nbsp;
<img
className="icon"
src={OpenNewTabIcon}
alt="open new tab"
/>
</a>
</InfoBanner>
<p className={`${baseClass}__component-label`}>
<b>YAML</b>
</p>
<YamlAce
{...fields.agent_options}
error={fields.agent_options.error}
wrapperClassName={`${baseClass}__text-editor-wrapper`}
/>
</div>
</div>
<div className={`${baseClass}__inputs`}>
<InputField {...fields.metadata} label="Metadata" type="textarea" />
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
text={
"Metadata provided by the identity provider. Either metadata or a metadata url must be provided."
}
/>
</div>
<div className={`${baseClass}__inputs`}>
<InputField
{...fields.metadata_url}
label="Metadata URL"
hint={
<span>
If available from the identity provider, this is the preferred
means of providing metadata.
</span>
}
/>
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
text={"A URL that references the identity provider metadata."}
/>
</div>
<div className={`${baseClass}__inputs`}>
<Checkbox {...fields.enable_sso_idp_login}>
Allow SSO login initiated by Identity Provider
</Checkbox>
</div>
</div>
<div className={`${baseClass}__section`}>
<h2>
<a id="smtp">
SMTP Options{" "}
<small
className={`smtp-options smtp-options--${
smtpConfigured ? "configured" : "notconfigured"
}`}
>
STATUS:{" "}
<em>{smtpConfigured ? "CONFIGURED" : "NOT CONFIGURED"}</em>
</small>
</a>
</h2>
<div className={`${baseClass}__inputs`}>
<Checkbox {...fields.enable_smtp}>Enable SMTP</Checkbox>
</div>
<div className={`${baseClass}__inputs`}>
<InputField {...fields.sender_address} label="Sender Address" />
</div>
<div className={`${baseClass}__details`}>
<IconToolTip text={"The sender address for emails from Fleet."} />
</div>
<div className={`${baseClass}__inputs ${baseClass}__inputs--smtp`}>
<InputField {...fields.server} label="SMTP Server" />
<InputField {...fields.port} label="&nbsp;" type="number" />
<Checkbox {...fields.enable_ssl_tls}>
Use SSL/TLS to connect (recommended)
</Checkbox>
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
text={
"The hostname / IP address and corresponding port of your organization&apos;s SMTP server."
}
/>
</div>
<div className={`${baseClass}__inputs`}>
<Dropdown
{...fields.authentication_type}
label="Authentication Type"
options={authTypeOptions}
/>
{renderSmtpSection()}
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
isHtml
text={
"\
<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>\
"
}
/>
</div>
</div>
<div className={`${baseClass}__section`}>
<h2>
<a id="osquery-enrollment-secrets">Osquery Enrollment Secrets</a>
</h2>
<div className={`${baseClass}__inputs`}>
<p className={`${baseClass}__enroll-secret-label`}>
Manage secrets with <code>fleetctl</code>. Active secrets:
</p>
<EnrollSecretTable secrets={enrollSecret} />
</div>
</div>
<div className={`${baseClass}__section`}>
<h2>
<a id="agent-options">Global agent options</a>
</h2>
<div className={`${baseClass}__yaml`}>
<div className={`${baseClass}__section`}>
<h2>
<a id="usage-stats">Usage statistics</a>
</h2>
<p className={`${baseClass}__section-description`}>
This code will be used by osquery when it checks for configuration
options.
Help us improve Fleet by sending us anonymous usage statistics.
<br />
<br />
This information helps our team better understand feature adoption
and usage, and allows us to see how Fleet is adding value, so that
we can make better product decisions.
<br />
<br />
<b>
Changes to these configuration options will be applied to all
hosts in your organization that do not belong to any team.
</b>
</p>
<InfoBanner className={`${baseClass}__config-docs`}>
How do global agent options interact with team-level agent
options?&nbsp;
<a
href="https://github.com/fleetdm/fleet/blob/master/docs/1-Using-Fleet/2-fleetctl-CLI.md#osquery-configuration-options"
className={`${baseClass}__learn-more`}
target="_blank"
rel="noreferrer"
rel="noopener noreferrer"
>
Learn more about agent options&nbsp;
Learn more about usage statistics&nbsp;
<img className="icon" src={OpenNewTabIcon} alt="open new tab" />
</a>
</InfoBanner>
<p className={`${baseClass}__component-label`}>
<b>YAML</b>
</p>
<YamlAce
{...fields.agent_options}
error={fields.agent_options.error}
wrapperClassName={`${baseClass}__text-editor-wrapper`}
/>
<div className={`${baseClass}__inputs ${baseClass}__inputs--usage`}>
<Checkbox {...fields.enable_analytics}>
Enable usage statistics
</Checkbox>
</div>
<div className={`${baseClass}__inputs ${baseClass}__inputs--usage`}>
<Button
type="button"
variant="inverse"
onClick={toggleUsageStatsPreviewModal}
>
Preview payload
</Button>
</div>
</div>
</div>
<div className={`${baseClass}__section`}>
<h2>
<a
id="advanced-options"
onClick={onToggleAdvancedOptions}
className={`${baseClass}__show-options`}
>
<Header showAdvancedOptions={showAdvancedOptions} />
</a>
</h2>
{renderAdvancedOptions()}
</div>
<Button type="submit" variant="brand">
Update settings
</Button>
</form>
<div className={`${baseClass}__section`}>
<h2>
<a
id="advanced-options"
onClick={onToggleAdvancedOptions}
className={`${baseClass}__show-options`}
>
<Header showAdvancedOptions={showAdvancedOptions} />
</a>
</h2>
{renderAdvancedOptions()}
</div>
<Button type="submit" variant="brand">
Update settings
</Button>
</form>
{renderUsageStatsPreviewModal()}
</>
);
}
}
@@ -15,14 +15,17 @@
font-size: $x-small;
}
a {
color: $core-vibrant-blue;
font-size: $x-small;
font-weight: $bold;
text-decoration: none;
}
&__learn-more {
color: $core-vibrant-blue;
font-size: $x-small;
font-weight: $bold;
text-decoration: none;
&--inline {
margin-left: $pad-medium;
}
img {
width: 12px;
height: 12px;
@@ -30,6 +33,7 @@
}
}
&__enroll-secret-label {
font-size: 15px;
@@ -147,6 +151,9 @@
}
}
}
&--usage {
margin-top: 24px;
}
}
&__details {
@@ -219,4 +226,12 @@
&__yaml {
width: calc(100% - 2px); // because the outline extended beyond the sticky page description
}
&__usage-stats-preview-modal {
.flex-end {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
}
}