Add dashboard display.
* src/cuirass/http.scm (url-handler): New "/eval/id/dashboard" route. * src/cuirass/templates.scm (evaluation-dashboard): New procedure. * src/static/css/cuirass.css (content-fixed-margin, dashboard, div.tooltip): New sections.
This commit is contained in:
parent
63ce1c940b
commit
ec0be96407
|
@ -812,6 +812,25 @@ into a specification record and return it."
|
|||
(respond-compressed-file log)
|
||||
(respond-not-found (uri->string (request-uri request))))))
|
||||
|
||||
(('GET "eval" (= string->number id) "dashboard")
|
||||
(let* ((params (request-parameters request))
|
||||
(system (assq-ref params 'system))
|
||||
(default-system "x86_64-linux")
|
||||
(spec-name (db-get-evaluation-specification id))
|
||||
(spec (db-get-specification spec-name))
|
||||
(systems (specification-systems spec)))
|
||||
(respond-html
|
||||
(html-page
|
||||
"Dashboard"
|
||||
(evaluation-dashboard id systems
|
||||
#:current-system
|
||||
(or system default-system))
|
||||
`(((#:name . ,spec-name)
|
||||
(#:link . ,(string-append "/jobset/" spec-name)))
|
||||
((#:name . ,(string-append "Evaluation " (number->string id)))
|
||||
(#:link . ,(string-append "/eval/" (number->string id)))))
|
||||
#:margin? #f))))
|
||||
|
||||
(('GET "search")
|
||||
(let* ((params (request-parameters request))
|
||||
(query (and=> (assq-ref params 'query) uri-decode))
|
||||
|
|
|
@ -52,7 +52,8 @@
|
|||
running-builds-table
|
||||
global-metrics-content
|
||||
workers-status
|
||||
machine-status))
|
||||
machine-status
|
||||
evaluation-dashboard))
|
||||
|
||||
(define (navigation-items navigation)
|
||||
(match navigation
|
||||
|
@ -104,7 +105,9 @@ the " (code "guix-master") " specification for the " (code "i686-linux") "
|
|||
system whose names start with " (code "guile-") ":" (br)
|
||||
(code "spec:guix-master system:i686-linux status:success guile-")))))
|
||||
|
||||
(define* (html-page title body navigation #:optional query)
|
||||
(define* (html-page title body navigation
|
||||
#:optional query
|
||||
#:key (margin? #t))
|
||||
"Return HTML page with given TITLE and BODY."
|
||||
`(html (@ (xmlns "http://www.w3.org/1999/xhtml")
|
||||
(xml:lang "en")
|
||||
|
@ -198,7 +201,10 @@ columnDefs: [
|
|||
Home))
|
||||
,@(navigation-items navigation)))
|
||||
,(search-form query))
|
||||
(div (@ (class "container content"))
|
||||
(div (@ (id "content")
|
||||
(class ,(if margin?
|
||||
"container content"
|
||||
"content-fixed-margin")))
|
||||
,body)
|
||||
(footer
|
||||
(@ (class "footer text-center"))
|
||||
|
@ -844,8 +850,15 @@ if ($('.param-select-row').is(':visible')) {
|
|||
(td ,(input-changes (assq-ref row #:checkouts)))
|
||||
(td ,@(evaluation-badges row))
|
||||
(td
|
||||
(a (@ (href "/eval/" ,(assq-ref row #:id)
|
||||
"/dashboard"))
|
||||
(div
|
||||
(@ (class "oi oi-monitor d-inline-block")
|
||||
(title "Dashboard")
|
||||
(aria-hidden "true"))
|
||||
""))
|
||||
(div
|
||||
(@ (class "dropdown"))
|
||||
(@ (class "dropdown d-inline-block ml-2"))
|
||||
(a (@ (class "oi oi-menu dropdown-toggle no-dropdown-arrow")
|
||||
(href "#")
|
||||
(data-toggle "dropdown")
|
||||
|
@ -1148,6 +1161,10 @@ $(document).ready(function() {
|
|||
status
|
||||
id)
|
||||
" "
|
||||
(a (@ (class "oi oi-monitor mr-2")
|
||||
(style "font-size:0.8em")
|
||||
(href "/eval/" ,id "/dashboard")
|
||||
(role "button")))
|
||||
(a (@ (id "paginate")
|
||||
(class "oi oi-collapse-down")
|
||||
(style "font-size:0.7em")
|
||||
|
@ -1707,3 +1724,122 @@ text-dark d-flex position-absolute w-100"))
|
|||
#:labels
|
||||
'("Free store disk space percentage")
|
||||
#:colors (list "#3e95cd"))))))))
|
||||
|
||||
(define* (evaluation-dashboard evaluation systems
|
||||
#:key current-system)
|
||||
(let ((jobs
|
||||
(format #f "/api/jobs?evaluation=~a&system=~a"
|
||||
evaluation current-system)))
|
||||
`((p (@ (class "lead"))
|
||||
,(format #f "Dasboard evaluation #~a" evaluation))
|
||||
(form (@ (id "get-dashboard")
|
||||
(class "row g-3 mb-3")
|
||||
(action "/eval/" ,evaluation "/dashboard")
|
||||
(method "GET"))
|
||||
(div (@ (class "col-auto"))
|
||||
(select (@ (id "system")
|
||||
(name "system")
|
||||
(class "form-control"))
|
||||
,@(map (lambda (system)
|
||||
`(option
|
||||
(@ (value ,system)
|
||||
,@(if (string=? system current-system)
|
||||
'((selected))
|
||||
'()))
|
||||
,system))
|
||||
systems)))
|
||||
(div (@ (class "col-auto"))
|
||||
(button
|
||||
(@ (type "submit")
|
||||
(class "btn btn-primary"))
|
||||
" Go")))
|
||||
(script ,(format #f "
|
||||
function radius(count) {
|
||||
if (count < 100)
|
||||
return 15;
|
||||
else if (count < 1000)
|
||||
return 10;
|
||||
else
|
||||
return 5;
|
||||
}
|
||||
|
||||
function color(status) {
|
||||
switch (status) {
|
||||
case -3:
|
||||
case -2:
|
||||
return 'gray';
|
||||
case -1:
|
||||
return 'orange';
|
||||
case 0:
|
||||
return 'green';
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
return 'red';
|
||||
}
|
||||
}
|
||||
|
||||
function svgWidth() {
|
||||
var width = d3.select('#content').style('width').slice(0, -2);
|
||||
return Math.round(Number(width));
|
||||
}
|
||||
|
||||
d3.json('~a').then(function (data) {
|
||||
var width = svgWidth();
|
||||
var circle_radius = radius(data.length);
|
||||
var margin_x = circle_radius;
|
||||
var margin_y = circle_radius;
|
||||
var margin_circle_x = 3;
|
||||
var margin_circle_y = (2.5 * circle_radius);
|
||||
var circle_count_x =
|
||||
Math.floor((width - 2 * margin_x) /
|
||||
((2 * circle_radius) + margin_circle_x));
|
||||
var height = ((data.length / circle_count_x) *
|
||||
margin_circle_y) +
|
||||
circle_radius + 2 * margin_y;
|
||||
|
||||
console.log(width);
|
||||
console.log(height);
|
||||
|
||||
var div = d3.select('body').append('div')
|
||||
.attr('class', 'tooltip')
|
||||
.style('opacity', 0);
|
||||
var svg = d3.select('#content').append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height);
|
||||
var circles = svg.append('g')
|
||||
.selectAll('circle')
|
||||
.data(data)
|
||||
.enter()
|
||||
.append('a')
|
||||
.attr('xlink:href', d => '/build/' + d.build + '/details')
|
||||
.append('circle')
|
||||
.attr('r', circle_radius)
|
||||
.attr('cx', function(d, i) {
|
||||
return margin_x +
|
||||
(i % circle_count_x)
|
||||
* (circle_radius * 2 + margin_circle_x);
|
||||
})
|
||||
.attr('cy', function (d, i) {
|
||||
return margin_y + Math.floor(i / circle_count_x)
|
||||
* margin_circle_y;
|
||||
})
|
||||
.style('fill', d => color(d.status))
|
||||
.on('mouseover', function(event, d) {
|
||||
var circle = d3.select(this)
|
||||
.style('fill', 'steelblue');
|
||||
div.style('opacity', .9);
|
||||
div.html(d.name)
|
||||
.style('left', (event.pageX + 30) + 'px')
|
||||
.style('top', (event.pageY - 30) + 'px');
|
||||
})
|
||||
.on('mouseout', function(event, d) {
|
||||
var circle = d3.select(this)
|
||||
.style('fill', color(d.status));
|
||||
div.style('opacity', 0);
|
||||
div.html('')
|
||||
.style('left', '0px')
|
||||
.style('top', '0px');
|
||||
})
|
||||
});" jobs)))))
|
||||
|
|
|
@ -15,6 +15,10 @@ body {
|
|||
padding: 1em 0em 1em;
|
||||
}
|
||||
|
||||
.content-fixed-margin {
|
||||
margin: 1em 3em;
|
||||
}
|
||||
|
||||
#search input:focus {
|
||||
width: 500px;
|
||||
}
|
||||
|
@ -66,3 +70,24 @@ a.dropdown-toggle:focus + .dropdown-menu {
|
|||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
|
||||
#dashboard {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
div.tooltip {
|
||||
position: absolute;
|
||||
opacity:0.8;
|
||||
z-index:1000;
|
||||
text-align:left;
|
||||
border-radius:4px;
|
||||
-moz-border-radius:4px;
|
||||
-webkit-border-radius:4px;
|
||||
padding:8px;
|
||||
color:#fff;
|
||||
background-color:#000;
|
||||
font: 12px sans-serif;
|
||||
max-width: 1000px;
|
||||
height: 30px;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue