Merge lp:~fabien-morin/unifield-web/fm-us-2925 into lp:unifield-web

Proposed by jftempo
Status: Merged
Merged at revision: 4883
Proposed branch: lp:~fabien-morin/unifield-web/fm-us-2925
Merge into: lp:unifield-web
Diff against target: 615 lines (+460/-5)
6 files modified
addons/openerp/controllers/database.py (+272/-3)
addons/openerp/controllers/templates/auto_create.mako (+70/-0)
addons/openerp/controllers/templates/auto_create_progress.mako (+80/-0)
addons/openerp/controllers/utils.py (+12/-0)
addons/openerp/po/messages/fr.po (+2/-2)
addons/openerp/static/css/database.css (+24/-0)
To merge this branch: bzr merge lp:~fabien-morin/unifield-web/fm-us-2925
Reviewer Review Type Date Requested Status
UniField Dev Team Pending
Review via email: mp+334060@code.launchpad.net
To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'addons/openerp/controllers/database.py'
2--- addons/openerp/controllers/database.py 2017-07-11 12:19:46 +0000
3+++ addons/openerp/controllers/database.py 2017-11-21 17:05:56 +0000
4@@ -22,10 +22,12 @@
5 import re
6 import time
7 import os
8+import sys
9
10 import cherrypy
11 import formencode
12
13+from openobject import paths
14 from openobject.controllers import BaseController
15 from openobject.tools import url, expose, redirect, validate, error_handler
16 import openobject
17@@ -35,6 +37,9 @@
18 from openerp.utils import rpc, get_server_version, is_server_local, serve_file
19 from tempfile import NamedTemporaryFile
20 import shutil
21+import ConfigParser
22+from ConfigParser import NoOptionError, NoSectionError
23+import threading
24
25 def get_lang_list():
26 langs = [('en_US', 'English (US)')]
27@@ -50,6 +55,8 @@
28 except:
29 return []
30
31+class DatabaseExist(Exception): pass
32+
33 class ReplacePasswordField(openobject.widgets.PasswordField):
34 params = {
35 'autocomplete': 'Autocomplete field',
36@@ -116,6 +123,23 @@
37 validator = openobject.validators.Schema(chained_validators=[formencode.validators.FieldsMatch("admin_password","confirm_password")])
38
39
40+class FormAutoCreate(DBForm):
41+ name = "auto_create"
42+ string = _('Instance Auto Creation')
43+ action = '/openerp/database/do_auto_create'
44+ submit_text = _('Start auto creation')
45+ #form_attrs = {'onsubmit': 'return window.confirm(_("Do you really want to drop the selected database?"))'}
46+ fields = [
47+ ReplacePasswordField(name='password', label=_('Super admin password:')),
48+ ]
49+
50+
51+class AutoCreateProgress(DBForm):
52+ name = "get_auto_create_progress"
53+ string = _('Auto Creation Progress')
54+ action = '/openerp/database/get_auto_create_progress'
55+
56+
57 class FormDrop(DBForm):
58 name = "drop"
59 string = _('Drop database')
60@@ -127,6 +151,7 @@
61 ReplacePasswordField(name='password', label=_('Drop password:')),
62 ]
63
64+
65 class FormBackup(DBForm):
66 name = "backup"
67 string = _('Backup database')
68@@ -137,10 +162,12 @@
69 ReplacePasswordField(name='password', label=_('Backup password:')),
70 ]
71
72+
73 class FileField(openobject.widgets.FileField):
74 def adjust_value(self, value, **params):
75 return False
76
77+
78 class FormRestore(DBForm):
79 name = "restore"
80 string = _('Restore database')
81@@ -169,6 +196,7 @@
82
83
84 _FORMS = {
85+ 'auto_create': FormAutoCreate(),
86 'create': FormCreate(),
87 'drop': FormDrop(),
88 'backup': FormBackup(),
89@@ -176,9 +204,13 @@
90 'password': FormPassword()
91 }
92
93+
94 class DatabaseCreationError(Exception): pass
95+
96+
97 class DatabaseCreationCrash(DatabaseCreationError): pass
98
99+
100 class Database(BaseController):
101
102 _cp_path = "/openerp/database"
103@@ -248,7 +280,7 @@
104 break
105 else:
106 time.sleep(1)
107- except:
108+ except Exception as e:
109 raise DatabaseCreationCrash()
110 except DatabaseCreationCrash:
111 self.msg = {'message': (_("The server crashed during installation.\nWe suggest you to drop this database.")),
112@@ -258,15 +290,252 @@
113 self.msg = {'message': _('Bad super admin password'),
114 'title' : e.title}
115 return self.create()
116- except Exception:
117+ except Exception as e:
118 self.msg = {'message':_("Could not create database.")}
119-
120 return self.create()
121
122 if ok:
123 raise redirect('/openerp/menu', {'next': '/openerp/home'})
124 raise redirect('/openerp/login', db=dbname)
125
126+ @expose(template="/openerp/controllers/templates/auto_create.mako")
127+ def auto_create(self, tg_errors=None, **kw):
128+ form = _FORMS['auto_create']
129+ error = self.msg
130+ self.msg = {}
131+ return dict(form=form, error=error)
132+
133+ @expose()
134+ def get_auto_create_progress(self, **kw):
135+ config_file_name = 'uf_auto_install.conf'
136+ if sys.platform == 'win32':
137+ config_file_path = os.path.join(paths.root(), '..', 'UFautoInstall', config_file_name)
138+ else:
139+ config_file_path = os.path.join(paths.root(), '..', 'unifield-server', 'UFautoInstall', config_file_name)
140+ if not os.path.exists(config_file_path):
141+ return False
142+ config = ConfigParser.ConfigParser()
143+ config.read(config_file_path)
144+ dbname = config.get('instance', 'db_name')
145+ self.resume, self.progress, self.state, self.error, monitor_status = rpc.session.execute_db('creation_get_resume_progress', dbname)
146+ my_dict = {
147+ 'resume': self.resume,
148+ 'progress': self.progress,
149+ 'state': self.state,
150+ 'error': self.error,
151+ 'monitor_status': monitor_status,
152+ }
153+ import json
154+ return json.dumps(my_dict)
155+
156+ @expose(template="/openerp/controllers/templates/auto_create_progress.mako")
157+ def auto_create_progress(self, tg_errors=None, **kw):
158+ finish = ""
159+ finished = "False"
160+ data_collected = "False"
161+ return dict(finish=finish, percent=self.progress, resume=self.resume, total=finished,
162+ data_collected=data_collected)
163+
164+ def check_not_empty_string(self, config, section, option):
165+ if not config.has_option(section, option) or not config.get(section, option):
166+ self.msg = {'message': ustr(_('The option \'%s\' from section \'[%s]\' cannot be empty, please set a value.') % (option, section)),
167+ 'title': ustr(_('Empty option'))}
168+
169+ def check_mandatory_int(self, config, section, option):
170+ try:
171+ value = config.getint(section, option)
172+ except ValueError:
173+ self.msg = {'message': ustr(_('The option \'%s\' from section \'[%s]\' have to be a int.') % (option, section)),
174+ 'title': ustr(_('Wrong option value'))}
175+ return
176+ if not value:
177+ self.msg = {'message': ustr(_('The option \'%s\' from section \'[%s]\' cannot be empty, please set a value.') % (option, section)),
178+ 'title': ustr(_('Empty option'))}
179+
180+ def check_possible_value(self, config, section, option, possible_values):
181+ value = config.get(section, option)
182+ if value not in possible_values:
183+ self.msg = {'message': ustr(_('The option \'%s\' from section \'[%s]\' have to be one of those values: %r. (currently it is \'%s\').') % (option, section, possible_values, value)),
184+ 'title': ustr(_('Wrong option'))}
185+
186+ def check_config_file(self, file_path):
187+ '''
188+ perform some basic checks to avoid crashing later
189+ '''
190+ if not os.path.exists(file_path):
191+ self.msg = {'message': ustr(_("The auto creation config file '%s' does not exists.") % file_path),
192+ 'title': ustr(_('Auto creation file not found'))}
193+
194+ config = ConfigParser.ConfigParser()
195+ config.read(file_path)
196+ try:
197+ db_name = config.get('instance', 'db_name')
198+ if not re.match('^[a-zA-Z][a-zA-Z0-9_-]+$', db_name):
199+ self.msg = {'message': ustr(_("You must avoid all accents, space or special characters.")),
200+ 'title': ustr(_('Bad database name'))}
201+ return
202+
203+ admin_password = config.get('instance', 'admin_password')
204+ res = rpc.session.execute_db('check_super_password_validity', admin_password)
205+ if res is not True:
206+ self.msg = {'message': res,
207+ 'title': ustr(_('Bad admin password'))}
208+ return
209+
210+ # check the mandatory string fields have a value
211+ not_empty_string_option_list = (
212+ ('instance', 'oc'),
213+ ('instance', 'admin_password'),
214+ ('instance', 'sync_user'),
215+ ('instance', 'sync_pwd'),
216+ ('instance', 'instance_level'),
217+ ('instance', 'parent_instance'),
218+ ('instance', 'lang'),
219+ ('backup', 'auto_bck_path'),
220+ ('reconfigure', 'prop_instance_code'),
221+ ('reconfigure', 'address_contact_name'),
222+ ('reconfigure', 'address_street'),
223+ ('reconfigure', 'address_street2'),
224+ ('reconfigure', 'address_zip'),
225+ ('reconfigure', 'address_city'),
226+ ('reconfigure', 'address_country'),
227+ ('reconfigure', 'address_phone'),
228+ ('reconfigure', 'address_email'),
229+ ('reconfigure', 'delivery_process'),
230+ ('reconfigure', 'functional_currency'),
231+ )
232+ for section, option in not_empty_string_option_list:
233+ self.check_not_empty_string(config, section, option)
234+ if self.msg:
235+ return
236+
237+ # check mandatory integer values
238+ not_empty_int_option_list = (
239+ ('backup', 'auto_bck_interval_nb'),
240+ ('partner', 'external_account_receivable'),
241+ ('partner', 'external_account_payable'),
242+ ('partner', 'internal_account_receivable'),
243+ ('partner', 'internal_account_payable'),
244+ ('company', 'default_counterpart'),
245+ ('company', 'salaries_default_account'),
246+ ('company', 'rebilling_intersection_account'),
247+ ('company', 'intermission_counterpart'),
248+ ('company', 'counterpart_bs_debit_balance'),
249+ ('company', 'counterpart_bs_crebit_balance'),
250+ ('company', 'debit_account_pl_positive'),
251+ ('company', 'credit_account_pl_negative'),
252+ ('company', 'scheduler_range_days'),
253+ )
254+ for section, option in not_empty_int_option_list:
255+ self.check_mandatory_int(config, section, option)
256+ if self.msg:
257+ return
258+
259+ # check value is in possibles values
260+ possible_value_list = (
261+ ('instance', 'instance_level', ('coordo', 'project')),
262+ ('instance', 'lang', ('fr_MF', 'es_MF', 'en_MF')),
263+ ('backup', 'auto_bck_interval_unit', ('minutes', 'hours', 'work_days', 'days', 'weeks', 'months')),
264+ ('reconfigure', 'delivery_process', ('complex', 'simple')),
265+ )
266+
267+ for section, option, possible_values in possible_value_list:
268+ self.check_possible_value(config, section, option, possible_values)
269+ if self.msg:
270+ return
271+
272+ except NoOptionError as e:
273+ self.msg = {'message': ustr(_('No option \'%s\' found for the section \'[%s]\' in the config file \'%s\'') % (e.option, e.section, file_path)),
274+ 'title': ustr(_('Option missing in configuration file'))}
275+ return
276+ except NoSectionError as e:
277+ self.msg = {'message': ustr(_('No section \'%s\' found in the config file \'%s\'') % (e.section, file_path)),
278+ 'title': ustr(_('Option missing in configuration file'))}
279+ return
280+
281+ def database_creation(self, password, dbname, admin_password):
282+ try:
283+ res = rpc.session.execute_db('create', password, dbname, False, 'en_US', admin_password)
284+ while True:
285+ try:
286+ progress, users = rpc.session.execute_db('get_progress', password, res)
287+ if progress == 1.0:
288+ for x in users:
289+ if x['login'] == 'admin':
290+ rpc.session.login(dbname, 'admin', x['password'])
291+ ok = True
292+ break
293+ else:
294+ time.sleep(1)
295+ except Exception as e:
296+ raise DatabaseCreationCrash()
297+ except DatabaseCreationCrash:
298+ self.msg = {'message': (_("The server crashed during installation.\nWe suggest you to drop this database.")),
299+ 'title': (_('Error during database creation'))}
300+ except openobject.errors.AccessDenied, e:
301+ self.msg = {'message': _('Bad super admin password'),
302+ 'title' : e.title}
303+
304+ def background_auto_creation(self, password, dbname, db_exists, config_dict):
305+ if not db_exists:
306+ # create database
307+ self.database_creation(password, dbname, config_dict['instance'].get('admin_password'))
308+
309+ rpc.session.execute_db('instance_auto_creation', password, dbname)
310+ self.resume, self.progress, self.state, self.error, monitor_status = rpc.session.execute_db('creation_get_resume_progress', dbname)
311+
312+ @expose()
313+ @validate(form=_FORMS['auto_create'])
314+ @error_handler(auto_create)
315+ def do_auto_create(self, password, **kw):
316+ self.msg = {}
317+ self.progress = 0.03
318+ self.state = 'draft'
319+ try:
320+ config_file_name = 'uf_auto_install.conf'
321+ if sys.platform == 'win32':
322+ config_file_path = os.path.join(paths.root(), '..', 'UFautoInstall', config_file_name)
323+ else:
324+ config_file_path = os.path.join(paths.root(), '..', 'unifield-server', 'UFautoInstall', config_file_name)
325+
326+ self.check_config_file(config_file_path)
327+ if self.msg:
328+ return self.auto_create()
329+ config = ConfigParser.ConfigParser()
330+ config.read(config_file_path)
331+
332+ config_dict = {x:dict(config.items(x)) for x in config.sections()}
333+ dbname = config_dict['instance'].get('db_name')
334+ db_exists = False
335+
336+ # check the database not already exists
337+ if dbname in get_db_list():
338+ db_exists = True
339+ self.resume = _('Database with this name exists, resume from the last point...\n')
340+ else:
341+ self.resume = _('Empty database creation in progress...\n')
342+ #raise DatabaseExist
343+
344+ create_thread = threading.Thread(target=self.background_auto_creation,
345+ args=(password, dbname, db_exists,
346+ config_dict))
347+ create_thread.start()
348+ create_thread.join(0.5)
349+
350+ except openobject.errors.AccessDenied, e:
351+ self.msg = {'message': _('Wrong password'),
352+ 'title' : e.title}
353+ except DatabaseExist:
354+ pass
355+ #self.msg = {'message': ustr(_('The database already exist')),
356+ # 'title': 'Database exist'}
357+ except Exception as e:
358+ self.msg = {'message' : _("Could not auto create database: %s") % e}
359+
360+ if self.msg:
361+ return self.auto_create()
362+ return self.auto_create_progress()
363+
364 @expose(template="/openerp/controllers/templates/database.mako")
365 def drop(self, tg_errors=None, **kw):
366 form = _FORMS['drop']
367
368=== added file 'addons/openerp/controllers/templates/auto_create.mako'
369--- addons/openerp/controllers/templates/auto_create.mako 1970-01-01 00:00:00 +0000
370+++ addons/openerp/controllers/templates/auto_create.mako 2017-11-21 17:05:56 +0000
371@@ -0,0 +1,70 @@
372+<%inherit file="/openerp/controllers/templates/base_dispatch.mako"/>
373+<%def name="current_for(name)"><%
374+ if form.name == name: context.write('current')
375+%></%def>
376+<%def name="header()">
377+ <title>${form.string}</title>
378+
379+ <script type="text/javascript" src="/openerp/static/javascript/openerp/openerp.ui.waitbox.js"></script>
380+ <link rel="stylesheet" type="text/css" href="/openerp/static/css/waitbox.css"/>
381+ <link rel="stylesheet" type="text/css" href="/openerp/static/css/database.css"/>
382+ <script type="text/javascript">
383+ function on_create() {
384+ new openerp.ui.WaitBox().showAfter(2000);
385+ return true;
386+ }
387+ </script>
388+ % if error:
389+ <script type="text/javascript">
390+ var $error_tbl = jQuery('<table class="errorbox">');
391+ $error_tbl.append('<tr><td style="padding: 4px 2px;" width="10%"><img src="/openerp/static/images/warning.png"></td><td class="error_message_content">${error["message"]}</td></tr>');
392+ $error_tbl.append('<tr><td style="padding: 0 8px 5px 0; vertical-align:top;" align="right" colspan="2"><a class="button-a" id="error_btn" onclick="$error_tbl.dialog(\'close\');">OK</a></td></tr>');
393+
394+ jQuery(document).ready(function () {
395+ jQuery(document.body).append($error_tbl);
396+ var error_dialog_options = {
397+ modal: true,
398+ resizable: false,
399+ title: '<div class="error_message_header">${error.get("title", "Warning")}</div>'
400+ };
401+ % if error.get('redirect_to'):
402+ error_dialog_options['close'] = function( event, ui ) {
403+ $(location).attr('href','${error['redirect_to']}');
404+ };
405+ % endif
406+ $error_tbl.dialog(error_dialog_options);
407+ })
408+ </script>
409+ % endif
410+</%def>
411+
412+<%def name="content()">
413+ <table width="100%">
414+ <tr><%include file="header.mako"/></tr>
415+ </table>
416+ <div class="db-form">
417+ <h1>Automated instance creation detected</h1>
418+
419+ <div class='auto_instance_text'>
420+ <p>If you have checked the following points, you can start the
421+process of instance auto creation by login with the Super admin password. Points to check:
422+ <ul>
423+ <li>A Folder 'UFautoInstall' is present in Unifield/Server folder.</li>
424+ <li>This folder contain a file 'uf_auto_install.conf'</li>
425+ <li>This file is correct (required fields, correct values)</li>
426+ <li>The folder also contain an 'import' directory
427+(Unifield/Server/UFautoInstall/import)</li>
428+ <li>This 'import' directory contain files where the name of
429+the file is the model to import and the extension is csv (typically,
430+'account.analytic.journal.csv' and 'account.journal.csv')</li>
431+ <li>The connexion to the SYNC_SERVER is ok (credentials,
432+address, port, ...)</li>
433+ <li>The parents instance (HQ, and Coordo if it is a
434+project) exists and are present as instance in the SYNC_SERVER</li>
435+ </ul>
436+ </p>
437+ </div>
438+ <div>${form.display()}</div>
439+ </div>
440+<%include file="footer.mako"/>
441+</%def>
442
443=== added file 'addons/openerp/controllers/templates/auto_create_progress.mako'
444--- addons/openerp/controllers/templates/auto_create_progress.mako 1970-01-01 00:00:00 +0000
445+++ addons/openerp/controllers/templates/auto_create_progress.mako 2017-11-21 17:05:56 +0000
446@@ -0,0 +1,80 @@
447+<%inherit file="/openerp/controllers/templates/base_dispatch.mako"/>
448+<%def name="header()">
449+ <title>Instance creation progression</title>
450+
451+ <script type="text/javascript" src="/openerp/static/javascript/openerp/openerp.ui.waitbox.js"></script>
452+ <script type="text/javascript">
453+ $(document).ready(function(){
454+ interval = setInterval(function()
455+ {
456+ $.ajax({
457+ type: 'get',
458+ dataType: "json",
459+ url: 'get_auto_create_progress',
460+ success: function (data) {
461+
462+ if (data){
463+ if (data.error){
464+ clearInterval(interval);
465+ var $error_tbl = jQuery('<table class="errorbox">');
466+ $error_tbl.append('<tr><td style="padding: 4px 2px;" width="10%"><img src="/openerp/static/images/warning.png"></td><td class="error_message_content">' + data.error + '</td></tr>');
467+ $error_tbl.append('<tr><td style="padding: 0 8px 5px 0; vertical-align:top;" align="right" colspan="2"><a class="button-a" id="error_btn" onclick="$error_tbl.dialog(\'close\');">OK</a></td></tr>');
468+
469+ jQuery(document).ready(function () {
470+ jQuery(document.body).append($error_tbl);
471+ var error_dialog_options = {
472+ modal: true,
473+ resizable: false,
474+ title: '<div class="error_message_header">Error</div>'
475+ };
476+ $error_tbl.dialog(error_dialog_options);
477+ })
478+
479+ };
480+ $("div.auto_creation_resume textarea").val(data.resume);
481+ $("div.progressbar").text((data.progress*100).toPrecision(3)+'%');
482+ $("div.progressbar").css({"width":(data.progress*100).toPrecision(3)+'%'});
483+ $("div.my_state").text(data.state);
484+ if (data.state === 'done') {
485+ clearInterval(interval);
486+ };
487+ if (data.monitor_status) {
488+ $("div.my_monitor_status").text(data.monitor_status);
489+ };
490+ }
491+ },
492+ error: function (xhr, status, error) {
493+ }
494+ });
495+ }, 3000)
496+ });
497+ </script>
498+
499+ <link rel="stylesheet" type="text/css" href="/openerp/static/css/waitbox.css"/>
500+ <link rel="stylesheet" type="text/css" href="/openerp/static/css/database.css"/>
501+
502+</%def>
503+
504+<%def name="content()">
505+ <table width="100%">
506+ <tr><%include file="header.mako"/></tr>
507+ </table>
508+
509+
510+
511+ <div class="db-form">
512+ <h1>Automated instance creation in progress...</h1>
513+
514+ <div class="my_state"></div>
515+ <div class="my_monitor_status"></div>
516+
517+ <div class="instance_creation_progress">
518+ <div class="progressbar" style="width:${'%d'%(percent*100)}%">${'%d'%(percent*100)}%</div>
519+ </div>
520+
521+ <div class="auto_creation_resume">
522+ <textarea rows="20" cols="80">${resume}</textarea>
523+ </div>
524+ </div>
525+<%include file="footer.mako"/>
526+</%def>
527
528=== modified file 'addons/openerp/controllers/utils.py'
529--- addons/openerp/controllers/utils.py 2016-11-17 15:29:41 +0000
530+++ addons/openerp/controllers/utils.py 2017-11-21 17:05:56 +0000
531@@ -20,11 +20,13 @@
532 ###############################################################################
533 import re
534 import os
535+import sys
536
537 import cherrypy
538 from openerp.utils import rpc
539
540 from openobject import tools
541+from openobject import paths
542 from openobject.tools import expose, redirect
543 import openobject
544
545@@ -94,6 +96,16 @@
546 url = rpc.session.connection_string
547 url = str(url[:-1])
548
549+ config_file_name = 'uf_auto_install.conf'
550+ if sys.platform == 'win32':
551+ config_file_path = os.path.join(paths.root(), '..', 'UFautoInstall', config_file_name)
552+ else:
553+ config_file_path = os.path.join(paths.root(), '..', 'unifield-server', 'UFautoInstall', config_file_name)
554+ if os.path.exists(config_file_path):
555+ raise redirect('/openerp/database/auto_create')
556+ return auto_install(target, db, user, password, action, message,
557+ origArgs, url)
558+
559 result = get_db_list()
560 dblist = result['dblist']
561 bad_regional = result['bad_regional']
562
563=== modified file 'addons/openerp/po/messages/fr.po'
564--- addons/openerp/po/messages/fr.po 2017-10-27 14:32:19 +0000
565+++ addons/openerp/po/messages/fr.po 2017-11-21 17:05:56 +0000
566@@ -275,7 +275,7 @@
567
568 #: controllers/database.py:173
569 msgid "You must avoid all accents, space or special characters."
570-msgstr "Les accents , espaces et caractères spéciaux ne sont pas autorisés ."
571+msgstr "Les accents, espaces et caractères spéciaux ne sont pas autorisés ."
572
573 #: controllers/database.py:174
574 msgid "Bad database name"
575@@ -648,7 +648,7 @@
576 "données. Il met en relation, améliore et\n"
577 " gère les processus dans les domaines des ventes, de la "
578 "finance, de la logistique,\n"
579-" de la gestion de projets , la production, les services, "
580+" de la gestion de projets, la production, les services, "
581 "la gestion de la relation client, etc.\n"
582 " "
583
584
585=== modified file 'addons/openerp/static/css/database.css'
586--- addons/openerp/static/css/database.css 2012-11-15 08:31:02 +0000
587+++ addons/openerp/static/css/database.css 2017-11-21 17:05:56 +0000
588@@ -69,3 +69,27 @@
589 text-align: right;
590 padding: 0 5px 0 0;
591 }
592+
593+.instance_creation_progress {
594+ width: 20%;
595+ margin-left: 40%;
596+ margin-top: 2em;
597+ margin-bottom: 2em;
598+ border: solid 1px lightslategrey;
599+}
600+
601+.instance_creation_progress .progressbar {
602+ background-color: #4CAF50;
603+ text-align: center;
604+ font-size: 15px;
605+ line-height: 2;
606+}
607+
608+.auto_creation_resume {
609+ text-align: center;
610+}
611+
612+.auto_instance_text {
613+ font-size: 1.5em;
614+ margin: 3em;
615+}

Subscribers

People subscribed via source and target branches