2017-07-13 18:57:28 +00:00
|
|
|
/* laminar.js
|
|
|
|
* frontend application for Laminar Continuous Integration
|
|
|
|
* https://laminar.ohwg.net
|
|
|
|
*/
|
2017-09-23 11:36:04 +00:00
|
|
|
const wsp = function(path) {
|
|
|
|
return new WebSocket((location.protocol === 'https:'?'wss://':'ws://')
|
|
|
|
+ location.host + path);
|
|
|
|
}
|
2017-07-13 18:57:28 +00:00
|
|
|
const WebsocketHandler = function() {
|
|
|
|
function setupWebsocket(path, next) {
|
2017-09-23 11:36:04 +00:00
|
|
|
var ws = wsp(path);
|
2017-07-13 18:57:28 +00:00
|
|
|
ws.onmessage = function(msg) {
|
|
|
|
msg = JSON.parse(msg.data);
|
|
|
|
// "status" is the first message the websocket always delivers.
|
|
|
|
// Use this to confirm the navigation. The component is not
|
|
|
|
// created until next() is called, so creating a reference
|
|
|
|
// for other message types must be deferred
|
|
|
|
if (msg.type === 'status') {
|
|
|
|
next(comp => {
|
|
|
|
// Set up bidirectional reference
|
|
|
|
// 1. needed to reference the component for other msg types
|
|
|
|
this.comp = comp;
|
|
|
|
// 2. needed to close the ws on navigation away
|
|
|
|
comp.ws = this;
|
|
|
|
// Update html and nav titles
|
|
|
|
document.title = comp.$root.title = msg.title;
|
|
|
|
// Component-specific callback handler
|
|
|
|
comp[msg.type](msg.data);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// at this point, the component must be defined
|
|
|
|
if (!this.comp)
|
|
|
|
return console.error("Page component was undefined");
|
|
|
|
else if (typeof this.comp[msg.type] === 'function')
|
|
|
|
this.comp[msg.type](msg.data);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
return {
|
|
|
|
beforeRouteEnter(to, from, next) {
|
|
|
|
setupWebsocket(to.path, (fn) => { next(fn); });
|
|
|
|
},
|
|
|
|
beforeRouteUpdate(to, from, next) {
|
|
|
|
this.ws.close();
|
|
|
|
setupWebsocket(to.path, (fn) => { fn(this); next(); });
|
|
|
|
},
|
|
|
|
beforeRouteLeave(to, from, next) {
|
|
|
|
this.ws.close();
|
|
|
|
next();
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}();
|
2015-09-26 20:54:27 +00:00
|
|
|
|
2017-07-13 18:57:28 +00:00
|
|
|
const Utils = {
|
|
|
|
methods: {
|
|
|
|
runIcon(result) {
|
2017-12-02 16:30:45 +00:00
|
|
|
return result === "success" ? '<img src="/tick.gif">' : result === "failed" || result === "aborted" ? '<img src="/cross.gif">' : '<img src="/spin.gif">';
|
2017-07-13 18:57:28 +00:00
|
|
|
},
|
|
|
|
formatDate: function(unix) {
|
|
|
|
// TODO: reimplement when toLocaleDateString() accepts formatting options on most browsers
|
|
|
|
var d = new Date(1000 * unix);
|
|
|
|
var m = d.getMinutes();
|
|
|
|
if (m < 10) m = '0' + m;
|
|
|
|
return d.getHours() + ':' + m + ' on ' + ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][d.getDay()] + ' ' +
|
|
|
|
d.getDate() + '. ' + ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
|
|
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
|
|
|
|
][d.getMonth()] + ' ' +
|
|
|
|
d.getFullYear();
|
|
|
|
},
|
2017-11-03 14:47:30 +00:00
|
|
|
formatDuration: function(start, end) {
|
|
|
|
if(!end)
|
|
|
|
end = Math.floor(Date.now()/1000);
|
|
|
|
if(end - start > 3600)
|
2017-12-01 10:22:39 +00:00
|
|
|
return Math.floor((end-start)/3600) + ' hours, ' + Math.floor(((end-start)%3600)/60) + ' minutes';
|
2017-11-03 14:47:30 +00:00
|
|
|
else if(end - start > 60)
|
2017-12-01 10:22:39 +00:00
|
|
|
return Math.floor((end-start)/60) + ' minutes, ' + ((end-start)%60) + ' seconds';
|
2017-11-03 14:47:30 +00:00
|
|
|
else
|
|
|
|
return (end-start) + ' seconds';
|
|
|
|
}
|
2017-07-13 18:57:28 +00:00
|
|
|
}
|
|
|
|
};
|
2015-09-13 20:25:26 +00:00
|
|
|
|
2017-07-13 18:57:28 +00:00
|
|
|
const ProgressUpdater = {
|
|
|
|
data() { return { jobsRunning: [] }; },
|
|
|
|
methods: {
|
|
|
|
updateProgress(o) {
|
|
|
|
if (o.etc) {
|
|
|
|
var p = ((new Date()).getTime() / 1000 - o.started) / (o.etc - o.started);
|
|
|
|
if (p > 1.2) {
|
|
|
|
o.overtime = true;
|
|
|
|
} else if (p >= 1) {
|
|
|
|
o.progress = 99;
|
|
|
|
} else {
|
|
|
|
o.progress = 100 * p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
beforeDestroy() {
|
|
|
|
clearInterval(this.updateTimer);
|
|
|
|
},
|
|
|
|
watch: {
|
|
|
|
jobsRunning(val) {
|
|
|
|
// this function handles several cases:
|
|
|
|
// - the route has changed to a different run of the same job
|
|
|
|
// - the current job has ended
|
|
|
|
// - the current job has started (practically hard to reach)
|
|
|
|
clearInterval(this.updateTimer);
|
|
|
|
if (val.length) {
|
|
|
|
// TODO: first, a non-animated progress update
|
|
|
|
this.updateTimer = setInterval(() => {
|
|
|
|
this.jobsRunning.forEach(this.updateProgress);
|
|
|
|
this.$forceUpdate();
|
|
|
|
}, 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2015-09-26 20:54:27 +00:00
|
|
|
|
2017-07-13 18:57:28 +00:00
|
|
|
const Home = function() {
|
|
|
|
var state = {
|
|
|
|
jobsQueued: [],
|
|
|
|
jobsRecent: []
|
|
|
|
};
|
2015-09-26 20:54:27 +00:00
|
|
|
|
2017-07-13 18:57:28 +00:00
|
|
|
var chtUtilization, chtBuildsPerDay, chtBuildsPerJob, chtTimePerJob;
|
2015-09-26 20:54:27 +00:00
|
|
|
|
2017-07-13 18:57:28 +00:00
|
|
|
var updateUtilization = function(busy) {
|
|
|
|
chtUtilization.segments[0].value += busy ? 1 : -1;
|
|
|
|
chtUtilization.segments[1].value -= busy ? 1 : -1;
|
|
|
|
chtUtilization.update();
|
|
|
|
}
|
2015-09-26 20:54:27 +00:00
|
|
|
|
2017-07-13 18:57:28 +00:00
|
|
|
return {
|
|
|
|
template: '#home',
|
|
|
|
mixins: [WebsocketHandler, Utils, ProgressUpdater],
|
|
|
|
data: function() {
|
|
|
|
return state;
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
status: function(msg) {
|
|
|
|
state.jobsQueued = msg.queued;
|
|
|
|
state.jobsRunning = msg.running;
|
|
|
|
state.jobsRecent = msg.recent;
|
|
|
|
this.$forceUpdate();
|
2015-09-26 20:54:27 +00:00
|
|
|
|
2017-07-13 18:57:28 +00:00
|
|
|
// setup charts
|
|
|
|
chtUtilization = new Chart(document.getElementById("chartUtil").getContext("2d")).Pie(
|
|
|
|
[{
|
|
|
|
value: msg.executorsBusy,
|
|
|
|
color: "tan",
|
|
|
|
label: "Busy"
|
|
|
|
},
|
|
|
|
{
|
2017-12-02 17:10:00 +00:00
|
|
|
value: msg.executorsTotal - msg.executorsBusy,
|
2017-07-13 18:57:28 +00:00
|
|
|
color: "darkseagreen",
|
|
|
|
label: "Idle"
|
|
|
|
}
|
|
|
|
], {
|
|
|
|
animationEasing: 'easeInOutQuad'
|
|
|
|
}
|
|
|
|
);
|
|
|
|
chtBuildsPerDay = new Chart(document.getElementById("chartBpd").getContext("2d")).Line({
|
|
|
|
labels: function() {
|
|
|
|
res = [];
|
|
|
|
var now = new Date();
|
|
|
|
for (var i = 6; i >= 0; --i) {
|
|
|
|
var then = new Date(now.getTime() - i * 86400000);
|
|
|
|
res.push(["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][then.getDay()]);
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}(),
|
|
|
|
datasets: [{
|
|
|
|
label: "Successful Builds",
|
2017-11-03 14:58:47 +00:00
|
|
|
fillColor: "rgba(143,188,143,0.65)", //darkseagreen at 0.65
|
2017-07-13 18:57:28 +00:00
|
|
|
strokeColor: "forestgreen",
|
|
|
|
data: msg.buildsPerDay.map(function(e) {
|
|
|
|
return e.success || 0;
|
|
|
|
})
|
|
|
|
}, {
|
|
|
|
label: "Failed Bulids",
|
2017-11-03 14:58:47 +00:00
|
|
|
fillColor: "rgba(233,150,122,0.65)", //darksalmon at 0.65
|
2017-07-13 18:57:28 +00:00
|
|
|
strokeColor: "crimson",
|
|
|
|
data: msg.buildsPerDay.map(function(e) {
|
|
|
|
return e.failed || 0;
|
|
|
|
})
|
|
|
|
}]
|
|
|
|
}, {
|
|
|
|
showTooltips: false
|
|
|
|
});
|
|
|
|
chtBuildsPerJob = new Chart(document.getElementById("chartBpj").getContext("2d")).HorizontalBar({
|
|
|
|
labels: Object.keys(msg.buildsPerJob),
|
|
|
|
datasets: [{
|
|
|
|
fillColor: "lightsteelblue",
|
|
|
|
data: Object.keys(msg.buildsPerJob).map(function(e) {
|
|
|
|
return msg.buildsPerJob[e];
|
|
|
|
})
|
|
|
|
}]
|
|
|
|
}, {});
|
|
|
|
chtTimePerJob = new Chart(document.getElementById("chartTpj").getContext("2d")).HorizontalBar({
|
|
|
|
labels: Object.keys(msg.timePerJob),
|
|
|
|
datasets: [{
|
|
|
|
fillColor: "lightsteelblue",
|
|
|
|
data: Object.keys(msg.timePerJob).map(function(e) {
|
|
|
|
return msg.timePerJob[e];
|
|
|
|
})
|
|
|
|
}]
|
|
|
|
}, {});
|
2015-09-26 20:54:27 +00:00
|
|
|
|
2015-11-01 10:34:18 +00:00
|
|
|
|
2017-07-13 18:57:28 +00:00
|
|
|
},
|
|
|
|
job_queued: function(data) {
|
|
|
|
state.jobsQueued.splice(0, 0, data);
|
|
|
|
this.$forceUpdate();
|
|
|
|
},
|
|
|
|
job_started: function(data) {
|
|
|
|
state.jobsQueued.splice(state.jobsQueued.length - data.queueIndex - 1, 1);
|
|
|
|
state.jobsRunning.splice(0, 0, data);
|
|
|
|
this.$forceUpdate();
|
|
|
|
updateUtilization(true);
|
|
|
|
},
|
|
|
|
job_completed: function(data) {
|
|
|
|
if (data.result === "success")
|
|
|
|
chtBuildsPerDay.datasets[0].points[6].value++;
|
|
|
|
else
|
|
|
|
chtBuildsPerDay.datasets[1].points[6].value++;
|
|
|
|
chtBuildsPerDay.update();
|
|
|
|
|
|
|
|
for (var i = 0; i < state.jobsRunning.length; ++i) {
|
|
|
|
var job = state.jobsRunning[i];
|
|
|
|
if (job.name == data.name && job.number == data.number) {
|
|
|
|
state.jobsRunning.splice(i, 1);
|
|
|
|
state.jobsRecent.splice(0, 0, data);
|
|
|
|
this.$forceUpdate();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
updateUtilization(false);
|
|
|
|
for (var j = 0; j < chtBuildsPerJob.datasets[0].bars.length; ++j) {
|
|
|
|
if (chtBuildsPerJob.datasets[0].bars[j].label == job.name) {
|
|
|
|
chtBuildsPerJob.datasets[0].bars[j].value++;
|
|
|
|
chtBuildsPerJob.update();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}();
|
|
|
|
|
|
|
|
const Jobs = function() {
|
|
|
|
var state = {
|
|
|
|
jobs: [],
|
|
|
|
search: '',
|
|
|
|
tags: [],
|
|
|
|
tag: null
|
|
|
|
};
|
|
|
|
return {
|
|
|
|
template: '#jobs',
|
2017-11-07 06:21:01 +00:00
|
|
|
mixins: [WebsocketHandler, Utils, ProgressUpdater],
|
2017-07-13 18:57:28 +00:00
|
|
|
data: function() { return state; },
|
|
|
|
computed: {
|
|
|
|
filteredJobs() {
|
|
|
|
var ret = this.jobs;
|
|
|
|
var tag = this.tag;
|
|
|
|
if (tag) {
|
|
|
|
ret = ret.filter(function(job) {
|
|
|
|
return job.tags.indexOf(tag) >= 0;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
var search = this.search;
|
|
|
|
if (search) {
|
|
|
|
ret = ret.filter(function(job) {
|
|
|
|
return job.name.indexOf(search) > -1;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
status: function(msg) {
|
|
|
|
state.jobs = msg.jobs;
|
2017-11-07 06:21:01 +00:00
|
|
|
state.jobsRunning = msg.running;
|
|
|
|
// mix running and completed jobs
|
|
|
|
for (var i in msg.running) {
|
|
|
|
var idx = state.jobs.findIndex(job => job.name === msg.running[i].name);
|
|
|
|
if (idx > -1)
|
|
|
|
state.jobs[idx] = msg.running[i];
|
2017-12-02 15:55:21 +00:00
|
|
|
else {
|
|
|
|
// special case: first run of a job.
|
|
|
|
state.jobs.unshift(msg.running[i]);
|
|
|
|
state.jobs.sort(function(a, b){return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;});
|
|
|
|
}
|
2017-11-07 06:21:01 +00:00
|
|
|
}
|
2017-07-13 18:57:28 +00:00
|
|
|
var tags = {};
|
|
|
|
for (var i in state.jobs) {
|
|
|
|
for (var j in state.jobs[i].tags) {
|
|
|
|
tags[state.jobs[i].tags[j]] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
state.tags = Object.keys(tags);
|
|
|
|
},
|
2017-11-07 06:21:01 +00:00
|
|
|
job_started: function(data) {
|
|
|
|
var updAt = null;
|
|
|
|
for (var i in state.jobsRunning) {
|
|
|
|
if (state.jobsRunning[i].name === data.name) {
|
|
|
|
updAt = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (updAt === null) {
|
|
|
|
state.jobsRunning.unshift(data);
|
|
|
|
} else {
|
|
|
|
state.jobsRunning[updAt] = data;
|
|
|
|
}
|
2017-12-02 15:55:21 +00:00
|
|
|
updAt = null;
|
2017-11-07 06:21:01 +00:00
|
|
|
for (var i in state.jobs) {
|
|
|
|
if (state.jobs[i].name === data.name) {
|
2017-12-02 15:55:21 +00:00
|
|
|
updAt = i;
|
2017-11-07 06:21:01 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-12-02 15:55:21 +00:00
|
|
|
if (updAt === null) {
|
|
|
|
// first execution of new job. TODO insert without resort
|
|
|
|
state.jobs.unshift(data);
|
|
|
|
state.jobs.sort(function(a, b){return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;});
|
|
|
|
} else {
|
|
|
|
state.jobs[updAt] = data;
|
|
|
|
}
|
|
|
|
this.$forceUpdate();
|
2017-11-07 06:21:01 +00:00
|
|
|
},
|
2017-07-13 18:57:28 +00:00
|
|
|
job_completed: function(data) {
|
|
|
|
for (var i in state.jobs) {
|
|
|
|
if (state.jobs[i].name === data.name) {
|
|
|
|
state.jobs[i] = data;
|
2017-11-07 06:21:01 +00:00
|
|
|
// forceUpdate in second loop
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (var i in state.jobsRunning) {
|
|
|
|
if (state.jobsRunning[i].name === data.name) {
|
|
|
|
state.jobsRunning.splice(i, 1);
|
2017-07-13 18:57:28 +00:00
|
|
|
this.$forceUpdate();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}();
|
|
|
|
|
|
|
|
var Job = function() {
|
|
|
|
var state = {
|
|
|
|
jobsRunning: [],
|
|
|
|
jobsRecent: [],
|
|
|
|
lastSuccess: null,
|
|
|
|
lastFailed: null,
|
|
|
|
nQueued: 0,
|
|
|
|
};
|
|
|
|
return Vue.extend({
|
|
|
|
template: '#job',
|
2017-11-07 06:24:44 +00:00
|
|
|
mixins: [WebsocketHandler, Utils, ProgressUpdater],
|
2017-07-13 18:57:28 +00:00
|
|
|
data: function() {
|
|
|
|
return state;
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
status: function(msg) {
|
|
|
|
state.jobsRunning = msg.running;
|
|
|
|
state.jobsRecent = msg.recent;
|
|
|
|
state.lastSuccess = msg.lastSuccess;
|
|
|
|
state.lastFailed = msg.lastFailed;
|
2017-12-02 17:06:54 +00:00
|
|
|
state.nQueued = msg.nQueued;
|
2017-07-13 18:57:28 +00:00
|
|
|
|
|
|
|
var chtBt = new Chart(document.getElementById("chartBt").getContext("2d")).Bar({
|
|
|
|
labels: msg.recent.map(function(e) {
|
|
|
|
return '#' + e.number;
|
|
|
|
}).reverse(),
|
|
|
|
datasets: [{
|
|
|
|
fillColor: "darkseagreen",
|
|
|
|
strokeColor: "forestgreen",
|
|
|
|
data: msg.recent.map(function(e) {
|
2017-11-06 17:08:14 +00:00
|
|
|
return e.completed - e.started;
|
2017-07-13 18:57:28 +00:00
|
|
|
}).reverse()
|
|
|
|
}]
|
|
|
|
}, {
|
|
|
|
barValueSpacing: 1,
|
|
|
|
barStrokeWidth: 1,
|
|
|
|
barDatasetSpacing: 0
|
|
|
|
});
|
|
|
|
|
|
|
|
for (var i = 0, n = msg.recent.length; i < n; ++i) {
|
|
|
|
if (msg.recent[i].result != "success") {
|
|
|
|
chtBt.datasets[0].bars[n - i - 1].fillColor = "darksalmon";
|
|
|
|
chtBt.datasets[0].bars[n - i - 1].strokeColor = "crimson";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
chtBt.update();
|
|
|
|
|
|
|
|
},
|
|
|
|
job_queued: function() {
|
|
|
|
state.nQueued++;
|
|
|
|
},
|
|
|
|
job_started: function(data) {
|
|
|
|
state.nQueued--;
|
|
|
|
state.jobsRunning.splice(0, 0, data);
|
|
|
|
this.$forceUpdate();
|
|
|
|
},
|
|
|
|
job_completed: function(data) {
|
|
|
|
for (var i = 0; i < state.jobsRunning.length; ++i) {
|
|
|
|
var job = state.jobsRunning[i];
|
|
|
|
if (job.number === data.number) {
|
|
|
|
state.jobsRunning.splice(i, 1);
|
|
|
|
state.jobsRecent.splice(0, 0, data);
|
|
|
|
this.$forceUpdate();
|
|
|
|
// TODO: update the chart
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}();
|
|
|
|
|
|
|
|
const Run = function() {
|
|
|
|
var state = {
|
|
|
|
job: { artifacts: [] },
|
|
|
|
latestNum: null,
|
|
|
|
log: '',
|
|
|
|
autoscroll: false
|
|
|
|
};
|
|
|
|
var firstLog = false;
|
|
|
|
var logHandler = function(vm, d) {
|
|
|
|
state.log += d;
|
|
|
|
vm.$forceUpdate();
|
|
|
|
if (!firstLog) {
|
|
|
|
firstLog = true;
|
|
|
|
} else if (state.autoscroll) {
|
|
|
|
window.scrollTo(0, document.body.scrollHeight);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
|
|
template: '#run',
|
|
|
|
mixins: [WebsocketHandler, Utils, ProgressUpdater],
|
|
|
|
data: function() {
|
|
|
|
return state;
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
status: function(data) {
|
2017-08-14 05:45:28 +00:00
|
|
|
state.jobsRunning = [];
|
2017-07-13 18:57:28 +00:00
|
|
|
state.log = '';
|
|
|
|
state.job = data;
|
|
|
|
state.latestNum = data.latestNum;
|
2017-11-18 09:26:04 +00:00
|
|
|
state.jobsRunning = [data];
|
2017-07-13 18:57:28 +00:00
|
|
|
},
|
|
|
|
job_started: function(data) {
|
|
|
|
state.latestNum++;
|
|
|
|
this.$forceUpdate();
|
|
|
|
},
|
|
|
|
job_completed: function(data) {
|
|
|
|
state.job = data;
|
|
|
|
state.jobsRunning = [];
|
|
|
|
this.$forceUpdate();
|
|
|
|
},
|
|
|
|
runComplete: function(run) {
|
|
|
|
return !!run && (run.result === 'aborted' || run.result === 'failed' || run.result === 'success');
|
|
|
|
},
|
|
|
|
},
|
|
|
|
beforeRouteEnter(to, from, next) {
|
|
|
|
next(vm => {
|
2017-09-23 11:36:04 +00:00
|
|
|
vm.logws = wsp(to.path + '/log');
|
2017-07-13 18:57:28 +00:00
|
|
|
vm.logws.onmessage = function(msg) {
|
|
|
|
logHandler(vm, msg.data);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
beforeRouteUpdate(to, from, next) {
|
|
|
|
var vm = this;
|
|
|
|
vm.logws.close();
|
2017-09-23 11:36:04 +00:00
|
|
|
vm.logws = wsp(to.path + '/log');
|
2017-07-13 18:57:28 +00:00
|
|
|
vm.logws.onmessage = function(msg) {
|
|
|
|
logHandler(vm, msg.data);
|
|
|
|
}
|
|
|
|
next();
|
|
|
|
},
|
|
|
|
beforeRouteLeave(to, from, next) {
|
|
|
|
this.logws.close();
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}();
|
|
|
|
|
|
|
|
new Vue({
|
|
|
|
el: '#app',
|
|
|
|
data: {
|
|
|
|
title: '' // populated by status ws message
|
|
|
|
},
|
|
|
|
router: new VueRouter({
|
|
|
|
mode: 'history',
|
|
|
|
routes: [
|
|
|
|
{ path: '/', component: Home },
|
|
|
|
{ path: '/jobs', component: Jobs },
|
|
|
|
{ path: '/jobs/:name', component: Job },
|
|
|
|
{ path: '/jobs/:name/:number', component: Run }
|
|
|
|
],
|
|
|
|
}),
|
2015-09-26 20:54:27 +00:00
|
|
|
});
|