Skip to content

Commit 5073e21

Browse files
dcramermattrobenolt
authored andcommitted
Some basic components for setup wizard
1 parent 53d4153 commit 5073e21

File tree

13 files changed

+275
-3
lines changed

13 files changed

+275
-3
lines changed

Diff for: src/sentry/api/endpoints/system_options.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from __future__ import absolute_import
2+
3+
from rest_framework.response import Response
4+
5+
from sentry import options
6+
from sentry.api.base import Endpoint
7+
from sentry.api.permissions import SuperuserPermission
8+
9+
10+
class SystemOptionsEndpoint(Endpoint):
11+
permission_classes = (SuperuserPermission,)
12+
13+
option_names = set(['system.url-prefix', 'system.admin-email'])
14+
15+
def get(self, request):
16+
results = {
17+
k: options.get(k)
18+
for k in self.option_names
19+
}
20+
21+
return Response(results)

Diff for: src/sentry/api/urls.py

+4
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
from .endpoints.release_file_details import ReleaseFileDetailsEndpoint
5959
from .endpoints.shared_group_details import SharedGroupDetailsEndpoint
6060
from .endpoints.system_health import SystemHealthEndpoint
61+
from .endpoints.system_options import SystemOptionsEndpoint
6162
from .endpoints.team_details import TeamDetailsEndpoint
6263
from .endpoints.team_groups_new import TeamGroupsNewEndpoint
6364
from .endpoints.team_groups_trending import TeamGroupsTrendingEndpoint
@@ -266,6 +267,9 @@
266267
url(r'^internal/health/$',
267268
SystemHealthEndpoint.as_view(),
268269
name='sentry-api-0-system-health'),
270+
url(r'^internal/options/$',
271+
SystemOptionsEndpoint.as_view(),
272+
name='sentry-api-0-system-options'),
269273
url(r'^internal/stats/$',
270274
InternalStatsEndpoint.as_view(),
271275
name='sentry-api-0-internal-stats'),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import InputField from './inputField';
2+
3+
export default class EmailField extends InputField {
4+
getType() {
5+
return 'email';
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from 'react';
2+
3+
class FormField extends React.Component {
4+
constructor(props) {
5+
super(props);
6+
this.state = {};
7+
}
8+
}
9+
10+
FormField.defaultProps = {
11+
onChange: (value) => {},
12+
};
13+
14+
export default FormField;
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export {default as EmailField} from './emailField';
2+
export {default as TextField} from './textField';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
import FormField from './formField';
3+
4+
export default class InputField extends FormField {
5+
constructor(props) {
6+
super(props);
7+
this.state.value = props.defaultValue || '';
8+
}
9+
10+
onChange(e) {
11+
this.setState({
12+
value: e.target.value,
13+
}, () => {
14+
this.props.onChange(this.state.value);
15+
});
16+
}
17+
18+
render() {
19+
return (
20+
<div className="control-group">
21+
<div className="controls">
22+
<label>{this.props.label}</label>
23+
<input type={this.getType()}
24+
className="form-control"
25+
placeholder={this.props.placeholder}
26+
onChange={this.onChange.bind(this)}
27+
value={this.state.value} />
28+
{this.props.help &&
29+
<p className="help-block">{this.props.help}</p>
30+
}
31+
</div>
32+
</div>
33+
);
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import InputField from './inputField';
2+
3+
export default class TextField extends InputField {
4+
getType() {
5+
return 'text';
6+
}
7+
}

Diff for: src/sentry/static/sentry/app/views/app.jsx

+14-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import React from 'react';
22
import ApiMixin from '../mixins/apiMixin';
33
import Alerts from '../components/alerts';
44
import AlertActions from '../actions/alertActions.jsx';
5+
import ConfigStore from '../stores/configStore';
56
import Indicators from '../components/indicators';
7+
import InstallWizard from './installWizard';
68
import LoadingIndicator from '../components/loadingIndicator';
79
import OrganizationStore from '../stores/organizationStore';
8-
import ConfigStore from '../stores/configStore';
910

1011
const App = React.createClass({
1112
mixins: [
@@ -59,6 +60,18 @@ const App = React.createClass({
5960
},
6061

6162
render() {
63+
let user = ConfigStore.get('user');
64+
let needsUpgrade = ConfigStore.get('needsUpgrade');
65+
66+
if (user.isSuperuser && needsUpgrade) {
67+
return (
68+
<div>
69+
<Indicators className="indicators-container" />
70+
<InstallWizard />
71+
</div>
72+
);
73+
}
74+
6275
if (this.state.loading) {
6376
return (
6477
<LoadingIndicator triangle={true}>

Diff for: src/sentry/static/sentry/app/views/installWizard.jsx

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import React from 'react';
2+
import DocumentTitle from 'react-document-title';
3+
4+
import {t} from '../locale';
5+
import api from '../api';
6+
import ConfigStore from '../stores/configStore';
7+
import LoadingIndicator from '../components/loadingIndicator';
8+
import {EmailField, TextField} from '../components/forms';
9+
10+
const InstallWizardSettings = React.createClass({
11+
getDefaultRootUrl() {
12+
return `${document.location.protocol}//${document.location.host}`;
13+
},
14+
15+
onFieldChange(name, value) {
16+
this.setState({[name]: value});
17+
},
18+
19+
render() {
20+
let options = this.props.options;
21+
let requiredOptions = ['system.url-perfix', 'system.admin-email'];
22+
let missingOptions = new Set(requiredOptions.filter(option => options[option]));
23+
let formValid = false;
24+
25+
return (
26+
<div>
27+
<p>Welcome to Sentry, yo! Complete setup by filling out the required
28+
configuration.</p>
29+
30+
{missingOptions.has('system.url-prefix') &&
31+
<TextField label={t('Root URL')} defaultValue={this.getDefaultRootUrl()}
32+
placeholder="https://sentry.example.com"
33+
help={t('The root web address which is used to communication with the Sentry backend.')}
34+
onChange={this.onFieldChange.bind(this, 'system.url-prefix')} />
35+
}
36+
37+
{missingOptions.has('system.admin-email') &&
38+
<EmailField label={t('Admin Email')}
39+
placeholder="[email protected]"
40+
help={t('The technical contact for this Sentry installation.')}
41+
onChange={this.onFieldChange.bind(this, 'system.admin-email')} />
42+
}
43+
44+
<div className="form-actions" style={{marginTop: 25}}>
45+
<button className="btn btn-primary"
46+
disabled={!formValid}>{t('Continue')}</button>
47+
</div>
48+
</div>
49+
);
50+
}
51+
});
52+
53+
const InstallWizard = React.createClass({
54+
getInitialState() {
55+
return {
56+
loading: true,
57+
error: false,
58+
options: {},
59+
};
60+
},
61+
62+
componentWillMount() {
63+
this.fetchData();
64+
},
65+
66+
remountComponent() {
67+
this.setState(this.getInitialState(), this.fetchData);
68+
},
69+
70+
fetchData(callback) {
71+
api.request('/internal/options/', {
72+
method: 'GET',
73+
success: (data) => {
74+
this.setState({
75+
options: data,
76+
loading: false,
77+
error: false
78+
});
79+
},
80+
error: () => {
81+
this.setState({
82+
loading: false,
83+
error: true
84+
});
85+
}
86+
});
87+
},
88+
89+
render() {
90+
let {error, loading, options} = this.state;
91+
let version = ConfigStore.get('version');
92+
return (
93+
<DocumentTitle title="Sentry Setup">
94+
<div className="app">
95+
<div className="container">
96+
<div className="setup-wizard">
97+
<h1>
98+
<span>{t('Welcome to Sentry')}</span>
99+
<small>{version.current}</small>
100+
</h1>
101+
{loading ?
102+
<LoadingIndicator>
103+
Please wait while we loading configuration.
104+
</LoadingIndicator>
105+
: (error ?
106+
<div className="loading-error">
107+
<span className="icon" />
108+
{t('We were unable to load the required configuration from the Sentry server. Please take a look at the service logs.')}
109+
</div>
110+
:
111+
<InstallWizardSettings options={options} />
112+
)}
113+
</div>
114+
</div>
115+
</div>
116+
</DocumentTitle>
117+
);
118+
}
119+
});
120+
121+
export default InstallWizard;

Diff for: src/sentry/static/sentry/less/sentry.less

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
@import url("./dashboard.less");
1111
@import url("./organization.less");
1212
@import url("./project-settings.less");
13+
@import url("./setup-wizard.less");
1314
@import url("./docs.less");
1415
@import url("./auth.less");
1516
@import url("./misc.less");

Diff for: src/sentry/static/sentry/less/setup-wizard.less

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.setup-wizard {
2+
.clearfix;
3+
max-width: 600px;
4+
margin: 30px auto;
5+
border: 1px solid @trim;
6+
padding: 20px 20px 0;
7+
border-radius: 4px;
8+
9+
h1 {
10+
.clearfix;
11+
line-height: 36px;
12+
13+
> span {
14+
display: block;
15+
float: left;
16+
}
17+
> small {
18+
float: right;
19+
display: block;
20+
font-size: 18px;
21+
line-height: inherit;
22+
}
23+
}
24+
25+
.loading {
26+
margin: 0 0 20px;
27+
}
28+
29+
.loading-error {
30+
.clearfix;
31+
margin: 0 0 20px;
32+
33+
.icon:before {
34+
float: left;
35+
display: block;
36+
font-size: 50px;
37+
margin-right: 10px;
38+
color: @red;
39+
content: "\e615";
40+
}
41+
}
42+
}

Diff for: src/sentry/static/sentry/less/shared-components.less

+1-1
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ table.integrations {
614614
}
615615

616616
.loading-message {
617-
margin-top: 15px;
617+
margin-top: 20px;
618618
text-align: center;
619619
}
620620
}

Diff for: src/sentry/templatetags/sentry_react.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,17 @@ def get_react_config(context):
5555
if features.has('auth:register', actor=user):
5656
enabled_features.append('auth:register')
5757

58+
version_info = _get_version_info()
59+
5860
context = {
5961
'singleOrganization': settings.SENTRY_SINGLE_ORGANIZATION,
6062
'urlPrefix': settings.SENTRY_URL_PREFIX,
61-
'version': _get_version_info(),
63+
'version': version_info,
6264
'features': enabled_features,
6365
'mediaUrl': get_asset_url('sentry', ''),
66+
# TODO(dcramer): we should confirm that no options need configured
67+
# when upgrading, and if so, we simply bump the version??
68+
'needsUpgrade': options.get('sentry:version-configured') != version_info['current'],
6469
'messages': [{
6570
'message': msg.message,
6671
'level': msg.tags,

0 commit comments

Comments
 (0)