diff --git a/tailbone/templates/luigi/configure.mako b/tailbone/templates/luigi/configure.mako new file mode 100644 index 00000000..b8fba490 --- /dev/null +++ b/tailbone/templates/luigi/configure.mako @@ -0,0 +1,129 @@ +## -*- coding: utf-8; -*- +<%inherit file="/configure.mako" /> + +<%def name="form_content()"> + ${h.hidden('overnight_tasks', **{':value': 'JSON.stringify(overnightTasks)'})} + +

Overnight Tasks

+
+ + + + + +
+ + New Task + + + + + + +
+
+ +

Luigi Proper

+
+ + + + + + + + + + + + + + + + +
+ + + +<%def name="modify_this_page_vars()"> + ${parent.modify_this_page_vars()} + + + + +${parent.body()} diff --git a/tailbone/templates/luigi/index.mako b/tailbone/templates/luigi/index.mako new file mode 100644 index 00000000..16ea3489 --- /dev/null +++ b/tailbone/templates/luigi/index.mako @@ -0,0 +1,126 @@ +## -*- coding: utf-8; -*- +<%inherit file="/page.mako" /> + +<%def name="title()">Luigi Jobs + +<%def name="page_content()"> +
+
+ +
+ + + Luigi Task Visualiser + + + + Luigi Task History + + + % if master.has_perm('restart_scheduler'): + ${h.form(url('{}.restart_scheduler'.format(route_prefix)), **{'@submit': 'submitRestartSchedulerForm'})} + ${h.csrf_token(request)} + + {{ restartSchedulerFormSubmitting ? "Working, please wait..." : "Restart Luigi Scheduler" }} + + ${h.end_form()} + % endif +
+ + % if master.has_perm('launch'): +

Overnight Tasks

