introduce run job now
This commit is contained in:
parent
caf2c88ae1
commit
24a6fe7d91
3 changed files with 97 additions and 28 deletions
72
diffido.py
72
diffido.py
|
@ -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
13
dist/history.html
vendored
|
@ -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
40
dist/index.html
vendored
|
@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue