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()
|
||||||
|
|
||||||
|
|
275
cps/admin.py
275
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,23 +996,24 @@ def _config_string(to_save, x):
|
||||||
|
|
||||||
def _configuration_gdrive_helper(to_save):
|
def _configuration_gdrive_helper(to_save):
|
||||||
gdrive_error = None
|
gdrive_error = None
|
||||||
gdrive_secrets = {}
|
if to_save.get("config_use_google_drive"):
|
||||||
|
gdrive_secrets = {}
|
||||||
|
|
||||||
if not os.path.isfile(gdriveutils.SETTINGS_YAML):
|
if not os.path.isfile(gdriveutils.SETTINGS_YAML):
|
||||||
config.config_use_google_drive = False
|
config.config_use_google_drive = False
|
||||||
|
|
||||||
if gdrive_support:
|
if gdrive_support:
|
||||||
gdrive_error = gdriveutils.get_error_text(gdrive_secrets)
|
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:
|
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:
|
with open(gdriveutils.CLIENT_SECRETS, 'r') as settings:
|
||||||
gdrive_secrets = json.load(settings)['web']
|
gdrive_secrets = json.load(settings)['web']
|
||||||
if not gdrive_secrets:
|
if not gdrive_secrets:
|
||||||
return _configuration_result(_('client_secrets.json Is Not Configured For Web Application'))
|
return _configuration_result(_('client_secrets.json Is Not Configured For Web Application'))
|
||||||
gdriveutils.update_settings(
|
gdriveutils.update_settings(
|
||||||
gdrive_secrets['client_id'],
|
gdrive_secrets['client_id'],
|
||||||
gdrive_secrets['client_secret'],
|
gdrive_secrets['client_secret'],
|
||||||
gdrive_secrets['redirect_uris'][0]
|
gdrive_secrets['redirect_uris'][0]
|
||||||
)
|
)
|
||||||
|
|
||||||
# always show google drive settings, but in case of error deny support
|
# 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)
|
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
|
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,97 +1,24 @@
|
||||||
{% 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>
|
||||||
<form role="form" method="POST" autocomplete="off">
|
<form role="form" method="POST" autocomplete="off">
|
||||||
<div class="panel-group col-md-10 col-lg-6">
|
<div class="panel-group 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">
|
||||||
<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">
|
||||||
|
@ -146,6 +145,7 @@
|
||||||
</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>
|
||||||
<a href="{{ url_for('admin.admin') }}" class="btn btn-default">{{_('Cancel')}}</a>
|
<a href="{{ url_for('admin.admin') }}" class="btn btn-default">{{_('Cancel')}}</a>
|
||||||
|
@ -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:
|
||||||
|
|
20
cps/web.py
20
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:
|
||||||
headers = Headers()
|
try:
|
||||||
headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream")
|
headers = Headers()
|
||||||
df = getFileFromEbooksFolder(book.path, data.name + "." + book_format)
|
headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream")
|
||||||
return do_gdrive_download(df, headers, (book_format.upper() == 'TXT'))
|
df = getFileFromEbooksFolder(book.path, data.name + "." + book_format)
|
||||||
|
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