Merge branch 'Develop' into master
This commit is contained in:
		
						commit
						b2a28cd39a
					
				|  | @ -83,7 +83,9 @@ log = logger.create() | ||||||
| 
 | 
 | ||||||
| from . import services | from . import services | ||||||
| 
 | 
 | ||||||
| db.CalibreDB.setup_db(config, cli.settingspath) | db.CalibreDB.update_config(config) | ||||||
|  | db.CalibreDB.setup_db(config.config_calibre_dir, cli.settingspath) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| calibre_db = db.CalibreDB() | calibre_db = db.CalibreDB() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										245
									
								
								cps/admin.py
									
									
									
									
									
								
							
							
						
						
									
										245
									
								
								cps/admin.py
									
									
									
									
									
								
							|  | @ -40,7 +40,7 @@ from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError | ||||||
| from sqlalchemy.sql.expression import func, or_, text | from sqlalchemy.sql.expression import func, or_, text | ||||||
| 
 | 
 | ||||||
| from . import constants, logger, helper, services | from . import constants, logger, helper, services | ||||||
| from .cli import filepicker | # from .cli import filepicker | ||||||
| from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils | from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils | ||||||
| from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \ | from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \ | ||||||
|     valid_email, check_username |     valid_email, check_username | ||||||
|  | @ -97,19 +97,6 @@ def admin_required(f): | ||||||
|     return inner |     return inner | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def unconfigured(f): |  | ||||||
|     """ |  | ||||||
|     Checks if calibre-web instance is not configured |  | ||||||
|     """ |  | ||||||
|     @wraps(f) |  | ||||||
|     def inner(*args, **kwargs): |  | ||||||
|         if not config.db_configured: |  | ||||||
|             return f(*args, **kwargs) |  | ||||||
|         abort(403) |  | ||||||
| 
 |  | ||||||
|     return inner |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @admi.before_app_request | @admi.before_app_request | ||||||
| def before_request(): | def before_request(): | ||||||
|     if current_user.is_authenticated: |     if current_user.is_authenticated: | ||||||
|  | @ -124,10 +111,14 @@ def before_request(): | ||||||
|     g.shelves_access = ub.session.query(ub.Shelf).filter( |     g.shelves_access = ub.session.query(ub.Shelf).filter( | ||||||
|         or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all() |         or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all() | ||||||
|     if '/static/' not in request.path and not config.db_configured and \ |     if '/static/' not in request.path and not config.db_configured and \ | ||||||
|         request.endpoint not in ('admin.basic_configuration', |         request.endpoint not in ('admin.ajax_db_config', | ||||||
|                                  'login', |                                  'admin.simulatedbchange', | ||||||
|                                  'admin.config_pathchooser'): |                                  'admin.db_configuration', | ||||||
|         return redirect(url_for('admin.basic_configuration')) |                                  'web.login', | ||||||
|  |                                  'web.logout', | ||||||
|  |                                  'admin.load_dialogtexts', | ||||||
|  |                                  'admin.ajax_pathchooser'): | ||||||
|  |         return redirect(url_for('admin.db_configuration')) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @admi.route("/admin") | @admi.route("/admin") | ||||||
|  | @ -194,16 +185,46 @@ def admin(): | ||||||
|                                  feature_support=feature_support, kobo_support=kobo_support, |                                  feature_support=feature_support, kobo_support=kobo_support, | ||||||
|                                  title=_(u"Admin page"), page="admin") |                                  title=_(u"Admin page"), page="admin") | ||||||
| 
 | 
 | ||||||
|  | @admi.route("/admin/dbconfig", methods=["GET", "POST"]) | ||||||
|  | @login_required | ||||||
|  | @admin_required | ||||||
|  | def db_configuration(): | ||||||
|  |     if request.method == "POST": | ||||||
|  |         return _db_configuration_update_helper() | ||||||
|  |     return _db_configuration_result() | ||||||
| 
 | 
 | ||||||
| @admi.route("/admin/config", methods=["GET", "POST"]) | 
 | ||||||
|  | @admi.route("/admin/config", methods=["GET"]) | ||||||
| @login_required | @login_required | ||||||
| @admin_required | @admin_required | ||||||
| def configuration(): | def configuration(): | ||||||
|     if request.method == "POST": |     return render_title_template("config_edit.html", | ||||||
|         return _configuration_update_helper(True) |                                  config=config, | ||||||
|     return _configuration_result() |                                  provider=oauthblueprints, | ||||||
|  |                                  feature_support=feature_support, | ||||||
|  |                                  title=_(u"Basic Configuration"), page="config") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @admi.route("/admin/ajaxconfig", methods=["POST"]) | ||||||
|  | @login_required | ||||||
|  | @admin_required | ||||||
|  | def ajax_config(): | ||||||
|  |     return _configuration_update_helper() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @admi.route("/admin/ajaxdbconfig", methods=["POST"]) | ||||||
|  | @login_required | ||||||
|  | @admin_required | ||||||
|  | def ajax_db_config(): | ||||||
|  |     return _db_configuration_update_helper() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @admi.route("/admin/alive", methods=["GET"]) | ||||||
|  | @login_required | ||||||
|  | @admin_required | ||||||
|  | def calibreweb_alive(): | ||||||
|  |     return "", 200 | ||||||
|  | 
 | ||||||
| @admi.route("/admin/viewconfig") | @admi.route("/admin/viewconfig") | ||||||
| @login_required | @login_required | ||||||
| @admin_required | @admin_required | ||||||
|  | @ -539,10 +560,10 @@ def update_view_configuration(): | ||||||
|     return view_configuration() |     return view_configuration() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @admi.route("/ajax/loaddialogtexts/<element_id>") | @admi.route("/ajax/loaddialogtexts/<element_id>", methods=['POST']) | ||||||
| @login_required | @login_required | ||||||
| def load_dialogtexts(element_id): | def load_dialogtexts(element_id): | ||||||
|     texts = {"header": "", "main": ""} |     texts = {"header": "", "main": "", "valid": 1} | ||||||
|     if element_id == "config_delete_kobo_token": |     if element_id == "config_delete_kobo_token": | ||||||
|         texts["main"] = _('Do you really want to delete the Kobo Token?') |         texts["main"] = _('Do you really want to delete the Kobo Token?') | ||||||
|     elif element_id == "btndeletedomain": |     elif element_id == "btndeletedomain": | ||||||
|  | @ -563,6 +584,8 @@ def load_dialogtexts(element_id): | ||||||
|         texts["main"] = _('Are you sure you want to change the selected visibility restrictions for the selected user(s)?') |         texts["main"] = _('Are you sure you want to change the selected visibility restrictions for the selected user(s)?') | ||||||
|     elif element_id == "kobo_only_shelves_sync": |     elif element_id == "kobo_only_shelves_sync": | ||||||
|         texts["main"] = _('Are you sure you want to change shelf sync behavior for the selected user(s)?') |         texts["main"] = _('Are you sure you want to change shelf sync behavior for the selected user(s)?') | ||||||
|  |     elif element_id == "db_submit": | ||||||
|  |         texts["main"] = _('Are you sure you want to change Calibre libray location?') | ||||||
|     return json.dumps(texts) |     return json.dumps(texts) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -867,14 +890,6 @@ def list_restriction(res_type, user_id): | ||||||
|     return response |     return response | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @admi.route("/basicconfig/pathchooser/") |  | ||||||
| @unconfigured |  | ||||||
| def config_pathchooser(): |  | ||||||
|     if filepicker: |  | ||||||
|         return pathchooser() |  | ||||||
|     abort(403) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @admi.route("/ajax/pathchooser/") | @admi.route("/ajax/pathchooser/") | ||||||
| @login_required | @login_required | ||||||
| @admin_required | @admin_required | ||||||
|  | @ -963,16 +978,6 @@ def pathchooser(): | ||||||
|     return json.dumps(context) |     return json.dumps(context) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @admi.route("/basicconfig", methods=["GET", "POST"]) |  | ||||||
| @unconfigured |  | ||||||
| def basic_configuration(): |  | ||||||
|     logout_user() |  | ||||||
|     if request.method == "POST": |  | ||||||
|         log.debug("Basic Configuration send") |  | ||||||
|         return _configuration_update_helper(configured=filepicker) |  | ||||||
|     return _configuration_result(configured=filepicker) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def _config_int(to_save, x, func=int): | def _config_int(to_save, x, func=int): | ||||||
|     return config.set_from_dictionary(to_save, x, func) |     return config.set_from_dictionary(to_save, x, func) | ||||||
| 
 | 
 | ||||||
|  | @ -991,6 +996,7 @@ def _config_string(to_save, x): | ||||||
| 
 | 
 | ||||||
| def _configuration_gdrive_helper(to_save): | def _configuration_gdrive_helper(to_save): | ||||||
|     gdrive_error = None |     gdrive_error = None | ||||||
|  |     if to_save.get("config_use_google_drive"): | ||||||
|         gdrive_secrets = {} |         gdrive_secrets = {} | ||||||
| 
 | 
 | ||||||
|         if not os.path.isfile(gdriveutils.SETTINGS_YAML): |         if not os.path.isfile(gdriveutils.SETTINGS_YAML): | ||||||
|  | @ -1041,23 +1047,23 @@ def _configuration_oauth_helper(to_save): | ||||||
|     return reboot_required |     return reboot_required | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _configuration_logfile_helper(to_save, gdrive_error): | def _configuration_logfile_helper(to_save): | ||||||
|     reboot_required = False |     reboot_required = False | ||||||
|     reboot_required |= _config_int(to_save, "config_log_level") |     reboot_required |= _config_int(to_save, "config_log_level") | ||||||
|     reboot_required |= _config_string(to_save, "config_logfile") |     reboot_required |= _config_string(to_save, "config_logfile") | ||||||
|     if not logger.is_valid_logfile(config.config_logfile): |     if not logger.is_valid_logfile(config.config_logfile): | ||||||
|         return reboot_required, \ |         return reboot_required, \ | ||||||
|                _configuration_result(_('Logfile Location is not Valid, Please Enter Correct Path'), gdrive_error) |                _configuration_result(_('Logfile Location is not Valid, Please Enter Correct Path')) | ||||||
| 
 | 
 | ||||||
|     reboot_required |= _config_checkbox_int(to_save, "config_access_log") |     reboot_required |= _config_checkbox_int(to_save, "config_access_log") | ||||||
|     reboot_required |= _config_string(to_save, "config_access_logfile") |     reboot_required |= _config_string(to_save, "config_access_logfile") | ||||||
|     if not logger.is_valid_logfile(config.config_access_logfile): |     if not logger.is_valid_logfile(config.config_access_logfile): | ||||||
|         return reboot_required, \ |         return reboot_required, \ | ||||||
|                _configuration_result(_('Access Logfile Location is not Valid, Please Enter Correct Path'), gdrive_error) |                _configuration_result(_('Access Logfile Location is not Valid, Please Enter Correct Path')) | ||||||
|     return reboot_required, None |     return reboot_required, None | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _configuration_ldap_helper(to_save, gdrive_error): | def _configuration_ldap_helper(to_save): | ||||||
|     reboot_required = False |     reboot_required = False | ||||||
|     reboot_required |= _config_string(to_save, "config_ldap_provider_url") |     reboot_required |= _config_string(to_save, "config_ldap_provider_url") | ||||||
|     reboot_required |= _config_int(to_save, "config_ldap_port") |     reboot_required |= _config_int(to_save, "config_ldap_port") | ||||||
|  | @ -1084,44 +1090,37 @@ def _configuration_ldap_helper(to_save, gdrive_error): | ||||||
|         or not config.config_ldap_dn \ |         or not config.config_ldap_dn \ | ||||||
|         or not config.config_ldap_user_object: |         or not config.config_ldap_user_object: | ||||||
|         return reboot_required, _configuration_result(_('Please Enter a LDAP Provider, ' |         return reboot_required, _configuration_result(_('Please Enter a LDAP Provider, ' | ||||||
|                                                         'Port, DN and User Object Identifier'), gdrive_error) |                                                         'Port, DN and User Object Identifier')) | ||||||
| 
 | 
 | ||||||
|     if config.config_ldap_authentication > constants.LDAP_AUTH_ANONYMOUS: |     if config.config_ldap_authentication > constants.LDAP_AUTH_ANONYMOUS: | ||||||
|         if config.config_ldap_authentication > constants.LDAP_AUTH_UNAUTHENTICATE: |         if config.config_ldap_authentication > constants.LDAP_AUTH_UNAUTHENTICATE: | ||||||
|             if not config.config_ldap_serv_username or not bool(config.config_ldap_serv_password): |             if not config.config_ldap_serv_username or not bool(config.config_ldap_serv_password): | ||||||
|                 return reboot_required, _configuration_result('Please Enter a LDAP Service Account and Password', |                 return reboot_required, _configuration_result(_('Please Enter a LDAP Service Account and Password')) | ||||||
|                                                               gdrive_error) |  | ||||||
|         else: |         else: | ||||||
|             if not config.config_ldap_serv_username: |             if not config.config_ldap_serv_username: | ||||||
|                 return reboot_required, _configuration_result('Please Enter a LDAP Service Account', gdrive_error) |                 return reboot_required, _configuration_result(_('Please Enter a LDAP Service Account')) | ||||||
| 
 | 
 | ||||||
