* setup complete stages * [setup] better setup-in-progress card * restructure setup exception flow * use setup_stages hook * Add message for non-dev mode, fail instead of error * message to not include commits in app setup stagesversion-14
@@ -182,39 +182,72 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { | |||||
} | } | ||||
action_on_complete() { | action_on_complete() { | ||||
var me = this; | |||||
if (!this.current_slide.set_values()) return; | if (!this.current_slide.set_values()) return; | ||||
this.update_values(); | this.update_values(); | ||||
this.show_working_state(); | this.show_working_state(); | ||||
this.disable_keyboard_nav(); | this.disable_keyboard_nav(); | ||||
this.listen_for_setup_stages(); | |||||
return frappe.call({ | return frappe.call({ | ||||
method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete", | method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete", | ||||
args: {args: this.values}, | args: {args: this.values}, | ||||
callback: function() { | |||||
me.show_setup_complete_state(); | |||||
if(frappe.setup.welcome_page) { | |||||
localStorage.setItem("session_last_route", frappe.setup.welcome_page); | |||||
callback: (r) => { | |||||
if(r.message.status === 'ok') { | |||||
this.post_setup_success(); | |||||
} else if(r.message.fail !== undefined) { | |||||
this.abort_setup(r.message.fail); | |||||
} | } | ||||
setTimeout(function() { | |||||
// Reload | |||||
window.location.href = ''; | |||||
}, 2000); | |||||
setTimeout(()=> { | |||||
$('body').removeClass('setup-state'); | |||||
}, 20000); | |||||
}, | }, | ||||
error: function() { | |||||
var d = frappe.msgprint(__("There were errors.")); | |||||
d.custom_onhide = function() { | |||||
$(me.parent).find('.page-card-container').remove(); | |||||
$('body').removeClass('setup-state'); | |||||
me.container.show(); | |||||
frappe.set_route(me.page_name, me.slides.length - 1); | |||||
}; | |||||
} | |||||
error: this.abort_setup.bind(this, "Error in setup", true) | |||||
}); | }); | ||||
} | } | ||||
post_setup_success() { | |||||
this.set_setup_complete_message(__("Setup Complete"), __("Refreshing...")); | |||||
if(frappe.setup.welcome_page) { | |||||
localStorage.setItem("session_last_route", frappe.setup.welcome_page); | |||||
} | |||||
setTimeout(function() { | |||||
// Reload | |||||
window.location.href = ''; | |||||
}, 2000); | |||||
} | |||||
abort_setup(fail_msg, error=false) { | |||||
this.$working_state.find('.state-icon-container').html(''); | |||||
fail_msg = fail_msg ? fail_msg : __("Failed to complete setup"); | |||||
if(error && !frappe.boot.developer_mode) { | |||||
frappe.msgprint(`Don't worry. It's not you, it's us. We've | |||||
received the issue details and will get back to you on the solution. | |||||
Please feel free to contact us on support@erpnext.com in the meantime.`); | |||||
} | |||||
this.update_setup_message('Could not start up: ' + fail_msg); | |||||
this.$working_state.find('.title').html('Setup failed'); | |||||
this.$abort_btn.show(); | |||||
} | |||||
listen_for_setup_stages() { | |||||
frappe.realtime.on("setup_task", (data) => { | |||||
// console.log('data', data); | |||||
if(data.stage_status) { | |||||
// .html('Process '+ data.progress[0] + ' of ' + data.progress[1] + ': ' + data.stage_status); | |||||
this.update_setup_message(data.stage_status); | |||||
this.set_setup_load_percent((data.progress[0]+1)/data.progress[1] * 100); | |||||
} | |||||
if(data.fail_msg) { | |||||
this.abort_setup(data.fail_msg); | |||||
} | |||||
}) | |||||
} | |||||
update_setup_message(message) { | |||||
this.$working_state.find('.setup-message').html(message); | |||||
} | |||||
get_setup_slides_filtered_by_domain() { | get_setup_slides_filtered_by_domain() { | ||||
var filtered_slides = []; | var filtered_slides = []; | ||||
frappe.setup.slides.forEach(function(slide) { | frappe.setup.slides.forEach(function(slide) { | ||||
@@ -233,51 +266,56 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { | |||||
show_working_state() { | show_working_state() { | ||||
this.container.hide(); | this.container.hide(); | ||||
$('body').addClass('setup-state'); | |||||
frappe.set_route(this.page_name); | frappe.set_route(this.page_name); | ||||
this.working_state_message = this.get_message( | |||||
__("Setting Up"), | |||||
__("Sit tight while your system is being setup. This may take a few moments."), | |||||
true | |||||
).appendTo(this.parent); | |||||
this.$working_state = this.get_message( | |||||
__("Setting up your system"), | |||||
__("Starting Frappé ...")).appendTo(this.parent); | |||||
this.attach_abort_button(); | |||||
this.current_id = this.slides.length; | this.current_id = this.slides.length; | ||||
this.current_slide = null; | this.current_slide = null; | ||||
this.completed_state_message = this.get_message( | |||||
__("Setup Complete"), | |||||
__("Refreshing...") | |||||
); | |||||
} | } | ||||
show_setup_complete_state() { | |||||
this.working_state_message.hide(); | |||||
this.completed_state_message.appendTo(this.parent); | |||||
attach_abort_button() { | |||||
this.$abort_btn = $(`<button class='btn btn-default btn-xs text-muted' | |||||
style="margin-bottom: 30px;">${__('Retry')}</button>`); | |||||
this.$working_state.find('.content').append(this.$abort_btn); | |||||
this.$abort_btn.on('click', () => { | |||||
$(this.parent).find('.setup-in-progress').remove(); | |||||
this.container.show(); | |||||
frappe.set_route(this.page_name, this.slides.length - 1); | |||||
}); | |||||
this.$abort_btn.hide(); | |||||
} | } | ||||
get_message(title, message="", loading=false) { | |||||
const loading_html = loading | |||||
? '<div style="width:100%;height:100%" class="lds-rolling state-icon"><div></div></div>' | |||||
: `<div style="width:100%;height:100%" class="state-icon"> | |||||
<i class="fa fa-check-circle text-success" | |||||
style="font-size: 64px; margin-top: -8px;"></i> | |||||
</div>`; | |||||
return $(`<div class="page-card-container" data-state="setup"> | |||||
<div class="page-card"> | |||||
<div class="page-card-head"> | |||||
${loading | |||||
? `<span class="indicator orange">${title}</span>` | |||||
: `<span class="indicator green">${title}</span>` | |||||
} | |||||
</div> | |||||
<p>${message}</p> | |||||
<div class="state-icon-container"> | |||||
${loading_html} | |||||
</div> | |||||
get_message(title, message="") { | |||||
const loading_html = `<div class="progress-chart" style ="width: 150px;"> | |||||
<div class="progress" style="margin-top: 70px; margin-bottom: 0px"> | |||||
<div class="progress-bar" style="width: 2%; background-color: #5e64ff;"></div> | |||||
</div> | |||||
</div>`; | |||||
return $(`<div class="slides-wrapper setup-wizard-slide setup-in-progress"> | |||||
<div class="content text-center"> | |||||
<p class="title lead">${title}</p> | |||||
<div class="state-icon-container">${loading_html}</div> | |||||
<p class="setup-message text-muted" style="margin: 30px 0px;">${message}</p> | |||||
</div> | </div> | ||||
</div>`); | </div>`); | ||||
} | } | ||||
set_setup_complete_message(title, message) { | |||||
this.$working_state.find('.title').html(title); | |||||
this.$working_state.find('.setup-message').html(message); | |||||
} | |||||
set_setup_load_percent(percent) { | |||||
this.$working_state.find('.progress-bar').css({"width": percent + "%"}); | |||||
} | |||||
}; | }; | ||||
frappe.setup.SetupWizardSlide = class SetupWizardSlide extends frappe.ui.Slide { | frappe.setup.SetupWizardSlide = class SetupWizardSlide extends frappe.ui.Slide { | ||||
@@ -13,50 +13,117 @@ from werkzeug.useragents import UserAgent | |||||
from . import install_fixtures | from . import install_fixtures | ||||
from six import string_types | from six import string_types | ||||
def get_setup_stages(args): | |||||
# App setup stage functions should not include frappe.db.commit | |||||
# That is done by frappe after successful completion of all stages | |||||
stages = [ | |||||
{ | |||||
'status': 'Updating global settings', | |||||
'fail_msg': 'Failed to update global settings', | |||||
'tasks': [ | |||||
{ | |||||
'fn': update_global_settings, | |||||
'args': args, | |||||
'fail_msg': 'Failed to update global settings' | |||||
} | |||||
] | |||||
} | |||||
] | |||||
stages += get_stages_hooks(args) + get_setup_complete_hooks(args) | |||||
stages.append({ | |||||
# post executing hooks | |||||
'status': 'Wrapping up', | |||||
'fail_msg': 'Failed to complete setup', | |||||
'tasks': [ | |||||
{ | |||||
'fn': run_post_setup_complete, | |||||
'args': args, | |||||
'fail_msg': 'Failed to complete setup' | |||||
} | |||||
] | |||||
}) | |||||
return stages | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def setup_complete(args): | def setup_complete(args): | ||||
"""Calls hooks for `setup_wizard_complete`, sets home page as `desktop` | """Calls hooks for `setup_wizard_complete`, sets home page as `desktop` | ||||
and clears cache. If wizard breaks, calls `setup_wizard_exception` hook""" | and clears cache. If wizard breaks, calls `setup_wizard_exception` hook""" | ||||
# Setup complete: do not throw an exception, let the user continue to desk | |||||
if cint(frappe.db.get_single_value('System Settings', 'setup_complete')): | if cint(frappe.db.get_single_value('System Settings', 'setup_complete')): | ||||
# do not throw an exception if setup is already complete | |||||
# let the user continue to desk | |||||
return | return | ||||
#frappe.throw(_('Setup already complete')) | |||||
args = process_args(args) | |||||
args = parse_args(args) | |||||
try: | |||||
if args.language and args.language != "english": | |||||
set_default_language(get_language_code(args.language)) | |||||
stages = get_setup_stages(args) | |||||
frappe.clear_cache() | |||||
# update system settings | |||||
update_system_settings(args) | |||||
update_user_name(args) | |||||
for method in frappe.get_hooks("setup_wizard_complete"): | |||||
frappe.get_attr(method)(args) | |||||
try: | |||||
current_task = None | |||||
for idx, stage in enumerate(stages): | |||||
frappe.publish_realtime('setup_task', {"progress": [idx, len(stages)], | |||||
"stage_status": stage.get('status')}, user=frappe.session.user) | |||||
for task in stage.get('tasks'): | |||||
current_task = task | |||||
task.get('fn')(task.get('args')) | |||||
except Exception: | |||||
handle_setup_exception(args) | |||||
return {'status': 'fail', 'fail': current_task.get('fail_msg')} | |||||
else: | |||||
run_setup_success(args) | |||||
return {'status': 'ok'} | |||||
disable_future_access() | |||||
def update_global_settings(args): | |||||
if args.language and args.language != "english": | |||||
set_default_language(get_language_code(args.lang)) | |||||
frappe.clear_cache() | |||||
frappe.db.commit() | |||||
frappe.clear_cache() | |||||
except: | |||||
frappe.db.rollback() | |||||
if args: | |||||
traceback = frappe.get_traceback() | |||||
for hook in frappe.get_hooks("setup_wizard_exception"): | |||||
frappe.get_attr(hook)(traceback, args) | |||||
update_system_settings(args) | |||||
update_user_name(args) | |||||
raise | |||||
def run_post_setup_complete(args): | |||||
disable_future_access() | |||||
frappe.db.commit() | |||||
frappe.clear_cache() | |||||
else: | |||||
for hook in frappe.get_hooks("setup_wizard_success"): | |||||
frappe.get_attr(hook)(args) | |||||
install_fixtures.install() | |||||
def run_setup_success(args): | |||||
for hook in frappe.get_hooks("setup_wizard_success"): | |||||
frappe.get_attr(hook)(args) | |||||
install_fixtures.install() | |||||
def get_stages_hooks(args): | |||||
stages = [] | |||||
for method in frappe.get_hooks("setup_wizard_stages"): | |||||
stages += frappe.get_attr(method)(args) | |||||
return stages | |||||
def get_setup_complete_hooks(args): | |||||
stages = [] | |||||
for method in frappe.get_hooks("setup_wizard_complete"): | |||||
stages.append({ | |||||
'status': 'Executing method', | |||||
'fail_msg': 'Failed to execute method', | |||||
'tasks': [ | |||||
{ | |||||
'fn': frappe.get_attr(method), | |||||
'args': args, | |||||
'fail_msg': 'Failed to execute method' | |||||
} | |||||
] | |||||
}) | |||||
return stages | |||||
def handle_setup_exception(args): | |||||
frappe.db.rollback() | |||||
if args: | |||||
traceback = frappe.get_traceback() | |||||
for hook in frappe.get_hooks("setup_wizard_exception"): | |||||
frappe.get_attr(hook)(traceback, args) | |||||
def update_system_settings(args): | def update_system_settings(args): | ||||
number_format = get_country_info(args.get("country")).get("number_format", "#,###.##") | number_format = get_country_info(args.get("country")).get("number_format", "#,###.##") | ||||
@@ -126,7 +193,7 @@ def update_user_name(args): | |||||
if args.get('name'): | if args.get('name'): | ||||
add_all_roles_to(args.get("name")) | add_all_roles_to(args.get("name")) | ||||
def process_args(args): | |||||
def parse_args(args): | |||||
if not args: | if not args: | ||||
args = frappe.local.form_dict | args = frappe.local.form_dict | ||||
if isinstance(args, string_types): | if isinstance(args, string_types): | ||||
@@ -234,14 +301,6 @@ def email_setup_wizard_exception(traceback, args): | |||||
user_agent = frappe._dict() | user_agent = frappe._dict() | ||||
message = """ | message = """ | ||||
#### Basic Information | |||||
- **Site:** {site} | |||||
- **User:** {user} | |||||
- **Browser:** {user_agent.platform} {user_agent.browser} version: {user_agent.version} language: {user_agent.language} | |||||
- **Browser Languages**: `{accept_languages}` | |||||
--- | |||||
#### Traceback | #### Traceback | ||||
@@ -257,7 +316,16 @@ def email_setup_wizard_exception(traceback, args): | |||||
#### Request Headers | #### Request Headers | ||||
<pre>{headers}</pre>""".format( | |||||
<pre>{headers}</pre> | |||||
--- | |||||
#### Basic Information | |||||
- **Site:** {site} | |||||
- **User:** {user} | |||||
- **Browser:** {user_agent.platform} {user_agent.browser} version: {user_agent.version} language: {user_agent.language} | |||||
- **Browser Languages**: `{accept_languages}`""".format( | |||||
site=frappe.local.site, | site=frappe.local.site, | ||||
traceback=traceback, | traceback=traceback, | ||||
args="\n".join(pretty_args), | args="\n".join(pretty_args), | ||||
@@ -268,14 +336,13 @@ def email_setup_wizard_exception(traceback, args): | |||||
frappe.sendmail(recipients=frappe.local.conf.setup_wizard_exception_email, | frappe.sendmail(recipients=frappe.local.conf.setup_wizard_exception_email, | ||||
sender=frappe.session.user, | sender=frappe.session.user, | ||||
subject="Exception in Setup Wizard - {}".format(frappe.local.site), | |||||
subject="Setup failed: {}".format(frappe.local.site), | |||||
message=message, | message=message, | ||||
delayed=False) | delayed=False) | ||||
def get_language_code(lang): | def get_language_code(lang): | ||||
return frappe.db.get_value('Language', {'language_name':lang}) | return frappe.db.get_value('Language', {'language_name':lang}) | ||||
def enable_twofactor_all_roles(): | def enable_twofactor_all_roles(): | ||||
all_role = frappe.get_doc('Role',{'role_name':'All'}) | all_role = frappe.get_doc('Role',{'role_name':'All'}) | ||||
all_role.two_factor_auth = True | all_role.two_factor_auth = True | ||||
@@ -279,6 +279,9 @@ select.input-sm { | |||||
opacity: 1; | opacity: 1; | ||||
cursor: pointer; | cursor: pointer; | ||||
} | } | ||||
.setup-wizard-slide .progress-bar { | |||||
background-color: #5e64ff; | |||||
} | |||||
.page-card-container { | .page-card-container { | ||||
padding: 70px; | padding: 70px; | ||||
} | } | ||||
@@ -312,49 +315,3 @@ select.input-sm { | |||||
justify-content: center; | justify-content: center; | ||||
align-items: center; | align-items: center; | ||||
} | } | ||||
@keyframes lds-rolling { | |||||
0% { | |||||
-webkit-transform: translate(-50%, -50%) rotate(0deg); | |||||
transform: translate(-50%, -50%) rotate(0deg); | |||||
} | |||||
100% { | |||||
-webkit-transform: translate(-50%, -50%) rotate(360deg); | |||||
transform: translate(-50%, -50%) rotate(360deg); | |||||
} | |||||
} | |||||
@-webkit-keyframes lds-rolling { | |||||
0% { | |||||
-webkit-transform: translate(-50%, -50%) rotate(0deg); | |||||
transform: translate(-50%, -50%) rotate(0deg); | |||||
} | |||||
100% { | |||||
-webkit-transform: translate(-50%, -50%) rotate(360deg); | |||||
transform: translate(-50%, -50%) rotate(360deg); | |||||
} | |||||
} | |||||
.lds-rolling { | |||||
-webkit-transform: translate(-100px, -100px) scale(1) translate(100px, 100px); | |||||
transform: translate(-100px, -100px) scale(1) translate(100px, 100px); | |||||
} | |||||
.lds-rolling div { | |||||
position: absolute; | |||||
width: 60px; | |||||
height: 60px; | |||||
border: 3px solid #d1d8dd; | |||||
border-top-color: transparent; | |||||
border-radius: 50%; | |||||
-webkit-animation: lds-rolling 1s linear infinite; | |||||
animation: lds-rolling 1s linear infinite; | |||||
top: 50px; | |||||
left: 50px; | |||||
} | |||||
.lds-rolling div:after { | |||||
position: absolute; | |||||
width: 60px; | |||||
height: 60px; | |||||
border: 3px solid #d1d8dd; | |||||
border-top-color: transparent; | |||||
border-radius: 50%; | |||||
-webkit-transform: rotate(90deg); | |||||
transform: rotate(90deg); | |||||
} |
@@ -335,6 +335,10 @@ select.input-sm { | |||||
cursor: pointer; | cursor: pointer; | ||||
} | } | ||||
} | } | ||||
.progress-bar { | |||||
background-color: #5e64ff; | |||||
} | |||||
} | } | ||||
.page-card-container { | .page-card-container { | ||||
@@ -376,50 +380,4 @@ select.input-sm { | |||||
align-items: center; | align-items: center; | ||||
} | } | ||||
@keyframes lds-rolling { | |||||
0% { | |||||
-webkit-transform: translate(-50%, -50%) rotate(0deg); | |||||
transform: translate(-50%, -50%) rotate(0deg); | |||||
} | |||||
100% { | |||||
-webkit-transform: translate(-50%, -50%) rotate(360deg); | |||||
transform: translate(-50%, -50%) rotate(360deg); | |||||
} | |||||
} | |||||
@-webkit-keyframes lds-rolling { | |||||
0% { | |||||
-webkit-transform: translate(-50%, -50%) rotate(0deg); | |||||
transform: translate(-50%, -50%) rotate(0deg); | |||||
} | |||||
100% { | |||||
-webkit-transform: translate(-50%, -50%) rotate(360deg); | |||||
transform: translate(-50%, -50%) rotate(360deg); | |||||
} | |||||
} | |||||
.lds-rolling { | |||||
-webkit-transform: translate(-100px, -100px) scale(1) translate(100px, 100px); | |||||
transform: translate(-100px, -100px) scale(1) translate(100px, 100px); | |||||
div { | |||||
position: absolute; | |||||
width: 60px; | |||||
height: 60px; | |||||
border: 3px solid #d1d8dd; | |||||
border-top-color: transparent; | |||||
border-radius: 50%; | |||||
-webkit-animation: lds-rolling 1s linear infinite; | |||||
animation: lds-rolling 1s linear infinite; | |||||
top: 50px; | |||||
left: 50px; | |||||
&:after { | |||||
position: absolute; | |||||
width: 60px; | |||||
height: 60px; | |||||
border: 3px solid #d1d8dd; | |||||
border-top-color: transparent; | |||||
border-radius: 50%; | |||||
-webkit-transform: rotate(90deg); | |||||
transform: rotate(90deg); | |||||
} | |||||
} | |||||
} |