123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- import keyBy from 'lodash/keyBy';
- import {
- BooleanField,
- EmailField,
- NumberField,
- RadioBooleanField,
- TextField,
- } from 'sentry/components/forms';
- import {t, tct} from 'sentry/locale';
- import ConfigStore from 'sentry/stores/configStore';
- type Section = {
- key: string;
- heading?: string;
- };
- type Field = {
- key: string;
- label: React.ReactNode;
- allowEmpty?: boolean;
- component?: React.ComponentType<any>;
- defaultValue?: () => string | number | false;
- disabled?: boolean;
- disabledReason?: string;
- help?: React.ReactNode;
- max?: number;
- min?: number;
- noLabel?: string;
- placeholder?: string;
- required?: boolean;
- step?: number;
- yesFirst?: boolean;
- yesLabel?: string;
- };
- // This are ordered based on their display order visually
- const sections: Section[] = [
- {
- key: 'system',
- },
- {
- key: 'mail',
- heading: t('Outbound email'),
- },
- {
- key: 'auth',
- heading: t('Authentication'),
- },
- {
- key: 'beacon',
- heading: t('Beacon'),
- },
- ];
- const HIGH_THROUGHPUT_RATE_OPTION = {
- defaultValue: () => '0',
- component: NumberField,
- min: 0.0,
- max: 1.0,
- step: 0.0001,
- };
- const performanceOptionDefinitions: Field[] = [
- {
- key: 'performance.issues.all.problem-detection',
- label: t('Performance problem detection rate'),
- help: t(
- 'Controls the rate at which performance problems are detected across the entire system. A value of 0 will disable performance issue detection, and a value of 1.0 turns on detection for every ingested transaction.'
- ),
- ...HIGH_THROUGHPUT_RATE_OPTION,
- },
- {
- key: 'performance.issues.all.problem-creation',
- label: t('Performance problem creation rate'),
- help: t(
- 'Controls the rate at which performance issues are created across the entire system. A value of 0 will disable performance issue detection, and a value of 1.0 turns on creation for every detected performance problem.'
- ),
- ...HIGH_THROUGHPUT_RATE_OPTION,
- },
- {
- key: 'performance.issues.all.early-adopter-rollout',
- label: t('Performance issues creation EA Rollout'),
- help: t(
- 'Controls the rate at which performance issues are created for EA organizations.'
- ),
- ...HIGH_THROUGHPUT_RATE_OPTION,
- },
- {
- key: 'performance.issues.n_plus_one.problem-detection',
- label: t('N+1 detection rate'),
- help: t(
- 'Controls the rate at which performance problems are detected specifically for N+1 detection. Value of 0 will disable detection, a value of 1.0 fully enables it.'
- ),
- ...HIGH_THROUGHPUT_RATE_OPTION,
- },
- {
- key: 'performance.issues.n_plus_one.problem-creation',
- label: t('N+1 creation rate'),
- help: t(
- 'Controls the rate at which performance issues are created specifically for N+1 detection. Value of 0 will disable creation, a value of 1.0 fully enables it.'
- ),
- ...HIGH_THROUGHPUT_RATE_OPTION,
- },
- {
- key: 'performance.issues.n_plus_one_db.problem-detection',
- label: t('N+1 (DB) detection rate'),
- help: t(
- 'Controls the rate at which performance problems are detected specifically for N+1 detection. Value of 0 will disable detection, a value of 1.0 fully enables it.'
- ),
- ...HIGH_THROUGHPUT_RATE_OPTION,
- },
- {
- key: 'performance.issues.n_plus_one_db.problem-creation',
- label: t('N+1 (DB) creation rate'),
- help: t(
- 'Controls the rate at which performance issues are created specifically for N+1 detection. Value of 0 will disable creation, a value of 1.0 fully enables it.'
- ),
- ...HIGH_THROUGHPUT_RATE_OPTION,
- },
- {
- key: 'performance.issues.n_plus_one_db_ext.problem-creation',
- label: t('N+1 (DB) (Extended) creation rate'),
- help: t(
- 'Controls the rate at which performance issues are created specifically for N+1 detection (extended). Value of 0 will disable creation, a value of 1.0 fully enables it.'
- ),
- ...HIGH_THROUGHPUT_RATE_OPTION,
- },
- {
- key: 'performance.issues.n_plus_one_db.count_threshold',
- label: t('N+1 (DB) count threshold'),
- help: t(
- 'Detector threshold. Controls the number of spans required to trigger performance issues. This affects all organizations system-wide.'
- ),
- defaultValue: () => '5',
- component: NumberField,
- min: 0,
- max: Number.MAX_SAFE_INTEGER,
- step: 1,
- },
- {
- key: 'performance.issues.n_plus_one_db.duration_threshold', // TODO: For fixing typo later.
- label: t('N+1 (DB) duration threshold'),
- help: t(
- 'Detector threshold. Controls the threshold for the cumulative duration of involved spans required to trigger performance issues. This affects all organizations system-wide.'
- ),
- defaultValue: () => '100',
- component: NumberField,
- min: 0,
- max: Number.MAX_SAFE_INTEGER,
- step: 1,
- },
- ];
- // This are ordered based on their display order visually
- const definitions: Field[] = [
- {
- key: 'system.url-prefix',
- label: t('Root URL'),
- placeholder: 'https://sentry.example.com',
- help: t('The root web address which is used to communicate with the Sentry backend.'),
- defaultValue: () => `${document.location.protocol}//${document.location.host}`,
- },
- {
- key: 'system.admin-email',
- label: t('Admin Email'),
- placeholder: 'admin@example.com',
- help: t('The technical contact for this Sentry installation.'),
- // TODO(dcramer): this should not be hardcoded to a component
- component: EmailField,
- defaultValue: () => ConfigStore.get('user').email,
- },
- {
- key: 'system.support-email',
- label: t('Support Email'),
- placeholder: 'support@example.com',
- help: t('The support contact for this Sentry installation.'),
- // TODO(dcramer): this should not be hardcoded to a component
- component: EmailField,
- defaultValue: () => ConfigStore.get('user').email,
- },
- {
- key: 'system.security-email',
- label: t('Security Email'),
- placeholder: 'security@example.com',
- help: t('The security contact for this Sentry installation.'),
- // TODO(dcramer): this should not be hardcoded to a component
- component: EmailField,
- defaultValue: () => ConfigStore.get('user').email,
- },
- {
- key: 'system.rate-limit',
- label: t('Rate Limit'),
- placeholder: 'e.g. 500',
- help: t(
- 'The maximum number of events the system should accept per minute. A value of 0 will disable the default rate limit.'
- ),
- },
- {
- key: 'auth.allow-registration',
- label: t('Allow Registration'),
- help: t('Allow anyone to create an account and access this Sentry installation.'),
- component: BooleanField,
- defaultValue: () => false,
- },
- {
- key: 'auth.ip-rate-limit',
- label: t('IP Rate Limit'),
- placeholder: 'e.g. 10',
- help: t(
- 'The maximum number of times an authentication attempt may be made by a single IP address in a 60 second window.'
- ),
- },
- {
- key: 'auth.user-rate-limit',
- label: t('User Rate Limit'),
- placeholder: 'e.g. 10',
- help: t(
- 'The maximum number of times an authentication attempt may be made against a single account in a 60 second window.'
- ),
- },
- {
- key: 'api.rate-limit.org-create',
- label: 'Organization Creation Rate Limit',
- placeholder: 'e.g. 5',
- help: t(
- 'The maximum number of organizations which may be created by a single account in a one hour window.'
- ),
- },
- {
- key: 'beacon.anonymous',
- label: 'Usage Statistics',
- component: RadioBooleanField,
- // yes and no are inverted here due to the nature of this configuration
- noLabel: 'Send my contact information along with usage statistics',
- yesLabel: 'Please keep my usage information anonymous',
- yesFirst: false,
- help: tct(
- 'If enabled, any stats reported to sentry.io will exclude identifying information (such as your administrative email address). By anonymizing your installation the Sentry team will be unable to contact you about security updates. For more information on what data is sent to Sentry, see the [link:documentation].',
- {
- link: <a href="https://develop.sentry.dev/self-hosted/" />,
- }
- ),
- },
- {
- key: 'mail.from',
- label: t('Email From'),
- component: EmailField,
- defaultValue: () => `sentry@${document.location.hostname}`,
- help: t('Email address to be used in From for all outbound email.'),
- },
- {
- key: 'mail.host',
- label: t('SMTP Host'),
- placeholder: 'localhost',
- defaultValue: () => 'localhost',
- },
- {
- key: 'mail.port',
- label: t('SMTP Port'),
- placeholder: '25',
- defaultValue: () => '25',
- },
- {
- key: 'mail.username',
- label: t('SMTP Username'),
- defaultValue: () => '',
- },
- {
- key: 'mail.password',
- label: t('SMTP Password'),
- // TODO(mattrobenolt): We don't want to use a real password field unless
- // there's a way to reveal it. Without being able to see the password, it's
- // impossible to confirm if it's right.
- // component: PasswordField,
- defaultValue: () => '',
- },
- {
- key: 'mail.use-tls',
- label: t('Use STARTTLS? (exclusive with SSL)'),
- component: BooleanField,
- defaultValue: () => false,
- },
- {
- key: 'mail.use-ssl',
- label: t('Use SSL? (exclusive with STARTTLS)'),
- component: BooleanField,
- defaultValue: () => false,
- },
- ...performanceOptionDefinitions,
- ];
- const definitionsMap = keyBy(definitions, def => def.key);
- const disabledReasons = {
- diskPriority:
- 'This setting is defined in config.yml and may not be changed via the web UI.',
- smtpDisabled: 'SMTP mail has been disabled, so this option is unavailable',
- };
- export function getOption(option: string): Field {
- return definitionsMap[option];
- }
- export function getOptionDefault(option: string): string | number | false | undefined {
- const meta = getOption(option);
- return meta.defaultValue ? meta.defaultValue() : undefined;
- }
- function optionsForSection(section: Section) {
- return definitions.filter(option => option.key.split('.')[0] === section.key);
- }
- export function getOptionField(option: string, field: Field) {
- const meta = {...getOption(option), ...field};
- const Field = meta.component || TextField;
- return (
- <Field
- {...meta}
- name={option}
- key={option}
- defaultValue={getOptionDefault(option)}
- required={meta.required && !meta.allowEmpty}
- disabledReason={meta.disabledReason && disabledReasons[meta.disabledReason]}
- />
- );
- }
- function getSectionFieldSet(section: Section, fields: Field[]) {
- return (
- <fieldset key={section.key}>
- {section.heading && <legend>{section.heading}</legend>}
- {fields}
- </fieldset>
- );
- }
- export function getForm(fieldMap: Record<string, Field>) {
- const sets: React.ReactNode[] = [];
- for (const section of sections) {
- const set: Field[] = [];
- for (const option of optionsForSection(section)) {
- if (fieldMap[option.key]) {
- set.push(fieldMap[option.key]);
- }
- }
- if (set.length) {
- sets.push(getSectionFieldSet(section, set));
- }
- }
- return sets;
- }
|