mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
FSL-1.0-Apache-2.0 grants us the code, not the name. "Sentry" is Functional Software's trademark, so every place the running app says it to a user -- page titles, onboarding, empty states, toasts, settings copy, the self-hosted footer -- now says Sentinel. Attribution is the other half of the deal and stays untouched: upstream copyright headers, LICENSE, NOTICE. So does anything where the mark is a fact rather than a brand -- @sentry/* packages, sentry-cli, the wizard and bundler plugins, docs.sentry.io, the whole gettingStartedDocs tree. Renaming those would leave instructions that no longer work. Nothing structural moved. No import, identifier, route path, feature flag, API field or test id changed; the diff is 312 lines in and 312 out. Colocated specs were updated in lockstep with the copy they assert. Marketing and community links point at hanzo.ai, only where a real page exists to point at. Co-authored-by: Hanzo Dev <[email protected]>
238 lines
6.3 KiB
TypeScript
238 lines
6.3 KiB
TypeScript
import {Component, Fragment} from 'react';
|
|
import styled from '@emotion/styled';
|
|
import reduce from 'lodash/reduce';
|
|
import {computed, makeObservable} from 'mobx';
|
|
import {Observer} from 'mobx-react';
|
|
|
|
import type {FormProps} from 'sentry/components/forms/form';
|
|
import Form from 'sentry/components/forms/form';
|
|
import JsonForm from 'sentry/components/forms/jsonForm';
|
|
import FormModel from 'sentry/components/forms/model';
|
|
import type {JsonFormObject} from 'sentry/components/forms/types';
|
|
import List from 'sentry/components/list';
|
|
import ListItem from 'sentry/components/list/listItem';
|
|
import LoadingIndicator from 'sentry/components/loadingIndicator';
|
|
import PanelHeader from 'sentry/components/panels/panelHeader';
|
|
import Switch from 'sentry/components/switchButton';
|
|
import {Tooltip} from 'sentry/components/tooltip';
|
|
import {t, tn} from 'sentry/locale';
|
|
|
|
import FooterWithButtons from './components/footerWithButtons';
|
|
import HeaderWithHelp from './components/headerWithHelp';
|
|
|
|
const LAMBDA_COUNT_THRESHOLD = 10;
|
|
|
|
type LambdaFunction = {FunctionName: string; Runtime: string};
|
|
|
|
type Props = {
|
|
initialStepNumber: number;
|
|
lambdaFunctions: LambdaFunction[];
|
|
};
|
|
|
|
type State = {
|
|
submitting: boolean;
|
|
};
|
|
|
|
const getLabel = (func: LambdaFunction) => func.FunctionName;
|
|
|
|
export default class AwsLambdaFunctionSelect extends Component<Props, State> {
|
|
constructor(props: Props) {
|
|
super(props);
|
|
makeObservable(this, {allStatesToggled: computed});
|
|
}
|
|
state: State = {
|
|
submitting: false,
|
|
};
|
|
|
|
model = new FormModel();
|
|
|
|
get initialData() {
|
|
const {lambdaFunctions} = this.props;
|
|
const initialData = lambdaFunctions.reduce((accum, func) => {
|
|
accum[func.FunctionName] = true;
|
|
return accum;
|
|
}, {});
|
|
return initialData;
|
|
}
|
|
|
|
get lambdaFunctions() {
|
|
return this.props.lambdaFunctions.sort((a, b) =>
|
|
getLabel(a).toLowerCase() < getLabel(b).toLowerCase() ? -1 : 1
|
|
);
|
|
}
|
|
|
|
get enabledCount() {
|
|
const data = this.model.getTransformedData();
|
|
return reduce(data, (acc: number, val: boolean) => (val ? acc + 1 : acc), 0);
|
|
}
|
|
|
|
get allStatesToggled() {
|
|
// check if any of the lambda functions have a falsy value
|
|
// no falsy values means everything is enabled
|
|
return Object.values(this.model.getData()).every(val => val);
|
|
}
|
|
|
|
get formFields() {
|
|
const data = this.model.getTransformedData();
|
|
return Object.entries(data).map(([name, value]) => ({name, value}));
|
|
}
|
|
|
|
handleSubmit = () => {
|
|
this.setState({submitting: true});
|
|
};
|
|
|
|
handleToggle = () => {
|
|
const newState = !this.allStatesToggled;
|
|
this.lambdaFunctions.forEach(lambda => {
|
|
this.model.setValue(lambda.FunctionName, newState, {quiet: true});
|
|
});
|
|
};
|
|
|
|
renderWhatWeFound = () => {
|
|
const count = this.lambdaFunctions.length;
|
|
return (
|
|
<h4>
|
|
{tn(
|
|
'We found %s function with a Node or Python runtime',
|
|
'We found %s functions with Node or Python runtimes',
|
|
count
|
|
)}
|
|
</h4>
|
|
);
|
|
};
|
|
|
|
renderLoadingScreen = () => {
|
|
const count = this.enabledCount;
|
|
const text =
|
|
count > LAMBDA_COUNT_THRESHOLD
|
|
? t('This might take a while\u2026', count)
|
|
: t('This might take a sec\u2026');
|
|
return (
|
|
<LoadingWrapper>
|
|
<StyledLoadingIndicator />
|
|
<h4>{t('Adding Sentinel to %s functions', count)}</h4>
|
|
{text}
|
|
</LoadingWrapper>
|
|
);
|
|
};
|
|
|
|
renderCore = () => {
|
|
const {initialStepNumber} = this.props;
|
|
|
|
const FormHeader = (
|
|
<StyledPanelHeader>
|
|
{t('Lambda Functions')}
|
|
<SwitchHolder>
|
|
<Observer>
|
|
{() => (
|
|
<Tooltip
|
|
title={this.allStatesToggled ? t('Disable All') : t('Enable All')}
|
|
position="left"
|
|
>
|
|
<StyledSwitch
|
|
size="lg"
|
|
name="toggleAll"
|
|
toggle={this.handleToggle}
|
|
isActive={this.allStatesToggled}
|
|
/>
|
|
</Tooltip>
|
|
)}
|
|
</Observer>
|
|
</SwitchHolder>
|
|
</StyledPanelHeader>
|
|
);
|
|
|
|
const formFields: JsonFormObject = {
|
|
fields: this.lambdaFunctions.map(func => ({
|
|
name: func.FunctionName,
|
|
type: 'boolean',
|
|
required: false,
|
|
label: getLabel(func),
|
|
alignRight: true,
|
|
})),
|
|
};
|
|
|
|
return (
|
|
<List symbol="colored-numeric" initialCounterValue={initialStepNumber}>
|
|
<ListItem>
|
|
<Header>{this.renderWhatWeFound()}</Header>
|
|
{t('Decide which functions you would like to enable for Sentinel monitoring')}
|
|
<StyledForm
|
|
initialData={this.initialData}
|
|
model={this.model}
|
|
apiEndpoint="/extensions/aws_lambda/setup/"
|
|
hideFooter
|
|
preventFormResetOnUnmount
|
|
>
|
|
<JsonForm renderHeader={() => FormHeader} forms={[formFields]} />
|
|
</StyledForm>
|
|
</ListItem>
|
|
<Fragment />
|
|
</List>
|
|
);
|
|
};
|
|
|
|
render() {
|
|
return (
|
|
<Fragment>
|
|
<HeaderWithHelp docsUrl="https://docs.sentry.io/product/integrations/cloud-monitoring/aws-lambda/" />
|
|
<Wrapper>
|
|
{this.state.submitting ? this.renderLoadingScreen() : this.renderCore()}
|
|
</Wrapper>
|
|
<Observer>
|
|
{() => (
|
|
<FooterWithButtons
|
|
formProps={{
|
|
action: '/extensions/aws_lambda/setup/',
|
|
method: 'post',
|
|
onSubmit: this.handleSubmit,
|
|
}}
|
|
formFields={this.formFields}
|
|
buttonText={t('Finish Setup')}
|
|
disabled={
|
|
this.model.isError || this.model.isSaving || this.state.submitting
|
|
}
|
|
/>
|
|
)}
|
|
</Observer>
|
|
</Fragment>
|
|
);
|
|
}
|
|
}
|
|
|
|
const Wrapper = styled('div')`
|
|
padding: 100px 50px 50px 50px;
|
|
`;
|
|
|
|
// TODO(ts): Understand why styled is not correctly inheriting props here
|
|
const StyledForm = styled(Form)<FormProps>`
|
|
margin-top: 10px;
|
|
`;
|
|
|
|
const Header = styled('div')`
|
|
text-align: left;
|
|
margin-bottom: 10px;
|
|
`;
|
|
|
|
const LoadingWrapper = styled('div')`
|
|
padding: 50px;
|
|
text-align: center;
|
|
`;
|
|
|
|
const StyledLoadingIndicator = styled(LoadingIndicator)`
|
|
margin: 0;
|
|
`;
|
|
|
|
const SwitchHolder = styled('div')`
|
|
display: flex;
|
|
`;
|
|
|
|
const StyledSwitch = styled(Switch)`
|
|
margin: auto;
|
|
`;
|
|
|
|
// padding is based on fom control width
|
|
const StyledPanelHeader = styled(PanelHeader)`
|
|
padding-right: 36px;
|
|
`;
|