|     if config.config_ldap_group_object_filter: |     if config.config_ldap_group_object_filter: | ||||||
|         if config.config_ldap_group_object_filter.count("%s") != 1: |         if config.config_ldap_group_object_filter.count("%s") != 1: | ||||||
|             return reboot_required, \ |             return reboot_required, \ | ||||||
|                    _configuration_result(_('LDAP Group Object Filter Needs to Have One "%s" Format Identifier'), |                    _configuration_result(_('LDAP Group Object Filter Needs to Have One "%s" Format Identifier')) | ||||||
|                                          gdrive_error) |  | ||||||
|         if config.config_ldap_group_object_filter.count("(") != config.config_ldap_group_object_filter.count(")"): |         if config.config_ldap_group_object_filter.count("(") != config.config_ldap_group_object_filter.count(")"): | ||||||
|             return reboot_required, _configuration_result(_('LDAP Group Object Filter Has Unmatched Parenthesis'), |             return reboot_required, _configuration_result(_('LDAP Group Object Filter Has Unmatched Parenthesis')) | ||||||
|                                                           gdrive_error) |  | ||||||
| 
 | 
 | ||||||
|     if config.config_ldap_user_object.count("%s") != 1: |     if config.config_ldap_user_object.count("%s") != 1: | ||||||
|         return reboot_required, \ |         return reboot_required, \ | ||||||
|                _configuration_result(_('LDAP User Object Filter needs to Have One "%s" Format Identifier'), |                _configuration_result(_('LDAP User Object Filter needs to Have One "%s" Format Identifier')) | ||||||
|                                      gdrive_error) |  | ||||||
|     if config.config_ldap_user_object.count("(") != config.config_ldap_user_object.count(")"): |     if config.config_ldap_user_object.count("(") != config.config_ldap_user_object.count(")"): | ||||||
|         return reboot_required, _configuration_result(_('LDAP User Object Filter Has Unmatched Parenthesis'), |         return reboot_required, _configuration_result(_('LDAP User Object Filter Has Unmatched Parenthesis')) | ||||||
|                                                       gdrive_error) |  | ||||||
| 
 | 
 | ||||||
|     if to_save.get("ldap_import_user_filter") == '0': |     if to_save.get("ldap_import_user_filter") == '0': | ||||||
|         config.config_ldap_member_user_object = "" |         config.config_ldap_member_user_object = "" | ||||||
|     else: |     else: | ||||||
|         if config.config_ldap_member_user_object.count("%s") != 1: |         if config.config_ldap_member_user_object.count("%s") != 1: | ||||||
|             return reboot_required, \ |             return reboot_required, \ | ||||||
|                    _configuration_result(_('LDAP Member User Filter needs to Have One "%s" Format Identifier'), |                    _configuration_result(_('LDAP Member User Filter needs to Have One "%s" Format Identifier')) | ||||||
|                                          gdrive_error) |  | ||||||
|         if config.config_ldap_member_user_object.count("(") != config.config_ldap_member_user_object.count(")"): |         if config.config_ldap_member_user_object.count("(") != config.config_ldap_member_user_object.count(")"): | ||||||
|             return reboot_required, _configuration_result(_('LDAP Member User Filter Has Unmatched Parenthesis'), |             return reboot_required, _configuration_result(_('LDAP Member User Filter Has Unmatched Parenthesis')) | ||||||
|                                                           gdrive_error) |  | ||||||
| 
 | 
 | ||||||
|     if config.config_ldap_cacert_path or config.config_ldap_cert_path or config.config_ldap_key_path: |     if config.config_ldap_cacert_path or config.config_ldap_cert_path or config.config_ldap_key_path: | ||||||
|         if not (os.path.isfile(config.config_ldap_cacert_path) and |         if not (os.path.isfile(config.config_ldap_cacert_path) and | ||||||
|  | @ -1129,13 +1128,31 @@ def _configuration_ldap_helper(to_save, gdrive_error): | ||||||
|                 os.path.isfile(config.config_ldap_key_path)): |                 os.path.isfile(config.config_ldap_key_path)): | ||||||
|             return reboot_required, \ |             return reboot_required, \ | ||||||
|                    _configuration_result(_('LDAP CACertificate, Certificate or Key Location is not Valid, ' |                    _configuration_result(_('LDAP CACertificate, Certificate or Key Location is not Valid, ' | ||||||
|                                            'Please Enter Correct Path'), |                                            'Please Enter Correct Path')) | ||||||
|                                          gdrive_error) |  | ||||||
|     return reboot_required, None |     return reboot_required, None | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _configuration_update_helper(configured): | @admi.route("/ajax/simulatedbchange", methods=['POST']) | ||||||
|     reboot_required = False | @login_required | ||||||
|  | @admin_required | ||||||
|  | def simulatedbchange(): | ||||||
|  |     db_change, db_valid = _db_simulate_change() | ||||||
|  |     return Response(json.dumps({"change": db_change, "valid": db_valid}), mimetype='application/json') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def _db_simulate_change(): | ||||||
|  |     param = request.form.to_dict() | ||||||
|  |     to_save = {} | ||||||
|  |     to_save['config_calibre_dir'] = re.sub(r'[\\/]metadata\.db$', | ||||||
|  |                                            '', | ||||||
|  |                                            param['config_calibre_dir'], | ||||||
|  |                                            flags=re.IGNORECASE).strip() | ||||||
|  |     db_change = config.config_calibre_dir != to_save["config_calibre_dir"] and config.config_calibre_dir | ||||||
|  |     db_valid = calibre_db.check_valid_db(to_save["config_calibre_dir"], ub.app_DB_path) | ||||||
|  |     return db_change, db_valid | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def _db_configuration_update_helper(): | ||||||
|     db_change = False |     db_change = False | ||||||
|     to_save = request.form.to_dict() |     to_save = request.form.to_dict() | ||||||
|     gdrive_error = None |     gdrive_error = None | ||||||
|  | @ -1145,24 +1162,47 @@ def _configuration_update_helper(configured): | ||||||
|                                            to_save['config_calibre_dir'], |                                            to_save['config_calibre_dir'], | ||||||
|                                            flags=re.IGNORECASE) |                                            flags=re.IGNORECASE) | ||||||
|     try: |     try: | ||||||
|         db_change |= _config_string(to_save, "config_calibre_dir") |         db_change, db_valid = _db_simulate_change() | ||||||
| 
 | 
 | ||||||
|         # gdrive_error drive setup |         # gdrive_error drive setup | ||||||
|         gdrive_error = _configuration_gdrive_helper(to_save) |         gdrive_error = _configuration_gdrive_helper(to_save) | ||||||
|  |     except (OperationalError, InvalidRequestError): | ||||||
|  |         ub.session.rollback() | ||||||
|  |         log.error("Settings DB is not Writeable") | ||||||
|  |         _db_configuration_result(_("Settings DB is not Writeable"), gdrive_error) | ||||||
|  |     try: | ||||||
|  |         metadata_db = os.path.join(to_save['config_calibre_dir'], "metadata.db") | ||||||
|  |         if config.config_use_google_drive and is_gdrive_ready() and not os.path.exists(metadata_db): | ||||||
|  |             gdriveutils.downloadFile(None, "metadata.db", metadata_db) | ||||||
|  |             db_change = True | ||||||
|  |     except Exception as ex: | ||||||
|  |         return _db_configuration_result('{}'.format(ex), gdrive_error) | ||||||
| 
 | 
 | ||||||
|  |     if db_change or not db_valid or not config.db_configured: | ||||||
|  |         if not calibre_db.setup_db(to_save['config_calibre_dir'], ub.app_DB_path): | ||||||
|  |             return _db_configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), | ||||||
|  |                                             gdrive_error) | ||||||
|  |         _config_string(to_save, "config_calibre_dir") | ||||||
|  |         calibre_db.update_config(config) | ||||||
|  |         if not os.access(os.path.join(config.config_calibre_dir, "metadata.db"), os.W_OK): | ||||||
|  |             flash(_(u"DB is not Writeable"), category="warning") | ||||||
|  |             # warning = {'type': "warning", 'message': _(u"DB is not Writeable")} | ||||||
|  |     config.save() | ||||||
|  |     return _db_configuration_result(None, gdrive_error) | ||||||
|  | 
 | ||||||
|  | def _configuration_update_helper(): | ||||||
|  |     reboot_required = False | ||||||
|  |     to_save = request.form.to_dict() | ||||||
|  |     try: | ||||||
|         reboot_required |= _config_int(to_save, "config_port") |         reboot_required |= _config_int(to_save, "config_port") | ||||||
| 
 | 
 | ||||||
|         reboot_required |= _config_string(to_save, "config_keyfile") |         reboot_required |= _config_string(to_save, "config_keyfile") | ||||||
|         if config.config_keyfile and not os.path.isfile(config.config_keyfile): |         if config.config_keyfile and not os.path.isfile(config.config_keyfile): | ||||||
|             return _configuration_result(_('Keyfile Location is not Valid, Please Enter Correct Path'), |             return _configuration_result(_('Keyfile Location is not Valid, Please Enter Correct Path')) | ||||||
|                                          gdrive_error, |  | ||||||
|                                          configured) |  | ||||||
| 
 | 
 | ||||||
|         reboot_required |= _config_string(to_save, "config_certfile") |         reboot_required |= _config_string(to_save, "config_certfile") | ||||||
|         if config.config_certfile and not os.path.isfile(config.config_certfile): |         if config.config_certfile and not os.path.isfile(config.config_certfile): | ||||||
|             return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path'), |             return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path')) | ||||||
|                                          gdrive_error, |  | ||||||
|                                          configured) |  | ||||||
| 
 | 
 | ||||||
|         _config_checkbox_int(to_save, "config_uploading") |         _config_checkbox_int(to_save, "config_uploading") | ||||||
|         # Reboot on config_anonbrowse with enabled ldap, as decoraters are changed in this case |         # Reboot on config_anonbrowse with enabled ldap, as decoraters are changed in this case | ||||||
|  | @ -1186,15 +1226,14 @@ def _configuration_update_helper(configured): | ||||||
| 
 | 
 | ||||||
|         reboot_required |= _config_int(to_save, "config_login_type") |         reboot_required |= _config_int(to_save, "config_login_type") | ||||||
| 
 | 
 | ||||||
|         # LDAP configurator, |         # LDAP configurator | ||||||
|         if config.config_login_type == constants.LOGIN_LDAP: |         if config.config_login_type == constants.LOGIN_LDAP: | ||||||
|             reboot, message = _configuration_ldap_helper(to_save, gdrive_error) |             reboot, message = _configuration_ldap_helper(to_save) | ||||||
|             if message: |             if message: | ||||||
|                 return message |                 return message | ||||||
|             reboot_required |= reboot |             reboot_required |= reboot | ||||||
| 
 | 
 | ||||||
|         # Remote login configuration |         # Remote login configuration | ||||||
| 
 |  | ||||||
|         _config_checkbox(to_save, "config_remote_login") |         _config_checkbox(to_save, "config_remote_login") | ||||||
|         if not config.config_remote_login: |         if not config.config_remote_login: | ||||||
|             ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.token_type == 0).delete() |             ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.token_type == 0).delete() | ||||||
|  | @ -1218,7 +1257,7 @@ def _configuration_update_helper(configured): | ||||||
|         if config.config_login_type == constants.LOGIN_OAUTH: |         if config.config_login_type == constants.LOGIN_OAUTH: | ||||||
|             reboot_required |= _configuration_oauth_helper(to_save) |             reboot_required |= _configuration_oauth_helper(to_save) | ||||||
| 
 | 
 | ||||||
|         reboot, message = _configuration_logfile_helper(to_save, gdrive_error) |         reboot, message = _configuration_logfile_helper(to_save) | ||||||
|         if message: |         if message: | ||||||
|             return message |             return message | ||||||
|         reboot_required |= reboot |         reboot_required |= reboot | ||||||
|  | @ -1227,67 +1266,55 @@ def _configuration_update_helper(configured): | ||||||
|         if "config_rarfile_location" in to_save: |         if "config_rarfile_location" in to_save: | ||||||
|             unrar_status = helper.check_unrar(config.config_rarfile_location) |             unrar_status = helper.check_unrar(config.config_rarfile_location) | ||||||
|             if unrar_status: |             if unrar_status: | ||||||
|                 return _configuration_result(unrar_status, gdrive_error, configured) |                 return _configuration_result(unrar_status) | ||||||
|     except (OperationalError, InvalidRequestError): |     except (OperationalError, InvalidRequestError): | ||||||
|         ub.session.rollback() |         ub.session.rollback() | ||||||
|         log.error("Settings DB is not Writeable") |         log.error("Settings DB is not Writeable") | ||||||
|         _configuration_result(_("Settings DB is not Writeable"), gdrive_error, configured) |         _configuration_result(_("Settings DB is not Writeable")) | ||||||
| 
 |  | ||||||
|     try: |  | ||||||
|         metadata_db = os.path.join(config.config_calibre_dir, "metadata.db") |  | ||||||
|         if config.config_use_google_drive and is_gdrive_ready() and not os.path.exists(metadata_db): |  | ||||||
|             gdriveutils.downloadFile(None, "metadata.db", metadata_db) |  | ||||||
|             db_change = True |  | ||||||
|     except Exception as ex: |  | ||||||
|         return _configuration_result('%s' % ex, gdrive_error, configured) |  | ||||||
| 
 |  | ||||||
|     if db_change: |  | ||||||
|         if not calibre_db.setup_db(config, ub.app_DB_path): |  | ||||||
|             return _configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), |  | ||||||
|                                          gdrive_error, |  | ||||||
|                                          configured) |  | ||||||
|         if not os.access(os.path.join(config.config_calibre_dir, "metadata.db"), os.W_OK): |  | ||||||
|             flash(_(u"DB is not Writeable"), category="warning") |  | ||||||
| 
 | 
 | ||||||
|     config.save() |     config.save() | ||||||
|     flash(_(u"Calibre-Web configuration updated"), category="success") |  | ||||||
|     if reboot_required: |     if reboot_required: | ||||||
|         web_server.stop(True) |         web_server.stop(True) | ||||||
| 
 | 
 | ||||||
|     return _configuration_result(None, gdrive_error, configured) |     return _configuration_result(None, reboot_required) | ||||||
|  | 
 | ||||||
|  | def _configuration_result(error_flash=None, reboot=False): | ||||||
|  |     resp = {} | ||||||
|  |     if error_flash: | ||||||
|  |         log.error(error_flash) | ||||||
|  |         config.load() | ||||||
|  |         resp['result'] = [{'type': "danger", 'message': error_flash}] | ||||||
|  |     else: | ||||||
|  |         resp['result'] = [{'type': "success", 'message':_(u"Calibre-Web configuration updated")}] | ||||||
|  |     resp['reboot'] = reboot | ||||||
|  |     resp['config_upload']= config.config_upload_formats | ||||||
|  |     return Response(json.dumps(resp), mimetype='application/json') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _configuration_result(error_flash=None, gdrive_error=None, configured=True): | def _db_configuration_result(error_flash=None, gdrive_error=None): | ||||||
|     gdrive_authenticate = not is_gdrive_ready() |     gdrive_authenticate = not is_gdrive_ready() | ||||||
|     gdrivefolders = [] |     gdrivefolders = [] | ||||||
|     if gdrive_error is None: |     if not gdrive_error and config.config_use_google_drive: | ||||||
|         gdrive_error = gdriveutils.get_error_text() |         gdrive_error = gdriveutils.get_error_text() | ||||||
|     if gdrive_error and gdrive_support: |     if gdrive_error and gdrive_support: | ||||||
|         log.error(gdrive_error) |         log.error(gdrive_error) | ||||||
|         gdrive_error = _(gdrive_error) |         gdrive_error = _(gdrive_error) | ||||||
|  |         flash(gdrive_error, category="error") | ||||||
|     else: |     else: | ||||||
|         if not gdrive_authenticate and gdrive_support: |         if not gdrive_authenticate and gdrive_support: | ||||||
|             gdrivefolders = gdriveutils.listRootFolders() |             gdrivefolders = gdriveutils.listRootFolders() | ||||||
| 
 |  | ||||||
|     show_back_button = current_user.is_authenticated |  | ||||||
|     show_login_button = config.db_configured and not current_user.is_authenticated |  | ||||||
|     if error_flash: |     if error_flash: | ||||||
|         log.error(error_flash) |         log.error(error_flash) | ||||||
|         config.load() |         config.load() | ||||||
|         flash(error_flash, category="error") |         flash(error_flash, category="error") | ||||||
|         show_login_button = False |  | ||||||
| 
 | 
 | ||||||
|     return render_title_template("config_edit.html", |     return render_title_template("config_db.html", | ||||||
|                                  config=config, |                                  config=config, | ||||||
|                                  provider=oauthblueprints, |  | ||||||
|                                  show_back_button=show_back_button, |  | ||||||
|                                  show_login_button=show_login_button, |  | ||||||
|                                  show_authenticate_google_drive=gdrive_authenticate, |                                  show_authenticate_google_drive=gdrive_authenticate, | ||||||
|                                  filepicker=configured, |  | ||||||
|                                  gdriveError=gdrive_error, |                                  gdriveError=gdrive_error, | ||||||
|                                  gdrivefolders=gdrivefolders, |                                  gdrivefolders=gdrivefolders, | ||||||
|                                  feature_support=feature_support, |                                  feature_support=feature_support, | ||||||
|                                  title=_(u"Basic Configuration"), page="config") |                                  title=_(u"Database Configuration"), page="dbconfig") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _handle_new_user(to_save, content, languages, translations, kobo_support): | def _handle_new_user(to_save, content, languages, translations, kobo_support): | ||||||
|  |  | ||||||
|  | @ -45,7 +45,6 @@ parser.add_argument('-v', '--version', action='version', help='Shows version num | ||||||
|                     version=version_info()) |                     version=version_info()) | ||||||
| parser.add_argument('-i', metavar='ip-address', help='Server IP-Address to listen') | parser.add_argument('-i', metavar='ip-address', help='Server IP-Address to listen') | ||||||
| parser.add_argument('-s', metavar='user:pass', help='Sets specific username to new password') | parser.add_argument('-s', metavar='user:pass', help='Sets specific username to new password') | ||||||
| parser.add_argument('-f', action='store_true', help='Enables filepicker in unconfigured mode') |  | ||||||
| args = parser.parse_args() | args = parser.parse_args() | ||||||
| 
 | 
 | ||||||
| if sys.version_info < (3, 0): | if sys.version_info < (3, 0): | ||||||
|  | @ -114,6 +113,3 @@ user_credentials = args.s or None | ||||||
| if user_credentials and ":" not in user_credentials: | if user_credentials and ":" not in user_credentials: | ||||||
|     print("No valid 'username:password' format") |     print("No valid 'username:password' format") | ||||||
|     sys.exit(3) |     sys.exit(3) | ||||||
| 
 |  | ||||||
| # Handles enabling of filepicker |  | ||||||
| filepicker = args.f or None |  | ||||||
|  |  | ||||||
|  | @ -347,7 +347,7 @@ class _ConfigSQL(object): | ||||||
|             log.error(error) |             log.error(error) | ||||||
|         log.warning("invalidating configuration") |         log.warning("invalidating configuration") | ||||||
|         self.db_configured = False |         self.db_configured = False | ||||||
|         self.config_calibre_dir = None |         # self.config_calibre_dir = None | ||||||
|         self.save() |         self.save() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										42
									
								
								cps/db.py
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								cps/db.py
									
									
									
									
									
								
							|  | @ -524,19 +524,44 @@ class CalibreDB(): | ||||||
|         return cc_classes |         return cc_classes | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def setup_db(cls, config, app_db_path): |     def check_valid_db(cls, config_calibre_dir, app_db_path): | ||||||
|  |         if not config_calibre_dir: | ||||||
|  |             return False | ||||||
|  |         dbpath = os.path.join(config_calibre_dir, "metadata.db") | ||||||
|  |         if not os.path.exists(dbpath): | ||||||
|  |             return False | ||||||
|  |         try: | ||||||
|  |             check_engine = create_engine('sqlite://', | ||||||
|  |                           echo=False, | ||||||
|  |                           isolation_level="SERIALIZABLE", | ||||||
|  |                           connect_args={'check_same_thread': False}, | ||||||
|  |                           poolclass=StaticPool) | ||||||
|  |             with check_engine.begin() as connection: | ||||||
|  |                 connection.execute(text("attach database '{}' as calibre;".format(dbpath))) | ||||||
|  |                 connection.execute(text("attach database '{}' as app_settings;".format(app_db_path))) | ||||||
|  |             check_engine.connect() | ||||||
|  |         except Exception: | ||||||
|  |             return False | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def update_config(cls, config): | ||||||
|         cls.config = config |         cls.config = config | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def setup_db(cls, config_calibre_dir, app_db_path): | ||||||
|  |         # cls.config = config | ||||||
|         cls.dispose() |         cls.dispose() | ||||||
| 
 | 
 | ||||||
|         # toDo: if db changed -> delete shelfs, delete download books, delete read boks, kobo sync?? |         # toDo: if db changed -> delete shelfs, delete download books, delete read boks, kobo sync?? | ||||||
| 
 | 
 | ||||||
|         if not config.config_calibre_dir: |         if not config_calibre_dir: | ||||||
|             config.invalidate() |             cls.config.invalidate() | ||||||
|             return False |             return False | ||||||
| 
 | 
 | ||||||
|         dbpath = os.path.join(config.config_calibre_dir, "metadata.db") |         dbpath = os.path.join(config_calibre_dir, "metadata.db") | ||||||
|         if not os.path.exists(dbpath): |         if not os.path.exists(dbpath): | ||||||
|             config.invalidate() |             cls.config.invalidate() | ||||||
|             return False |             return False | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|  | @ -552,10 +577,10 @@ class CalibreDB(): | ||||||
|             conn = cls.engine.connect() |             conn = cls.engine.connect() | ||||||
|             # conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302 |             # conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302 | ||||||
|         except Exception as ex: |         except Exception as ex: | ||||||
|             config.invalidate(ex) |             cls.config.invalidate(ex) | ||||||
|             return False |             return False | ||||||
| 
 | 
 | ||||||
|         config.db_configured = True |         cls.config.db_configured = True | ||||||
| 
 | 
 | ||||||
|         if not cc_classes: |         if not cc_classes: | ||||||
|             try: |             try: | ||||||
|  | @ -828,7 +853,8 @@ class CalibreDB(): | ||||||
|     def reconnect_db(self, config, app_db_path): |     def reconnect_db(self, config, app_db_path): | ||||||
|         self.dispose() |         self.dispose() | ||||||
|         self.engine.dispose() |         self.engine.dispose() | ||||||
|         self.setup_db(config, app_db_path) |         self.setup_db(config.config_calibre_dir, app_db_path) | ||||||
|  |         self.update_config(config) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def lcase(s): | def lcase(s): | ||||||
|  |  | ||||||
|  | @ -35,6 +35,7 @@ def error_http(error): | ||||||
|                            error_code="Error {0}".format(error.code), |                            error_code="Error {0}".format(error.code), | ||||||
|                            error_name=error.name, |                            error_name=error.name, | ||||||
|                            issue=False, |                            issue=False, | ||||||
|  |                            unconfigured=not config.db_configured, | ||||||
|                            instance=config.config_calibre_web_title |                            instance=config.config_calibre_web_title | ||||||
|                            ), error.code |                            ), error.code | ||||||
| 
 | 
 | ||||||
|  | @ -44,6 +45,7 @@ def internal_error(error): | ||||||
|                            error_code="Internal Server Error", |                            error_code="Internal Server Error", | ||||||
|                            error_name=str(error), |                            error_name=str(error), | ||||||
|                            issue=True, |                            issue=True, | ||||||
|  |                            unconfigured=False, | ||||||
|                            error_stack=traceback.format_exc().split("\n"), |                            error_stack=traceback.format_exc().split("\n"), | ||||||
|                            instance=config.config_calibre_web_title |                            instance=config.config_calibre_web_title | ||||||
|                            ), 500 |                            ), 500 | ||||||
|  |  | ||||||
|  | @ -74,7 +74,7 @@ def google_drive_callback(): | ||||||
|             f.write(credentials.to_json()) |             f.write(credentials.to_json()) | ||||||
|     except (ValueError, AttributeError) as error: |     except (ValueError, AttributeError) as error: | ||||||
|         log.error(error) |         log.error(error) | ||||||
|     return redirect(url_for('admin.configuration')) |     return redirect(url_for('admin.db_configuration')) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @gdrive.route("/watch/subscribe") | @gdrive.route("/watch/subscribe") | ||||||
|  | @ -99,7 +99,7 @@ def watch_gdrive(): | ||||||
|             else: |             else: | ||||||
|                 flash(reason['message'], category="error") |                 flash(reason['message'], category="error") | ||||||
| 
 | 
 | ||||||
