diff --git a/cps/__init__.py b/cps/__init__.py index f6bb0cf7..3266a4e9 100644 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -83,7 +83,9 @@ log = logger.create() 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() diff --git a/cps/admin.py b/cps/admin.py index cfebbcf0..ac82bc8e 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -40,7 +40,7 @@ from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError from sqlalchemy.sql.expression import func, or_, text 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 .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \ valid_email, check_username @@ -97,19 +97,6 @@ def admin_required(f): 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 def before_request(): if current_user.is_authenticated: @@ -124,10 +111,14 @@ def before_request(): 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() if '/static/' not in request.path and not config.db_configured and \ - request.endpoint not in ('admin.basic_configuration', - 'login', - 'admin.config_pathchooser'): - return redirect(url_for('admin.basic_configuration')) + request.endpoint not in ('admin.ajax_db_config', + 'admin.simulatedbchange', + 'admin.db_configuration', + 'web.login', + 'web.logout', + 'admin.load_dialogtexts', + 'admin.ajax_pathchooser'): + return redirect(url_for('admin.db_configuration')) @admi.route("/admin") @@ -194,16 +185,46 @@ def admin(): feature_support=feature_support, kobo_support=kobo_support, 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 @admin_required def configuration(): - if request.method == "POST": - return _configuration_update_helper(True) - return _configuration_result() + return render_title_template("config_edit.html", + config=config, + 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") @login_required @admin_required @@ -539,10 +560,10 @@ def update_view_configuration(): return view_configuration() -@admi.route("/ajax/loaddialogtexts/") +@admi.route("/ajax/loaddialogtexts/", methods=['POST']) @login_required def load_dialogtexts(element_id): - texts = {"header": "", "main": ""} + texts = {"header": "", "main": "", "valid": 1} if element_id == "config_delete_kobo_token": texts["main"] = _('Do you really want to delete the Kobo Token?') 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)?') elif element_id == "kobo_only_shelves_sync": 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) @@ -867,14 +890,6 @@ def list_restriction(res_type, user_id): return response -@admi.route("/basicconfig/pathchooser/") -@unconfigured -def config_pathchooser(): - if filepicker: - return pathchooser() - abort(403) - - @admi.route("/ajax/pathchooser/") @login_required @admin_required @@ -963,16 +978,6 @@ def pathchooser(): 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): return config.set_from_dictionary(to_save, x, func) @@ -991,23 +996,24 @@ def _config_string(to_save, x): def _configuration_gdrive_helper(to_save): gdrive_error = None - gdrive_secrets = {} + if to_save.get("config_use_google_drive"): + gdrive_secrets = {} - if not os.path.isfile(gdriveutils.SETTINGS_YAML): - config.config_use_google_drive = False + if not os.path.isfile(gdriveutils.SETTINGS_YAML): + config.config_use_google_drive = False - if gdrive_support: - gdrive_error = gdriveutils.get_error_text(gdrive_secrets) - if "config_use_google_drive" in to_save and not config.config_use_google_drive and not gdrive_error: - with open(gdriveutils.CLIENT_SECRETS, 'r') as settings: - gdrive_secrets = json.load(settings)['web'] - if not gdrive_secrets: - return _configuration_result(_('client_secrets.json Is Not Configured For Web Application')) - gdriveutils.update_settings( - gdrive_secrets['client_id'], - gdrive_secrets['client_secret'], - gdrive_secrets['redirect_uris'][0] - ) + if gdrive_support: + gdrive_error = gdriveutils.get_error_text(gdrive_secrets) + if "config_use_google_drive" in to_save and not config.config_use_google_drive and not gdrive_error: + with open(gdriveutils.CLIENT_SECRETS, 'r') as settings: + gdrive_secrets = json.load(settings)['web'] + if not gdrive_secrets: + return _configuration_result(_('client_secrets.json Is Not Configured For Web Application')) + gdriveutils.update_settings( + gdrive_secrets['client_id'], + gdrive_secrets['client_secret'], + gdrive_secrets['redirect_uris'][0] + ) # always show google drive settings, but in case of error deny support new_gdrive_value = (not gdrive_error) and ("config_use_google_drive" in to_save) @@ -1041,23 +1047,23 @@ def _configuration_oauth_helper(to_save): return reboot_required -def _configuration_logfile_helper(to_save, gdrive_error): +def _configuration_logfile_helper(to_save): reboot_required = False reboot_required |= _config_int(to_save, "config_log_level") reboot_required |= _config_string(to_save, "config_logfile") if not logger.is_valid_logfile(config.config_logfile): 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_string(to_save, "config_access_logfile") if not logger.is_valid_logfile(config.config_access_logfile): 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 -def _configuration_ldap_helper(to_save, gdrive_error): +def _configuration_ldap_helper(to_save): reboot_required = False reboot_required |= _config_string(to_save, "config_ldap_provider_url") 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_user_object: 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_UNAUTHENTICATE: 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', - gdrive_error) + return reboot_required, _configuration_result(_('Please Enter a LDAP Service Account and Password')) else: 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.count("%s") != 1: return reboot_required, \ - _configuration_result(_('LDAP Group Object Filter Needs to Have One "%s" Format Identifier'), - gdrive_error) + _configuration_result(_('LDAP Group Object Filter Needs to Have One "%s" Format Identifier')) 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'), - gdrive_error) + return reboot_required, _configuration_result(_('LDAP Group Object Filter Has Unmatched Parenthesis')) if config.config_ldap_user_object.count("%s") != 1: return reboot_required, \ - _configuration_result(_('LDAP User Object Filter needs to Have One "%s" Format Identifier'), - gdrive_error) + _configuration_result(_('LDAP User Object Filter needs to Have One "%s" Format Identifier')) 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'), - gdrive_error) + return reboot_required, _configuration_result(_('LDAP User Object Filter Has Unmatched Parenthesis')) if to_save.get("ldap_import_user_filter") == '0': config.config_ldap_member_user_object = "" else: if config.config_ldap_member_user_object.count("%s") != 1: return reboot_required, \ - _configuration_result(_('LDAP Member User Filter needs to Have One "%s" Format Identifier'), - gdrive_error) + _configuration_result(_('LDAP Member User Filter needs to Have One "%s" Format Identifier')) 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'), - gdrive_error) + return reboot_required, _configuration_result(_('LDAP Member User Filter Has Unmatched Parenthesis')) 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 @@ -1129,13 +1128,31 @@ def _configuration_ldap_helper(to_save, gdrive_error): os.path.isfile(config.config_ldap_key_path)): return reboot_required, \ _configuration_result(_('LDAP CACertificate, Certificate or Key Location is not Valid, ' - 'Please Enter Correct Path'), - gdrive_error) + 'Please Enter Correct Path')) return reboot_required, None -def _configuration_update_helper(configured): - reboot_required = False +@admi.route("/ajax/simulatedbchange", methods=['POST']) +@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 to_save = request.form.to_dict() gdrive_error = None @@ -1145,24 +1162,47 @@ def _configuration_update_helper(configured): to_save['config_calibre_dir'], flags=re.IGNORECASE) try: - db_change |= _config_string(to_save, "config_calibre_dir") + db_change, db_valid = _db_simulate_change() # gdrive_error drive setup 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_string(to_save, "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'), - gdrive_error, - configured) + return _configuration_result(_('Keyfile Location is not Valid, Please Enter Correct Path')) reboot_required |= _config_string(to_save, "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'), - gdrive_error, - configured) + return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path')) _config_checkbox_int(to_save, "config_uploading") # 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") - # LDAP configurator, + # LDAP configurator 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: return message reboot_required |= reboot # Remote login configuration - _config_checkbox(to_save, "config_remote_login") if not config.config_remote_login: 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: 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: return message reboot_required |= reboot @@ -1227,67 +1266,55 @@ def _configuration_update_helper(configured): if "config_rarfile_location" in to_save: unrar_status = helper.check_unrar(config.config_rarfile_location) if unrar_status: - return _configuration_result(unrar_status, gdrive_error, configured) + return _configuration_result(unrar_status) except (OperationalError, InvalidRequestError): ub.session.rollback() log.error("Settings DB is not Writeable") - _configuration_result(_("Settings DB is not Writeable"), gdrive_error, configured) - - 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") + _configuration_result(_("Settings DB is not Writeable")) config.save() - flash(_(u"Calibre-Web configuration updated"), category="success") if reboot_required: 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() gdrivefolders = [] - if gdrive_error is None: + if not gdrive_error and config.config_use_google_drive: gdrive_error = gdriveutils.get_error_text() if gdrive_error and gdrive_support: log.error(gdrive_error) gdrive_error = _(gdrive_error) + flash(gdrive_error, category="error") else: if not gdrive_authenticate and gdrive_support: 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: log.error(error_flash) config.load() 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, - provider=oauthblueprints, - show_back_button=show_back_button, - show_login_button=show_login_button, show_authenticate_google_drive=gdrive_authenticate, - filepicker=configured, gdriveError=gdrive_error, gdrivefolders=gdrivefolders, 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): diff --git a/cps/cli.py b/cps/cli.py index d7dc596b..e46a0d5a 100644 --- a/cps/cli.py +++ b/cps/cli.py @@ -45,7 +45,6 @@ parser.add_argument('-v', '--version', action='version', help='Shows version num version=version_info()) 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('-f', action='store_true', help='Enables filepicker in unconfigured mode') args = parser.parse_args() if sys.version_info < (3, 0): @@ -114,6 +113,3 @@ user_credentials = args.s or None if user_credentials and ":" not in user_credentials: print("No valid 'username:password' format") sys.exit(3) - -# Handles enabling of filepicker -filepicker = args.f or None diff --git a/cps/config_sql.py b/cps/config_sql.py index ef90aee4..f3e82e7e 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -347,7 +347,7 @@ class _ConfigSQL(object): log.error(error) log.warning("invalidating configuration") self.db_configured = False - self.config_calibre_dir = None + # self.config_calibre_dir = None self.save() diff --git a/cps/db.py b/cps/db.py index 98ad5898..6b1d754f 100644 --- a/cps/db.py +++ b/cps/db.py @@ -524,19 +524,44 @@ class CalibreDB(): return cc_classes @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 + + @classmethod + def setup_db(cls, config_calibre_dir, app_db_path): + # cls.config = config cls.dispose() # toDo: if db changed -> delete shelfs, delete download books, delete read boks, kobo sync?? - if not config.config_calibre_dir: - config.invalidate() + if not config_calibre_dir: + cls.config.invalidate() 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): - config.invalidate() + cls.config.invalidate() return False try: @@ -552,10 +577,10 @@ class CalibreDB(): conn = cls.engine.connect() # conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302 except Exception as ex: - config.invalidate(ex) + cls.config.invalidate(ex) return False - config.db_configured = True + cls.config.db_configured = True if not cc_classes: try: @@ -828,7 +853,8 @@ class CalibreDB(): def reconnect_db(self, config, app_db_path): self.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): diff --git a/cps/gdrive.py b/cps/gdrive.py index 6d6dfd07..c0764015 100644 --- a/cps/gdrive.py +++ b/cps/gdrive.py @@ -74,7 +74,7 @@ def google_drive_callback(): f.write(credentials.to_json()) except (ValueError, AttributeError) as error: log.error(error) - return redirect(url_for('admin.configuration')) + return redirect(url_for('admin.db_configuration')) @gdrive.route("/watch/subscribe") @@ -99,7 +99,7 @@ def watch_gdrive(): else: flash(reason['message'], category="error") - return redirect(url_for('admin.configuration')) + return redirect(url_for('admin.db_configuration')) @gdrive.route("/watch/revoke") @@ -115,7 +115,7 @@ def revoke_watch_gdrive(): pass config.config_google_drive_watch_changes_response = {} config.save() - return redirect(url_for('admin.configuration')) + return redirect(url_for('admin.db_configuration')) @gdrive.route("/watch/callback", methods=['GET', 'POST']) diff --git a/cps/static/js/main.js b/cps/static/js/main.js index ca9f3e14..ce930cc3 100644 --- a/cps/static/js/main.js +++ b/cps/static/js/main.js @@ -141,7 +141,7 @@ function confirmDialog(id, dialogid, dataValue, yesFn, noFn) { $confirm.modal("hide"); }); $.ajax({ - method:"get", + method:"post", dataType: "json", url: getPath() + "/ajax/loaddialogtexts/" + id, success: function success(data) { @@ -179,18 +179,6 @@ $("#delete_confirm").click(function() { } }); $("#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 { @@ -218,8 +206,6 @@ $("#deleteModal").on("show.bs.modal", function(e) { $(e.currentTarget).find("#delete_confirm").data("ajax", $(e.relatedTarget).data("ajax")); }); - - $(function() { var updateTimerID; var updateText; @@ -556,6 +542,86 @@ $(function() { this.closest("form").submit(); }); + function handle_response(data) { + if (!jQuery.isEmptyObject(data)) { + data.forEach(function (item) { + $(".navbar").after('
' + + '
' + item.message + '
' + + '
'); + }); + } + } + + $('.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() { confirmDialog( $(this).attr('id'), @@ -568,7 +634,6 @@ $(function() { }); - $("#fileModal").on("show.bs.modal", function(e) { var target = $(e.relatedTarget); var path = $("#" + target.data("link"))[0].value; @@ -632,7 +697,6 @@ $(function() { $(".update-view").click(function(e) { var view = $(this).data("view"); - e.preventDefault(); e.stopPropagation(); $.ajax({ diff --git a/cps/templates/admin.html b/cps/templates/admin.html index 443a51d3..81ef955b 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -150,6 +150,7 @@ {% endif %} + {{_('Edit Calibre Database Configuration')}} {{_('Edit Basic Configuration')}} {{_('Edit UI Configuration')}} diff --git a/cps/templates/config_db.html b/cps/templates/config_db.html new file mode 100644 index 00000000..0d1d1bce --- /dev/null +++ b/cps/templates/config_db.html @@ -0,0 +1,74 @@ +{% extends "layout.html" %} +{% block flash %} + +{% endblock %} +{% block body %} +
+

{{title}}

+
+ +
+ + + + +
+ {% if feature_support['gdrive'] %} +
+ + +
+ {% if not gdriveError %} + {% if show_authenticate_google_drive and config.config_use_google_drive %} + + {% else %} + {% if not show_authenticate_google_drive %} +
+ + +
+ {% if config.config_google_drive_watch_changes_response %} + + + {% else %} + Enable watch of metadata.db + {% endif %} + {% endif %} + {% endif %} + {% endif %} + {% endif %} +
+
{{_('Save')}}
+ {{_('Cancel')}} +
+
+
+{% endblock %} +{% block modal %} +{{ filechooser_modal() }} +{{ change_confirm_modal() }} + +{% endblock %} diff --git a/cps/templates/config_edit.html b/cps/templates/config_edit.html index 716ed7ba..6c65fe2d 100644 --- a/cps/templates/config_edit.html +++ b/cps/templates/config_edit.html @@ -1,97 +1,24 @@ {% extends "layout.html" %} +{% block flash %} + +{% endblock %} {% block body %}

{{title}}

-
+
-
-
- -
- - {% if filepicker %} - - - - {% endif %} -
- {% if not filepicker %} -
- -
- {% endif %} - {% if feature_support['gdrive'] %} -
- - -
-
- {% if gdriveError %} -
- -
- {% else %} - {% if show_authenticate_google_drive and g.user.is_authenticated and config.config_use_google_drive %} - - {% else %} - {% if show_authenticate_google_drive and g.user.is_authenticated and not config.config_use_google_drive %} -
{{_('Please hit save to continue with setup')}}
- {% endif %} - {% if not g.user.is_authenticated and show_login_button %} -
{{_('Please finish Google Drive setup after login')}}
- {% endif %} - {% if g.user.is_authenticated %} - {% if not show_authenticate_google_drive %} -
- - -
- {% if config.config_google_drive_watch_changes_response %} - - - {% else %} - Enable watch of metadata.db - {% endif %} - {% endif %} - {% endif %} - {% endif %} - {% endif %} -
- {% endif %} -
-
-
- {% if show_back_button %} -
- -
+
@@ -124,13 +51,13 @@
-
+
@@ -159,13 +86,13 @@
-
+
@@ -379,13 +306,13 @@
-
+
@@ -417,18 +344,10 @@
- {% endif %}
- {% if not show_login_button %} - - {% endif %} - {% if show_back_button %} - {{_('Cancel')}} - {% endif %} - {% if show_login_button %} - {{_('Login')}} - {% endif %} + + {{_('Cancel')}}
@@ -436,15 +355,3 @@ {% block modal %} {{ filechooser_modal() }} {% endblock %} -{% block js %} - -{% endblock %} diff --git a/cps/templates/config_view_edit.html b/cps/templates/config_view_edit.html index e0a8094c..2ea1c53c 100644 --- a/cps/templates/config_view_edit.html +++ b/cps/templates/config_view_edit.html @@ -6,8 +6,8 @@ {% block body %}

{{title}}

-
-
+ +

@@ -71,7 +71,6 @@

-

@@ -146,6 +145,7 @@

+
{{_('Cancel')}} @@ -157,13 +157,6 @@ {{ restrict_modal() }} {% endblock %} {% block js %} - diff --git a/cps/templates/layout.html b/cps/templates/layout.html index 62e62855..87186724 100644 --- a/cps/templates/layout.html +++ b/cps/templates/layout.html @@ -111,6 +111,7 @@
{%endif%} {% endfor %} + {% block flash %}{% endblock %} {% if g.current_theme == 1 %} @@ -516,11 +516,11 @@ - + TestEbookConvertCalibreGDrive 6 - 6 - 0 + 5 + 1 0 0 @@ -548,11 +548,33 @@ - +
TestEbookConvertCalibreGDrive - test_convert_only
- PASS + +
+ FAIL +
+ + + + @@ -669,12 +691,12 @@ - + TestEditAdditionalBooks 13 - 12 - 0 - 0 + 5 + 2 + 5 1 Detail @@ -683,11 +705,33 @@ - +
TestEditAdditionalBooks - test_change_upload_formats
- PASS + +
+ FAIL +
+ + + + @@ -701,11 +745,31 @@ - +
TestEditAdditionalBooks - test_delete_role
- PASS + +
+ FAIL +
+ + + + @@ -746,38 +810,132 @@ - +
TestEditAdditionalBooks - test_title_sort
- PASS + +
+ ERROR +
+ + + + - +
TestEditAdditionalBooks - test_upload_edit_role
- PASS + +
+ ERROR +
+ + + + - +
TestEditAdditionalBooks - test_upload_metadata_cbr
- PASS + +
+ ERROR +
+ + + + - +
TestEditAdditionalBooks - test_upload_metadata_cbt
- PASS + +
+ ERROR +
+ + + + @@ -808,11 +966,89 @@ - +
TestEditAdditionalBooks - test_writeonly_path
- PASS + +
+ ERROR +
+ + + + + + + + + + + _ErrorHolder + 1 + 0 + 0 + 1 + 0 + + Detail + + + + + + + +
tearDownClass (test_edit_additional_books)
+ + +
+ ERROR +
+ + + + @@ -826,13 +1062,13 @@ 0 1 - Detail + Detail - +
TestEditBooks - test_download_book
@@ -841,7 +1077,7 @@ - +
TestEditBooks - test_edit_author
@@ -850,7 +1086,7 @@ - +
TestEditBooks - test_edit_category
@@ -859,7 +1095,7 @@ - +
TestEditBooks - test_edit_comments
@@ -868,7 +1104,7 @@ - +
TestEditBooks - test_edit_custom_bool
@@ -877,7 +1113,7 @@ - +
TestEditBooks - test_edit_custom_categories
@@ -886,7 +1122,7 @@ - +
TestEditBooks - test_edit_custom_comment
@@ -895,7 +1131,7 @@ - +
TestEditBooks - test_edit_custom_date
@@ -904,7 +1140,7 @@ - +
TestEditBooks - test_edit_custom_float
@@ -913,7 +1149,7 @@ - +
TestEditBooks - test_edit_custom_int
@@ -922,7 +1158,7 @@ - +
TestEditBooks - test_edit_custom_rating
@@ -931,7 +1167,7 @@ - +
TestEditBooks - test_edit_custom_single_select
@@ -940,7 +1176,7 @@ - +
TestEditBooks - test_edit_custom_text
@@ -949,7 +1185,7 @@ - +
TestEditBooks - test_edit_language
@@ -958,7 +1194,7 @@ - +
TestEditBooks - test_edit_publisher
@@ -967,7 +1203,7 @@ - +
TestEditBooks - test_edit_publishing_date
@@ -976,7 +1212,7 @@ - +
TestEditBooks - test_edit_rating
@@ -985,7 +1221,7 @@ - +
TestEditBooks - test_edit_series
@@ -994,7 +1230,7 @@ - +
TestEditBooks - test_edit_title
@@ -1003,19 +1239,19 @@ - +
TestEditBooks - test_rename_uppercase_lowercase
- SKIP + SKIP
-