Fuel Welcome page

Closes-Bug: #1377834

Related-Bug: #1377846

Change-Id: I9cbd77166ea49a0e553d89dade887236e6753898
This commit is contained in:
Julia Aranovich 2014-10-22 15:05:52 +04:00 committed by Alexander Kislitsky
parent 339834b01c
commit d1f3c52411
12 changed files with 883 additions and 105 deletions

View File

@ -2,21 +2,54 @@
- pk: 1
model: "nailgun.master_node_settings"
fields:
master_node_uid: "62410d05-ecbd-4912-83fe-5db51ebb273e"
settings:
send_anonymous_statistic:
type: "checkbox"
value: true
send_user_info:
type: "checkbox"
value: true
user_info:
name:
type: "text"
value: "Test User"
email:
type: "text"
value: "test@email.com"
company:
type: "text"
value: "Test Company"
master_node_uid: "62410d05-ecbd-4912-83fe-5db51ebb273e"
settings:
statistics:
send_anonymous_statistic:
type: "checkbox"
value: true
label: "statistics.setting_labels.send_anonymous_statistic"
weight: 10
send_user_info:
type: "checkbox"
value: true
label: "statistics.setting_labels.send_user_info"
weight: 20
restrictions:
- "fuel_settings:statistics.send_anonymous_statistic.value == false"
- condition: &commumity_iso "not ('mirantis' in version:feature_groups)"
action: "hide"
name:
type: "text"
value: ""
label: "statistics.setting_labels.name"
weight: 30
regex:
source: &non_empty_string '\S'
error: "statistics.errors.name"
restrictions: &user_info_restrictions
- "fuel_settings:statistics.send_anonymous_statistic.value == false"
- "fuel_settings:statistics.send_user_info.value == false"
- condition: *commumity_iso
action: "hide"
email:
type: "text"
value: ""
label: "statistics.setting_labels.email"
weight: 40
regex:
source: *non_empty_string
error: "statistics.errors.email"
restrictions: *user_info_restrictions
company:
type: "text"
value: ""
label: "statistics.setting_labels.company"
weight: 50
regex:
source: *non_empty_string
error: "statistics.errors.company"
restrictions: *user_info_restrictions
user_choice_saved:
type: "hidden"
value: false

View File

@ -35,21 +35,7 @@ class MasterNodeSettings(NailgunObject):
"description": "Serialized ActionLog object",
"type": "object",
"properties": {
"settings": {
"type": "object",
"properties": {
"send_anonymous_statistic": {"type": "object"},
"send_user_info": {"type": "object"},
"user_info": {
"type": "object",
"properties": {
"name": {"type": "object"},
"company": {"type": "object"},
"email": {"type": "object"}
}
}
}
}
"settings": {"type": "object"}
}
}

View File