|     return redirect(url_for('admin.configuration')) |     return redirect(url_for('admin.db_configuration')) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @gdrive.route("/watch/revoke") | @gdrive.route("/watch/revoke") | ||||||
|  | @ -115,7 +115,7 @@ def revoke_watch_gdrive(): | ||||||
|             pass |             pass | ||||||
|         config.config_google_drive_watch_changes_response = {} |         config.config_google_drive_watch_changes_response = {} | ||||||
|         config.save() |         config.save() | ||||||
|     return redirect(url_for('admin.configuration')) |     return redirect(url_for('admin.db_configuration')) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @gdrive.route("/watch/callback", methods=['GET', 'POST']) | @gdrive.route("/watch/callback", methods=['GET', 'POST']) | ||||||
|  |  | ||||||
|  | @ -417,3 +417,9 @@ div.log { | ||||||
|   white-space: nowrap; |   white-space: nowrap; | ||||||
|   padding: 0.5em; |   padding: 0.5em; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #detailcover { cursor:zoom-in; } | ||||||
|  | #detailcover:-webkit-full-screen { cursor:zoom-out; } | ||||||
|  | #detailcover:-moz-full-screen { cursor:zoom-out; } | ||||||
|  | #detailcover:-ms-fullscreen { cursor:zoom-out; } | ||||||
|  | #detailcover:fullscreen { cursor:zoom-out; } | ||||||
|  |  | ||||||
							
								
								
									
										45
									
								
								cps/static/js/fullscreen.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								cps/static/js/fullscreen.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | ||||||
|  | /* This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) | ||||||
|  |  *    Copyright (C) 2021  OzzieIsaacs | ||||||
|  |  * | ||||||
|  |  *  This program is free software: you can redistribute it and/or modify | ||||||
|  |  *  it under the terms of the GNU General Public License as published by | ||||||
|  |  *  the Free Software Foundation, either version 3 of the License, or | ||||||
|  |  *  (at your option) any later version. | ||||||
|  |  * | ||||||
|  |  *  This program is distributed in the hope that it will be useful, | ||||||
|  |  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  *  GNU General Public License for more details. | ||||||
|  |  * | ||||||
|  |  *  You should have received a copy of the GNU General Public License | ||||||
|  |  *  along with this program. If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | function toggleFullscreen(elem) { | ||||||
|  |   if (!document.fullscreenElement && !document.mozFullScreenElement && | ||||||
|  |     !document.webkitFullscreenElement && !document.msFullscreenElement) { | ||||||
|  |     if (elem.requestFullscreen) { | ||||||
|  |       elem.requestFullscreen(); | ||||||
|  |     } else if (elem.msRequestFullscreen) { | ||||||
|  |       elem.msRequestFullscreen(); | ||||||
|  |     } else if (elem.mozRequestFullScreen) { | ||||||
|  |       elem.mozRequestFullScreen(); | ||||||
|  |     } else if (elem.webkitRequestFullscreen) { | ||||||
|  |       elem.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     if (document.exitFullscreen) { | ||||||
|  |       document.exitFullscreen(); | ||||||
|  |     } else if (document.msExitFullscreen) { | ||||||
|  |       document.msExitFullscreen(); | ||||||
|  |     } else if (document.mozCancelFullScreen) { | ||||||
|  |       document.mozCancelFullScreen(); | ||||||
|  |     } else if (document.webkitExitFullscreen) { | ||||||
|  |       document.webkitExitFullscreen(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | $("#detailcover").click(function() { | ||||||
|  |   toggleFullscreen(this); | ||||||
|  | }); | ||||||
|  | @ -141,7 +141,7 @@ function confirmDialog(id, dialogid, dataValue, yesFn, noFn) { | ||||||
|         $confirm.modal("hide"); |         $confirm.modal("hide"); | ||||||
|     }); |     }); | ||||||
|     $.ajax({ |     $.ajax({ | ||||||
|         method:"get", |         method:"post", | ||||||
|         dataType: "json", |         dataType: "json", | ||||||
|         url: getPath() + "/ajax/loaddialogtexts/" + id, |         url: getPath() + "/ajax/loaddialogtexts/" + id, | ||||||
|         success: function success(data) { |         success: function success(data) { | ||||||
|  | @ -179,18 +179,6 @@ $("#delete_confirm").click(function() { | ||||||
|                         } |                         } | ||||||
|                     }); |                     }); | ||||||
|                     $("#books-table").bootstrapTable("refresh"); |                     $("#books-table").bootstrapTable("refresh"); | ||||||
|                     /*$.ajax({ |  | ||||||
|                         method:"get", |  | ||||||
|                         url: window.location.pathname + "/../../ajax/listbooks", |  | ||||||
|                         async: true, |  | ||||||
|                         timeout: 900, |  | ||||||
|                         success:function(data) { |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|                             $("#book-table").bootstrapTable("load", data); |  | ||||||
|                             loadSuccess(); |  | ||||||
|                         } |  | ||||||
|                     });*/ |  | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|         } else { |         } else { | ||||||
|  | @ -218,8 +206,6 @@ $("#deleteModal").on("show.bs.modal", function(e) { | ||||||
|     $(e.currentTarget).find("#delete_confirm").data("ajax", $(e.relatedTarget).data("ajax")); |     $(e.currentTarget).find("#delete_confirm").data("ajax", $(e.relatedTarget).data("ajax")); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| $(function() { | $(function() { | ||||||
|     var updateTimerID; |     var updateTimerID; | ||||||
|     var updateText; |     var updateText; | ||||||
|  | @ -556,6 +542,86 @@ $(function() { | ||||||
|         this.closest("form").submit(); |         this.closest("form").submit(); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|  |     function handle_response(data) { | ||||||
|  |         if (!jQuery.isEmptyObject(data)) { | ||||||
|  |             data.forEach(function (item) { | ||||||
|  |                 $(".navbar").after('<div class="row-fluid text-center" style="margin-top: -20px;">' + | ||||||
|  |                     '<div id="flash_' + item.type + '" class="alert alert-' + item.type + '">' + item.message + '</div>' + | ||||||
|  |                     '</div>'); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $('.collapse').on('shown.bs.collapse', function(){ | ||||||
|  |         $(this).parent().find(".glyphicon-plus").removeClass("glyphicon-plus").addClass("glyphicon-minus"); | ||||||
|  |     }).on('hidden.bs.collapse', function(){ | ||||||
|  |     $(this).parent().find(".glyphicon-minus").removeClass("glyphicon-minus").addClass("glyphicon-plus"); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     function changeDbSettings() { | ||||||
|  |         $("#db_submit").closest('form').submit(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $("#db_submit").click(function(e) { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         e.stopPropagation(); | ||||||
|  |         this.blur(); | ||||||
|  |         $.ajax({ | ||||||
|  |             method:"post", | ||||||
|  |             dataType: "json", | ||||||
|  |             url: window.location.pathname + "/../../ajax/simulatedbchange", | ||||||
|  |             data: {config_calibre_dir: $("#config_calibre_dir").val()}, | ||||||
|  |             success: function success(data) { | ||||||
|  |                 if ( data.change ) { | ||||||
|  |                     if ( data.valid ) { | ||||||
|  |                         confirmDialog( | ||||||
|  |                         "db_submit", | ||||||
|  |                     "GeneralChangeModal", | ||||||
|  |                             0, | ||||||
|  |                             changeDbSettings | ||||||
|  |                         ); | ||||||
|  |                     } | ||||||
|  |                     else { | ||||||
|  |                         $("#InvalidDialog").modal('show'); | ||||||
|  |                     } | ||||||
|  |                 } else {                	 | ||||||
|  |                     changeDbSettings(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     $("#config_submit").click(function(e) { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         e.stopPropagation(); | ||||||
|  |         this.blur(); | ||||||
|  |         window.scrollTo({top: 0, behavior: 'smooth'}); | ||||||
|  |         var request_path = "/../../admin/ajaxconfig"; | ||||||
|  |         var loader = "/../.."; | ||||||
|  |         $("#flash_success").remove(); | ||||||
|  |         $("#flash_danger").remove(); | ||||||
|  |         $.post(window.location.pathname + request_path, $(this).closest("form").serialize(), function(data) { | ||||||
|  |             $('#config_upload_formats').val(data.config_upload); | ||||||
|  |             if(data.reboot) { | ||||||
|  |                 $("#spinning_success").show(); | ||||||
|  |                 var rebootInterval = setInterval(function(){ | ||||||
|  |                     $.get({ | ||||||
|  |                         url:window.location.pathname + "/../../admin/alive", | ||||||
|  |                         success: function (d, statusText, xhr) { | ||||||
|  |                             if (xhr.status < 400) { | ||||||
|  |                                 $("#spinning_success").hide(); | ||||||
|  |                                 clearInterval(rebootInterval); | ||||||
|  |                                 handle_response(data.result); | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                     }); | ||||||
|  |                 }, 1000); | ||||||
|  |             } else { | ||||||
|  |                 handle_response(data.result); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     $("#delete_shelf").click(function() { |     $("#delete_shelf").click(function() { | ||||||
|         confirmDialog( |         confirmDialog( | ||||||
|             $(this).attr('id'), |             $(this).attr('id'), | ||||||
|  | @ -568,7 +634,6 @@ $(function() { | ||||||
| 
 | 
 | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     $("#fileModal").on("show.bs.modal", function(e) { |     $("#fileModal").on("show.bs.modal", function(e) { | ||||||
|         var target = $(e.relatedTarget); |         var target = $(e.relatedTarget); | ||||||
|         var path = $("#" + target.data("link"))[0].value; |         var path = $("#" + target.data("link"))[0].value; | ||||||
|  | @ -632,7 +697,6 @@ $(function() { | ||||||
| 
 | 
 | ||||||
|     $(".update-view").click(function(e) { |     $(".update-view").click(function(e) { | ||||||
|         var view = $(this).data("view"); |         var view = $(this).data("view"); | ||||||
| 
 |  | ||||||
|         e.preventDefault(); |         e.preventDefault(); | ||||||
|         e.stopPropagation(); |         e.stopPropagation(); | ||||||
|         $.ajax({ |         $.ajax({ | ||||||
|  |  | ||||||
|  | @ -150,6 +150,7 @@ | ||||||
|         </div> |         </div> | ||||||
|         {% endif %} |         {% endif %} | ||||||
|       </div> |       </div> | ||||||
|  |       <a class="btn btn-default" id="db_config" href="{{url_for('admin.db_configuration')}}">{{_('Edit Calibre Database Configuration')}}</a> | ||||||
|       <a class="btn btn-default" id="basic_config" href="{{url_for('admin.configuration')}}">{{_('Edit Basic Configuration')}}</a> |       <a class="btn btn-default" id="basic_config" href="{{url_for('admin.configuration')}}">{{_('Edit Basic Configuration')}}</a> | ||||||
|       <a class="btn btn-default" id="view_config" href="{{url_for('admin.view_configuration')}}">{{_('Edit UI Configuration')}}</a> |       <a class="btn btn-default" id="view_config" href="{{url_for('admin.view_configuration')}}">{{_('Edit UI Configuration')}}</a> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| {% if book %} | {% if book %} | ||||||
|   <div class="col-sm-3 col-lg-3 col-xs-12"> |   <div class="col-sm-3 col-lg-3 col-xs-12"> | ||||||
|     <div class="cover"> |     <div class="cover"> | ||||||
|         <img src="{{ url_for('web.get_cover', book_id=book.id, edit=1|uuidfilter)  }}" alt="{{ book.title }}"/> |         <img id="detailcover" src="{{ url_for('web.get_cover', book_id=book.id, edit=1|uuidfilter)  }}" alt="{{ book.title }}"/> | ||||||
|     </div> |     </div> | ||||||
| {% if g.user.role_delete_books() %} | {% if g.user.role_delete_books() %} | ||||||
|     <div class="text-center"> |     <div class="text-center"> | ||||||
|  | @ -331,6 +331,7 @@ | ||||||
| <script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/locales/bootstrap-datepicker.' + g.user.locale + '.min.js') }}" charset="UTF-8"></script> | <script src="{{ url_for('static', filename='js/libs/bootstrap-datepicker/locales/bootstrap-datepicker.' + g.user.locale + '.min.js') }}" charset="UTF-8"></script> | ||||||
| {% endif %} | {% endif %} | ||||||
| <script src="{{ url_for('static', filename='js/edit_books.js') }}"></script> | <script src="{{ url_for('static', filename='js/edit_books.js') }}"></script> | ||||||
|  | <script src="{{ url_for('static', filename='js/fullscreen.js') }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
| {% block header %} | {% block header %} | ||||||
| <meta name="referrer" content="never"> | <meta name="referrer" content="never"> | ||||||
|  |  | ||||||
							
								
								
									
										74
									
								
								cps/templates/config_db.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								cps/templates/config_db.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | ||||||
|  | {% extends "layout.html" %} | ||||||
|  | {% block flash %} | ||||||
|  | <div id="spinning_success" class="row-fluid text-center" style="margin-top: -20px; display:none;"> | ||||||
|  |     <div class="alert alert-info"><img id="img-spinner" src="{{ url_for('static', filename='css/libs/images/loading-icon.gif') }}"/></div> | ||||||
|  | </div> | ||||||
|  | {% endblock %} | ||||||
|  | {% block body %} | ||||||
|  | <div class="discover"> | ||||||
|  |   <h2>{{title}}</h2> | ||||||
|  |   <form role="form" method="POST" class="col-md-10 col-lg-6" action="{{ url_for('admin.db_configuration') }}" autocomplete="off"> | ||||||
|  |        <label for="config_calibre_dir">{{_('Location of Calibre Database')}}</label> | ||||||
|  |        <div class="form-group required input-group"> | ||||||
|  |         <input type="text" class="form-control" id="config_calibre_dir" name="config_calibre_dir" value="{% if config.config_calibre_dir != None %}{{ config.config_calibre_dir }}{% endif %}" autocomplete="off"> | ||||||
|  |         <span class="input-group-btn"> | ||||||
|  |           <button type="button" data-toggle="modal" id="calibre_modal_path" data-link="config_calibre_dir" data-filefilter="metadata.db" data-target="#fileModal" id="library_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button> | ||||||
|  |         </span> | ||||||
|  |       </div> | ||||||
|  |     {% if feature_support['gdrive'] %} | ||||||
|  |       <div class="form-group required"> | ||||||
|  |         <input type="checkbox" id="config_use_google_drive" name="config_use_google_drive" data-control="gdrive_settings" {% if config.config_use_google_drive %}checked{% endif %} > | ||||||
|  |         <label for="config_use_google_drive">{{_('Use Google Drive?')}}</label> | ||||||
|  |       </div> | ||||||
|  |       {% if not gdriveError %} | ||||||
|  |         {% if show_authenticate_google_drive and config.config_use_google_drive %} | ||||||
|  |           <div class="form-group required"> | ||||||
|  |             <a href="{{ url_for('gdrive.authenticate_google_drive') }}" id="gdrive_auth" class="btn btn-primary">{{_('Authenticate Google Drive')}}</a> | ||||||
|  |           </div> | ||||||
|  |         {% else %} | ||||||
|  |             {% if not show_authenticate_google_drive %} | ||||||
|  |             <div class="form-group required"> | ||||||
|  |               <label for="config_google_drive_folder">{{_('Google Drive Calibre folder')}}</label> | ||||||
|  |               <select name="config_google_drive_folder" id="config_google_drive_folder" class="form-control"> | ||||||
|  |                 {%  for gdrivefolder in gdrivefolders %} | ||||||
|  |                 <option value="{{ gdrivefolder.title }}" {% if gdrivefolder.title == config.config_google_drive_folder %}selected{% endif %}>{{ gdrivefolder.title }}</option> | ||||||
|  |                 {% endfor %} | ||||||
|  |               </select> | ||||||
|  |             </div> | ||||||
|  |             {% if config.config_google_drive_watch_changes_response %} | ||||||
|  |               <label for="config_google_drive_watch_changes_response">{{_('Metadata Watch Channel ID')}}</label> | ||||||
|  |               <div class="form-group input-group required"> | ||||||
|  |                 <input type="text" class="form-control" name="config_google_drive_watch_changes_response" id="config_google_drive_watch_changes_response" value="{{ config.config_google_drive_watch_changes_response['id'] }} expires on {{ config.config_google_drive_watch_changes_response['expiration'] |  strftime }}" autocomplete="off" disabled=""> | ||||||
|  |                 <span class="input-group-btn"><a href="{{ url_for('gdrive.revoke_watch_gdrive') }}" id="watch_revoke" class="btn btn-primary">{{_('Revoke')}}</a></span> | ||||||
|  |               </div> | ||||||
|  |             {% else %} | ||||||
|  |               <a href="{{ url_for('gdrive.watch_gdrive') }}" id="enable_gdrive_watch" class="btn btn-primary">Enable watch of metadata.db</a> | ||||||
|  |             {% endif %} | ||||||
|  |           {% endif %} | ||||||
|  |         {% endif %} | ||||||
|  |       {% endif %} | ||||||
|  |     {% endif %} | ||||||
|  |     <div class="col-sm-12"> | ||||||
|  |       <div id="db_submit" name="submit" class="btn btn-default">{{_('Save')}}</div> | ||||||
|  |       <a href="{{ url_for('admin.admin') }}" id="config_back" class="btn btn-default">{{_('Cancel')}}</a> | ||||||
|  |     </div> | ||||||
|  |   </form> | ||||||
|  | </div> | ||||||
|  | {% endblock %} | ||||||
|  | {% block modal %} | ||||||
|  | {{ filechooser_modal() }} | ||||||
|  | {{ change_confirm_modal() }} | ||||||
|  | <div id="InvalidDialog" class="modal fade" role="dialog"> | ||||||
|  |   <div class="modal-dialog modal-sm"> | ||||||
|  |     <!-- Modal content--> | ||||||
|  |     <div class="modal-content"> | ||||||
|  |       <div class="modal-header bg-info"></div> | ||||||
|  |       <div class="modal-body text-center"> | ||||||
|  |         <p>{{_('New db location is invalid, please enter valid path')}}</p> | ||||||
|  |           <p></p> | ||||||
|  |         <button type="button" class="btn btn-default" id="invalid_confirm" data-dismiss="modal">{{_('OK')}}</button> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </div> | ||||||
|  | {% endblock %} | ||||||
|  | @ -1,4 +1,9 @@ | ||||||
| {% extends "layout.html" %} | {% extends "layout.html" %} | ||||||
|  | {% block flash %} | ||||||
|  | <div id="spinning_success" class="row-fluid text-center" style="margin-top: -20px; display:none;"> | ||||||
|  |     <div class="alert alert-info"><img id="img-spinner" src="{{ url_for('static', filename='css/libs/images/loading-icon.gif') }}"/></div> | ||||||
|  | </div> | ||||||
|  | {% endblock %} | ||||||
| {% block body %} | {% block body %} | ||||||
| <div class="discover"> | <div class="discover"> | ||||||
|   <h2>{{title}}</h2> |   <h2>{{title}}</h2> | ||||||
|  | @ -7,91 +12,13 @@ | ||||||
|   <div class="panel panel-default"> |   <div class="panel panel-default"> | ||||||
|     <div class="panel-heading"> |     <div class="panel-heading"> | ||||||
|       <h4 class="panel-title"> |       <h4 class="panel-title"> | ||||||
|         <a class="accordion-toggle" data-toggle="collapse" href="#collapseOne"> |         <a class="accordion-toggle" data-toggle="collapse" href="#collapseone"> | ||||||
|           <span class="glyphicon glyphicon-minus"></span> |  | ||||||
|           {{_('Library Configuration')}} |  | ||||||
|         </a> |  | ||||||
|       </h4> |  | ||||||
|     </div> |  | ||||||
|     <div id="collapseOne" class="panel-collapse collapse in"> |  | ||||||
|       <div class="panel-body"> |  | ||||||
|        <label for="config_calibre_dir">{{_('Location of Calibre Database')}}</label> |  | ||||||
|        <div class="form-group required{% if filepicker %} input-group{% endif %}"> |  | ||||||
|         <input type="text" class="form-control" id="config_calibre_dir" name="config_calibre_dir" value="{% if config.config_calibre_dir != None %}{{ config.config_calibre_dir }}{% endif %}" autocomplete="off"> |  | ||||||
|        {% if filepicker %} |  | ||||||
|         <span class="input-group-btn"> |  | ||||||
|           <button type="button" data-toggle="modal" id="calibre_modal_path" data-link="config_calibre_dir" data-filefilter="metadata.db" data-target="#fileModal" id="library_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button> |  | ||||||
|         </span> |  | ||||||
|        {% endif %} |  | ||||||
|       </div> |  | ||||||
|       {% if not filepicker %} |  | ||||||
|        <div class="form-group"> |  | ||||||
|         <label id="filepicker-hint">{{_('To activate serverside filepicker start Calibre-Web with -f option')}}</label> |  | ||||||
|       </div> |  | ||||||
|       {% endif %} |  | ||||||
|     {% if feature_support['gdrive']  %} |  | ||||||
|     <div class="form-group required"> |  | ||||||
|       <input type="checkbox" id="config_use_google_drive" name="config_use_google_drive" data-control="gdrive_settings" {% if config.config_use_google_drive %}checked{% endif %} > |  | ||||||
|       <label for="config_use_google_drive">{{_('Use Google Drive?')}}</label> |  | ||||||
|     </div> |  | ||||||
|     <div data-related="gdrive_settings"> |  | ||||||
|       {% if gdriveError %} |  | ||||||
|         <div class="form-group"> |  | ||||||
|           <label id="gdrive_error"> |  | ||||||
|             {{_('Google Drive config problem')}}: {{ gdriveError }} |  | ||||||
|           </label> |  | ||||||
|         </div> |  | ||||||
|       {% else %} |  | ||||||
|         {% if show_authenticate_google_drive and g.user.is_authenticated and config.config_use_google_drive %} |  | ||||||
|           <div class="form-group required"> |  | ||||||
|             <a href="{{ url_for('gdrive.authenticate_google_drive') }}" id="gdrive_auth" class="btn btn-primary">{{_('Authenticate Google Drive')}}</a> |  | ||||||
|           </div> |  | ||||||
|         {% else %} |  | ||||||
|             {% if show_authenticate_google_drive and g.user.is_authenticated and not config.config_use_google_drive %} |  | ||||||
|             <div >{{_('Please hit save to continue with setup')}}</div> |  | ||||||
|             {% endif %} |  | ||||||
|           {% if not g.user.is_authenticated and show_login_button %} |  | ||||||
|           <div >{{_('Please finish Google Drive setup after login')}}</div> |  | ||||||
|             {% endif %} |  | ||||||
|           {% if g.user.is_authenticated %} |  | ||||||
|             {% if not show_authenticate_google_drive %} |  | ||||||
|           <div class="form-group required"> |  | ||||||
|             <label for="config_google_drive_folder">{{_('Google Drive Calibre folder')}}</label> |  | ||||||
|               <select name="config_google_drive_folder" id="config_google_drive_folder" class="form-control"> |  | ||||||
|                 {%  for gdrivefolder in gdrivefolders %} |  | ||||||
|                 <option value="{{ gdrivefolder.title }}" {% if gdrivefolder.title == config.config_google_drive_folder %}selected{% endif %}>{{ gdrivefolder.title }}</option> |  | ||||||
|                 {% endfor %} |  | ||||||
|               </select> |  | ||||||
|           </div> |  | ||||||
|           {% if config.config_google_drive_watch_changes_response %} |  | ||||||
|             <label for="config_google_drive_watch_changes_response">{{_('Metadata Watch Channel ID')}}</label> |  | ||||||
|             <div class="form-group input-group required"> |  | ||||||
|               <input type="text" class="form-control" name="config_google_drive_watch_changes_response" id="config_google_drive_watch_changes_response" value="{{ config.config_google_drive_watch_changes_response['id'] }} expires on {{ config.config_google_drive_watch_changes_response['expiration'] |  strftime }}" autocomplete="off" disabled=""> |  | ||||||
|               <span class="input-group-btn"><a href="{{ url_for('gdrive.revoke_watch_gdrive') }}" id="watch_revoke" class="btn btn-primary">{{_('Revoke')}}</a></span> |  | ||||||
|             </div> |  | ||||||
|           {% else %} |  | ||||||
|             <a href="{{ url_for('gdrive.watch_gdrive') }}" id="enable_gdrive_watch" class="btn btn-primary">Enable watch of metadata.db</a> |  | ||||||
|           {% endif %} |  | ||||||
|           {% endif %} |  | ||||||
|           {% endif %} |  | ||||||
|         {% endif %} |  | ||||||
|       {% endif %} |  | ||||||
|     </div> |  | ||||||
|     {% endif %} |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
|     {% if show_back_button %} |  | ||||||
|   <div class="panel panel-default"> |  | ||||||
|     <div class="panel-heading"> |  | ||||||
|       <h4 class="panel-title"> |  | ||||||
|         <a class="accordion-toggle" data-toggle="collapse" href="#collapsetwo"> |  | ||||||
|           <span class="glyphicon glyphicon-plus"></span> |           <span class="glyphicon glyphicon-plus"></span> | ||||||
|           {{_('Server Configuration')}} |           {{_('Server Configuration')}} | ||||||
|         </a> |         </a> | ||||||
|       </h4> |       </h4> | ||||||
|     </div> |     </div> | ||||||
|     <div id="collapsetwo" class="panel-collapse collapse"> |     <div id="collapseone" class="panel-collapse collapse"> | ||||||
|       <div class="panel-body"> |       <div class="panel-body"> | ||||||
|         <div class="form-group"> |         <div class="form-group"> | ||||||
|           <label for="config_port">{{_('Server Port')}}</label> |           <label for="config_port">{{_('Server Port')}}</label> | ||||||
|  | @ -124,13 +51,13 @@ | ||||||
|   <div class="panel panel-default"> |   <div class="panel panel-default"> | ||||||
|     <div class="panel-heading"> |     <div class="panel-heading"> | ||||||
|       <h4 class="panel-title"> |       <h4 class="panel-title"> | ||||||
|         <a class="accordion-toggle" data-toggle="collapse" href="#collapsethree"> |         <a class="accordion-toggle" data-toggle="collapse" href="#collapsetwo"> | ||||||
|           <span class="glyphicon glyphicon-plus"></span> |           <span class="glyphicon glyphicon-plus"></span> | ||||||
|           {{_('Logfile Configuration')}} |           {{_('Logfile Configuration')}} | ||||||
|         </a> |         </a> | ||||||
|       </h4> |       </h4> | ||||||
|     </div> |     </div> | ||||||
|     <div id="collapsethree" class="panel-collapse collapse"> |     <div id="collapsetwo" class="panel-collapse collapse"> | ||||||
|       <div class="panel-body"> |       <div class="panel-body"> | ||||||
|         <div class="form-group"> |         <div class="form-group"> | ||||||
|         <label for="config_log_level">{{_('Log Level')}}</label> |         <label for="config_log_level">{{_('Log Level')}}</label> | ||||||
|  | @ -159,13 +86,13 @@ | ||||||
|   <div class="panel panel-default"> |   <div class="panel panel-default"> | ||||||
|     <div class="panel-heading"> |     <div class="panel-heading"> | ||||||
|       <h4 class="panel-title"> |       <h4 class="panel-title"> | ||||||
|         <a class="accordion-toggle" data-toggle="collapse" href="#collapsefive"> |         <a class="accordion-toggle" data-toggle="collapse" href="#collapsefour"> | ||||||
|           <span class="glyphicon glyphicon-plus"></span> |           <span class="glyphicon glyphicon-plus"></span> | ||||||
|           {{_('Feature Configuration')}} |           {{_('Feature Configuration')}} | ||||||
|         </a> |         </a> | ||||||
|       </h4> |       </h4> | ||||||
|     </div> |     </div> | ||||||
|     <div id="collapsefive" class="panel-collapse collapse"> |     <div id="collapsefour" class="panel-collapse collapse"> | ||||||
|       <div class="panel-body"> |       <div class="panel-body"> | ||||||
|     <div class="form-group"> |     <div class="form-group"> | ||||||
|         <input type="checkbox" id="config_uploading" data-control="upload_settings" name="config_uploading" {% if config.config_uploading %}checked{% endif %}> |         <input type="checkbox" id="config_uploading" data-control="upload_settings" name="config_uploading" {% if config.config_uploading %}checked{% endif %}> | ||||||
|  | @ -379,13 +306,13 @@ | ||||||
|   <div class="panel panel-default"> |   <div class="panel panel-default"> | ||||||
|     <div class="panel-heading"> |     <div class="panel-heading"> | ||||||
|       <h4 class="panel-title"> |       <h4 class="panel-title"> | ||||||
|         <a class="accordion-toggle" data-toggle="collapse" href="#collapseeight"> |         <a class="accordion-toggle" data-toggle="collapse" href="#collapsefive"> | ||||||
|           <span class="glyphicon glyphicon-plus"></span> |           <span class="glyphicon glyphicon-plus"></span> | ||||||
|            {{_('External binaries')}} |            {{_('External binaries')}} | ||||||
|         </a> |         </a> | ||||||
|       </h4> |       </h4> | ||||||
|     </div> |     </div> | ||||||
|     <div id="collapseeight" class="panel-collapse collapse"> |     <div id="collapsefive" class="panel-collapse collapse"> | ||||||
|       <div class="panel-body"> |       <div class="panel-body"> | ||||||
|            <label for="config_converterpath">{{_('Path to Calibre E-Book Converter')}}</label> |            <label for="config_converterpath">{{_('Path to Calibre E-Book Converter')}}</label> | ||||||
|            <div class="form-group input-group"> |            <div class="form-group input-group"> | ||||||
|  | @ -417,18 +344,10 @@ | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|   {% endif %} |  | ||||||
| </div> | </div> | ||||||
|     <div class="col-sm-12"> |     <div class="col-sm-12"> | ||||||
|     {% if not show_login_button %} |     <button type="button" name="submit" id="config_submit" class="btn btn-default">{{_('Save')}}</button> | ||||||
|     <button type="submit" name="submit" class="btn btn-default">{{_('Save')}}</button> |     <a href="{{ url_for('admin.admin') }}" id="config_back" class="btn btn-default">{{_('Cancel')}}</a> | ||||||
|     {% endif %} |  | ||||||
|     {% if show_back_button %} |  | ||||||
|       <a href="{{ url_for('admin.admin') }}" class="btn btn-default">{{_('Cancel')}}</a> |  | ||||||
|     {% endif %} |  | ||||||
|     {% if show_login_button %} |  | ||||||
|       <a href="{{ url_for('web.login') }}" name="login" class="btn btn-default">{{_('Login')}}</a> |  | ||||||
|     {% endif %} |  | ||||||
|     </div> |     </div> | ||||||
|   </form> |   </form> | ||||||
| </div> | </div> | ||||||
|  | @ -436,15 +355,3 @@ | ||||||
| {% block modal %} | {% block modal %} | ||||||
| {{ filechooser_modal() }} | {{ filechooser_modal() }} | ||||||
| {% endblock %} | {% endblock %} | ||||||
| {% block js %} |  | ||||||
| <script type="text/javascript"> |  | ||||||
|     $(document).on('change', '#config_use_google_drive', function() { |  | ||||||
|         $('#config_google_drive_folder').prop('required', $(this).prop('checked')); |  | ||||||
|     }); |  | ||||||
|     $('.collapse').on('shown.bs.collapse', function(){ |  | ||||||
|         $(this).parent().find(".glyphicon-plus").removeClass("glyphicon-plus").addClass("glyphicon-minus"); |  | ||||||
|     }).on('hidden.bs.collapse', function(){ |  | ||||||
|     $(this).parent().find(".glyphicon-minus").removeClass("glyphicon-minus").addClass("glyphicon-plus"); |  | ||||||
|     }); |  | ||||||
| </script> |  | ||||||
| {% endblock %} |  | ||||||
|  |  | ||||||
|  | @ -6,8 +6,8 @@ | ||||||
| {% block body %} | {% block body %} | ||||||
| <div class="discover"> | <div class="discover"> | ||||||
|   <h2>{{title}}</h2> |   <h2>{{title}}</h2> | ||||||
|   <form role="form" method="POST" autocomplete="off" class="col-md-10 col-lg-6"> |   <form role="form" method="POST" autocomplete="off" > | ||||||
| <div class="panel-group"> | <div class="panel-group class="col-md-10 col-lg-6"> | ||||||
|   <div class="panel panel-default"> |   <div class="panel panel-default"> | ||||||
|     <div class="panel-heading"> |     <div class="panel-heading"> | ||||||
|       <h4 class="panel-title"> |       <h4 class="panel-title"> | ||||||
|  | @ -71,7 +71,6 @@ | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|   </div> |  | ||||||
|   <div class="panel panel-default"> |   <div class="panel panel-default"> | ||||||
|     <div class="panel-heading"> |     <div class="panel-heading"> | ||||||
|       <h4 class="panel-title"> |       <h4 class="panel-title"> | ||||||
|  | @ -145,6 +144,7 @@ | ||||||
|         <a href="#" id="get_column_values" data-id="0" class="btn btn-default" data-toggle="modal" data-target="#restrictModal">{{_('Add Allowed/Denied custom column values')}}</a> |         <a href="#" id="get_column_values" data-id="0" class="btn btn-default" data-toggle="modal" data-target="#restrictModal">{{_('Add Allowed/Denied custom column values')}}</a> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|  |   </div> | ||||||
| </div> | </div> | ||||||
|     <div class="col-sm-12"> |     <div class="col-sm-12"> | ||||||
|     <button type="submit" name="submit" class="btn btn-default">{{_('Save')}}</button> |     <button type="submit" name="submit" class="btn btn-default">{{_('Save')}}</button> | ||||||
|  | @ -157,13 +157,6 @@ | ||||||
| {{ restrict_modal() }} | {{ restrict_modal() }} | ||||||
| {% endblock %} | {% endblock %} | ||||||
| {% block js %} | {% block js %} | ||||||
| <script type="text/javascript"> |  | ||||||
|         $('.collapse').on('shown.bs.collapse', function(){ |  | ||||||
|             $(this).parent().find(".glyphicon-plus").removeClass("glyphicon-plus").addClass("glyphicon-minus"); |  | ||||||
|         }).on('hidden.bs.collapse', function(){ |  | ||||||
|         $(this).parent().find(".glyphicon-minus").removeClass("glyphicon-minus").addClass("glyphicon-plus"); |  | ||||||
|         }); |  | ||||||
| </script> |  | ||||||
| <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table.min.js') }}"></script> | <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table.min.js') }}"></script> | ||||||
| <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-editable.min.js') }}"></script> | <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-table-editable.min.js') }}"></script> | ||||||
| <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script> | <script src="{{ url_for('static', filename='js/libs/bootstrap-table/bootstrap-editable.min.js') }}"></script> | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
|   <div class="row"> |   <div class="row"> | ||||||
|     <div class="col-sm-3 col-lg-3 col-xs-5"> |     <div class="col-sm-3 col-lg-3 col-xs-5"> | ||||||
|       <div class="cover"> |       <div class="cover"> | ||||||
|           <img src="{{ url_for('web.get_cover', book_id=entry.id, edit=1|uuidfilter) }}" alt="{{ entry.title }}" /> |           <img id="detailcover" src="{{ url_for('web.get_cover', book_id=entry.id, edit=1|uuidfilter) }}" alt="{{ entry.title }}" /> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|     <div class="col-sm-9 col-lg-9 book-meta"> |     <div class="col-sm-9 col-lg-9 book-meta"> | ||||||
|  | @ -316,4 +316,5 @@ | ||||||
|   </a> |   </a> | ||||||
| </script> | </script> | ||||||
| <script src="{{ url_for('static', filename='js/details.js') }}"></script> | <script src="{{ url_for('static', filename='js/details.js') }}"></script> | ||||||
|  | <script src="{{ url_for('static', filename='js/fullscreen.js') }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
|  | @ -27,6 +27,9 @@ | ||||||
|   </div> |   </div> | ||||||
|   <div class="row"> |   <div class="row"> | ||||||
|     <div class="col-md-offset-4 text-left"> |     <div class="col-md-offset-4 text-left"> | ||||||
|  |         {% if unconfigured %} | ||||||
|  |             <div>{{_('Calibre-Web Instance is unconfigured, please contact your administrator')}}</div> | ||||||
|  |         {% endif %} | ||||||
|       {% for element in error_stack %} |       {% for element in error_stack %} | ||||||
|         <div>{{ element }}</div> |         <div>{{ element }}</div> | ||||||
|       {% endfor %} |       {% endfor %} | ||||||
|  | @ -39,13 +42,15 @@ | ||||||
|     </div> |     </div> | ||||||
|       </div> |       </div> | ||||||
|     {% endif %} |     {% endif %} | ||||||
| 
 |  | ||||||
|     <div class="row"> |     <div class="row"> | ||||||
|       <div class="col errorlink"> |       <div class="col errorlink"> | ||||||
|  |       {% if not unconfigured %} | ||||||
|         <a href="{{url_for('web.index')}}" title="{{ _('Return to Home') }}">{{_('Return to Home')}}</a> |         <a href="{{url_for('web.index')}}" title="{{ _('Return to Home') }}">{{_('Return to Home')}}</a> | ||||||
|  |       {% else %} | ||||||
|  |         <a href="{{url_for('web.logout')}}" title="{{ _('Logout User') }}">{{ _('Logout User') }}</a> | ||||||
|  |       {% endif %} | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
| 
 |  | ||||||
|   </div> |   </div> | ||||||
|   </body> |   </body> | ||||||
| </html> | </html> | ||||||
|  |  | ||||||
|  | @ -111,6 +111,7 @@ | ||||||
|       </div> |       </div> | ||||||
|       {%endif%} |       {%endif%} | ||||||
|     {% endfor %} |     {% endfor %} | ||||||
|  |     {% block flash %}{% endblock %} | ||||||
|     {% if g.current_theme == 1 %} |     {% if g.current_theme == 1 %} | ||||||
|       <div id="loader" hidden="true"> |       <div id="loader" hidden="true"> | ||||||
|         <center> |         <center> | ||||||
|  |  | ||||||
|  | @ -47,7 +47,7 @@ | ||||||
|             {% endfor %} |             {% endfor %} | ||||||
|         </select> |         </select> | ||||||
|     </div> |     </div> | ||||||
|     {% if registered_oauth.keys()| length > 0 and not new_user %} |     {% if registered_oauth.keys()| length > 0 and not new_user and profile %} | ||||||
|       {% for id, name in registered_oauth.items() %} |       {% for id, name in registered_oauth.items() %} | ||||||
|     <div class="form-group"> |     <div class="form-group"> | ||||||
|       <label>{{ name }} {{_('OAuth Settings')}}</label> |       <label>{{ name }} {{_('OAuth Settings')}}</label> | ||||||
|  |  | ||||||
|  | @ -188,7 +188,7 @@ class User(UserBase, Base): | ||||||
|     allowed_column_value = Column(String, default="") |     allowed_column_value = Column(String, default="") | ||||||
|     remote_auth_token = relationship('RemoteAuthToken', backref='user', lazy='dynamic') |     remote_auth_token = relationship('RemoteAuthToken', backref='user', lazy='dynamic') | ||||||
|     view_settings = Column(JSON, default={}) |     view_settings = Column(JSON, default={}) | ||||||
|     kobo_only_shelves_sync = Column(Integer, default=1) |     kobo_only_shelves_sync = Column(Integer, default=0) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if oauth_support: | if oauth_support: | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								cps/web.py
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								cps/web.py
									
									
									
									
									
								
							|  | @ -1381,10 +1381,14 @@ def serve_book(book_id, book_format, anyname): | ||||||
|         return "File not in Database" |         return "File not in Database" | ||||||
|     log.info('Serving book: %s', data.name) |     log.info('Serving book: %s', data.name) | ||||||
|     if config.config_use_google_drive: |     if config.config_use_google_drive: | ||||||
|  |         try: | ||||||
|             headers = Headers() |             headers = Headers() | ||||||
|             headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream") |             headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream") | ||||||
|             df = getFileFromEbooksFolder(book.path, data.name + "." + book_format) |             df = getFileFromEbooksFolder(book.path, data.name + "." + book_format) | ||||||
|             return do_gdrive_download(df, headers, (book_format.upper() == 'TXT')) |             return do_gdrive_download(df, headers, (book_format.upper() == 'TXT')) | ||||||
|  |         except AttributeError as ex: | ||||||
|  |             log.debug_or_exception(ex) | ||||||
|  |             return "File Not Found" | ||||||
|     else: |     else: | ||||||
|         if book_format.upper() == 'TXT': |         if book_format.upper() == 'TXT': | ||||||
|             try: |             try: | ||||||
|  | @ -1394,11 +1398,11 @@ def serve_book(book_id, book_format, anyname): | ||||||
|                 return make_response( |                 return make_response( | ||||||
|                     rawdata.decode(result['encoding']).encode('utf-8')) |                     rawdata.decode(result['encoding']).encode('utf-8')) | ||||||
|             except FileNotFoundError: |             except FileNotFoundError: | ||||||
|  |                 log.error("File Not Found") | ||||||
|                 return "File Not Found" |                 return "File Not Found" | ||||||
|         return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format) |         return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| @web.route("/download/<int:book_id>/<book_format>", defaults={'anyname': 'None'}) | @web.route("/download/<int:book_id>/<book_format>", defaults={'anyname': 'None'}) | ||||||
| @web.route("/download/<int:book_id>/<book_format>/<anyname>") | @web.route("/download/<int:book_id>/<book_format>/<anyname>") | ||||||
| @login_required_if_no_ano | @login_required_if_no_ano | ||||||
|  | @ -1489,9 +1493,9 @@ def register(): | ||||||
| 
 | 
 | ||||||
| @web.route('/login', methods=['GET', 'POST']) | @web.route('/login', methods=['GET', 'POST']) | ||||||
| def login(): | def login(): | ||||||
|     if not config.db_configured: |     #if not config.db_configured: | ||||||
|         log.debug(u"Redirect to initial configuration") |     #    log.debug(u"Redirect to initial configuration") | ||||||
|         return redirect(url_for('admin.basic_configuration')) |     #    return redirect(url_for('admin.basic_configuration')) | ||||||
|     if current_user is not None and current_user.is_authenticated: |     if current_user is not None and current_user.is_authenticated: | ||||||
|         return redirect(url_for('web.index')) |         return redirect(url_for('web.index')) | ||||||
|     if config.config_login_type == constants.LOGIN_LDAP and not services.ldap: |     if config.config_login_type == constants.LOGIN_LDAP and not services.ldap: | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ Flask-Babel>=0.11.1,<2.1.0 | ||||||
| Flask-Login>=0.3.2,<0.5.1 | Flask-Login>=0.3.2,<0.5.1 | ||||||
| Flask-Principal>=0.3.2,<0.5.1 | Flask-Principal>=0.3.2,<0.5.1 | ||||||
| backports_abc>=0.4 | backports_abc>=0.4 | ||||||
| Flask>=1.0.2,<2.0.0 | Flask>=1.0.2,<2.1.0 | ||||||
| iso-639>=0.4.5,<0.5.0 | iso-639>=0.4.5,<0.5.0 | ||||||
| PyPDF3>=1.0.0,<1.0.4 | PyPDF3>=1.0.0,<1.0.4 | ||||||
| pytz>=2016.10 | pytz>=2016.10 | ||||||
|  |  | ||||||
|  | @ -37,20 +37,20 @@ | ||||||
|       <div class="row"> |       <div class="row"> | ||||||
|         <div class="col-xs-6 col-md-6 col-sm-offset-3" style="margin-top:50px;"> |         <div class="col-xs-6 col-md-6 col-sm-offset-3" style="margin-top:50px;"> | ||||||
|              |              | ||||||
|             <p class='text-justify attribute'><strong>Start Time: </strong>2021-05-19 20:16:09</p> |             <p class='text-justify attribute'><strong>Start Time: </strong>2021-05-27 20:44:36</p> | ||||||
|              |              | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <div class="row"> |       <div class="row"> | ||||||
|         <div class="col-xs-6 col-md-6 col-sm-offset-3"> |         <div class="col-xs-6 col-md-6 col-sm-offset-3"> | ||||||
|              |              | ||||||
|             <p class='text-justify attribute'><strong>Stop Time: </strong>2021-05-19 23:20:56</p> |             <p class='text-justify attribute'><strong>Stop Time: </strong>2021-05-28 00:00:32</p> | ||||||
|              |              | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <div class="row"> |       <div class="row"> | ||||||
|         <div class="col-xs-6 col-md-6 col-sm-offset-3"> |         <div class="col-xs-6 col-md-6 col-sm-offset-3"> | ||||||
|            <p class='text-justify attribute'><strong>Duration: </strong>2h 32 min</p> |            <p class='text-justify attribute'><strong>Duration: </strong>2h 37 min</p> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       </div> |       </div> | ||||||
|  | @ -1270,12 +1270,12 @@ | ||||||
|      |      | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     <tr id="su" class="passClass"> |     <tr id="su" class="errorClass"> | ||||||
|         <td>TestEditBooksOnGdrive</td> |         <td>TestEditBooksOnGdrive</td> | ||||||
|         <td class="text-center">20</td> |         <td class="text-center">20</td> | ||||||
|         <td class="text-center">20</td> |         <td class="text-center">17</td> | ||||||
|         <td class="text-center">0</td> |         <td class="text-center">1</td> | ||||||
|         <td class="text-center">0</td> |         <td class="text-center">2</td> | ||||||
|         <td class="text-center">0</td> |         <td class="text-center">0</td> | ||||||
|         <td class="text-center"> |         <td class="text-center"> | ||||||
|             <a onclick="showClassDetail('c13', 20)">Detail</a> |             <a onclick="showClassDetail('c13', 20)">Detail</a> | ||||||
|  | @ -1293,11 +1293,35 @@ | ||||||
|      |      | ||||||
|      |      | ||||||
|      |      | ||||||
|         <tr id='pt13.2' class='hiddenRow bg-success'> |         <tr id="ft13.2" class="none bg-danger"> | ||||||
|             <td> |             <td> | ||||||
|                 <div class='testcase'>TestEditBooksOnGdrive - test_edit_author</div> |                 <div class='testcase'>TestEditBooksOnGdrive - test_edit_author</div> | ||||||
|             </td> |             </td> | ||||||
|             <td colspan='6' align='center'>PASS</td> |             <td colspan='6'> | ||||||
|  |                 <div class="text-center"> | ||||||
|  |                     <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft13.2')">FAIL</a> | ||||||
|  |                 </div> | ||||||
|  |                 <!--css div popup start--> | ||||||
|  |                 <div id="div_ft13.2" class="popup_window test_output" style="display:block;"> | ||||||
|  |                     <div class='close_button pull-right'> | ||||||
|  |                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();' | ||||||
|  |                                 onclick='document.getElementById('div_ft13.2').style.display='none'"><span | ||||||
|  |                                 aria-hidden="true">×</span></button> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="text-left pull-left"> | ||||||
|  |                         <pre class="text-left">Traceback (most recent call last): | ||||||
|  |   File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 373, in test_edit_author | ||||||
|  |     self.assertEqual(u'Pipo, Pipe', author.get_attribute('value')) | ||||||
|  | AssertionError: 'Pipo, Pipe' != 'Pipo| Pipe' | ||||||
|  | - Pipo, Pipe | ||||||
|  | ?     ^ | ||||||
|  | + Pipo| Pipe | ||||||
|  | ?     ^</pre> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="clearfix"></div> | ||||||
|  |                 </div> | ||||||
|  |                 <!--css div popup end--> | ||||||
|  |             </td> | ||||||
|         </tr> |         </tr> | ||||||
|      |      | ||||||
|      |      | ||||||
|  | @ -1419,20 +1443,74 @@ | ||||||
|      |      | ||||||
|      |      | ||||||
|      |      | ||||||
|         <tr id='pt13.16' class='hiddenRow bg-success'> |         <tr id="et13.16" class="none bg-info"> | ||||||
|             <td> |             <td> | ||||||
|                 <div class='testcase'>TestEditBooksOnGdrive - test_edit_title</div> |                 <div class='testcase'>TestEditBooksOnGdrive - test_edit_title</div> | ||||||
|             </td> |             </td> | ||||||
|             <td colspan='6' align='center'>PASS</td> |             <td colspan='6'> | ||||||
|  |                 <div class="text-center"> | ||||||
|  |                     <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et13.16')">ERROR</a> | ||||||
|  |                 </div> | ||||||
|  |                 <!--css div popup start--> | ||||||
|  |                 <div id="div_et13.16" class="popup_window test_output" style="display:block;"> | ||||||
|  |                     <div class='close_button pull-right'> | ||||||
|  |                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();' | ||||||
|  |                                 onclick='document.getElementById('div_et13.16').style.display='none'"><span | ||||||
|  |                                 aria-hidden="true">×</span></button> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="text-left pull-left"> | ||||||
|  |                         <pre class="text-left">Traceback (most recent call last): | ||||||
|  |   File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 238, in test_edit_title | ||||||
|  |     self.edit_book(content={'book_title': u'Very long extra super turbo cool title without any issue of displaying including ö utf-8 characters'}) | ||||||
|  |   File "/home/ozzie/Development/calibre-web-test/test/helper_ui.py", line 1610, in edit_book | ||||||
|  |     submit.click() | ||||||
|  |   File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.8/site-packages/selenium/webdriver/remote/webelement.py", line 80, in click | ||||||
|  |     self._execute(Command.CLICK_ELEMENT) | ||||||
|  |   File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.8/site-packages/selenium/webdriver/remote/webelement.py", line 633, in _execute | ||||||
|  |     return self._parent.execute(command, params) | ||||||
|  |   File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.8/site-packages/selenium/webdriver/remote/webdriver.py", line 321, in execute | ||||||
|  |     self.error_handler.check_response(response) | ||||||
|  |   File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.8/site-packages/selenium/webdriver/remote/errorhandler.py", line 242, in check_response | ||||||
|  |     raise exception_class(message, screen, stacktrace) | ||||||
|  | selenium.common.exceptions.StaleElementReferenceException: Message: The element reference of <button id="submit" class="btn btn-default" type="submit"> is stale; either the element is no longer attached to the DOM, it is not in the current frame context, or the document has been refreshed</pre> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="clearfix"></div> | ||||||
|  |                 </div> | ||||||
|  |                 <!--css div popup end--> | ||||||
|  |             </td> | ||||||
|         </tr> |         </tr> | ||||||
|      |      | ||||||
|      |      | ||||||
|      |      | ||||||
|         <tr id='pt13.17' class='hiddenRow bg-success'> |         <tr id="et13.17" class="none bg-info"> | ||||||
|             <td> |             <td> | ||||||
|                 <div class='testcase'>TestEditBooksOnGdrive - test_upload_book_epub</div> |                 <div class='testcase'>TestEditBooksOnGdrive - test_upload_book_epub</div> | ||||||
|             </td> |             </td> | ||||||
|             <td colspan='6' align='center'>PASS</td> |             <td colspan='6'> | ||||||
|  |                 <div class="text-center"> | ||||||
|  |                     <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_et13.17')">ERROR</a> | ||||||
|  |                 </div> | ||||||
|  |                 <!--css div popup start--> | ||||||
|  |                 <div id="div_et13.17" class="popup_window test_output" style="display:block;"> | ||||||
|  |                     <div class='close_button pull-right'> | ||||||
|  |                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();' | ||||||
|  |                                 onclick='document.getElementById('div_et13.17').style.display='none'"><span | ||||||
|  |                                 aria-hidden="true">×</span></button> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="text-left pull-left"> | ||||||
|  |                         <pre class="text-left">Traceback (most recent call last): | ||||||
|  |   File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 831, in test_upload_book_epub | ||||||
|  |     self.fill_basic_config({'config_uploading':1}) | ||||||
|  |   File "/home/ozzie/Development/calibre-web-test/test/helper_ui.py", line 333, in fill_basic_config | ||||||
|  |     cls._fill_basic_config(elements) | ||||||
|  |   File "/home/ozzie/Development/calibre-web-test/test/helper_ui.py", line 290, in _fill_basic_config | ||||||
|  |     accordions[o].click() | ||||||
|  | IndexError: list index out of range</pre> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="clearfix"></div> | ||||||
|  |                 </div> | ||||||
|  |                 <!--css div popup end--> | ||||||
|  |             </td> | ||||||
|         </tr> |         </tr> | ||||||
|      |      | ||||||
|      |      | ||||||
|  | @ -2074,11 +2152,11 @@ | ||||||
|      |      | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     <tr id="su" class="failClass"> |     <tr id="su" class="passClass"> | ||||||
|         <td>TestLogin</td> |         <td>TestLogin</td> | ||||||
|         <td class="text-center">14</td> |         <td class="text-center">14</td> | ||||||
|         <td class="text-center">13</td> |         <td class="text-center">14</td> | ||||||
|         <td class="text-center">1</td> |         <td class="text-center">0</td> | ||||||
|         <td class="text-center">0</td> |         <td class="text-center">0</td> | ||||||
|         <td class="text-center">0</td> |         <td class="text-center">0</td> | ||||||
|         <td class="text-center"> |         <td class="text-center"> | ||||||
|  | @ -2196,31 +2274,11 @@ | ||||||
|      |      | ||||||
|      |      | ||||||
|      |      | ||||||
|         <tr id="ft23.13" class="none bg-danger"> |         <tr id='pt23.13' class='hiddenRow bg-success'> | ||||||
|             <td> |             <td> | ||||||
|                 <div class='testcase'>TestLogin - test_proxy_login</div> |                 <div class='testcase'>TestLogin - test_proxy_login</div> | ||||||
|             </td> |             </td> | ||||||
|             <td colspan='6'> |             <td colspan='6' align='center'>PASS</td> | ||||||
|                 <div class="text-center"> |  | ||||||
|                     <a class="popup_link text-center" onfocus='blur()' onclick="showTestDetail('div_ft23.13')">FAIL</a> |  | ||||||
|                 </div> |  | ||||||
|                 <!--css div popup start--> |  | ||||||
|                 <div id="div_ft23.13" class="popup_window test_output" style="display:block;"> |  | ||||||
|                     <div class='close_button pull-right'> |  | ||||||
|                         <button type="button" class="close" aria-label="Close" onfocus='this.blur();' |  | ||||||
|                                 onclick='document.getElementById('div_ft23.13').style.display='none'"><span |  | ||||||
|                                 aria-hidden="true">×</span></button> |  | ||||||
|                     </div> |  | ||||||
|                     <div class="text-left pull-left"> |  | ||||||
|                         <pre class="text-left">Traceback (most recent call last): |  | ||||||
|   File "/home/ozzie/Development/calibre-web-test/test/test_login.py", line 342, in test_proxy_login |  | ||||||
|     self.assertTrue("Calibre-Web | login" in resp.text) |  | ||||||
| AssertionError: False is not true</pre> |  | ||||||
|                     </div> |  | ||||||
|                     <div class="clearfix"></div> |  | ||||||
|                 </div> |  | ||||||
|                 <!--css div popup end--> |  | ||||||
|             </td> |  | ||||||
|         </tr> |         </tr> | ||||||
|      |      | ||||||
|      |      | ||||||
|  | @ -3858,9 +3916,9 @@ AssertionError: False is not true</pre> | ||||||
|     <tr id='total_row' class="text-center bg-grey"> |     <tr id='total_row' class="text-center bg-grey"> | ||||||
|         <td>Total</td> |         <td>Total</td> | ||||||
|         <td>340</td> |         <td>340</td> | ||||||
|         <td>332</td> |         <td>330</td> | ||||||
|         <td>1</td> |         <td>1</td> | ||||||
|         <td>0</td> |         <td>2</td> | ||||||
|         <td>7</td> |         <td>7</td> | ||||||
|         <td> </td> |         <td> </td> | ||||||
|     </tr> |     </tr> | ||||||
|  | @ -3913,7 +3971,7 @@ AssertionError: False is not true</pre> | ||||||
|            |            | ||||||
|             <tr> |             <tr> | ||||||
|               <th>Flask</th> |               <th>Flask</th> | ||||||
|               <td>1.1.4</td> |               <td>2.0.1</td> | ||||||
|               <td>Basic</td> |               <td>Basic</td> | ||||||
|             </tr> |             </tr> | ||||||
|            |            | ||||||
|  | @ -3997,13 +4055,13 @@ AssertionError: False is not true</pre> | ||||||
|            |            | ||||||
|             <tr> |             <tr> | ||||||
|               <th>Werkzeug</th> |               <th>Werkzeug</th> | ||||||
|               <td>1.0.1</td> |               <td>2.0.1</td> | ||||||
|               <td>Basic</td> |               <td>Basic</td> | ||||||
|             </tr> |             </tr> | ||||||
|            |            | ||||||
|             <tr> |             <tr> | ||||||
|               <th>google-api-python-client</th> |               <th>google-api-python-client</th> | ||||||
|               <td>2.4.0</td> |               <td>2.6.0</td> | ||||||
|               <td>TestCliGdrivedb</td> |               <td>TestCliGdrivedb</td> | ||||||
|             </tr> |             </tr> | ||||||
|            |            | ||||||
|  | @ -4027,7 +4085,7 @@ AssertionError: False is not true</pre> | ||||||
|            |            | ||||||
|             <tr> |             <tr> | ||||||
|               <th>PyDrive2</th> |               <th>PyDrive2</th> | ||||||
|               <td>1.8.2</td> |               <td>1.8.3</td> | ||||||
|               <td>TestCliGdrivedb</td> |               <td>TestCliGdrivedb</td> | ||||||
|             </tr> |             </tr> | ||||||
|            |            | ||||||
|  | @ -4039,7 +4097,7 @@ AssertionError: False is not true</pre> | ||||||
|            |            | ||||||
|             <tr> |             <tr> | ||||||
|               <th>google-api-python-client</th> |               <th>google-api-python-client</th> | ||||||
|               <td>2.4.0</td> |               <td>2.6.0</td> | ||||||
|               <td>TestEbookConvertCalibreGDrive</td> |               <td>TestEbookConvertCalibreGDrive</td> | ||||||
|             </tr> |             </tr> | ||||||
|            |            | ||||||
|  | @ -4063,7 +4121,7 @@ AssertionError: False is not true</pre> | ||||||
|            |            | ||||||
|             <tr> |             <tr> | ||||||
|               <th>PyDrive2</th> |               <th>PyDrive2</th> | ||||||
|               <td>1.8.2</td> |               <td>1.8.3</td> | ||||||
|               <td>TestEbookConvertCalibreGDrive</td> |               <td>TestEbookConvertCalibreGDrive</td> | ||||||
|             </tr> |             </tr> | ||||||
|            |            | ||||||
|  | @ -4075,7 +4133,7 @@ AssertionError: False is not true</pre> | ||||||
|            |            | ||||||
|             <tr> |             <tr> | ||||||
|               <th>google-api-python-client</th> |               <th>google-api-python-client</th> | ||||||
|               <td>2.4.0</td> |               <td>2.6.0</td> | ||||||
|               <td>TestEbookConvertGDriveKepubify</td> |               <td>TestEbookConvertGDriveKepubify</td> | ||||||
|             </tr> |             </tr> | ||||||
|            |            | ||||||
|  | @ -4099,7 +4157,7 @@ AssertionError: False is not true</pre> | ||||||
|            |            | ||||||
|             <tr> |             <tr> | ||||||
|               <th>PyDrive2</th> |               <th>PyDrive2</th> | ||||||
|               <td>1.8.2</td> |               <td>1.8.3</td> | ||||||
|               <td>TestEbookConvertGDriveKepubify</td> |               <td>TestEbookConvertGDriveKepubify</td> | ||||||
|             </tr> |             </tr> | ||||||
|            |            | ||||||
|  | @ -4135,7 +4193,7 @@ AssertionError: False is not true</pre> | ||||||
|            |            | ||||||
|             <tr> |             <tr> | ||||||
|               <th>google-api-python-client</th> |               <th>google-api-python-client</th> | ||||||
|               <td>2.4.0</td> |               <td>2.6.0</td> | ||||||
|               <td>TestEditBooksOnGdrive</td> |               <td>TestEditBooksOnGdrive</td> | ||||||
|             </tr> |             </tr> | ||||||
|            |            | ||||||
|  | @ -4159,7 +4217,7 @@ AssertionError: False is not true</pre> | ||||||
|            |            | ||||||
|             <tr> |             <tr> | ||||||
|               <th>PyDrive2</th> |               <th>PyDrive2</th> | ||||||
|               <td>1.8.2</td> |               <td>1.8.3</td> | ||||||
|               <td>TestEditBooksOnGdrive</td> |               <td>TestEditBooksOnGdrive</td> | ||||||
|             </tr> |             </tr> | ||||||
|            |            | ||||||
|  | @ -4171,7 +4229,7 @@ AssertionError: False is not true</pre> | ||||||
|            |            | ||||||
|             <tr> |             <tr> | ||||||
|               <th>google-api-python-client</th> |               <th>google-api-python-client</th> | ||||||
|               <td>2.4.0</td> |               <td>2.6.0</td> | ||||||
|               <td>TestSetupGdrive</td> |               <td>TestSetupGdrive</td> | ||||||
|             </tr> |             </tr> | ||||||
|            |            | ||||||
|  | @ -4189,7 +4247,7 @@ AssertionError: False is not true</pre> | ||||||
|            |            | ||||||
|             <tr> |             <tr> | ||||||
|               <th>PyDrive2</th> |               <th>PyDrive2</th> | ||||||
|               <td>1.8.2</td> |               <td>1.8.3</td> | ||||||
|               <td>TestSetupGdrive</td> |               <td>TestSetupGdrive</td> | ||||||
|             </tr> |             </tr> | ||||||
|            |            | ||||||
|  | @ -4243,7 +4301,7 @@ AssertionError: False is not true</pre> | ||||||
|            |            | ||||||
|             <tr> |             <tr> | ||||||
|               <th>SQLAlchemy-Utils</th> |               <th>SQLAlchemy-Utils</th> | ||||||
|               <td>0.37.3</td> |               <td>0.37.4</td> | ||||||
|               <td>TestOAuthLogin</td> |               <td>TestOAuthLogin</td> | ||||||
|             </tr> |             </tr> | ||||||
|            |            | ||||||
|  | @ -4267,7 +4325,7 @@ AssertionError: False is not true</pre> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <script> | <script> | ||||||
|     drawCircle(332, 1, 0, 7); |     drawCircle(330, 1, 2, 7); | ||||||
|     showCase(5); |     showCase(5); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user