+ % for task in overnight_tasks: + + + % endfor + % endif + +
+ + +<%def name="modify_this_page_vars()"> + ${parent.modify_this_page_vars()} + % if master.has_perm('restart_scheduler'): + + % endif + + +<%def name="finalize_this_page_vars()"> + ${parent.finalize_this_page_vars()} + % if master.has_perm('launch'): + + % endif + + +<%def name="render_this_page_template()"> + ${parent.render_this_page_template()} + % if master.has_perm('launch'): + + % endif + + + +${parent.body()} diff --git a/tailbone/views/datasync.py b/tailbone/views/datasync.py index 20f970e4..0f198795 100644 --- a/tailbone/views/datasync.py +++ b/tailbone/views/datasync.py @@ -26,7 +26,6 @@ DataSync Views from __future__ import unicode_literals, absolute_import -import getpass import json import subprocess import logging @@ -234,7 +233,6 @@ class DataSyncThreadView(MasterView): 'rattail.datasync', 'supervisor_process_name'), 'restart_command': self.rattail_config.get( 'tailbone', 'datasync.restart'), - 'system_user': getpass.getuser(), } def configure_gather_settings(self, data): diff --git a/tailbone/views/luigi.py b/tailbone/views/luigi.py new file mode 100644 index 00000000..6b0b60e3 --- /dev/null +++ b/tailbone/views/luigi.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2022 Lance Edgar +# +# This file is part of Rattail. +# +# Rattail 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. +# +# Rattail 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 +# Rattail. If not, see . +# +################################################################################ +""" +Views for Luigi +""" + +from __future__ import unicode_literals, absolute_import + +import json + +from rattail.util import simple_error + +from tailbone.views import MasterView + + +class LuigiJobView(MasterView): + """ + Simple views for Luigi jobs. + """ + normalized_model_name = 'luigijobs' + model_key = 'jobname' + model_title = "Luigi Job" + route_prefix = 'luigi' + url_prefix = '/luigi' + + viewable = False + creatable = False + editable = False + deletable = False + configurable = True + + def __init__(self, request, context=None): + super(LuigiJobView, self).__init__(request, context=context) + app = self.get_rattail_app() + self.luigi_handler = app.get_luigi_handler() + + def index(self): + luigi_url = self.rattail_config.get('luigi', 'url') + history_url = '{}/history'.format(luigi_url.rstrip('/')) if luigi_url else None + return self.render_to_response('index', { + 'use_buefy': self.get_use_buefy(), + 'index_url': None, + 'luigi_url': luigi_url, + 'luigi_history_url': history_url, + 'overnight_tasks': self.luigi_handler.get_all_overnight_tasks(), + }) + + def launch(self): + key = self.request.POST['job'] + assert key + self.luigi_handler.restart_overnight_task(key) + self.request.session.flash("Scheduled overnight task for immediate launch: {}".format(key)) + return self.redirect(self.get_index_url()) + + def restart_scheduler(self): + try: + self.luigi_handler.restart_supervisor_process() + self.request.session.flash("Luigi scheduler has been restarted.") + + except Exception as error: + self.request.session.flash(simple_error(error), 'error') + + return self.redirect(self.request.get_referrer( + default=self.get_index_url())) + + def configure_get_simple_settings(self): + return [ + + # luigi proper + {'section': 'luigi', + 'option': 'url'}, + {'section': 'luigi', + 'option': 'scheduler.supervisor_process_name'}, + {'section': 'luigi', + 'option': 'scheduler.restart_command'}, + + ] + + def configure_get_context(self, **kwargs): + context = super(LuigiJobView, self).configure_get_context(**kwargs) + context['overnight_tasks'] = self.luigi_handler.get_all_overnight_tasks() + return context + + def configure_gather_settings(self, data): + settings = super(LuigiJobView, self).configure_gather_settings(data) + + keys = [] + for task in json.loads(data['overnight_tasks']): + keys.append(task['key']) + + if keys: + settings.append({'name': 'luigi.overnight_tasks', + 'value': ', '.join(keys)}) + + return settings + + def configure_remove_settings(self): + super(LuigiJobView, self).configure_remove_settings() + self.luigi_handler.purge_luigi_settings(self.Session()) + + @classmethod + def defaults(cls, config): + cls._defaults(config) + cls._luigi_defaults(config) + + @classmethod + def _luigi_defaults(cls, config): + route_prefix = cls.get_route_prefix() + permission_prefix = cls.get_permission_prefix() + url_prefix = cls.get_url_prefix() + model_title_plural = cls.get_model_title_plural() + + # launch job + config.add_tailbone_permission(permission_prefix, + '{}.launch'.format(permission_prefix), + label="Launch any Luigi job") + config.add_route('{}.launch'.format(route_prefix), + '{}/launch'.format(url_prefix), + request_method='POST') + config.add_view(cls, attr='launch', + route_name='{}.launch'.format(route_prefix), + permission='{}.launch'.format(permission_prefix)) + + # restart luigid scheduler + config.add_tailbone_permission(permission_prefix, + '{}.restart_scheduler'.format(permission_prefix), + label="Restart the Luigi Scheduler daemon") + config.add_route('{}.restart_scheduler'.format(route_prefix), + '{}/restart-scheduler'.format(url_prefix), + request_method='POST') + config.add_view(cls, attr='restart_scheduler', + route_name='{}.restart_scheduler'.format(route_prefix), + permission='{}.restart_scheduler'.format(permission_prefix)) + + +def defaults(config, **kwargs): + base = globals() + + LuigiJobView = kwargs.get('LuigiJobView', base['LuigiJobView']) + LuigiJobView.defaults(config) + + +def includeme(config): + defaults(config) diff --git a/tailbone/views/master.py b/tailbone/views/master.py index 1915ac83..1906d620 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -29,6 +29,7 @@ from __future__ import unicode_literals, absolute_import import os import csv import datetime +import getpass import shutil import tempfile import logging @@ -4324,6 +4325,10 @@ class MasterView(View): context = self.configure_get_context() return self.render_to_response('configure', context) + def template_kwargs_configure(self, **kwargs): + kwargs['system_user'] = getpass.getuser() + return kwargs + def configure_flash_settings_saved(self): self.request.session.flash("Settings have been saved.")