mirror of
				https://github.com/janeczku/calibre-web
				synced 2025-10-31 15:23:02 +00:00 
			
		
		
		
	Added Scheduled Tasks Settings
This commit is contained in:
		
							
								
								
									
										62
									
								
								cps/admin.py
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								cps/admin.py
									
									
									
									
									
								
							| @@ -159,23 +159,6 @@ def shutdown(): | |||||||
|     return json.dumps(showtext), 400 |     return json.dumps(showtext), 400 | ||||||
|  |  | ||||||
|  |  | ||||||
| @admi.route("/clear-cache") |  | ||||||
| @login_required |  | ||||||
| @admin_required |  | ||||||
| def clear_cache(): |  | ||||||
|     cache_type = request.args.get('cache_type'.strip()) |  | ||||||
|     showtext = {} |  | ||||||
|  |  | ||||||
|     if cache_type == constants.CACHE_TYPE_THUMBNAILS: |  | ||||||
|         log.info('clearing cover thumbnail cache') |  | ||||||
|         showtext['text'] = _(u'Cleared cover thumbnail cache') |  | ||||||
|         helper.clear_cover_thumbnail_cache() |  | ||||||
|         return json.dumps(showtext) |  | ||||||
|  |  | ||||||
|     showtext['text'] = _(u'Unknown command') |  | ||||||
|     return json.dumps(showtext) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @admi.route("/admin/view") | @admi.route("/admin/view") | ||||||
| @login_required | @login_required | ||||||
| @admin_required | @admin_required | ||||||
| @@ -205,6 +188,7 @@ 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"]) | @admi.route("/admin/dbconfig", methods=["GET", "POST"]) | ||||||
| @login_required | @login_required | ||||||
| @admin_required | @admin_required | ||||||
| @@ -245,6 +229,7 @@ def ajax_db_config(): | |||||||
| def calibreweb_alive(): | def calibreweb_alive(): | ||||||
|     return "", 200 |     return "", 200 | ||||||
|  |  | ||||||
|  |  | ||||||
| @admi.route("/admin/viewconfig") | @admi.route("/admin/viewconfig") | ||||||
| @login_required | @login_required | ||||||
| @admin_required | @admin_required | ||||||
| @@ -257,6 +242,7 @@ def view_configuration(): | |||||||
|                                  restrictColumns=restrict_columns, |                                  restrictColumns=restrict_columns, | ||||||
|                                  title=_(u"UI Configuration"), page="uiconfig") |                                  title=_(u"UI Configuration"), page="uiconfig") | ||||||
|  |  | ||||||
|  |  | ||||||
| @admi.route("/admin/usertable") | @admi.route("/admin/usertable") | ||||||
| @login_required | @login_required | ||||||
| @admin_required | @admin_required | ||||||
| @@ -339,6 +325,7 @@ def list_users(): | |||||||
|     response.headers["Content-Type"] = "application/json; charset=utf-8" |     response.headers["Content-Type"] = "application/json; charset=utf-8" | ||||||
|     return response |     return response | ||||||
|  |  | ||||||
|  |  | ||||||
| @admi.route("/ajax/deleteuser", methods=['POST']) | @admi.route("/ajax/deleteuser", methods=['POST']) | ||||||
| @login_required | @login_required | ||||||
| @admin_required | @admin_required | ||||||
| @@ -372,6 +359,7 @@ def delete_user(): | |||||||
|     success.extend(errors) |     success.extend(errors) | ||||||
|     return Response(json.dumps(success), mimetype='application/json') |     return Response(json.dumps(success), mimetype='application/json') | ||||||
|  |  | ||||||
|  |  | ||||||
| @admi.route("/ajax/getlocale") | @admi.route("/ajax/getlocale") | ||||||
| @login_required | @login_required | ||||||
| @admin_required | @admin_required | ||||||
| @@ -517,6 +505,7 @@ def update_table_settings(): | |||||||
|         return "Invalid request", 400 |         return "Invalid request", 400 | ||||||
|     return "" |     return "" | ||||||
|  |  | ||||||
|  |  | ||||||
| def check_valid_read_column(column): | def check_valid_read_column(column): | ||||||
|     if column != "0": |     if column != "0": | ||||||
|         if not calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.id == column) \ |         if not calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.id == column) \ | ||||||
| @@ -524,6 +513,7 @@ def check_valid_read_column(column): | |||||||
|             return False |             return False | ||||||
|     return True |     return True | ||||||
|  |  | ||||||
|  |  | ||||||
| def check_valid_restricted_column(column): | def check_valid_restricted_column(column): | ||||||
|     if column != "0": |     if column != "0": | ||||||
|         if not calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.id == column) \ |         if not calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.id == column) \ | ||||||
| @@ -532,7 +522,6 @@ def check_valid_restricted_column(column): | |||||||
|     return True |     return True | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @admi.route("/admin/viewconfig", methods=["POST"]) | @admi.route("/admin/viewconfig", methods=["POST"]) | ||||||
| @login_required | @login_required | ||||||
| @admin_required | @admin_required | ||||||
| @@ -564,7 +553,6 @@ def update_view_configuration(): | |||||||
|     _config_int(to_save, "config_books_per_page") |     _config_int(to_save, "config_books_per_page") | ||||||
|     _config_int(to_save, "config_authors_max") |     _config_int(to_save, "config_authors_max") | ||||||
|  |  | ||||||
|  |  | ||||||
|     config.config_default_role = constants.selected_roles(to_save) |     config.config_default_role = constants.selected_roles(to_save) | ||||||
|     config.config_default_role &= ~constants.ROLE_ANONYMOUS |     config.config_default_role &= ~constants.ROLE_ANONYMOUS | ||||||
|  |  | ||||||
| @@ -1210,6 +1198,7 @@ def _db_configuration_update_helper(): | |||||||
|     config.save() |     config.save() | ||||||
|     return _db_configuration_result(None, gdrive_error) |     return _db_configuration_result(None, gdrive_error) | ||||||
|  |  | ||||||
|  |  | ||||||
| def _configuration_update_helper(): | def _configuration_update_helper(): | ||||||
|     reboot_required = False |     reboot_required = False | ||||||
|     to_save = request.form.to_dict() |     to_save = request.form.to_dict() | ||||||
| @@ -1299,6 +1288,7 @@ def _configuration_update_helper(): | |||||||
|  |  | ||||||
|     return _configuration_result(None, reboot_required) |     return _configuration_result(None, reboot_required) | ||||||
|  |  | ||||||
|  |  | ||||||
| def _configuration_result(error_flash=None, reboot=False): | def _configuration_result(error_flash=None, reboot=False): | ||||||
|     resp = {} |     resp = {} | ||||||
|     if error_flash: |     if error_flash: | ||||||
| @@ -1388,6 +1378,7 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support): | |||||||
|         log.error("Settings DB is not Writeable") |         log.error("Settings DB is not Writeable") | ||||||
|         flash(_("Settings DB is not Writeable"), category="error") |         flash(_("Settings DB is not Writeable"), category="error") | ||||||
|  |  | ||||||
|  |  | ||||||
| def _delete_user(content): | def _delete_user(content): | ||||||
|     if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, |     if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN, | ||||||
|                                         ub.User.id != content.id).count(): |                                         ub.User.id != content.id).count(): | ||||||
| @@ -1572,6 +1563,39 @@ def update_mailsettings(): | |||||||
|     return edit_mailsettings() |     return edit_mailsettings() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @admi.route("/admin/scheduledtasks") | ||||||
|  | @login_required | ||||||
|  | @admin_required | ||||||
|  | def edit_scheduledtasks(): | ||||||
|  |     content = config.get_scheduled_task_settings() | ||||||
|  |     return render_title_template("schedule_edit.html", content=content, title=_(u"Edit Scheduled Tasks Settings")) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @admi.route("/admin/scheduledtasks", methods=["POST"]) | ||||||
|  | @login_required | ||||||
|  | @admin_required | ||||||
|  | def update_scheduledtasks(): | ||||||
|  |     to_save = request.form.to_dict() | ||||||
|  |     _config_int(to_save, "schedule_start_time") | ||||||
|  |     _config_int(to_save, "schedule_end_time") | ||||||
|  |     _config_checkbox(to_save, "schedule_generate_book_covers") | ||||||
|  |     _config_checkbox(to_save, "schedule_generate_series_covers") | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         config.save() | ||||||
|  |         flash(_(u"Scheduled tasks settings updated"), category="success") | ||||||
|  |     except IntegrityError as ex: | ||||||
|  |         ub.session.rollback() | ||||||
|  |         log.error("An unknown error occurred while saving scheduled tasks settings") | ||||||
|  |         flash(_(u"An unknown error occurred. Please try again later."), category="error") | ||||||
|  |     except OperationalError: | ||||||
|  |         ub.session.rollback() | ||||||
|  |         log.error("Settings DB is not Writeable") | ||||||
|  |         flash(_("Settings DB is not Writeable"), category="error") | ||||||
|  |  | ||||||
|  |     return edit_scheduledtasks() | ||||||
|  |  | ||||||
|  |  | ||||||
| @admi.route("/admin/user/<int:user_id>", methods=["GET", "POST"]) | @admi.route("/admin/user/<int:user_id>", methods=["GET", "POST"]) | ||||||
| @login_required | @login_required | ||||||
| @admin_required | @admin_required | ||||||
|   | |||||||
| @@ -133,13 +133,18 @@ class _Settings(_Base): | |||||||
|     config_calibre = Column(String) |     config_calibre = Column(String) | ||||||
|     config_rarfile_location = Column(String, default=None) |     config_rarfile_location = Column(String, default=None) | ||||||
|     config_upload_formats = Column(String, default=','.join(constants.EXTENSIONS_UPLOAD)) |     config_upload_formats = Column(String, default=','.join(constants.EXTENSIONS_UPLOAD)) | ||||||
|     config_unicode_filename =Column(Boolean, default=False) |     config_unicode_filename = Column(Boolean, default=False) | ||||||
|  |  | ||||||
|     config_updatechannel = Column(Integer, default=constants.UPDATE_STABLE) |     config_updatechannel = Column(Integer, default=constants.UPDATE_STABLE) | ||||||
|  |  | ||||||
|     config_reverse_proxy_login_header_name = Column(String) |     config_reverse_proxy_login_header_name = Column(String) | ||||||
|     config_allow_reverse_proxy_header_login = Column(Boolean, default=False) |     config_allow_reverse_proxy_header_login = Column(Boolean, default=False) | ||||||
|  |  | ||||||
|  |     schedule_start_time = Column(Integer, default=4) | ||||||
|  |     schedule_end_time = Column(Integer, default=6) | ||||||
|  |     schedule_generate_book_covers = Column(Boolean, default=False) | ||||||
|  |     schedule_generate_series_covers = Column(Boolean, default=False) | ||||||
|  |  | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return self.__class__.__name__ |         return self.__class__.__name__ | ||||||
|  |  | ||||||
| @@ -170,7 +175,6 @@ class _ConfigSQL(object): | |||||||
|         if change: |         if change: | ||||||
|             self.save() |             self.save() | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _read_from_storage(self): |     def _read_from_storage(self): | ||||||
|         if self._settings is None: |         if self._settings is None: | ||||||
|             log.debug("_ConfigSQL._read_from_storage") |             log.debug("_ConfigSQL._read_from_storage") | ||||||
| @@ -254,6 +258,8 @@ class _ConfigSQL(object): | |||||||
|         return bool((self.mail_server != constants.DEFAULT_MAIL_SERVER and self.mail_server_type == 0) |         return bool((self.mail_server != constants.DEFAULT_MAIL_SERVER and self.mail_server_type == 0) | ||||||
|                     or (self.mail_gmail_token != {} and self.mail_server_type == 1)) |                     or (self.mail_gmail_token != {} and self.mail_server_type == 1)) | ||||||
|  |  | ||||||
|  |     def get_scheduled_task_settings(self): | ||||||
|  |         return {k:v for k, v in self.__dict__.items() if k.startswith('schedule_')} | ||||||
|  |  | ||||||
|     def set_from_dictionary(self, dictionary, field, convertor=None, default=None, encode=None): |     def set_from_dictionary(self, dictionary, field, convertor=None, default=None, encode=None): | ||||||
|         """Possibly updates a field of this object. |         """Possibly updates a field of this object. | ||||||
| @@ -289,7 +295,6 @@ class _ConfigSQL(object): | |||||||
|                 storage[k] = v |                 storage[k] = v | ||||||
|         return storage |         return storage | ||||||
|  |  | ||||||
|  |  | ||||||
|     def load(self): |     def load(self): | ||||||
|         '''Load all configuration values from the underlying storage.''' |         '''Load all configuration values from the underlying storage.''' | ||||||
|         s = self._read_from_storage()  # type: _Settings |         s = self._read_from_storage()  # type: _Settings | ||||||
| @@ -407,6 +412,7 @@ def autodetect_calibre_binary(): | |||||||
|             return element |             return element | ||||||
|     return "" |     return "" | ||||||
|  |  | ||||||
|  |  | ||||||
| def autodetect_unrar_binary(): | def autodetect_unrar_binary(): | ||||||
|     if sys.platform == "win32": |     if sys.platform == "win32": | ||||||
|         calibre_path = ["C:\\program files\\WinRar\\unRAR.exe", |         calibre_path = ["C:\\program files\\WinRar\\unRAR.exe", | ||||||
| @@ -418,6 +424,7 @@ def autodetect_unrar_binary(): | |||||||
|             return element |             return element | ||||||
|     return "" |     return "" | ||||||
|  |  | ||||||
|  |  | ||||||
| def autodetect_kepubify_binary(): | def autodetect_kepubify_binary(): | ||||||
|     if sys.platform == "win32": |     if sys.platform == "win32": | ||||||
|         calibre_path = ["C:\\program files\\kepubify\\kepubify-windows-64Bit.exe", |         calibre_path = ["C:\\program files\\kepubify\\kepubify-windows-64Bit.exe", | ||||||
| @@ -429,6 +436,7 @@ def autodetect_kepubify_binary(): | |||||||
|             return element |             return element | ||||||
|     return "" |     return "" | ||||||
|  |  | ||||||
|  |  | ||||||
| def _migrate_database(session): | def _migrate_database(session): | ||||||
|     # make sure the table is created, if it does not exist |     # make sure the table is created, if it does not exist | ||||||
|     _Base.metadata.create_all(session.bind) |     _Base.metadata.create_all(session.bind) | ||||||
| @@ -452,6 +460,7 @@ def load_configuration(session): | |||||||
|     #    session.commit() |     #    session.commit() | ||||||
|     return conf |     return conf | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_flask_session_key(session): | def get_flask_session_key(session): | ||||||
|     flask_settings = session.query(_Flask_Settings).one_or_none() |     flask_settings = session.query(_Flask_Settings).one_or_none() | ||||||
|     if flask_settings == None: |     if flask_settings == None: | ||||||
|   | |||||||
| @@ -19,7 +19,6 @@ | |||||||
| from __future__ import division, print_function, unicode_literals | from __future__ import division, print_function, unicode_literals | ||||||
|  |  | ||||||
| from .services.background_scheduler import BackgroundScheduler | from .services.background_scheduler import BackgroundScheduler | ||||||
| from .services.worker import WorkerThread |  | ||||||
| from .tasks.database import TaskReconnectDatabase | from .tasks.database import TaskReconnectDatabase | ||||||
| from .tasks.thumbnail import TaskGenerateCoverThumbnails, TaskGenerateSeriesThumbnails | from .tasks.thumbnail import TaskGenerateCoverThumbnails, TaskGenerateSeriesThumbnails | ||||||
|  |  | ||||||
| @@ -28,13 +27,19 @@ def register_jobs(): | |||||||
|     scheduler = BackgroundScheduler() |     scheduler = BackgroundScheduler() | ||||||
|  |  | ||||||
|     if scheduler: |     if scheduler: | ||||||
|         # Reconnect metadata.db once every 12 hours |         # Reconnect Calibre database (metadata.db) | ||||||
|         scheduler.add_task(user=None, task=lambda: TaskReconnectDatabase(), trigger='cron', hour='4,16') |         scheduler.schedule_task(user=None, task=lambda: TaskReconnectDatabase(), trigger='cron', hour='4,16') | ||||||
|  |  | ||||||
|         # Generate all missing book cover thumbnails once every 24 hours |         # Generate all missing book cover thumbnails | ||||||
|         scheduler.add_task(user=None, task=lambda: TaskGenerateCoverThumbnails(), trigger='cron', hour=4) |         scheduler.schedule_task(user=None, task=lambda: TaskGenerateCoverThumbnails(), trigger='cron', hour=4) | ||||||
|  |  | ||||||
|  |         # Generate all missing series thumbnails | ||||||
|  |         scheduler.schedule_task(user=None, task=lambda: TaskGenerateSeriesThumbnails(), trigger='cron', hour=4) | ||||||
|  |  | ||||||
|  |  | ||||||
| def register_startup_jobs(): | def register_startup_jobs(): | ||||||
|     WorkerThread.add(None, TaskGenerateCoverThumbnails()) |     scheduler = BackgroundScheduler() | ||||||
|     # WorkerThread.add(None, TaskGenerateSeriesThumbnails()) |  | ||||||
|  |     if scheduler: | ||||||
|  |         scheduler.schedule_task_immediately(None, task=lambda: TaskGenerateCoverThumbnails()) | ||||||
|  |         scheduler.schedule_task_immediately(None, task=lambda: TaskGenerateSeriesThumbnails()) | ||||||
|   | |||||||
| @@ -40,25 +40,31 @@ class BackgroundScheduler: | |||||||
|  |  | ||||||
|         if cls._instance is None: |         if cls._instance is None: | ||||||
|             cls._instance = super(BackgroundScheduler, cls).__new__(cls) |             cls._instance = super(BackgroundScheduler, cls).__new__(cls) | ||||||
|  |  | ||||||
|             scheduler = BScheduler() |  | ||||||
|             atexit.register(lambda: scheduler.shutdown()) |  | ||||||
|  |  | ||||||
|             cls.log = logger.create() |             cls.log = logger.create() | ||||||
|             cls.scheduler = scheduler |             cls.scheduler = BScheduler() | ||||||
|             cls.scheduler.start() |             cls.scheduler.start() | ||||||
|  |  | ||||||
|  |             atexit.register(lambda: cls.scheduler.shutdown()) | ||||||
|  |  | ||||||
|         return cls._instance |         return cls._instance | ||||||
|  |  | ||||||
|     def add(self, func, trigger, **trigger_args): |     def _add(self, func, trigger, **trigger_args): | ||||||
|         if use_APScheduler: |         if use_APScheduler: | ||||||
|             return self.scheduler.add_job(func=func, trigger=trigger, **trigger_args) |             return self.scheduler.add_job(func=func, trigger=trigger, **trigger_args) | ||||||
|  |  | ||||||
|     def add_task(self, user, task, trigger, **trigger_args): |     # Expects a lambda expression for the task, so that the task isn't instantiated before the task is scheduled | ||||||
|  |     def schedule_task(self, user, task, trigger, **trigger_args): | ||||||
|         if use_APScheduler: |         if use_APScheduler: | ||||||
|             def scheduled_task(): |             def scheduled_task(): | ||||||
|                 worker_task = task() |                 worker_task = task() | ||||||
|                 self.log.info(f'Running scheduled task in background: {worker_task.name} - {worker_task.message}') |  | ||||||
|                 WorkerThread.add(user, worker_task) |                 WorkerThread.add(user, worker_task) | ||||||
|  |  | ||||||
|             return self.add(func=scheduled_task, trigger=trigger, **trigger_args) |             return self._add(func=scheduled_task, trigger=trigger, **trigger_args) | ||||||
|  |  | ||||||
|  |     # Expects a lambda expression for the task, so that the task isn't instantiated before the task is scheduled | ||||||
|  |     def schedule_task_immediately(self, user, task): | ||||||
|  |         if use_APScheduler: | ||||||
|  |             def scheduled_task(): | ||||||
|  |                 WorkerThread.add(user, task()) | ||||||
|  |  | ||||||
|  |             return self._add(func=scheduled_task, trigger='date') | ||||||
|   | |||||||
| @@ -167,9 +167,9 @@ class TaskGenerateCoverThumbnails(CalibreTask): | |||||||
|                 try: |                 try: | ||||||
|                     stream = urlopen(web_content_link) |                     stream = urlopen(web_content_link) | ||||||
|                     with Image(file=stream) as img: |                     with Image(file=stream) as img: | ||||||
|                         height = self.get_thumbnail_height(thumbnail) |                         height = get_resize_height(thumbnail.resolution) | ||||||
|                         if img.height > height: |                         if img.height > height: | ||||||
|                             width = self.get_thumbnail_width(height, img) |                             width = get_resize_width(thumbnail.resolution, img.width, img.height) | ||||||
|                             img.resize(width=width, height=height, filter='lanczos') |                             img.resize(width=width, height=height, filter='lanczos') | ||||||
|                             img.format = thumbnail.format |                             img.format = thumbnail.format | ||||||
|                             filename = self.cache.get_cache_file_path(thumbnail.filename, |                             filename = self.cache.get_cache_file_path(thumbnail.filename, | ||||||
| @@ -212,16 +212,6 @@ class TaskGenerateSeriesThumbnails(CalibreTask): | |||||||
|             constants.COVER_THUMBNAIL_MEDIUM |             constants.COVER_THUMBNAIL_MEDIUM | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|     # get all series |  | ||||||
|     # get all books in series with covers and count >= 4 books |  | ||||||
|     # get the dimensions from the first book in the series & pop the first book from the series list of books |  | ||||||
|     # randomly select three other books in the series |  | ||||||
|  |  | ||||||
|     # resize the covers in the sequence? |  | ||||||
|     # create an image sequence from the 4 selected books of the series |  | ||||||
|     # join pairs of books in the series with wand's concat |  | ||||||
|     # join the two sets of pairs with wand's |  | ||||||
|  |  | ||||||
|     def run(self, worker_thread): |     def run(self, worker_thread): | ||||||
|         if self.calibre_db.session and use_IM: |         if self.calibre_db.session and use_IM: | ||||||
|             all_series = self.get_series_with_four_plus_books() |             all_series = self.get_series_with_four_plus_books() | ||||||
|   | |||||||
| @@ -156,6 +156,31 @@ | |||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
|  |   <div class="row"> | ||||||
|  |     <div class="col"> | ||||||
|  |       <h2>{{_('Scheduled Tasks')}}</h2> | ||||||
|  |         <div class="col-xs-12 col-sm-12"> | ||||||
|  |           <div class="row"> | ||||||
|  |             <div class="col-xs-6 col-sm-3">{{_('Time at which tasks start to run')}}</div> | ||||||
|  |             <div class="col-xs-6 col-sm-3">{{config.schedule_start_time}}:00</div> | ||||||
|  |           </div> | ||||||
|  |           <div class="row"> | ||||||
|  |             <div class="col-xs-6 col-sm-3">{{_('Time at which tasks stop running')}}</div> | ||||||
|  |             <div class="col-xs-6 col-sm-3">{{config.schedule_end_time}}:00</div> | ||||||
|  |           </div> | ||||||
|  |           <div class="row"> | ||||||
|  |             <div class="col-xs-6 col-sm-3">{{_('Generate book cover thumbnails')}}</div> | ||||||
|  |             <div class="col-xs-6 col-sm-3">{{ display_bool_setting(config.schedule_generate_book_covers) }}</div> | ||||||
|  |           </div> | ||||||
|  |           <div class="row"> | ||||||
|  |             <div class="col-xs-6 col-sm-3">{{_('Generate series cover thumbnails')}}</div> | ||||||
|  |             <div class="col-xs-6 col-sm-3">{{ display_bool_setting(config.schedule_generate_series_covers) }}</div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       <a class="btn btn-default scheduledtasks" id="admin_edit_scheduled_tasks" href="{{url_for('admin.edit_scheduledtasks')}}">{{_('Edit Scheduled Tasks Settings')}}</a> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |  | ||||||
|   <div class="row form-group"> |   <div class="row form-group"> | ||||||
|     <h2>{{_('Administration')}}</h2> |     <h2>{{_('Administration')}}</h2> | ||||||
|     <a class="btn btn-default" id="debug" href="{{url_for('admin.download_debug')}}">{{_('Download Debug Package')}}</a> |     <a class="btn btn-default" id="debug" href="{{url_for('admin.download_debug')}}">{{_('Download Debug Package')}}</a> | ||||||
| @@ -163,7 +188,6 @@ | |||||||
|   </div> |   </div> | ||||||
|   <div class="row form-group"> |   <div class="row form-group"> | ||||||
|     <div class="btn btn-default" id="restart_database" data-toggle="modal" data-target="#StatusDialog">{{_('Reconnect Calibre Database')}}</div> |     <div class="btn btn-default" id="restart_database" data-toggle="modal" data-target="#StatusDialog">{{_('Reconnect Calibre Database')}}</div> | ||||||
|     <div class="btn btn-default" id="clear_cover_thumbnail_cache" data-toggle="modal" data-target="#ClearCacheDialog">{{_('Clear Cover Thumbnail Cache')}}</div> |  | ||||||
|   </div> |   </div> | ||||||
|   <div class="row form-group"> |   <div class="row form-group"> | ||||||
|     <div class="btn btn-default" id="admin_restart" data-toggle="modal" data-target="#RestartDialog">{{_('Restart')}}</div> |     <div class="btn btn-default" id="admin_restart" data-toggle="modal" data-target="#RestartDialog">{{_('Restart')}}</div> | ||||||
| @@ -248,21 +272,4 @@ | |||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
| </div> | </div> | ||||||
| <div id="ClearCacheDialog" 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>{{_('Are you sure you want to clear the cover thumbnail cache?')}}</p> |  | ||||||
|         <div id="spinner3" class="spinner" style="display:none;"> |  | ||||||
|           <img id="img-spinner3" src="{{ url_for('static', filename='css/libs/images/loading-icon.gif') }}"/> |  | ||||||
|         </div> |  | ||||||
|         <p></p> |  | ||||||
|         <button type="button" class="btn btn-default" id="clear_cache" >{{_('OK')}}</button> |  | ||||||
|         <button type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| </div> |  | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
							
								
								
									
										38
									
								
								cps/templates/schedule_edit.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								cps/templates/schedule_edit.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | {% extends "layout.html" %} | ||||||
|  | {% block header %} | ||||||
|  | <link href="{{ url_for('static', filename='css/libs/bootstrap-table.min.css') }}" rel="stylesheet"> | ||||||
|  | <link href="{{ url_for('static', filename='css/libs/bootstrap-editable.css') }}" rel="stylesheet"> | ||||||
|  | {% endblock %} | ||||||
|  | {% block body %} | ||||||
|  | <div class="discover"> | ||||||
|  |   <h1>{{title}}</h1> | ||||||
|  |   <form role="form" class="col-md-10 col-lg-6" method="POST" autocomplete="off"> | ||||||
|  |     <div class="form-group"> | ||||||
|  |       <label for="schedule_start_time">{{_('Time at which tasks start to run')}}</label> | ||||||
|  |       <select name="schedule_start_time" id="schedule_start_time" class="form-control"> | ||||||
|  |         {% for n in range(24) %} | ||||||
|  |           <option value="{{n}}" {% if content.schedule_start_time == n %}selected{% endif %}>{{n}}{{_(':00')}}</option> | ||||||
|  |         {% endfor %} | ||||||
|  |       </select> | ||||||
|  |     </div> | ||||||
|  |     <div class="form-group"> | ||||||
|  |       <label for="schedule_end_time">{{_('Time at which tasks stop running')}}</label> | ||||||
|  |       <select name="schedule_end_time" id="schedule_end_time" class="form-control"> | ||||||
|  |         {% for n in range(24) %} | ||||||
|  |           <option value="{{n}}" {% if content.schedule_end_time == n %}selected{% endif %}>{{n}}{{_(':00')}}</option> | ||||||
|  |         {% endfor %} | ||||||
|  |       </select> | ||||||
|  |     </div> | ||||||
|  |     <div class="form-group"> | ||||||
|  |       <input type="checkbox" id="schedule_generate_book_covers" name="schedule_generate_book_covers" checked> | ||||||
|  |       <label for="schedule_generate_book_covers">{{_('Generate Book Cover Thumbnails')}}</label> | ||||||
|  |     </div> | ||||||
|  |     <div class="form-group"> | ||||||
|  |       <input type="checkbox" id="schedule_generate_series_covers" name="schedule_generate_series_covers" {% if config.schedule_generate_series_covers %}checked{% endif %}> | ||||||
|  |       <label for="schedule_generate_series_covers">{{_('Generate Series Cover Thumbnails')}}</label> | ||||||
|  |     </div> | ||||||
|  |     <button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save')}}</button> | ||||||
|  |     <a href="{{ url_for('admin.admin') }}" id="email_back" class="btn btn-default">{{_('Cancel')}}</a> | ||||||
|  |   </form> | ||||||
|  | </div> | ||||||
|  | {% endblock %} | ||||||
		Reference in New Issue
	
	Block a user
	 mmonkey
					mmonkey