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]>
270 lines
8.5 KiB
TypeScript
270 lines
8.5 KiB
TypeScript
import type {RouteComponentProps} from 'react-router';
|
|
|
|
import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
|
|
import {Alert} from 'sentry/components/alert';
|
|
import AutoSelectText from 'sentry/components/autoSelectText';
|
|
import {Button} from 'sentry/components/button';
|
|
import Confirm from 'sentry/components/confirm';
|
|
import FieldGroup from 'sentry/components/forms/fieldGroup';
|
|
import ExternalLink from 'sentry/components/links/externalLink';
|
|
import LoadingIndicator from 'sentry/components/loadingIndicator';
|
|
import Panel from 'sentry/components/panels/panel';
|
|
import PanelBody from 'sentry/components/panels/panelBody';
|
|
import PanelHeader from 'sentry/components/panels/panelHeader';
|
|
import PluginList from 'sentry/components/pluginList';
|
|
import TextCopyInput from 'sentry/components/textCopyInput';
|
|
import {t, tct} from 'sentry/locale';
|
|
import type {Organization, Plugin, Project} from 'sentry/types';
|
|
import getDynamicText from 'sentry/utils/getDynamicText';
|
|
import routeTitleGen from 'sentry/utils/routeTitle';
|
|
import withPlugins from 'sentry/utils/withPlugins';
|
|
import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView';
|
|
import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
|
|
import TextBlock from 'sentry/views/settings/components/text/textBlock';
|
|
|
|
const TOKEN_PLACEHOLDER = 'YOUR_TOKEN';
|
|
const WEBHOOK_PLACEHOLDER = 'YOUR_WEBHOOK_URL';
|
|
import {hasEveryAccess} from 'sentry/components/acl/access';
|
|
|
|
type Props = {
|
|
organization: Organization;
|
|
plugins: {loading: boolean; plugins: Plugin[]};
|
|
project: Project;
|
|
} & RouteComponentProps<{projectId: string}, {}>;
|
|
|
|
type State = {
|
|
data: {
|
|
token: string;
|
|
webhookUrl: string;
|
|
} | null;
|
|
} & DeprecatedAsyncView['state'];
|
|
|
|
const placeholderData = {
|
|
token: TOKEN_PLACEHOLDER,
|
|
webhookUrl: WEBHOOK_PLACEHOLDER,
|
|
};
|
|
|
|
class ProjectReleaseTracking extends DeprecatedAsyncView<Props, State> {
|
|
getTitle() {
|
|
const {projectId} = this.props.params;
|
|
return routeTitleGen(t('Releases'), projectId, false);
|
|
}
|
|
|
|
getEndpoints(): ReturnType<DeprecatedAsyncView['getEndpoints']> {
|
|
const {organization} = this.props;
|
|
const {projectId} = this.props.params;
|
|
|
|
// Allow 403s
|
|
return [
|
|
[
|
|
'data',
|
|
`/projects/${organization.slug}/${projectId}/releases/token/`,
|
|
{},
|
|
{allowError: err => err && err.status === 403},
|
|
],
|
|
];
|
|
}
|
|
|
|
handleRegenerateToken = () => {
|
|
const {organization} = this.props;
|
|
const {projectId} = this.props.params;
|
|
this.api.request(`/projects/${organization.slug}/${projectId}/releases/token/`, {
|
|
method: 'POST',
|
|
data: {project: projectId},
|
|
success: data => {
|
|
this.setState({
|
|
data: {
|
|
token: data.token,
|
|
webhookUrl: data.webhookUrl,
|
|
},
|
|
});
|
|
addSuccessMessage(
|
|
t(
|
|
'Your deploy token has been regenerated. You will need to update any existing deploy hooks.'
|
|
)
|
|
);
|
|
},
|
|
error: () => {
|
|
addErrorMessage(t('Unable to regenerate deploy token, please try again'));
|
|
},
|
|
});
|
|
};
|
|
|
|
getReleaseWebhookIntructions() {
|
|
const {webhookUrl} = this.state.data || placeholderData;
|
|
return (
|
|
'curl ' +
|
|
webhookUrl +
|
|
' \\' +
|
|
'\n ' +
|
|
'-X POST \\' +
|
|
'\n ' +
|
|
"-H 'Content-Type: application/json' \\" +
|
|
'\n ' +
|
|
'-d \'{"version": "abcdefg"}\''
|
|
);
|
|
}
|
|
|
|
renderBody() {
|
|
const {organization, project, plugins} = this.props;
|
|
const hasWrite = hasEveryAccess(['project:write'], {organization, project});
|
|
|
|
if (plugins.loading) {
|
|
return <LoadingIndicator />;
|
|
}
|
|
|
|
const pluginList = plugins.plugins.filter(
|
|
(p: Plugin) => p.type === 'release-tracking' && p.hasConfiguration
|
|
);
|
|
|
|
let {token, webhookUrl} = this.state.data || placeholderData;
|
|
|
|
token = getDynamicText({value: token, fixed: '__TOKEN__'});
|
|
webhookUrl = getDynamicText({value: webhookUrl, fixed: '__WEBHOOK_URL__'});
|
|
|
|
return (
|
|
<div>
|
|
<SettingsPageHeader title={t('Release Tracking')} />
|
|
<TextBlock>
|
|
{t(
|
|
'Configure release tracking for this project to automatically record new releases of your application.'
|
|
)}
|
|
</TextBlock>
|
|
|
|
{!hasWrite && (
|
|
<Alert type="warning">
|
|
{t(
|
|
'You do not have sufficient permissions to access Release tokens, placeholders are displayed below.'
|
|
)}
|
|
</Alert>
|
|
)}
|
|
|
|
<Panel>
|
|
<PanelHeader>{t('Client Configuration')}</PanelHeader>
|
|
<PanelBody withPadding>
|
|
<p>
|
|
{tct(
|
|
'Start by binding the [release] attribute in your application, take a look at [link] to see how to configure this for the SDK you are using.',
|
|
{
|
|
link: (
|
|
<ExternalLink href="https://docs.sentry.io/platform-redirect/?next=/configuration/releases/">
|
|
our docs
|
|
</ExternalLink>
|
|
),
|
|
release: <code>release</code>,
|
|
}
|
|
)}
|
|
</p>
|
|
<p>
|
|
{t(
|
|
"This will annotate each event with the version of your application, as well as automatically create a release entity in the system the first time it's seen."
|
|
)}
|
|
</p>
|
|
<p>
|
|
{t(
|
|
'In addition you may configure a release hook (or use our API) to push a release and include additional metadata with it.'
|
|
)}
|
|
</p>
|
|
</PanelBody>
|
|
</Panel>
|
|
|
|
<Panel>
|
|
<PanelHeader>{t('Deploy Token')}</PanelHeader>
|
|
<PanelBody>
|
|
<FieldGroup
|
|
label={t('Token')}
|
|
help={t('A unique secret which is used to generate deploy hook URLs')}
|
|
>
|
|
<TextCopyInput>{token}</TextCopyInput>
|
|
</FieldGroup>
|
|
<FieldGroup
|
|
label={t('Regenerate Token')}
|
|
help={t(
|
|
'If a service becomes compromised, you should regenerate the token and re-configure any deploy hooks with the newly generated URL.'
|
|
)}
|
|
>
|
|
<div>
|
|
<Confirm
|
|
disabled={!hasWrite}
|
|
priority="danger"
|
|
onConfirm={this.handleRegenerateToken}
|
|
message={t(
|
|
'Are you sure you want to regenerate your token? Your current token will no longer be usable.'
|
|
)}
|
|
>
|
|
<Button priority="danger">{t('Regenerate Token')}</Button>
|
|
</Confirm>
|
|
</div>
|
|
</FieldGroup>
|
|
</PanelBody>
|
|
</Panel>
|
|
|
|
<Panel>
|
|
<PanelHeader>{t('Webhook')}</PanelHeader>
|
|
<PanelBody withPadding>
|
|
<p>
|
|
{t(
|
|
'If you simply want to integrate with an existing system, sometimes its easiest just to use a webhook.'
|
|
)}
|
|
</p>
|
|
|
|
<AutoSelectText>
|
|
<pre>{webhookUrl}</pre>
|
|
</AutoSelectText>
|
|
|
|
<p>
|
|
{t(
|
|
'The release webhook accepts the same parameters as the "Create a new Release" API endpoint.'
|
|
)}
|
|
</p>
|
|
|
|
{getDynamicText({
|
|
value: (
|
|
<AutoSelectText>
|
|
<pre>{this.getReleaseWebhookIntructions()}</pre>
|
|
</AutoSelectText>
|
|
),
|
|
fixed: (
|
|
<pre>
|
|
{`curl __WEBHOOK_URL__ \\
|
|
-X POST \\
|
|
-H 'Content-Type: application/json' \\
|
|
-d \'{"version": "abcdefg"}\'`}
|
|
</pre>
|
|
),
|
|
})}
|
|
</PanelBody>
|
|
</Panel>
|
|
|
|
<PluginList
|
|
organization={organization}
|
|
project={project}
|
|
pluginList={pluginList}
|
|
/>
|
|
|
|
<Panel>
|
|
<PanelHeader>{t('API')}</PanelHeader>
|
|
<PanelBody withPadding>
|
|
<p>
|
|
{t(
|
|
'You can notify Sentinel when you release new versions of your application via our HTTP API.'
|
|
)}
|
|
</p>
|
|
|
|
<p>
|
|
{tct('See the [link:releases documentation] for more information.', {
|
|
link: <ExternalLink href="https://docs.sentry.io/workflow/releases/" />,
|
|
})}
|
|
</p>
|
|
</PanelBody>
|
|
</Panel>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default withPlugins(ProjectReleaseTracking);
|
|
|
|
// Export for tests
|
|
export {ProjectReleaseTracking};
|