introduce run job now

This commit is contained in:
Davide Alberani 2018-01-26 11:10:02 +01:00
parent caf2c88ae1
commit 24a6fe7d91
3 changed files with 97 additions and 28 deletions

View file

@ -69,7 +69,15 @@ def read_schedules():
return {'schedules': {}} return {'schedules': {}}
try: try:
with open(SCHEDULES_FILE, 'r') as fd: with open(SCHEDULES_FILE, 'r') as fd:
return json.loads(fd.read()) schedules = json.loads(fd.read())
for id_ in schedules.get('schedules', {}).keys():
schedule = schedules['schedules'][id_]
try:
schedule['last_history'] = get_last_history(id_)
except:
schedule['last_history'] = {}
continue
return schedules
except Exception as e: except Exception as e:
logger.error('unable to read %s: %s' % (SCHEDULES_FILE, e)) logger.error('unable to read %s: %s' % (SCHEDULES_FILE, e))
return {'schedules': {}} return {'schedules': {}}
@ -104,7 +112,7 @@ def next_id(schedules):
return str(max([int(i) for i in ids]) + 1) return str(max([int(i) for i in ids]) + 1)
def get_schedule(id_, add_id=True): def get_schedule(id_, add_id=True, add_history=False):
"""Return information about a single schedule """Return information about a single schedule
:param id_: ID of the schedule :param id_: ID of the schedule
@ -118,6 +126,8 @@ def get_schedule(id_, add_id=True):
except Exception: except Exception:
return {} return {}
data = schedules.get('schedules', {}).get(id_, {}) data = schedules.get('schedules', {}).get(id_, {})
if add_history and data:
data['last_history'] = get_last_history(id_)
if add_id: if add_id:
data['id'] = str(id_) data['id'] = str(id_)
return data return data
@ -161,6 +171,9 @@ def run_job(id_=None, *args, **kwargs):
if not url: if not url:
return False return False
logger.debug('running job id:%s title:%s url: %s' % (id_, schedule.get('title', ''), url)) logger.debug('running job id:%s title:%s url: %s' % (id_, schedule.get('title', ''), url))
if not schedule.get('enabled'):
logger.info('not running job %s: disabled' % id_)
return True
req = requests.get(url, allow_redirects=True, timeout=(30.10, 240)) req = requests.get(url, allow_redirects=True, timeout=(30.10, 240))
content = req.text content = req.text
xpath = schedule.get('xpath') xpath = schedule.get('xpath')
@ -219,9 +232,10 @@ def run_job(id_=None, *args, **kwargs):
# send notification # send notification
diff = get_diff(id_).get('diff') diff = get_diff(id_).get('diff')
if not diff: if not diff:
return return True
send_email(to=email, subject='%s page changed' % schedule.get('title'), send_email(to=email, subject='%s page changed' % schedule.get('title'),
body='changes:\n\n%s' % diff) body='changes:\n\n%s' % diff)
return True
def safe_run_job(id_=None, *args, **kwargs): def safe_run_job(id_=None, *args, **kwargs):
@ -263,20 +277,27 @@ def send_email(to, subject='diffido', body='', from_=None):
return True return True
def get_history(id_): def get_history(id_, limit=None, add_info=False):
"""Read the history of a schedule """Read the history of a schedule
:param id_: ID of the schedule :param id_: ID of the schedule
:type id_: str :type id_: str
:param limit: number of entries to fetch
:type limit: int
:param add_info: add information about the schedule itself
:type add_info: int
:returns: information about the schedule and its history :returns: information about the schedule and its history
:rtype: dict""" :rtype: dict"""
def _history(id_, queue): def _history(id_, limit, queue):
os.chdir('storage/%s' % id_) os.chdir('storage/%s' % id_)
p = subprocess.Popen([GIT_CMD, 'log', '--pretty=oneline', '--shortstat'], stdout=subprocess.PIPE) cmd = [GIT_CMD, 'log', '--pretty=oneline', '--shortstat']
if limit is not None:
cmd.append('-%s' % limit)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
stdout, _ = p.communicate() stdout, _ = p.communicate()
queue.put(stdout) queue.put(stdout)
queue = multiprocessing.Queue() queue = multiprocessing.Queue()
p = multiprocessing.Process(target=_history, args=(id_, queue)) p = multiprocessing.Process(target=_history, args=(id_, limit, queue))
p.start() p.start()
res = queue.get().decode('utf-8') res = queue.get().decode('utf-8')
p.join() p.join()
@ -287,13 +308,26 @@ def get_history(id_):
info['deletions'] = int(info['deletions'] or 0) info['deletions'] = int(info['deletions'] or 0)
info['changes'] = max(info['insertions'], info['deletions']) info['changes'] = max(info['insertions'], info['deletions'])
history.append(info) history.append(info)
lastid = None last_id = None
if history and 'id' in history[0]: if history and 'id' in history[0]:
lastid = history[0]['id'] last_id = history[0]['id']
for idx, item in enumerate(history): for idx, item in enumerate(history):
item['seq'] = idx + 1 item['seq'] = idx + 1
schedule = get_schedule(id_) data = {'history': history, 'last_id': last_id}
return {'history': history, 'lastid': lastid, 'schedule': schedule} if add_info:
data['schedule'] = get_schedule(id_)
return data
def get_last_history(id_):
"""Read the last history entry of a schedule
:param id_: ID of the schedule
:type id_: str
:returns: information about the schedule and its history
:rtype: dict"""
history = get_history(id_, limit=1)
return history.get('history', [{}])[0]
def get_diff(id_, commit_id='HEAD', old_commit_id=None): def get_diff(id_, commit_id='HEAD', old_commit_id=None):
@ -505,7 +539,7 @@ class SchedulesHandler(BaseHandler):
def get(self, id_=None, *args, **kwargs): def get(self, id_=None, *args, **kwargs):
"""Get a schedule.""" """Get a schedule."""
if id_ is not None: if id_ is not None:
return self.write({'schedule': get_schedule(id_)}) return self.write({'schedule': get_schedule(id_, add_history=True)})
schedules = read_schedules() schedules = read_schedules()
self.write(schedules) self.write(schedules)
@ -547,6 +581,15 @@ class SchedulesHandler(BaseHandler):
self.build_success(message='removed schedule %s' % id_) self.build_success(message='removed schedule %s' % id_)
class RunScheduleHandler(BaseHandler):
"""Reset schedules handler."""
@gen.coroutine
def post(self, id_, *args, **kwargs):
if run_job(id_):
return self.build_success('job run')
self.build_error('job not run')
class ResetSchedulesHandler(BaseHandler): class ResetSchedulesHandler(BaseHandler):
"""Reset schedules handler.""" """Reset schedules handler."""
@gen.coroutine @gen.coroutine
@ -558,7 +601,7 @@ class HistoryHandler(BaseHandler):
"""History handler.""" """History handler."""
@gen.coroutine @gen.coroutine
def get(self, id_, *args, **kwargs): def get(self, id_, *args, **kwargs):
self.write(get_history(id_)) self.write(get_history(id_, add_info=True))
class DiffHandler(BaseHandler): class DiffHandler(BaseHandler):
@ -614,12 +657,15 @@ def serve():
scheduler=scheduler) scheduler=scheduler)
_reset_schedules_path = r'schedules/reset' _reset_schedules_path = r'schedules/reset'
_schedule_run_path = r'schedules/(?P<id_>\d+)/run'
_schedules_path = r'schedules/?(?P<id_>\d+)?' _schedules_path = r'schedules/?(?P<id_>\d+)?'
_history_path = r'history/?(?P<id_>\d+)' _history_path = r'history/?(?P<id_>\d+)'
_diff_path = r'diff/(?P<id_>\d+)/(?P<commit_id>[0-9a-f]+)/?(?P<old_commit_id>[0-9a-f]+)?/?' _diff_path = r'diff/(?P<id_>\d+)/(?P<commit_id>[0-9a-f]+)/?(?P<old_commit_id>[0-9a-f]+)?/?'
application = tornado.web.Application([ application = tornado.web.Application([
(r'/api/%s' % _reset_schedules_path, ResetSchedulesHandler, init_params), (r'/api/%s' % _reset_schedules_path, ResetSchedulesHandler, init_params),
(r'/api/v%s/%s' % (API_VERSION, _reset_schedules_path), ResetSchedulesHandler, init_params), (r'/api/v%s/%s' % (API_VERSION, _reset_schedules_path), ResetSchedulesHandler, init_params),
(r'/api/%s' % _schedule_run_path, RunScheduleHandler, init_params),
(r'/api/v%s/%s' % (API_VERSION, _schedule_run_path), RunScheduleHandler, init_params),
(r'/api/%s' % _schedules_path, SchedulesHandler, init_params), (r'/api/%s' % _schedules_path, SchedulesHandler, init_params),
(r'/api/v%s/%s' % (API_VERSION, _schedules_path), SchedulesHandler, init_params), (r'/api/v%s/%s' % (API_VERSION, _schedules_path), SchedulesHandler, init_params),
(r'/api/%s' % _history_path, HistoryHandler, init_params), (r'/api/%s' % _history_path, HistoryHandler, init_params),

13
dist/history.html vendored
View file

@ -21,8 +21,8 @@
</md-table-toolbar> </md-table-toolbar>
<md-table-row slot="md-table-row" slot-scope="{item}"> <md-table-row slot="md-table-row" slot-scope="{item}">
<md-table-cell> <md-table-cell>
(<a v-if="item.seq > 1" :href="'/diff.html?id=' + id + '&oldid=' + item.id + '&diff=' + lastid">cur</a><span v-if="item.seq == 1">cur</span> | <a :href="'/diff.html?id=' + id + '&diff=' + item.id">prev</a>) (<a v-if="item.seq > 1" :href="'/diff.html?id=' + id + '&oldid=' + item.id + '&diff=' + last_id">cur</a><span v-if="item.seq == 1">cur</span> | <a :href="'/diff.html?id=' + id + '&diff=' + item.id">prev</a>)
<md-radio name="oldid" v-model="oldid" :value="item.id" v-if="item.seq > 1" :seq="item.seq"></md-radio><span id="placeholder" v-if="item.seq == 1"> ---- </span> <md-radio name="oldid" v-model="oldid" :value="item.id" v-if="item.seq > 1" :seq="item.seq"></md-radio><span class="placeholder" v-if="item.seq == 1"></span>
<md-radio name="diff" v-model="diff" :value="item.id" :seq="item.seq"></md-radio> <md-radio name="diff" v-model="diff" :value="item.id" :seq="item.seq"></md-radio>
</md-table-cell> </md-table-cell>
<md-table-cell md-label="commit ID" md-sort-by="id">${ item.id }</md-table-cell> <md-table-cell md-label="commit ID" md-sort-by="id">${ item.id }</md-table-cell>
@ -50,7 +50,7 @@ var app = new Vue({
filtered_history: [], filtered_history: [],
oldid: null, oldid: null,
diff: null, diff: null,
lasstid: null, last_id: null,
{% if isinstance(id, str) %} {% if isinstance(id, str) %}
id: "{{id}}", id: "{{id}}",
{% else %} {% else %}
@ -67,7 +67,7 @@ var app = new Vue({
self.history = response.data.history; self.history = response.data.history;
self.updateFilter(); self.updateFilter();
self.schedule = response.data.schedule; self.schedule = response.data.schedule;
self.lastid = response.data.lastid; self.last_id = response.data.last_id;
}); });
}, },
updateFilter: function() { updateFilter: function() {
@ -96,5 +96,10 @@ body {
height: 80%; height: 80%;
} }
.placeholder {
width: 36px;
display: inline-block;
}
</style> </style>
{% end %} {% end %}

40
dist/index.html vendored
View file

@ -21,14 +21,24 @@
<md-table id="schedules-table" v-model="schedules"> <md-table id="schedules-table" v-model="schedules">
<md-table-row slot="md-table-row" slot-scope="{item}"> <md-table-row slot="md-table-row" slot-scope="{item}">
<md-table-cell md-label="#" md-sort-by="id" md-numeric><a :href="'/schedule.html?id=' + item.id">${ item.id }</a></md-table-cell> <md-table-cell md-label="#" md-sort-by="id" md-numeric><a :href="'/schedule.html?id=' + item.id">${ item.id }</a></md-table-cell>
<md-table-cell md-label="enabled" md-sort-by="enabled">
<md-icon v-if="item.enabled">check_box</md-icon>
<md-icon v-if="!item.enabled">check_box_outline_blank</md-icon>
</md-table-cell>
<md-table-cell md-label="title" md-sort-by="title"><a :href="'/schedule.html?id=' + item.id">${ item.title }</a></md-table-cell> <md-table-cell md-label="title" md-sort-by="title"><a :href="'/schedule.html?id=' + item.id">${ item.title }</a></md-table-cell>
<md-table-cell md-label="url" md-sort-by="email"><a :href="item.url" target="_new">${ item.url }</a></md-table-cell> <md-table-cell md-label="url" md-sort-by="url"><a :href="item.url" target="_new">${ item.url }</a></md-table-cell>
<md-table-cell md-label="trigger" md-sort-by="trigger">${ triggerString(item) }</md-table-cell> <md-table-cell md-label="trigger" md-sort-by="trigger">${ triggerString(item) }</md-table-cell>
<md-table-cell md-label="history" md-sort-by="email"> <md-table-cell md-label="last check" md-sort-by="last_history">${ item.last_history && item.last_history.message }</md-table-cell>
<md-table-cell md-label="history">
<md-button :href="'/history.html?id=' + item.id" class="md-icon-button md-primary"> <md-button :href="'/history.html?id=' + item.id" class="md-icon-button md-primary">
<md-icon>history</md-icon> <md-icon>history</md-icon>
</md-button> </md-button>
</md-table-cell> </md-table-cell>
<md-table-cell md-label="run now">
<md-button class="md-icon-button md-primary" @click="runSchedule(item.id)">
<md-icon>play_circle_outline</md-icon>
</md-button>
</md-table-cell>
</md-table-row> </md-table-row>
</md-table> </md-table>
</md-card-content> </md-card-content>
@ -50,11 +60,9 @@ var app = new Vue({
mounted: function() { mounted: function() {
this.getSchedules(); this.getSchedules();
}, },
computed: {
},
methods: { methods: {
getSchedules: function() { getSchedules: function() {
self = this; var self = this;
var data = axios.get('/api/schedules').then(function(response) { var data = axios.get('/api/schedules').then(function(response) {
var schedules = []; var schedules = [];
_.forEach(response.data.schedules || {}, function(value, key) { _.forEach(response.data.schedules || {}, function(value, key) {
@ -69,18 +77,28 @@ var app = new Vue({
return 'cron: ' + item.cron_crontab; return 'cron: ' + item.cron_crontab;
} }
if (item.trigger == 'interval') { if (item.trigger == 'interval') {
trigger = 'interval: '; var text = 'interval: ';
var pieces = [];
_.each(['weeks', 'days', 'hours', 'minutes', 'seconds'], function(value, key) { _.each(['weeks', 'days', 'hours', 'minutes', 'seconds'], function(value, key) {
if ('interval_' + value) { var int_val = item['interval_' + value];
if (trigger) { if (int_val && parseInt(int_val)) {
trigger = trigger + ' '; var unit = value;
if (int_val == 1) {
unit = unit.slice(0, -1);
} }
trigger = trigger + '' pieces.push('' + int_val + ' ' + unit);
} }
}); });
return trigger; text = text + _.join(pieces, ', ');
return text;
} }
return ''; return '';
},
runSchedule: function(id) {
var self = this;
var data = axios.post('/api/schedules/' + id + '/run').then(function(response) {
setTimeout(self.getSchedules, 2500);
});
} }
} }
}); });