@ -27,26 +27,95 @@ class TestMasterNodeSettingsHandler(BaseIntegrationTest):
# fixture file which is located in fixtures directory for nailgun
expected = {
"settings": {
"send_anonymous_statistic": {
"type": "checkbox",
"value": True
},
"send_user_info": {
"type": "checkbox",
"value": True
},
"user_info": {
"statistics": {
"send_anonymous_statistic": {
"type": "checkbox",
"value": True,
"label": "statistics.setting_labels."
"send_anonymous_statistic",
"weight": 10
},
"send_user_info": {
"type": "checkbox",
"value": True,
"label": "statistics.setting_labels.send_user_info",
"weight": 20,
"restrictions": [
"fuel_settings:statistics."
"send_anonymous_statistic.value == false",
{
"condition":
"not ('mirantis' in version:feature_groups)",
"action": "hide"
}
]
},
"name": {
"type": "text",
"value": "Test User"
},
"company": {
"type": "text",
"value": "Test Company"
"value": "",
"label": "statistics.setting_labels.name",
"weight": 30,
"regex": {
"source": "\S",
"error": "statistics.errors.name"
},
"restrictions": [
"fuel_settings:statistics."
"send_anonymous_statistic.value == false",
"fuel_settings:statistics."
"send_user_info.value == false",
{
"condition":
"not ('mirantis' in version:feature_groups)",
"action": "hide"
}
]
},
"email": {
"type": "text",
"value": "test@email.com"
"value": "",
"label": "statistics.setting_labels.email",
"weight": 40,
"regex": {
"source": "\S",
"error": "statistics.errors.email"
},
"restrictions": [
"fuel_settings:statistics."
"send_anonymous_statistic.value == false",
"fuel_settings:statistics."
"send_user_info.value == false",
{
"condition":
"not ('mirantis' in version:feature_groups)",
"action": "hide"
}
]
},
"company": {
"type": "text",
"value": "",
"label": "statistics.setting_labels.company",
"weight": 50,
"regex": {
"source": "\S",
"error": "statistics.errors.company"
},
"restrictions": [
"fuel_settings:statistics."
"send_anonymous_statistic.value == false",
"fuel_settings:statistics."
"send_user_info.value == false",
{
"condition":
"not ('mirantis' in version:feature_groups)",
"action": "hide"
}
]
},
"user_choice_saved": {
"type": "hidden",
"value": False
}
}
}
@ -61,26 +130,95 @@ class TestMasterNodeSettingsHandler(BaseIntegrationTest):
def test_put_controller(self):
data = {
"settings": {
"send_anonymous_statistic": {
"type": "checkbox",
"value": False
},
"send_user_info": {
"type": "checkbox",
"value": True
},
"user_info": {
"statistics": {
"send_anonymous_statistic": {
"type": "checkbox",
"value": False,
"label": "statistics.setting_labels."
"send_anonymous_statistic",
"weight": 10
},
"send_user_info": {
"type": "checkbox",
"value": True,
"label": "statistics.setting_labels.send_user_info",
"weight": 20,
"restrictions": [
"fuel_settings:statistics."
"send_anonymous_statistic.value == false",
{
"condition":
"not ('mirantis' in version:feature_groups)",
"action": "hide"
}
]
},
"name": {
"type": "text",
"value": "Some User"
},
"company": {
"type": "text",
"value": "Some Company"
"value": "Some User",
"label": "statistics.setting_labels.name",
"weight": 30,
"regex": {
"source": "\S",
"error": "statistics.errors.name"
},
"restrictions": [
"fuel_settings:statistics."
"send_anonymous_statistic.value == false",
"fuel_settings:statistics."
"send_user_info.value == false",
{
"condition":
"not ('mirantis' in version:feature_groups)",
"action": "hide"
}
]
},
"email": {
"type": "text",
"value": "user@email.com"
"value": "user@email.com",
"label": "statistics.setting_labels.email",
"weight": 40,
"regex": {
"source": "\S",
"error": "statistics.errors.email"
},
"restrictions": [
"fuel_settings:statistics."
"send_anonymous_statistic.value == false",
"fuel_settings:statistics."
"send_user_info.value == false",
{
"condition":
"not ('mirantis' in version:feature_groups)",
"action": "hide"
}
]
},
"company": {
"type": "text",
"value": "Some Company",
"label": "statistics.setting_labels.company",
"weight": 50,
"regex": {
"source": "\S",
"error": "statistics.errors.company"
},
"restrictions": [
"fuel_settings:statistics."
"send_anonymous_statistic.value == false",
"fuel_settings:statistics."
"send_user_info.value == false",
{
"condition":
"not ('mirantis' in version:feature_groups)",
"action": "hide"
}
]
},
"user_choice_saved": {
"type": "hidden",
"value": True
}
}
}
@ -102,7 +240,7 @@ class TestMasterNodeSettingsHandler(BaseIntegrationTest):
def test_patch_controller(self):
data = {
"settings": {
"user_info": {
"statistics": {
"company": {
"value": "Other Company"
}
@ -112,26 +250,95 @@ class TestMasterNodeSettingsHandler(BaseIntegrationTest):
expected = {
"settings": {
"send_anonymous_statistic": {
"type": "checkbox",
"value": True
},
"send_user_info": {
"type": "checkbox",
"value": True
},
"user_info": {
"statistics": {
"send_anonymous_statistic": {
"type": "checkbox",
"value": True,
"label": "statistics.setting_labels."
"send_anonymous_statistic",
"weight": 10
},
"send_user_info": {
"type": "checkbox",
"value": True,
"label": "statistics.setting_labels.send_user_info",
"weight": 20,
"restrictions": [
"fuel_settings:statistics."
"send_anonymous_statistic.value == false",
{
"condition":
"not ('mirantis' in version:feature_groups)",
"action": "hide"
}
]
},
"name": {
"type": "text",
"value": "Test User"
},
"company": {
"type": "text",
"value": "Other Company"
"value": "",
"label": "statistics.setting_labels.name",
"weight": 30,
"regex": {
"source": "\S",
"error": "statistics.errors.name"
},
"restrictions": [
"fuel_settings:statistics."
"send_anonymous_statistic.value == false",
"fuel_settings:statistics."
"send_user_info.value == false",
{
"condition":
"not ('mirantis' in version:feature_groups)",
"action": "hide"
}
]
},
"email": {
"type": "text",
"value": "test@email.com"
"value": "",
"label": "statistics.setting_labels.email",
"weight": 40,
"regex": {
"source": "\S",
"error": "statistics.errors.email"
},
"restrictions": [
"fuel_settings:statistics."
"send_anonymous_statistic.value == false",
"fuel_settings:statistics."
"send_user_info.value == false",
{
"condition":
"not ('mirantis' in version:feature_groups)",
"action": "hide"
}
]
},
"company": {
"type": "text",
"value": "Other Company",
"label": "statistics.setting_labels.company",
"weight": 50,
"regex": {
"source": "\S",
"error": "statistics.errors.company"
},
"restrictions": [
"fuel_settings:statistics."
"send_anonymous_statistic.value == false",
"fuel_settings:statistics."
"send_user_info.value == false",
{
"condition":
"not ('mirantis' in version:feature_groups)",
"action": "hide"
}
]
},
"user_choice_saved": {
"type": "hidden",
"value": False
}
}
}
@ -153,9 +360,7 @@ class TestMasterNodeSettingsHandler(BaseIntegrationTest):
def test_validation_error(self):
data = {
"settings": {
"send_user_info": "I'm not an object, bro:)"
}
"settings": "I'm not an object, bro:)"
}
resp = self.app.put(

View File

@ -3593,3 +3593,101 @@ i.btn-cluster-details {
}
}
}
/** Fuel Welcome page **/
.welcome-page {
.width-height-mixin;
.box-sizing-mixin;
padding: 40px;
color: #4d545b;
> div {
width: 800px;
background-color: @fuel-white-color;
margin: 0 auto;
padding: 30px;
padding-bottom: 10px;
.box-sizing-mixin;
.border-radius-mixin(10px);
.center { text-align: center; }
h2 {
line-height: 40px;
.font-layout-mixin(36px, #4d545b);
.indent-mixin();
margin-bottom: 20px;
}
.welcome-checkbox-box {
text-align: center;
margin-bottom: 16px;
padding-top: 4px;
div {
display: inline-block;
margin: 0 auto;
}
/* overrides for reusable inputs */
.parameter-name, .custom-tumbler {
float: none;
font-weight: normal;
}
.label-wrapper { .position(relative, @top: -7px, @left: 3px); }
}
.welcome-form-box {
margin-bottom: 15px;
.welcome-form-item {
display: inline-block;
margin-left: 175px;
font-weight: normal;
.input-label { width: 80px; }
}
}
.welcome-form-error {
text-align: center;
color: @fuel-deep-red;
margin-bottom: 20px;
}
.welcome-button-box {
text-align: center;
button { .width-height-mixin(230px, 44px); }
}
}
}
.statistics-settings {
margin-bottom: 10px;
/* overrides for reusable inputs */
.checkbox-or-radio .parameter-name { float: none; }
.parameter-name {
font-weight: normal;
margin: 0;
.label-wrapper { .position(relative, @top: 7px); }
padding: 4px;
}
.parameter-description { margin-top: 10px; }
.input-label { width: 80px; }
}
.statistics-text-box {
width: 100%;
margin-bottom: 20px;
.font-layout-mixin(14px, #4d545b, lighter);
a { cursor: pointer; }
p {
text-align: justify;
margin: 0px;
margin-bottom: 20px;
}
}
.statistics-disclaimer-box {
background-color: #ecf0f2;
border: 1px solid #c9cdce;
overflow-y: auto;
padding: 20px;
margin-bottom: 16px;
line-height: 16px;
.width-height-mixin(100%, 300px);
.font-layout-mixin(14px, #5b636b, lighter);
.box-sizing-mixin;
p { margin-bottom: 10px; }
ul { margin-bottom: 20px; }
}

View File

@ -79,6 +79,70 @@
"log_in": "Log in",
"title": "Log In"
},
"statistics": {
"help_to_improve": "Help us to improve your experience by sending Mirantis information about the settings, features, and deployment actions when you use Mirantis OpenStack.",
"statistics_includes": "Usage statistics include information such as settings, button/menu clicks, hardware configuration, and version information. The usage statistics do not include information such as passwords, ip addresses, or node names. For a complete list of statistics that we gather, ",
"click_here": "click here",
"privacy_policy": "Mirantis privacy policy (\"Privacy Policy\") describes our practices regarding the information we collect on the Mirantis web sites and through the use of our products and services, and how it is used and shared with third parties. You can read the policy ",
"privacy_policy_link": "here",
"statistics_includes_full": "Usage statistics include the following. Information such as passwords, ip addresses, and node names are not included.",
"actions_title": "Actions:",
"actions": {
"operation_type": "Operation type (adding cluster, adding node, deployment, removing node, e.t.c.)",
"operation_time": "Operation start and finish time",
"actual_time": "Actual time that is took to complete the operation",
"network_verification": "Network verification - whether it was used, and what was the result",
"ostf_results": "Is OSTF used, and tests results"
},
"settings_title": "Environment Settings:",
"settings": {
"envronments_amount": "Number of environments",
"nistribution": "Distribution / OS",
"network_type": "Network type",
"kernel_parameters": "Kernel parameters",
"admin_network_parameters": "Admin network parameters",
"pxe_parameters": "PXE parameters",
"dns_parameters": "DNS parameters",
"storage_options": "Storage options",
"related_projects": "Related Projects",
"modified_settings": "Settings modified on Settings tab",
"networking_configuration": "Networking configuration"
},
"node_settings_title": "Node Settings:",
"node_settings": {
"deployed_nodes_amount": "Number of nodes deployed",
"deployed_roles": "Roles deployed to each node",
"disk_layout": "Disk layout",
"interfaces_configuration": "Interfaces configuration"
},
"system_info_title": "System Info:",
"system_info": {
"hypervisor": "Hypervisor",
"hardware_info": "Hardware info",
"fuel_version": "Fuel version info",
"openstack_version": "OpenStack version info"
},
"setting_labels": {
"send_anonymous_statistic": "Send usage statistics to Mirantis",
"send_user_info": "Identify my error reports so that Mirantis Support can assist me",
"name": "Name",
"email": "Email",
"company": "Company"
},
"errors": {
"name": "Please fill your name",
"email": "Please fill email address",
"company": "Please fill company name"
}
},
"welcome_page": {
"title": "Welcome to Mirantis OpenStack",
"support": "Our support team can optionally use your error reports to assist you.",
"provide_contacts": "Please provide your contact information below.",
"start_fuel": "Start Using Fuel",
"change_settings": "You can change your settings at any time by updating your user profile information.",
"thanks": "Thanks for helping to improve Mirantis OpenStack!"
},
"navbar": {
"environments": "Environments",
"releases": "Releases",
@ -119,7 +183,9 @@
"diagnostic_snapshot": "Diagnostic Snapshot",
"capacity_audit": "Capacity Audit",
"capacity_audit_text": "To better manage your resources, you can run this report to find out what OpenStack roles have been deployed across all of your environments.",
"view_capacity_audit": "View Capacity Audit"
"view_capacity_audit": "View Capacity Audit",
"send_statistics_title": "Send Statistics About Usage",
"save_changes": "Save Changes"
},
"release_page": {
"title": "Releases",

View File

@ -25,6 +25,7 @@ define(
'keystone_client',
'views/common',
'jsx!views/login_page',
'jsx!views/welcome_page',
'views/cluster_page',
'views/cluster_page_tabs/nodes_tab',
'jsx!views/clusters_page',
@ -33,13 +34,14 @@ define(
'jsx!views/support_page',
'jsx!views/capacity_page'
],
function(React, utils, layoutComponents, Coccyx, coccyxMixins, models, KeystoneClient, commonViews, LoginPage, ClusterPage, NodesTab, ClustersPage, ReleasesPage, NotificationsPage, SupportPage, CapacityPage) {
function(React, utils, layoutComponents, Coccyx, coccyxMixins, models, KeystoneClient, commonViews, LoginPage, WelcomePage, ClusterPage, NodesTab, ClustersPage, ReleasesPage, NotificationsPage, SupportPage, CapacityPage) {
'use strict';
var AppRouter = Backbone.Router.extend({
routes: {
login: 'login',
logout: 'logout',
welcome: 'welcome',
clusters: 'listClusters',
'cluster/:id': 'showCluster',
'cluster/:id/:tab(/:opt1)(/:opt2)': 'showClusterTab',
@ -72,8 +74,10 @@ function(React, utils, layoutComponents, Coccyx, coccyxMixins, models, KeystoneC
cacheTokenFor: 10 * 60 * 1000,
tenant: 'admin'
});
var version = this.version = new models.FuelVersion();
this.settings = new models.FuelSettings();
var version = this.version = new models.FuelVersion();
version.fetch().then(_.bind(function() {
this.user = new models.User({authenticated: !version.get('auth_required')});
@ -179,7 +183,7 @@ function(React, utils, layoutComponents, Coccyx, coccyxMixins, models, KeystoneC
this.page = utils.universalMount(new NewPage(options), this.content);
this.navbar.setActive(_.result(this.page, 'navbarActiveElement'));
this.updateTitle();
this.toggleElements(NewPage != LoginPage);
this.toggleElements(!this.page.hiddenLayout);
},
// routes
login: function() {
@ -198,6 +202,9 @@ function(React, utils, layoutComponents, Coccyx, coccyxMixins, models, KeystoneC
app.navigate('#login', {trigger: true, replace: true});
});
},
welcome: function() {
this.setPage(WelcomePage, {settings: this.settings});
},
showCluster: function(id) {
this.navigate('#cluster/' + id + '/nodes', {trigger: true, replace: true});
},
@ -286,7 +293,7 @@ function(React, utils, layoutComponents, Coccyx, coccyxMixins, models, KeystoneC
showSupportPage: function() {
var tasks = new models.Tasks();
tasks.fetch().done(_.bind(function() {
this.setPage(SupportPage, {tasks: tasks});
this.setPage(SupportPage, {tasks: tasks, settings: this.settings});
}, this));
},
showCapacityPage: function() {

View File

@ -352,6 +352,70 @@ define(['utils', 'deepModel'], function(utils) {
});
_.extend(models.Settings.prototype, cacheMixin);
models.FuelSettings = Backbone.DeepModel.extend({
constructorName: 'FuelSettings',
url: '/api/settings',
cacheFor: 60 * 1000,
isNew: function() {
return false;
},
parse: function(response) {
return response.settings;
},
toJSON: function(options) {
return {settings: this.constructor.__super__.toJSON.call(this, options)};
},
expandRestrictions: function(restrictions, path) {
if (_.isUndefined(this.expandedRestrictions)) this.expandedRestrictions = {};
if (restrictions && restrictions.length) {
var result = _.map(restrictions, utils.expandRestriction, this);
if (path) {
this.expandedRestrictions[path] = result;
} else {
this.expandedRestrictions = result;
}
}
},
processRestrictions: function() {
_.each(this.attributes, function(group, groupName) {
if (group.metadata) this.expandRestrictions(group.metadata.restrictions, groupName + '.metadata');
_.each(group, function(setting, settingName) {
this.expandRestrictions(setting.restrictions, this.makePath(groupName, settingName));
_.each(setting.values, function(value) {
this.expandRestrictions(value.restrictions, this.makePath(groupName, settingName, value.data));
}, this);
}, this);
}, this);
},
checkRestrictions: function(models, action, path) {
var restrictions = path ? this.expandedRestrictions[path] : this.expandedRestrictions;
if (action) restrictions = _.where(restrictions, {action: action});
return _.any(restrictions, function(restriction) {
return utils.evaluateExpression(restriction.condition, models).value;
});
},
validate: function(attrs, options) {
var errors = {},
models = options ? options.models : {},
checkRestrictions = _.bind(function(path) {
return this.checkRestrictions(models, null, path);
}, this);
_.each(attrs, function(group, groupName) {
if (checkRestrictions(this.makePath(groupName, 'metadata'))) return;
_.each(group, function(setting, settingName) {
var path = this.makePath(groupName, settingName);
if (!setting.regex || !setting.regex.source || checkRestrictions(path)) return;
if (!setting.value.match(new RegExp(setting.regex.source))) errors[path] = setting.regex.error;
}, this);
}, this);
return _.isEmpty(errors) ? null : errors;
},
makePath: function() {
return _.toArray(arguments).join('.');
}
});
_.extend(models.FuelSettings.prototype, cacheMixin);
models.Disk = Backbone.Model.extend({
constructorName: 'Disk',
urlRoot: '/api/nodes/',

View File

@ -51,11 +51,13 @@ define(['jquery', 'underscore', 'react'], function($, _, React) {
label: React.PropTypes.renderable,
description: React.PropTypes.renderable,
disabled: React.PropTypes.bool,
inputClassName: React.PropTypes.renderable,
wrapperClassName: React.PropTypes.renderable,
labelClassName: React.PropTypes.renderable,
descriptionClassName: React.PropTypes.renderable,
tooltipText: React.PropTypes.renderable,
toggleable: React.PropTypes.bool
toggleable: React.PropTypes.bool,
onChange: React.PropTypes.func
},
getInitialState: function() {
return {visible: false};
@ -68,25 +70,28 @@ define(['jquery', 'underscore', 'react'], function($, _, React) {
return this.props.type == 'radio' || this.props.type == 'checkbox';
},
onChange: function() {
var input = this.refs.input.getDOMNode();
return this.props.onChange(this.props.name, this.props.type == 'checkbox' ? input.checked : input.value);
if (this.props.onChange) {
var input = this.refs.input.getDOMNode();
return this.props.onChange(this.props.name, this.props.type == 'checkbox' ? input.checked : input.value);
}
},
renderInput: function() {
var input = null,
className = 'parameter-input';
classes = {'parameter-input': true};
classes[this.props.inputClassName] = this.props.inputClassName;
switch (this.props.type) {
case 'select':
input = (<select ref='input' key='input' className={className} onChange={this.onChange}>{this.props.children}</select>);
input = (<select ref='input' key='input' className={cx(classes)} onChange={this.onChange}>{this.props.children}</select>);
break;
case 'textarea':
input = <textarea ref='input' key='input' className={className} onChange={this.onChange} />;
input = <textarea ref='input' key='input' className={cx(classes)} onChange={this.onChange} />;
break;
case 'password':
var type = (this.props.toggleable && this.state.visible) ? 'text' : 'password';
input = <input ref='input' key='input' className={className} type={type} onChange={this.onChange} />;
input = <input ref='input' key='input' className={cx(classes)} type={type} onChange={this.onChange} />;
break;
default:
input = <input ref='input' key='input' className={className} onChange={this.onChange} value={this.props.value} />;
input = <input ref='input' key='input' className={cx(classes)} onChange={this.onChange} value={this.props.value} />;
}
return this.isCheckboxOrRadio() ? (
<div key='input-wrapper' className='custom-tumbler'>
@ -137,11 +142,13 @@ define(['jquery', 'underscore', 'react'], function($, _, React) {
) : null;
},
renderWrapper: function(children) {
var classes = {
'parameter-box': true,
clearfix: !this.isCheckboxOrRadio(),
'has-error': !_.isUndefined(this.props.error) && !_.isNull(this.props.error)
};
var isCheckboxOrRadio = this.isCheckboxOrRadio(),
classes = {
'parameter-box': true,
'checkbox-or-radio': isCheckboxOrRadio,
clearfix: !isCheckboxOrRadio,
'has-error': !_.isUndefined(this.props.error) && !_.isNull(this.props.error)
};
classes[this.props.wrapperClassName] = this.props.wrapperClassName;
return (<div className={cx(classes)}>{children}</div>);
},

View File

@ -27,6 +27,7 @@ function($, React, controls) {
LoginPage = React.createClass({
breadcrumbsPath: [],
hiddenLayout: true,
propTypes: {
app: React.PropTypes.object
},
@ -66,15 +67,25 @@ function($, React, controls) {
username: username,
token: keystoneClient.token
});
this.loginRedirect();
this.goToWelcomePage();
}, this));
},
loginRedirect: function() {
app.navigate('#', {trigger: true, replace: true});
},
goToWelcomePage: function() {
app.settings.fetch({cache: true})
.always(_.bind(function() {
if (!app.settings.get('statistics').user_choice_saved.value) {
app.navigate('#welcome', {trigger: true, replace: true});
} else {
this.loginRedirect();
}
}, this));
},
componentDidMount: function() {
if (app.user.get('authenticated')) {
this.loginRedirect();
this.goToWelcomePage();
} else {
$(this.refs.username.getDOMNode()).focus();
}

View File

@ -0,0 +1,181 @@
/*
* Copyright 2014 Mirantis, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
**/
define([
'jquery',
'underscore',
'react',
'utils',
'models',
'jsx!views/controls'
], function($, _, React, utils, models, controls) {
'use strict';
return {
propTypes: {
settings: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {
loading: true,
actionInProgress: false,
showItems: false
};
},
saveSettings: function(e) {
e.preventDefault();
this.props.settings.isValid({models: this.configModels});
if (this.props.settings.validationError) {
this.forceUpdate();
return $.Deferred().reject();
}
this.setState({actionInProgress: true});
return this.props.settings.save(null, {patch: true, wait: true, validate: false})
.done(this.updateInitialSettings)
.fail(function() {
this.setState({actionInProgress: false});
utils.showErrorDialog();
});
},
onSettingChange: function(name, value) {
this.setState({actionInProgress: false});
this.props.settings.set(this.props.settings.makePath('statistics', name, 'value'), value);
this.props.settings.isValid({models: this.configModels});
},
checkRestrictions: function(name, action) {
action = action || 'disable';
return this.props.settings.checkRestrictions(this.configModels, action, this.props.settings.makePath('statistics', name));
},
toggleItemsList: function(e) {
e.preventDefault();
this.setState({showItems: !this.state.showItems});
},
hasChanges: function() {
return !_.isEqual(this.props.settings.toJSON().settings, this.initialSettings);
},
updateInitialSettings: function() {
this.initialSettings = _.cloneDeep(this.props.settings.attributes);
},
componentWillUnmount: function() {
this.props.settings.set(_.cloneDeep(this.initialSettings), {silent: true});
},
componentDidMount: function() {
this.props.settings.fetch({cache: true})
.always(_.bind(function() {
this.props.settings.processRestrictions();
this.configModels = {
fuel_settings: this.props.settings,
version: app.version,
default: this.props.settings
};
this.updateInitialSettings();
this.setState({loading: false});
}, this));
},
getError: function(name) {
return this.props.settings.validationError && this.props.settings.validationError[this.props.settings.makePath('statistics', name)];
},
renderInput: function(settingName, labelClassName, wrapperClassName, hideErrors) {
if (this.checkRestrictions('metadata', 'hide') || this.checkRestrictions(settingName, 'hide')) return null;
var setting = this.props.settings.get(this.props.settings.makePath('statistics', settingName)),
error = this.getError(settingName),
disabled = this.checkRestrictions('metadata') || this.checkRestrictions(settingName);
return <controls.Input
key={settingName}
type={setting.type}
name={settingName}
label={setting.label && $.t(setting.label)}
checked={!disabled && setting.value}
value={setting.value}
disabled={disabled}
inputClassName={setting.type == 'text' && 'input-xlarge'}
labelClassName={labelClassName}
wrapperClassName={wrapperClassName}
onChange={this.onSettingChange}
error={error ? hideErrors ? '' : $.t(error) : null}
/>;
},
renderList: function(list, key) {
return (
<p key={key}>
{$.t('statistics.' + key + '_title')}
<ul>
{_.map(list, function(item) {
return <li key={item}>{$.t('statistics.' + key + '.' + item)}</li>;
})}
</ul>
</p>
);
},
renderIntro: function() {
var ns = 'statistics.',
lists = {
actions: [
'operation_type',
'operation_time',
'actual_time',
'network_verification',
'ostf_results'
],
settings: [
'envronments_amount',
'nistribution',
'network_type',
'kernel_parameters',
'admin_network_parameters',
'pxe_parameters',
'dns_parameters',
'storage_options',
'related_projects',
'modified_settings',
'networking_configuration'
],
node_settings: [
'deployed_nodes_amount',
'deployed_roles',
'disk_layout',
'interfaces_configuration'
],
system_info: [
'hypervisor',
'hardware_info',
'fuel_version',
'openstack_version'
]
};
return (
<div>
<div className='statistics-text-box'>
<p>{$.t(ns + 'help_to_improve')}</p>
<p>
{$.t(ns + 'statistics_includes')}
<a onClick={this.toggleItemsList}>{$.t(ns + 'click_here')}</a>.
</p>
<p>
{$.t(ns + 'privacy_policy')}
<a href='https://www.mirantis.com/company/privacy-policy/' target='_blank'>{$.t(ns + 'privacy_policy_link')}</a>.
</p>
</div>
{this.state.showItems &&
<div className='statistics-disclaimer-box'>
<p>{$.t(ns + 'statistics_includes_full')}</p>
{_.map(lists, this.renderList)}
</div>
}
</div>
);
}
};
});

View File

@ -17,9 +17,10 @@ define(
[
'react',
'jsx!component_mixins',
'models'
'models',
'jsx!views/statistics_mixin'
],
function(React, componentMixins, models) {
function(React, componentMixins, models, statisticsMixin) {
'use strict';
var SupportPage = React.createClass({
@ -32,7 +33,8 @@ function(React, componentMixins, models) {
render: function() {
var elements = [
<DiagnosticSnapshot key='DiagnosticSnapshot' tasks={this.props.tasks} task={this.props.tasks.findTask({name: 'dump'})} />,
<CapacityAudit key='CapacityAudit' />
<CapacityAudit key='CapacityAudit' />,
<StatisticsSettings key='StatisticsSettings' settings={this.props.settings} />
];
if (_.contains(app.version.get('feature_groups'), 'mirantis')) {
elements.unshift(
@ -171,5 +173,33 @@ function(React, componentMixins, models) {
}
});
var StatisticsSettings = React.createClass({
mixins: [
statisticsMixin,
React.BackboneMixin('settings')
],
render: function() {
if (this.state.loading) return null;
var settings = this.props.settings.get('statistics'),
sortedSettings = _.chain(_.keys(settings))
.without('metadata')
.sortBy(function(settingName) {return settings[settingName].weight;}, this)
.value();
return (
<SupportPageElement title={$.t('support_page.send_statistics_title')}>
{this.renderIntro()}
<div className='statistics-settings'>
{_.map(sortedSettings, this.renderInput, this)}
</div>
<p>
<a className='btn' disabled={this.state.actionInProgress || !this.hasChanges()} onClick={this.saveSettings}>
{$.t('support_page.save_changes')}
</a>
</p>
</SupportPageElement>
);
}
});
return SupportPage;
});

View File

@ -0,0 +1,90 @@
/*
* Copyright 2014 Mirantis, Inc.
*
* Licensed under the Apache License, Version 2.0 (the 'License'); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
**/
define(
[
'jquery',
'react',
'jsx!views/controls',
'jsx!views/statistics_mixin'
],
function($, React, controls, statisticsMixin) {
'use strict';
var WelcomePage = React.createClass({
mixins: [
statisticsMixin,
React.BackboneMixin('settings')
],
breadcrumbsPath: [],
hiddenLayout: true,
title: function() {
return $.t('welcome_page.title');
},
onStartButtonClick: function(e) {
this.props.settings.get('statistics').user_choice_saved.value = true;
this.saveSettings(e)
.done(function() {
app.navigate('#clusters', {trigger: true});
})
.fail(_.bind(function() {
this.props.settings.get('statistics').user_choice_saved.value = false;
}, this));
},
render: function() {
if (this.state.loading) return <controls.ProgressBar />;
var ns = 'welcome_page.',
contacts = ['name', 'email', 'company'],
error = _.compact(_.map(contacts, this.getError, this))[0];
return (
<div className='welcome-page'>
<div>
<h2 className='center'>{$.t(ns + 'title')}</h2>
{this.renderIntro()}
{this.renderInput('send_anonymous_statistic', null, 'welcome-checkbox-box')}
<div className='welcome-text-box'>
<p className='center'>
{$.t(ns + 'support')}<br/>
{$.t(ns + 'provide_contacts')}
</p>
</div>
{this.renderInput('send_user_info', null, 'welcome-checkbox-box')}
<form className='form-horizontal'>
{ _.map(contacts, function(settingName) {
return this.renderInput(settingName, 'welcome-form-item', 'welcome-form-box', true);
}, this)}
{error &&
<div className='welcome-form-error'>{$.t(error)}</div>
}
<div className='welcome-button-box'>
<button className='btn btn-large btn-success' disabled={this.state.actionInProgress} onClick={this.onStartButtonClick}>
{$.t(ns + 'start_fuel')}
</button>
</div>
</form>
<div className='welcome-text-box'>
<p className='center'>
{$.t(ns + 'change_settings')}<br/>
{$.t(ns + 'thanks')}
</p>
</div>
</div>
</div>
);
}
});
return WelcomePage;
});