Files
fleet/frontend/components/forms/admin/AppConfigForm/AppConfigForm.jsx
T
RachelElysia 7dee7c56ef Schedules Page: Surface all teams schedule on team schedule page (#2080)
* Render inherited queries table on teams page
* e2e team schedules on e2e premium testing
2021-09-23 13:10:43 -04:00

702 lines
25 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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";
import Dropdown from "components/forms/fields/Dropdown";
import Form from "components/forms/Form";
import formFieldInterface from "interfaces/form_field";
import enrollSecretInterface from "interfaces/enroll_secret";
import EnrollSecretTable from "components/config/EnrollSecretTable";
import InputField from "components/forms/fields/InputField";
import OrgLogoIcon from "components/icons/OrgLogoIcon";
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 = [
{ label: "Plain", value: "authmethod_plain" },
{ label: "Cram MD5", value: "authmethod_cram_md5" },
{ label: "Login", value: "authmethod_login" },
];
const authTypeOptions = [
{ label: "Username and Password", value: "authtype_username_password" },
{ label: "None", value: "authtype_none" },
];
const percentageOfHosts = [
{ label: "1%", value: 1 },
{ label: "5%", value: 5 },
{ label: "10%", value: 10 },
{ label: "25%", value: 25 },
];
const numberOfDays = [
{ label: "1 day", value: 1 },
{ label: "3 days", value: 3 },
{ label: "7 days", value: 7 },
{ label: "14 days", value: 14 },
];
const baseClass = "app-config-form";
const formFields = [
"authentication_method",
"authentication_type",
"domain",
"enable_ssl_tls",
"enable_start_tls",
"server_url",
"org_logo_url",
"org_name",
"osquery_enroll_secret",
"password",
"port",
"sender_address",
"server",
"user_name",
"verify_ssl_certs",
"idp_name",
"entity_id",
"issuer_uri",
"idp_image_url",
"metadata",
"metadata_url",
"enable_sso",
"enable_sso_idp_login",
"enable_smtp",
"host_expiry_enabled",
"host_expiry_window",
"live_query_disabled",
"agent_options",
"enable_host_status_webhook",
"destination_url",
"host_percentage",
"days_count",
"enable_analytics",
];
class AppConfigForm extends Component {
static propTypes = {
fields: PropTypes.shape({
authentication_method: formFieldInterface.isRequired,
authentication_type: formFieldInterface.isRequired,
domain: formFieldInterface.isRequired,
enable_ssl_tls: formFieldInterface.isRequired,
enable_start_tls: formFieldInterface.isRequired,
server_url: formFieldInterface.isRequired,
org_logo_url: formFieldInterface.isRequired,
org_name: formFieldInterface.isRequired,
password: formFieldInterface.isRequired,
port: formFieldInterface.isRequired,
sender_address: formFieldInterface.isRequired,
server: formFieldInterface.isRequired,
user_name: formFieldInterface.isRequired,
verify_ssl_certs: formFieldInterface.isRequired,
entity_id: formFieldInterface.isRequired,
issuer_uri: formFieldInterface.isRequired,
idp_image_url: formFieldInterface.isRequired,
metadata: formFieldInterface.isRequired,
metadata_url: formFieldInterface.isRequired,
idp_name: formFieldInterface.isRequired,
enable_sso: formFieldInterface.isRequired,
enable_sso_idp_login: formFieldInterface.isRequired,
enable_smtp: formFieldInterface.isRequired,
host_expiry_enabled: formFieldInterface.isRequired,
host_expiry_window: formFieldInterface.isRequired,
live_query_disabled: formFieldInterface.isRequired,
agent_options: formFieldInterface.isRequired,
enable_host_status_webhook: formFieldInterface.isRequired,
destination_url: formFieldInterface,
host_percentage: formFieldInterface,
days_count: formFieldInterface,
enable_analytics: formFieldInterface.isRequired,
}).isRequired,
enrollSecret: PropTypes.arrayOf(enrollSecretInterface).isRequired,
handleSubmit: PropTypes.func.isRequired,
smtpConfigured: PropTypes.bool.isRequired,
};
constructor(props) {
super(props);
this.state = {
showHostStatusWebhookPreviewModal: false,
showUsageStatsPreviewModal: false,
};
}
onToggleAdvancedOptions = (evt) => {
evt.preventDefault();
const { showAdvancedOptions } = this.state;
this.setState({ showAdvancedOptions: !showAdvancedOptions });
return false;
};
toggleHostStatusWebhookPreviewModal = () => {
const { showHostStatusWebhookPreviewModal } = this.state;
this.setState({
showHostStatusWebhookPreviewModal: !showHostStatusWebhookPreviewModal,
});
};
toggleUsageStatsPreviewModal = () => {
const { showUsageStatsPreviewModal } = this.state;
this.setState({
showUsageStatsPreviewModal: !showUsageStatsPreviewModal,
});
};
renderAdvancedOptions = () => {
const { fields } = this.props;
return (
<div>
<p className={`${baseClass}__section-description`}>
Most users do not need to modify these options.
</p>
<div className={`${baseClass}__inputs`}>
<div className={`${baseClass}__smtp-section`}>
<InputField {...fields.domain} label="Domain" />
<Checkbox {...fields.verify_ssl_certs}>Verify SSL certs</Checkbox>
<Checkbox {...fields.enable_start_tls}>Enable STARTTLS</Checkbox>
<Checkbox {...fields.host_expiry_enabled}>Host expiry</Checkbox>
<InputField
{...fields.host_expiry_window}
disabled={!fields.host_expiry_enabled.value}
label="Host Expiry Window"
/>
<Checkbox {...fields.live_query_disabled}>
Disable live queries
</Checkbox>
</div>
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
isHtml
text={
'\
<p><strong>Domain</strong> - If you need to specify a HELO domain, you can do it here <em className="hint hint--brand">(Default: <strong>Blank</strong>)</em></p>\
<p><strong>Verify SSL Certs</strong> - Turn this off (not recommended) if you use a self-signed certificate <em className="hint hint--brand">(Default: <strong>On</strong>)</em></p>\
<p><strong>Enable STARTTLS</strong> - Detects if STARTTLS is enabled in your SMTP server and starts to use it. <em className="hint hint--brand">(Default: <strong>On</strong>)</em></p>\
<p><strong>Host Expiry</strong> - When enabled, allows automatic cleanup of hosts that have not communicated with Fleet in some number of days. <em className="hint hint--brand">(Default: <strong>Off</strong>)</em></p>\
<p><strong>Host Expiry Window</strong> - If a host has not communicated with Fleet in the specified number of days, it will be removed.</p>\
<p><strong>Disable Live Queries</strong> - When enabled, disables the ability to run live queries (ad hoc queries executed via the UI or fleetctl). <em className="hint hint--brand">(Default: <strong>Off</strong>)</em></p>\
'
}
/>
</div>
</div>
);
};
renderSmtpSection = () => {
const { fields } = this.props;
if (fields.authentication_type.value === "authtype_none") {
return false;
}
return (
<div className={`${baseClass}__smtp-section`}>
<InputField {...fields.user_name} label="SMTP Username" />
<InputField
{...fields.password}
label="SMTP Password"
type="password"
/>
<Dropdown
{...fields.authentication_method}
label="Auth Method"
options={authMethodOptions}
placeholder=""
/>
</div>
);
};
renderHostStatusWebhookPreviewModal = () => {
const { toggleHostStatusWebhookPreviewModal } = this;
const { showHostStatusWebhookPreviewModal } = this.state;
if (!showHostStatusWebhookPreviewModal) {
return null;
}
const json = {
message:
"More than X% of your hosts have not checked into Fleet for more than Y days. Youve been sent this message because the Host status webhook is enabled in your Fleet instance.",
data: {
unseen_hosts: 1,
total_hosts: 2,
days_unseen: 3,
},
};
return (
<Modal
title="Host status webhook"
onExit={toggleHostStatusWebhookPreviewModal}
className={`${baseClass}__host-status-webhook-preview-modal`}
>
<p>
An example request sent to your configured <b>Destination URL</b>.
</p>
<div className={`${baseClass}__host-status-webhook-preview`}>
<pre dangerouslySetInnerHTML={{ __html: syntaxHighlight(json) }} />
</div>
<div className="flex-end">
<Button type="button" onClick={toggleHostStatusWebhookPreviewModal}>
Done
</Button>
</div>
</Modal>
);
};
renderUsageStatsPreviewModal = () => {
const { toggleUsageStatsPreviewModal } = this;
const { showUsageStatsPreviewModal } = this.state;
if (!showUsageStatsPreviewModal) {
return null;
}
const json = {
anonymous_identifier: "wmTH972f06USpahr41LHpgLKAhgZL",
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 {
renderAdvancedOptions,
renderSmtpSection,
toggleHostStatusWebhookPreviewModal,
toggleUsageStatsPreviewModal,
renderHostStatusWebhookPreviewModal,
renderUsageStatsPreviewModal,
} = this;
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"
/>
</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>
<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}__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'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`}>
<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/2f42c281f98e39a72ab4a5125ecd26d303a16a6b/docs/1-Using-Fleet/1-Fleet-UI.md#configuring-agent-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}__section`}>
<h2>
<a id="host-status-webhook">Host status webhook</a>
</h2>
<div className={`${baseClass}__host-status-webhook`}>
<p className={`${baseClass}__section-description`}>
Send an alert if a portion of your hosts go offline.
</p>
<Checkbox {...fields.enable_host_status_webhook}>
Enable host status webhook
</Checkbox>
<p className={`${baseClass}__section-description`}>
A request will be sent to your configured <b>Destination URL</b>{" "}
if the configured <b>Percentage of hosts</b> have not checked
into Fleet for the configured <b>Number of days</b>.
</p>
</div>
<div
className={`${baseClass}__inputs ${baseClass}__inputs--webhook`}
>
<Button
type="button"
variant="inverse"
onClick={toggleHostStatusWebhookPreviewModal}
>
Preview request
</Button>
</div>
<div className={`${baseClass}__inputs`}>
<InputField
{...fields.destination_url}
placeholder="https://server.com/example"
label="Destination URL"
/>
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
isHtml
text={
"\
<center><p>Provide a URL to deliver <br/>the webhook request to.</p></center>\
"
}
/>
</div>
<div
className={`${baseClass}__inputs ${baseClass}__host-percentage`}
>
<Dropdown
{...fields.host_percentage}
label="Percentage of hosts"
options={percentageOfHosts}
/>
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
isHtml
text={
"\
<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>\
"
}
/>
</div>
<div className={`${baseClass}__inputs ${baseClass}__days-count`}>
<Dropdown
{...fields.days_count}
label="Number of days"
options={numberOfDays}
/>
</div>
<div className={`${baseClass}__details`}>
<IconToolTip
isHtml
text={
"\
<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>\
"
}
/>
</div>
</div>
<div className={`${baseClass}__section`}>
<h2>
<a id="usage-stats">Usage statistics</a>
</h2>
<p className={`${baseClass}__section-description`}>
Help improve Fleet by sending 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 />
<a
href="https://github.com/fleetdm/fleet/blob/2f42c281f98e39a72ab4a5125ecd26d303a16a6b/docs/1-Using-Fleet/11-Usage-statistics.md"
className={`${baseClass}__learn-more`}
target="_blank"
rel="noopener noreferrer"
>
Learn more about usage statistics&nbsp;
<img className="icon" src={OpenNewTabIcon} alt="open new tab" />
</a>
</p>
<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 className={`${baseClass}__section`}>
<h2>Advanced options</h2>
{renderAdvancedOptions()}
</div>
<Button type="submit" variant="brand">
Update settings
</Button>
</form>
{renderUsageStatsPreviewModal()}
{renderHostStatusWebhookPreviewModal()}
</>
);
}
}
export default Form(AppConfigForm, {
fields: formFields,
validate,
});