1
0
mirror of https://github.com/ohwgiles/laminar.git synced 2026-11-03 09:04:47 +00:00

webui refresh

WebUI rewritten in a more modern style, bootstrap is dropped in favour of
plain css/grid. Hand-crafted svgs replace utf-8 glyphs for a more uniform
look and smoother animation. webmanifest added for better mobile behaviour.

No doubt minor tweaks will follow...

resolves #57
This commit is contained in:
Oliver Giles
2020-06-27 10:45:47 +12:00
parent 4554039703
commit ae560b9de4
6 changed files with 465 additions and 374 deletions

View File

@@ -103,15 +103,24 @@ const ServerEventHandler = function() {
const Utils = {
methods: {
runIcon(result) {
var marker = '⚙';
var classname = result;
if (result === 'success')
marker = '✔';
else if (result === 'failed' || result === 'aborted')
marker = '✘';
else
classname = 'spin';
return '<span title="' + result + '" class="status ' + classname + '">' + marker + '&#xfe0e;</span>';
return (result == 'success') ? /* checkmark */
`<svg class="status success" viewBox="0 0 100 100">
<path d="m 23,46 c -6,0 -17,3 -17,11 0,8 9,30 12,32 3,2 14,5 20,-2 6,-6 24,-36
56,-71 5,-3 -9,-8 -23,-2 -13,6 -33,42 -41,47 -6,-3 -5,-12 -8,-15 z" />
</svg>`
: (result == 'failed' || result == 'aborted') ? /* cross */
`<svg class="status failed" viewBox="0 0 100 100">
<path d="m 19,20 c 2,8 12,29 15,32 -5,5 -18,21 -21,26 2,3 8,15 11,18 4,-6 17,-21
21,-26 5,5 11,15 15,20 8,-2 15,-9 20,-15 -3,-3 -17,-18 -20,-24 3,-5 23,-26 30,-33 -3,-5 -8,-9
-12,-12 -6,5 -26,26 -29,30 -6,-8 -11,-15 -15,-23 -3,0 -12,5 -15,7 z" />
</svg>`
: /* spinner */
`<svg class="status running" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" stroke-width="15" fill="none" stroke-dasharray="175">
<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="2s" values="0 50 50;360 50 50"></animateTransform>
</circle>
</svg>`
;
},
formatDate: function(unix) {
// TODO: reimplement when toLocaleDateString() accepts formatting options on most browsers
@@ -145,7 +154,8 @@ const ProgressUpdater = {
var p = (Math.floor(Date.now()/1000) + this.$root.clockSkew - o.started) / (o.etc - o.started);
if (p > 1.2) {
o.overtime = true;
} else if (p >= 1) {
}
if (p >= 1) {
o.progress = 99;
} else {
o.progress = 100 * p;
@@ -178,7 +188,8 @@ const Home = function() {
var state = {
jobsQueued: [],
jobsRecent: [],
resultChanged: []
resultChanged: [],
lowPassRates: [],
};
var chtUtilization, chtBuildsPerDay, chtBuildsPerJob, chtTimePerJob;
@@ -201,6 +212,7 @@ const Home = function() {
state.jobsRunning = msg.running;
state.jobsRecent = msg.recent;
state.resultChanged = msg.resultChanged;
state.lowPassRates = msg.lowPassRates;
this.$forceUpdate();
// setup charts
@@ -243,6 +255,7 @@ const Home = function() {
}]
},
options:{
title: { display: true, text: 'Builds per day' },
tooltips:{callbacks:{title: function(tip, data) {
return buildsPerDayDates[tip[0].index].long;
}}},
@@ -263,6 +276,7 @@ const Home = function() {
}]
},
options:{
title: { display: true, text: 'Builds per job' },
scales:{xAxes:[{ticks:{userCallback: (label, index, labels)=>{
if(Number.isInteger(label))
return label;
@@ -281,6 +295,7 @@ const Home = function() {
}]
},
options:{
title: { display: true, text: 'Mean run time this week' },
scales:{xAxes:[{
ticks:{userCallback: tpjScale.scale},
scaleLabel: {
@@ -293,52 +308,6 @@ const Home = function() {
}}}
}
});
var chtResultChanges = new Chart(document.getElementById("chartResultChanges"), {
type: 'horizontalBar',
data: {
labels: msg.resultChanged.map((e)=>{ return e.name; }),
datasets: [{
//label: '% Passed',
backgroundColor: msg.resultChanged.map((e)=>{return e.lastFailure > e.lastSuccess ? 'firebrick' : 'forestgreen';}),
data: msg.resultChanged.map((e)=>{ return e.lastSuccess - e.lastFailure; }),
itemid: msg.resultChanged.map((e)=> { return 'rcd_' + e.name; })
}]
},
options:{
scales:{
xAxes:[{ticks:{display: false}}],
yAxes:[{ticks:{display: false}}]
},
tooltips:{
enabled:false
}
}
});
var chtPassRates = new Chart(document.getElementById("chartPassRates"), {
type: 'horizontalBar',
data: {
labels: msg.lowPassRates.map((e)=>{ return e.name }),
datasets: [{
stack: 'passrate',
label: '% Passed',
backgroundColor: "forestgreen",
data: msg.lowPassRates.map((e)=>{ return e.passRate*100; })
},{
stack:'passrate',
label: '% Failed',
backgroundColor: "firebrick",
data: msg.lowPassRates.map((e)=>{ return (1-e.passRate)*100; })
}],
},
options:{
scales:{xAxes:[{ticks:{callback:(val,idx,values)=>{
return val + '%';
}}}]},
tooltips:{
enabled:false
}
}
});
var btcScale = timeScale(Math.max(msg.buildTimeChanges.map((e)=>{return Math.max(e.durations)})));
var chtBuildTimeChanges = new Chart(document.getElementById("chartBuildTimeChanges"), {
type: 'line',
@@ -352,6 +321,7 @@ const Home = function() {
}})
},
options:{
title: { display: true, text: 'Build time changes' },
legend:{display:true},
scales:{
xAxes:[{ticks:{display: false}}],
@@ -377,6 +347,9 @@ const Home = function() {
data: msg.buildTimeDist,
backgroundColor: "steelblue",
}]
},
options: {
title: { display: true, text: 'Build time distribution' }
}
});
},
@@ -573,6 +546,7 @@ var Job = function() {
}]
},
options: {
title: { display: true, text: 'Build time' },
scales:{
xAxes:[{},{
id: 'avg',
@@ -651,9 +625,7 @@ const Run = function() {
job: { artifacts: [], upstream: {} },
latestNum: null,
log: '',
autoscroll: false
};
var firstLog = false;
const logFetcher = (vm, name, num) => {
const abort = new AbortController();
fetch('log/'+name+'/'+num, {signal:abort.signal}).then(res => {
@@ -668,11 +640,6 @@ const Run = function() {
return;
state.log += ansi_up.ansi_to_html(value.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\033\[\{([^:]+):(\d+)\033\\/g, (m,$1,$2)=>{return '<a href="jobs/'+$1+'" onclick="return vroute(this);">'+$1+'</a>:<a href="jobs/'+$1+'/'+$2+'" onclick="return vroute(this);">#'+$2+'</a>';}));
vm.$forceUpdate();
if (!firstLog) {
firstLog = true;
} else if (state.autoscroll) {
window.scrollTo(0, document.body.scrollHeight);
}
return pump();
});
}();
@@ -780,7 +747,8 @@ new Vue({
new Notification('Job ' + data.result, {
body: data.name + ' ' + '#' + data.number + ': ' + data.result
});
}
},
runIcon: Utils.methods.runIcon
},
watch: {
notify(e) { localStorage.setItem('showNotifications', e ? 1 : 0); }