638 lines
23 KiB
HTML
638 lines
23 KiB
HTML
<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
~ Copyright (c) 2015-2018 Skymind, Inc.
|
|
~ Copyright (c) 2019 Konduit K.K.
|
|
~
|
|
~ This program and the accompanying materials are made available under the
|
|
~ terms of the Apache License, Version 2.0 which is available at
|
|
~ https://www.apache.org/licenses/LICENSE-2.0.
|
|
~
|
|
~ Unless required by applicable law or agreed to in writing, software
|
|
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
~ License for the specific language governing permissions and limitations
|
|
~ under the License.
|
|
~
|
|
~ SPDX-License-Identifier: Apache-2.0
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
|
|
|
|
<html>
|
|
<head>
|
|
<style type="text/css">
|
|
|
|
.hd {
|
|
background-color: #000000;
|
|
height: 41px;
|
|
font-size: 20px;
|
|
color: #FFFFFF;
|
|
font-family: "Open Sans", sans-serif;
|
|
font-weight: 200;
|
|
}
|
|
|
|
html, body {
|
|
width: 100%;
|
|
height: 100%;
|
|
padding: 0;
|
|
}
|
|
|
|
.bgcolor {
|
|
background-color: #EFEFEF;
|
|
}
|
|
|
|
h1 {
|
|
font-family: "Open Sans", sans-serif;
|
|
font-size: 28px;
|
|
font-style: bold;
|
|
font-variant: normal;
|
|
font-weight: 500;
|
|
line-height: 26.4px;
|
|
}
|
|
|
|
h3 {
|
|
font-family: "Open Sans", sans-serif;
|
|
font-size: 16px;
|
|
font-style: normal;
|
|
font-variant: normal;
|
|
font-weight: 500;
|
|
line-height: 26.4px;
|
|
}
|
|
|
|
table {
|
|
font-family: "Open Sans", sans-serif;
|
|
font-size: 14px;
|
|
}
|
|
|
|
table.resultsTable {
|
|
border-collapse:collapse;
|
|
background-color: white;
|
|
/*border-collapse: collapse;*/
|
|
padding: 15px;
|
|
}
|
|
|
|
table.resultsTable td, table.resultsTable tr, table.resultsTable th {
|
|
border:solid black 1px;
|
|
white-space: pre; /* assume text is preprocessed for formatting */
|
|
}
|
|
|
|
table.resultsTable th {
|
|
background-color: /*headingbgcol*/#063E53;
|
|
color: white;
|
|
padding-left: 4px;
|
|
padding-right: 4px;
|
|
}
|
|
|
|
table.resultsTable td {
|
|
/*background-color: white;*/
|
|
padding-left: 4px;
|
|
padding-right: 4px;
|
|
}
|
|
|
|
/* Properties for table cells in the tables generated using the RenderableComponent mechanism */
|
|
.renderableComponentTable {
|
|
/*table-layout:fixed; */ /*Avoids scrollbar, but makes fixed width for all columns :( */
|
|
width: 100%
|
|
}
|
|
.renderableComponentTable td {
|
|
padding-left: 4px;
|
|
padding-right: 4px;
|
|
white-space: pre; /* assume text is pre-processed (important for line breaks etc)*/
|
|
word-wrap:break-word;
|
|
vertical-align: top;
|
|
}
|
|
|
|
/** CSS for result table rows */
|
|
.resultTableRow {
|
|
background-color: #FFFFFF;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/** CSS for result table CONTENT rows (i.e., only visible when expanded) */
|
|
.resultTableRowSelected {
|
|
background-color: rgba(0, 157, 255, 0.16);
|
|
}
|
|
|
|
.resultsHeadingDiv {
|
|
background-color: #063E53;
|
|
color: white;
|
|
font-family: "Open Sans", sans-serif;
|
|
font-size: 16px;
|
|
font-style: bold;
|
|
font-variant: normal;
|
|
font-weight: 500;
|
|
line-height: 26.4px;
|
|
cursor: default;
|
|
padding-top: 8px;
|
|
padding-bottom: 8px;
|
|
padding-left: 45px;
|
|
padding-right: 45px;
|
|
border-style: solid;
|
|
border-width: 1px;
|
|
border-color: #AAAAAA;
|
|
}
|
|
|
|
div.outerelements {
|
|
padding-bottom: 30px;
|
|
}
|
|
|
|
#accordion, #accordion2 {
|
|
padding-bottom: 20px;
|
|
}
|
|
|
|
#accordion .ui-accordion-header, #accordion2 .ui-accordion-header, #accordion3 .ui-accordion-header {
|
|
background-color: /*headingbgcolor*/#063E53; /*Color when collapsed*/
|
|
color: /*headingtextcolor*/white;
|
|
font-family: "Open Sans", sans-serif;
|
|
font-size: 16px;
|
|
font-style: bold;
|
|
font-variant: normal;
|
|
margin: 0px;
|
|
background-image: none; /* Necessary, otherwise color changes don't make a difference */
|
|
}
|
|
|
|
#accordion .ui-accordion-content {
|
|
width: 100%;
|
|
background-color: white; /*background color of accordian content (elements in front may have different color */
|
|
color: black; /* text etc color */
|
|
font-size: 10pt;
|
|
line-height: 16pt;
|
|
overflow:visible !important;
|
|
}
|
|
|
|
/** Line charts */
|
|
path {
|
|
stroke: steelblue;
|
|
stroke-width: 2;
|
|
fill: none;
|
|
}
|
|
.axis path, .axis line {
|
|
fill: none;
|
|
stroke: #000;
|
|
shape-rendering: crispEdges;
|
|
}
|
|
.tick line {
|
|
opacity: 0.2;
|
|
shape-rendering: crispEdges;
|
|
}
|
|
|
|
</style>
|
|
<title>DL4J - Arbiter UI</title>
|
|
</head>
|
|
<body class="bgcolor">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link id="bootstrap-style" href="/assets/webjars/bootstrap/4.3.1/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<script src="/assets/webjars/jquery/2.2.0/jquery.min.js"></script>
|
|
<link href="/assets/webjars/jquery-ui/1.10.2/themes/base/jquery-ui.css" rel="stylesheet">
|
|
<script src="/assets/webjars/jquery-ui/1.10.2/ui/minified/jquery-ui.min.js"></script>
|
|
<script src="/assets/webjars/d3js/3.3.5/d3.min.js" charset="utf-8"></script>
|
|
<script src="/assets/webjars/bootstrap/4.3.1/dist/js/bootstrap.min.js"></script>
|
|
<script src="/assets/dl4j-ui.js"></script>
|
|
|
|
<script>
|
|
//Store last update times:
|
|
var lastStatusUpdateTime = -1;
|
|
var lastSettingsUpdateTime = -1;
|
|
var lastResultsUpdateTime = -1;
|
|
|
|
var resultTableSortIndex = 0;
|
|
var resultTableSortOrder = "ascending";
|
|
var resultsTableContent;
|
|
|
|
var selectedCandidateIdx = null;
|
|
|
|
//Multi-session mode
|
|
var multiSession = null;
|
|
//Session selection
|
|
var currSession = "";
|
|
|
|
function getSessionIdFromUrl() {
|
|
// path is like /arbiter/:sessionId/overview
|
|
var sessionIdRegexp = /\/arbiter\/([^\/]+)/g;
|
|
var match = sessionIdRegexp.exec(window.location.pathname)
|
|
return match[1];
|
|
}
|
|
|
|
function getCurrSession(callback) {
|
|
if (multiSession) {
|
|
if (currSession == "") {
|
|
// get only once
|
|
currSession = getSessionIdFromUrl();
|
|
}
|
|
//we don't show session selector in multi-session mode (one can list sessions at /arbiter)
|
|
callback();
|
|
} else {
|
|
$.ajax({
|
|
url: "/arbiter/sessions/current",
|
|
async: true,
|
|
error: function (query, status, error) {
|
|
console.log("Error getting data: " + error);
|
|
},
|
|
success: function (data) {
|
|
currSession = data;
|
|
console.log("Current session: " + currSession);
|
|
|
|
//Update available sessions in session selector
|
|
$.get("/arbiter/sessions/all", function(data){
|
|
var keys = data; // JSON.stringify(data);
|
|
|
|
if(keys.length > 1){
|
|
$("#sessionSelectDiv").show();
|
|
|
|
var elem = $("#sessionSelect");
|
|
elem.empty();
|
|
|
|
var currSelectedIdx = 0;
|
|
for (var i = 0; i < keys.length; i++) {
|
|
if(keys[i] == currSession){
|
|
currSelectedIdx = i;
|
|
}
|
|
elem.append("<option value='" + keys[i] + "'>" + keys[i] + "</option>");
|
|
}
|
|
|
|
$("#sessionSelect option[value='" + keys[currSelectedIdx] +"']").attr("selected", "selected");
|
|
$("#sessionSelectDiv").show();
|
|
}
|
|
// console.log("Got sessions: " + keys + ", current: " + currSession);
|
|
callback();
|
|
});
|
|
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function getSessionSettings(callback) {
|
|
// load only once
|
|
if (multiSession != null) {
|
|
getCurrSession(callback);
|
|
} else {
|
|
$.ajax({
|
|
url: "/arbiter/multisession",
|
|
async: true,
|
|
error: function (query, status, error) {
|
|
console.log("Error getting data: " + error);
|
|
},
|
|
success: function (data) {
|
|
multiSession = data == "true";
|
|
getCurrSession(callback);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
//Initial update
|
|
doUpdate();
|
|
//Set basic interval function to do updates
|
|
setInterval(doUpdate,5000); //Loop every 5 seconds
|
|
|
|
function doUpdate(){
|
|
//Get the update status, and do something with it:
|
|
getSessionSettings(function(){
|
|
var sessionUpdateUrl = multiSession ? "/arbiter/" + currSession + "/lastUpdate" : "/arbiter/lastUpdate";
|
|
$.get(sessionUpdateUrl,function(data){
|
|
//Encoding: matches names in UpdateStatus class
|
|
var jsonObj = JSON.parse(JSON.stringify(data));
|
|
var statusTime = jsonObj['statusUpdateTime'];
|
|
var settingsTime = jsonObj['settingsUpdateTime'];
|
|
var resultsTime = jsonObj['resultsUpdateTime'];
|
|
//console.log("Last update times: " + statusTime + ", " + settingsTime + ", " + resultsTime);
|
|
|
|
//Check last update times for each part of document, and update as necessary
|
|
//First section: summary status
|
|
if(lastStatusUpdateTime != statusTime){
|
|
var summaryStatusUrl = multiSession ? "/arbiter/" + currSession + "/summary" : "/arbiter/summary";
|
|
$.get(summaryStatusUrl,function(data){
|
|
var summaryStatusDiv = $('#statusdiv');
|
|
summaryStatusDiv.html('');
|
|
|
|
var str = JSON.stringify(data);
|
|
var component = Component.getComponent(str);
|
|
component.render(summaryStatusDiv);
|
|
});
|
|
|
|
lastStatusUpdateTime = statusTime;
|
|
}
|
|
|
|
//Second section: Optimization settings
|
|
if(lastSettingsUpdateTime != settingsTime){
|
|
//Get JSON for components
|
|
var settingsUrl = multiSession ? "/arbiter/" + currSession + "/config" : "/arbiter/config";
|
|
$.get(settingsUrl,function(data){
|
|
var str = JSON.stringify(data);
|
|
|
|
var configDiv = $('#settingsdiv');
|
|
configDiv.html('');
|
|
|
|
var component = Component.getComponent(str);
|
|
component.render(configDiv);
|
|
});
|
|
|
|
lastSettingsUpdateTime = settingsTime;
|
|
}
|
|
|
|
//Third section: Summary results table (summary info for each candidate)
|
|
if(lastResultsUpdateTime != resultsTime){
|
|
//Get JSON for results table
|
|
var resultsUrl = multiSession ? "/arbiter/" + currSession + "/results" : "/arbiter/results";
|
|
$.get(resultsUrl,function(data){
|
|
//Expect an array of CandidateInfo type objects here
|
|
resultsTableContent = data;
|
|
drawResultTable();
|
|
});
|
|
|
|
lastResultsUpdateTime = resultsTime;
|
|
}
|
|
|
|
//Finally: Currently selected result
|
|
if(selectedCandidateIdx != null){
|
|
//Get JSON for components
|
|
var candidateInfoUrl = multiSession
|
|
? "/arbiter/" + currSession + "/candidateInfo/" + selectedCandidateIdx
|
|
: "/arbiter/candidateInfo/" + selectedCandidateIdx;
|
|
$.get(candidateInfoUrl,function(data){
|
|
var str = JSON.stringify(data);
|
|
|
|
var resultsViewDiv = $('#resultsviewdiv');
|
|
resultsViewDiv.html('');
|
|
|
|
var component = Component.getComponent(str);
|
|
component.render(resultsViewDiv);
|
|
});
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
function createTable(tableObj,tableId,appendTo){
|
|
//Expect RenderableComponentTable
|
|
var header = tableObj['header'];
|
|
var values = tableObj['table'];
|
|
var title = tableObj['title'];
|
|
var nRows = (values ? values.length : 0);
|
|
|
|
if(title){
|
|
appendTo.append("<h5>"+title+"</h5>");
|
|
}
|
|
|
|
var table;
|
|
if(tableId) table = $("<table id=\"" + tableId + "\" class=\"renderableComponentTable\">");
|
|
else table = $("<table class=\"renderableComponentTable\">");
|
|
if(header){
|
|
var headerRow = $("<tr>");
|
|
var len = header.length;
|
|
for( var i=0; i<len; i++ ){
|
|
headerRow.append($("<th>" + header[i] + "</th>"));
|
|
}
|
|
headerRow.append($("</tr>"));
|
|
table.append(headerRow);
|
|
}
|
|
|
|
if(values){
|
|
for( var i=0; i<nRows; i++ ){
|
|
var row = $("<tr>");
|
|
var rowValues = values[i];
|
|
var len = rowValues.length;
|
|
for( var j=0; j<len; j++ ){
|
|
row.append($('<td>'+rowValues[j]+'</td>'));
|
|
}
|
|
row.append($("</tr>"));
|
|
table.append(row);
|
|
}
|
|
}
|
|
|
|
table.append($("</table>"));
|
|
appendTo.append(table);
|
|
}
|
|
|
|
function drawResultTable(){
|
|
//Remove all elements from the table body
|
|
var tableBody = $('#resultsTableBody');
|
|
tableBody.empty();
|
|
|
|
//Recreate the table header, with appropriate sort order:
|
|
var tableHeader = $('#resultsTableHeader');
|
|
tableHeader.empty();
|
|
var headerRow = $("<tr />");
|
|
var char = (resultTableSortOrder== "ascending" ? "▾" : "▴");
|
|
if(resultTableSortIndex == 0) headerRow.append("$(<th>ID " + char + "</th>");
|
|
else headerRow.append("$(<th>ID</th>");
|
|
if(resultTableSortIndex == 1) headerRow.append("$(<th>Score " + char + "</th>");
|
|
else headerRow.append("$(<th>Score</th>");
|
|
if(resultTableSortIndex == 2) headerRow.append("$(<th>Status " + char + "</th>");
|
|
else headerRow.append("$(<th>Status</th>");
|
|
tableHeader.append(headerRow);
|
|
|
|
|
|
//Sort rows, and insert into table:
|
|
var sorted;
|
|
if(resultTableSortIndex == 0) sorted = resultsTableContent.sort(compareResultsIndex);
|
|
else if(resultTableSortIndex == 1) sorted = resultsTableContent.sort(compareScores);
|
|
else sorted = resultsTableContent.sort(compareStatus);
|
|
|
|
var len = (!resultsTableContent ? 0 : resultsTableContent.length);
|
|
for(var i=0; i<len; i++){
|
|
var row;
|
|
if(selectedCandidateIdx == sorted[i][0]){
|
|
//Selected row
|
|
row = $('<tr class="resultTableRowSelected" id="rTbl-' + sorted[i][0] + '"/>');
|
|
} else {
|
|
//Normal row
|
|
row = $('<tr class="resultTableRow" id="rTbl-' + sorted[i][0] + '"/>');
|
|
}
|
|
row.append($("<td>" + sorted[i][0] + "</td>"));
|
|
var score = sorted[i][1];
|
|
row.append($("<td>" + ((!score || score == "null") ? "-" : score) + "</td>"));
|
|
row.append($("<td>" + sorted[i][2] + "</td>"));
|
|
tableBody.append(row);
|
|
}
|
|
}
|
|
|
|
//Compare function for results, based on sort order
|
|
function compareResultsIndex(a, b){
|
|
return (resultTableSortOrder == "ascending" ? a[0] - b[0] : b[0] - a[0]);
|
|
}
|
|
function compareScores(a,b){
|
|
//TODO Not always numbers...
|
|
if(resultTableSortOrder == "ascending"){
|
|
if(a[1] == "NaN"){
|
|
return 1;
|
|
} else if(b[1] == "NaN"){
|
|
return -1;
|
|
}
|
|
return a[1] - b[1];
|
|
} else {
|
|
if(a[1] == "NaN"){
|
|
return -1;
|
|
} else if(b[1] == "NaN"){
|
|
return 1;
|
|
}
|
|
return b[1] - a[1];
|
|
}
|
|
}
|
|
function compareStatus(a,b){
|
|
//TODO: secondary sort on... score? index?
|
|
if(resultTableSortOrder == "ascending"){
|
|
return (a[2] < b[2] ? -1 : (a[2] > b[2] ? 1 : 0));
|
|
} else {
|
|
return (a[2] < b[2] ? 1 : (a[2] > b[2] ? -1 : 0));
|
|
}
|
|
}
|
|
|
|
//Do a HTTP request on the specified path, parse and insert into the provided element
|
|
function loadCandidateDetails(path, elementToAppendTo){
|
|
$.get(path, function (data) {
|
|
var str = JSON.stringify(data);
|
|
var component = Component.getComponent(str);
|
|
component.render(elementToAppendTo);
|
|
});
|
|
}
|
|
|
|
|
|
|
|
//Sorting by column: Intercept click events on table header
|
|
$(function(){
|
|
$("#resultsTableHeader").delegate("th", "click", function(e) {
|
|
//console.log("Header clicked on at: " + $(e.currentTarget).index() + " - " + $(e.currentTarget).html());
|
|
//Update the sort order for the table:
|
|
var clickIndex = $(e.currentTarget).index();
|
|
if(clickIndex == resultTableSortIndex){
|
|
//Switch sort order: ascending -> descending or descending -> ascending
|
|
if(resultTableSortOrder == "ascending"){
|
|
resultTableSortOrder = "descending";
|
|
} else {
|
|
resultTableSortOrder = "ascending";
|
|
}
|
|
} else {
|
|
//Sort on column, ascending:
|
|
resultTableSortIndex = clickIndex;
|
|
resultTableSortOrder = "ascending";
|
|
}
|
|
|
|
//Clear record of expanded rows
|
|
expandedRowsCandidateIDs = [];
|
|
|
|
//Redraw table
|
|
drawResultTable();
|
|
});
|
|
});
|
|
|
|
//Displaying model/candidate details: Intercept click events on table rows -> toggle selected, fire off update
|
|
$(function(){
|
|
$("#resultsTableBody").delegate("tr", "click", function(e){
|
|
var id = this.id; //Expect: rTbl-X where X is some index
|
|
var dashIdx = id.indexOf("-");
|
|
var candidateID = Number(id.substring(dashIdx+1));
|
|
// console.log("Clicked row: " + this.id + " with class: " + this.className + ", candidateId = " + candidateID);
|
|
|
|
if(this.className == "resultTableRow"){
|
|
//Set selected model
|
|
selectedCandidateIdx = candidateID;
|
|
|
|
//Fire off update
|
|
doUpdate();
|
|
}
|
|
});
|
|
});
|
|
|
|
function selectNewSession(){
|
|
var selector = $("#sessionSelect");
|
|
var currSelected = selector.val();
|
|
|
|
if(currSelected){
|
|
$.ajax({
|
|
url: "/arbiter/sessions/set/" + currSelected,
|
|
async: true,
|
|
error: function (query, status, error) {
|
|
console.log("Error setting session: " + error);
|
|
},
|
|
success: function (data) {
|
|
//Update UI immediately
|
|
doUpdate();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
</script>
|
|
<script>
|
|
$(function () {
|
|
$("#accordion").accordion({
|
|
collapsible: true,
|
|
heightStyle: "content"
|
|
});
|
|
});
|
|
$(function () {
|
|
$("#accordion2").accordion({
|
|
collapsible: true,
|
|
heightStyle: "content"
|
|
});
|
|
});
|
|
$(function () {
|
|
$("#accordion3").accordion({
|
|
collapsible: true,
|
|
heightStyle: "content"
|
|
});
|
|
});
|
|
</script>
|
|
<table style="width: 100%;
|
|
padding: 5px;" class="hd">
|
|
<tbody>
|
|
<tr style="height: 40px">
|
|
<td> <div style="width: 40px;
|
|
height: 40px;
|
|
float: left"></div>
|
|
<div style="height: 40px;
|
|
float: left;
|
|
padding-top: 10px">Deeplearning4J - Arbiter UI</div></td>
|
|
<td>
|
|
<div id="sessionSelectDiv" style="display:none; float:left; font-size: 10pt; color:black">
|
|
<span style="color: white">Session:</span>
|
|
<select id="sessionSelect" onchange='selectNewSession()'>
|
|
<option>(Session ID)</option>
|
|
</select>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
|
|
<div style="width: 1200px;
|
|
margin-left: auto;
|
|
margin-right: auto;">
|
|
<div class="outerelements" id="status">
|
|
<div id="accordion" class="hcol2">
|
|
<h3 class="hcol2 headingcolor ui-accordion-header">Summary</h3>
|
|
<div class="statusdiv" id="statusdiv">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="outerelements" id="settings">
|
|
<div id="accordion2">
|
|
<h3 class="ui-accordion-header headingcolor">Optimization Settings</h3>
|
|
<div class="settingsdiv" id="settingsdiv"></div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="outerelements" id="results">
|
|
<div class="resultsHeadingDiv">Results</div>
|
|
<div class="resultsdiv" id="resultsdiv">
|
|
<table style="width: 100%" id="resultsTable" class="resultsTable">
|
|
<col width="33%">
|
|
<col width="33%">
|
|
<col width="34%">
|
|
<thead id="resultsTableHeader"></thead>
|
|
<tbody id="resultsTableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="outerelements" id="resultview">
|
|
<div id="accordion3">
|
|
<h3 class="ui-accordion-header headingcolor">Selected Result</h3>
|
|
<div class="resultsviewdiv" id="resultsviewdiv"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html> |