mirror of
https://github.com/ansible/awx.git
synced 2026-02-13 17:24:45 -03:30
Job detail page refactoration
Handling play and task parallel arrival seems to be working now. Removed 'search' and 'status' labels from search dialogs. Job events viewer now displays JSON objects as nested tables. Re-ordered host event viewer fields, adding host name, and fixing links so that only the status column is clickable.
This commit is contained in:
@@ -828,7 +828,7 @@ function JobDetailController ($location, $rootScope, $scope, $compile, $routePar
|
|||||||
EventViewer({
|
EventViewer({
|
||||||
scope: scope,
|
scope: scope,
|
||||||
url: scope.job.related.job_events + '?id=' + id,
|
url: scope.job.related.job_events + '?id=' + id,
|
||||||
title: 'Host Result'
|
title: 'Host Event'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ angular.module('EventsViewerFormDefinition', [])
|
|||||||
label: 'Task',
|
label: 'Task',
|
||||||
section: 'Event'
|
section: 'Event'
|
||||||
},
|
},
|
||||||
|
item: {
|
||||||
|
label: 'Item',
|
||||||
|
section: 'Event'
|
||||||
|
},
|
||||||
module_name: {
|
module_name: {
|
||||||
label: 'Module',
|
label: 'Module',
|
||||||
section: 'Event'
|
section: 'Event'
|
||||||
@@ -45,10 +49,6 @@ angular.module('EventsViewerFormDefinition', [])
|
|||||||
label: 'Arguments',
|
label: 'Arguments',
|
||||||
section: 'Event'
|
section: 'Event'
|
||||||
},
|
},
|
||||||
item: {
|
|
||||||
label: 'Item',
|
|
||||||
section: 'Event'
|
|
||||||
},
|
|
||||||
rc: {
|
rc: {
|
||||||
label: 'Return Code',
|
label: 'Return Code',
|
||||||
section: 'Results'
|
section: 'Results'
|
||||||
|
|||||||
@@ -195,6 +195,28 @@ angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'EventsViewerFo
|
|||||||
section = params.section,
|
section = params.section,
|
||||||
html = '', e;
|
html = '', e;
|
||||||
|
|
||||||
|
function parseObject(obj) {
|
||||||
|
// parse nested JSON objects. a mini version of parseJSON without references to the event form object.
|
||||||
|
var key, html = '';
|
||||||
|
for (key in obj) {
|
||||||
|
if (typeof obj[key] === "boolean" || typeof obj[key] === "number" || typeof obj[key] === "string") {
|
||||||
|
html += "<tr><td class=\"key\">" + key + ":</td><td class=\"value\">" + obj[key] + "</td></tr>";
|
||||||
|
}
|
||||||
|
else if (typeof obj[key] === "object" && Array.isArray(obj[key])) {
|
||||||
|
html += "<tr><td class=\"key\">" + key + ":</td><td class=\"value\">";
|
||||||
|
obj[key].forEach(function(row) {
|
||||||
|
html += "[" + row + "],";
|
||||||
|
});
|
||||||
|
html = html.replace(/,$/,'');
|
||||||
|
html += "</td></tr>\n";
|
||||||
|
}
|
||||||
|
else if (typeof obj[key] === "object") {
|
||||||
|
html += "<tr><td class=\"key\">" + key + ":</td><td class=\"nested-table\"><table>\n<tbody>\n" + parseObject(obj[key]) + "</tbody>\n</table>\n</td></tr>\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
function parseJSON(obj) {
|
function parseJSON(obj) {
|
||||||
var html = '', key, keys, found = false;
|
var html = '', key, keys, found = false;
|
||||||
if (typeof obj === "object") {
|
if (typeof obj === "object") {
|
||||||
@@ -252,9 +274,8 @@ angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'EventsViewerFo
|
|||||||
}
|
}
|
||||||
else if (typeof obj[key] === "object") {
|
else if (typeof obj[key] === "object") {
|
||||||
found = true;
|
found = true;
|
||||||
html += "<tr><td class=\"key\">" + label + ":</td><td class=\"nested-table\">\n" + parseJSON(obj[key]) + "</td></tr>\n";
|
html += "<tr><td class=\"key\">" + label + ":</td><td class=\"nested-table\"><table>\n<tbody>\n" + parseObject(obj[key]) + "</tbody>\n</table>\n</td></tr>\n";
|
||||||
}
|
}
|
||||||
//}
|
|
||||||
});
|
});
|
||||||
html += "</tbody>\n";
|
html += "</tbody>\n";
|
||||||
html += "</table>\n";
|
html += "</table>\n";
|
||||||
|
|||||||
@@ -114,11 +114,11 @@ angular.module('HostEventsViewerHelper', ['ModalDialog', 'Utilities', 'EventView
|
|||||||
|
|
||||||
buildRow = function(res) {
|
buildRow = function(res) {
|
||||||
var html = '';
|
var html = '';
|
||||||
html += "<tr ng-click=\"showDetails(" + res.id + ")\" class=\"cursor-pointer\" aw-tool-tip=\"Click to view details\" data-placement=\"top\">\n";
|
html += "<tr>\n";
|
||||||
html += "<td class=\"col-md-3\"><i class=\"fa icon-job-" + res.status + "\"></i> <a href=\"\">" + res.status_text + "</a></td>\n";
|
html += "<td class=\"col-md-3\"><a href=\"\" ng-click=\"showDetails(" + res.id + ")\" aw-tool-tip=\"Click to view details\" data-placement=\"top\"><i class=\"fa icon-job-" + res.status + "\"></i> " + res.status_text + "</a></td>\n";
|
||||||
html += "<td class=\"col-md-3\"><a href=\"\">" + res.play + "</a></td>\n";
|
html += "<td class=\"col-md=3\">" + res.host_name + "</td>\n";
|
||||||
html += "<td class=\"col-md-3\"><a href=\"\">" + res.task + "</a></td>\n";
|
html += "<td class=\"col-md-3\">" + res.play + "</td>\n";
|
||||||
html += "<td class=\"col-md-3\"><a href=\"\">" + res.msg + "</a></td>";
|
html += "<td class=\"col-md-3\">" + res.task + "</td>\n";
|
||||||
html += "</tr>";
|
html += "</tr>";
|
||||||
return html;
|
return html;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
|||||||
status_tip: "Event ID: " + event.id + "<br />Status: " + status_text,
|
status_tip: "Event ID: " + event.id + "<br />Status: " + status_text,
|
||||||
created: event.created,
|
created: event.created,
|
||||||
modified: event.modified,
|
modified: event.modified,
|
||||||
hostCount: (scope.activePlay && scope.jobData.plays[scope.activePlay]) ? scope.jobData.plays[scope.activePlay].hostCount : 0,
|
hostCount: (scope.jobData.plays[event.parent]) ? scope.jobData.plays[event.parent].hostCount : 0,
|
||||||
reportedHosts: 0,
|
reportedHosts: 0,
|
||||||
successfulCount: 0,
|
successfulCount: 0,
|
||||||
failedCount: 0,
|
failedCount: 0,
|
||||||
@@ -451,8 +451,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
|||||||
}])
|
}])
|
||||||
|
|
||||||
// Each time a runner event is received update host summary totals and the parent task
|
// Each time a runner event is received update host summary totals and the parent task
|
||||||
.factory('UpdateHostStatus', ['UpdateTaskStatus', 'AddHostResult',
|
.factory('UpdateHostStatus', ['UpdateTaskStatus', 'AddHostResult', function(UpdateTaskStatus, AddHostResult) {
|
||||||
function(UpdateTaskStatus, AddHostResult) {
|
|
||||||
return function(params) {
|
return function(params) {
|
||||||
var scope = params.scope,
|
var scope = params.scope,
|
||||||
status = params.status, // successful, changed, unreachable, failed, skipped
|
status = params.status, // successful, changed, unreachable, failed, skipped
|
||||||
@@ -516,7 +515,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
|||||||
}])
|
}])
|
||||||
|
|
||||||
// Add a new host result
|
// Add a new host result
|
||||||
.factory('AddHostResult', ['SetTaskStyles', 'SetActiveTask', function(SetTaskStyles, SetActiveTask) {
|
.factory('AddHostResult', ['SetTaskStyles', 'SetActivePlay', 'SetActiveTask', function(SetTaskStyles, SetActivePlay, SetActiveTask) {
|
||||||
return function(params) {
|
return function(params) {
|
||||||
var scope = params.scope,
|
var scope = params.scope,
|
||||||
task_id = params.task_id,
|
task_id = params.task_id,
|
||||||
@@ -528,7 +527,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
|||||||
msg = params.message,
|
msg = params.message,
|
||||||
item = params.item,
|
item = params.item,
|
||||||
status_text = '',
|
status_text = '',
|
||||||
task;
|
task, play, play_id;
|
||||||
|
|
||||||
switch(status) {
|
switch(status) {
|
||||||
case "successful":
|
case "successful":
|
||||||
@@ -551,11 +550,16 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
|||||||
item = JSON.stringify(item);
|
item = JSON.stringify(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scope.jobData.plays[scope.activePlay].tasks[task_id].hostResults[event_id]) {
|
for (play in scope.jobData.plays) {
|
||||||
// host already exists. do nothing.
|
for (task in scope.jobData.plays[play].tasks) {
|
||||||
|
if (parseInt(task,10) === parseInt(task_id,10)) {
|
||||||
|
play_id = parseInt(play,10);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
scope.jobData.plays[scope.activePlay].tasks[task_id].hostResults[event_id] = {
|
if (play_id) {
|
||||||
|
scope.jobData.plays[play_id].tasks[task_id].hostResults[event_id] = {
|
||||||
id: event_id,
|
id: event_id,
|
||||||
status: status,
|
status: status,
|
||||||
status_text: status_text,
|
status_text: status_text,
|
||||||
@@ -568,15 +572,15 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
|||||||
};
|
};
|
||||||
|
|
||||||
// increment the unreachable count on the play
|
// increment the unreachable count on the play
|
||||||
if (status === 'unreachable' && scope.jobData.plays[scope.activePlay]) {
|
if (status === 'unreachable') {
|
||||||
scope.jobData.plays[scope.activePlay].unreachableCount++;
|
scope.jobData.plays[play_id].unreachableCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the task status bar
|
// update the task status bar
|
||||||
task = scope.jobData.plays[scope.activePlay].tasks[task_id];
|
task = scope.jobData.plays[play_id].tasks[task_id];
|
||||||
|
|
||||||
if (task_id === scope.jobData.plays[scope.activePlay].firstTask) {
|
if (task_id === scope.jobData.plays[play_id].firstTask) {
|
||||||
scope.jobData.plays[scope.activePlay].hostCount++;
|
scope.jobData.plays[play_id].hostCount++;
|
||||||
task.hostCount++;
|
task.hostCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,6 +595,8 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
|||||||
task: task
|
task: task
|
||||||
});
|
});
|
||||||
|
|
||||||
|
SetActivePlay({ scope: scope });
|
||||||
|
|
||||||
SetActiveTask({ scope: scope });
|
SetActiveTask({ scope: scope });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -609,6 +615,14 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
|
|||||||
task.successfulPct = (task.hostCount > 0) ? Math.ceil((100 * (task.successfulCount / task.hostCount))) : 0;
|
task.successfulPct = (task.hostCount > 0) ? Math.ceil((100 * (task.successfulCount / task.hostCount))) : 0;
|
||||||
task.unreachablePct = (task.hostCount > 0) ? Math.ceil((100 * (task.unreachableCount / task.hostCount))) : 0;
|
task.unreachablePct = (task.hostCount > 0) ? Math.ceil((100 * (task.unreachableCount / task.hostCount))) : 0;
|
||||||
|
|
||||||
|
// cap % at 100
|
||||||
|
task.missingPct = (task.missingPct > 100) ? 100 : task.missingPct;
|
||||||
|
task.failedPct = (task.failedPct > 100) ? 100 : task.failedPct;
|
||||||
|
task.changedPct = (task.changedPct > 100) ? 100 : task.changedPct;
|
||||||
|
task.skippedPct = (task.skippedPct > 100) ? 100 : task.skippedPct;
|
||||||
|
task.successfulPct = ( task.successfulPct > 100) ? 100 : task.successfulPct;
|
||||||
|
task.unreachablePct = (task.unreachablePct > 100) ? 100 : task.unreachablePct;
|
||||||
|
|
||||||
diff = (task.failedPct + task.changedPct + task.skippedPct + task.successfulPct + task.unreachablePct + task.missingPct) - 100;
|
diff = (task.failedPct + task.changedPct + task.skippedPct + task.successfulPct + task.unreachablePct + task.missingPct) - 100;
|
||||||
if (diff > 0) {
|
if (diff > 0) {
|
||||||
if (task.failedPct > diff) {
|
if (task.failedPct > diff) {
|
||||||
|
|||||||
@@ -31,3 +31,30 @@ table.eventviewer-status {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nested-table {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
table {
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: none;
|
||||||
|
width: auto;
|
||||||
|
td {
|
||||||
|
vertical-align: top;
|
||||||
|
padding: 0 3px 3px 3px;
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
/*tr td:first-of-type {
|
||||||
|
width: auto;
|
||||||
|
}*/
|
||||||
|
tr:last-of-type {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.key {
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -506,66 +506,6 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
#event-viewer-dialog {
|
|
||||||
padding-bottom: 5px;
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-left: 5px;
|
|
||||||
padding-right: 5px;
|
|
||||||
overflow: hidden;
|
|
||||||
.results {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
.spacer {
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
border-bottom: 1px solid @well-border;
|
|
||||||
}
|
|
||||||
tr {
|
|
||||||
border-top: 1px solid @well-border;
|
|
||||||
/*border-bottom: 1px solid @well-border;*/
|
|
||||||
}
|
|
||||||
tr:first-child {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
tr td:first-of-type {
|
|
||||||
width: 20%;
|
|
||||||
}
|
|
||||||
.key {
|
|
||||||
vertical-align: top;
|
|
||||||
padding: 3px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.value {
|
|
||||||
padding: 3px;
|
|
||||||
i {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.nested-table {
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
table {
|
|
||||||
border-top: none;
|
|
||||||
border-bottom: none;
|
|
||||||
width: auto;
|
|
||||||
tr {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
tr td:first-of-type {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
.key {
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-row {
|
.footer-row {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,6 @@
|
|||||||
<div class="col-lg-11 col-md-10 col-sm-10 col-xs-11" style="text-align:right;">
|
<div class="col-lg-11 col-md-10 col-sm-10 col-xs-11" style="text-align:right;">
|
||||||
<div id="play-search-form" class="search-form form-inline">
|
<div id="play-search-form" class="search-form form-inline">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Search</label>
|
|
||||||
<div class="search-name" style="display:inline-block; position:relative;">
|
<div class="search-name" style="display:inline-block; position:relative;">
|
||||||
<input type="text" class="input-xs form-control" id="search_play_name" ng-model="search_play_name"
|
<input type="text" class="input-xs form-control" id="search_play_name" ng-model="search_play_name"
|
||||||
placeholder="Play Name" ng-keypress="searchPlaysKeyPress($event)" >
|
placeholder="Play Name" ng-keypress="searchPlaysKeyPress($event)" >
|
||||||
@@ -92,7 +91,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Status</label>
|
|
||||||
<div class="btn-group" aw-toggle-button data-after-toggle="filterPlayStatus">
|
<div class="btn-group" aw-toggle-button data-after-toggle="filterPlayStatus">
|
||||||
<button class="btn btn-xs btn-primary active">All</button>
|
<button class="btn btn-xs btn-primary active">All</button>
|
||||||
<button class="btn btn-xs btn-default">Failed</button>
|
<button class="btn btn-xs btn-default">Failed</button>
|
||||||
@@ -141,7 +139,6 @@
|
|||||||
<div class="col-lg-11 col-md-10 col-sm-10" style="text-align:right;">
|
<div class="col-lg-11 col-md-10 col-sm-10" style="text-align:right;">
|
||||||
<div id="task-search-form" class="search-form form-inline">
|
<div id="task-search-form" class="search-form form-inline">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Search</label>
|
|
||||||
<div class="search-name" style="display:inline-block; position:relative;">
|
<div class="search-name" style="display:inline-block; position:relative;">
|
||||||
<input type="text" class="input-xs form-control" id="search_task_name" ng-model="search_task_name"
|
<input type="text" class="input-xs form-control" id="search_task_name" ng-model="search_task_name"
|
||||||
placeholder="Task Name" ng-keypress="searchTasksKeyPress($event)" >
|
placeholder="Task Name" ng-keypress="searchTasksKeyPress($event)" >
|
||||||
@@ -152,7 +149,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Status</label>
|
|
||||||
<div class="btn-group" aw-toggle-button data-after-toggle="filterTaskStatus">
|
<div class="btn-group" aw-toggle-button data-after-toggle="filterTaskStatus">
|
||||||
<button class="btn btn-xs btn-primary active">All</button>
|
<button class="btn btn-xs btn-primary active">All</button>
|
||||||
<button class="btn btn-xs btn-default">Failed</button>
|
<button class="btn btn-xs btn-default">Failed</button>
|
||||||
@@ -204,7 +200,6 @@
|
|||||||
<div class="col-lg-10 col-md-10 col-sm-10" style="text-align:right;">
|
<div class="col-lg-10 col-md-10 col-sm-10" style="text-align:right;">
|
||||||
<div id="host-search-form" class="search-form form-inline">
|
<div id="host-search-form" class="search-form form-inline">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Search</label>
|
|
||||||
<div class="search-name" style="display:inline-block; position:relative;">
|
<div class="search-name" style="display:inline-block; position:relative;">
|
||||||
<input type="text" class="input-xs form-control" id="search_host_name" ng-model="search_host_name"
|
<input type="text" class="input-xs form-control" id="search_host_name" ng-model="search_host_name"
|
||||||
placeholder="Host Name" ng-keypress="searchHostsKeyPress($event)" >
|
placeholder="Host Name" ng-keypress="searchHostsKeyPress($event)" >
|
||||||
@@ -215,7 +210,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Status</label>
|
|
||||||
<div class="btn-group" aw-toggle-button data-after-toggle="filterHostStatus">
|
<div class="btn-group" aw-toggle-button data-after-toggle="filterHostStatus">
|
||||||
<button class="btn btn-xs btn-primary active">All</button>
|
<button class="btn btn-xs btn-primary active">All</button>
|
||||||
<button class="btn btn-xs btn-default">Failed</button>
|
<button class="btn btn-xs btn-default">Failed</button>
|
||||||
@@ -277,7 +271,6 @@
|
|||||||
<div class="col-lg-10 col-md-10 col-sm-10" style="text-align:right;">
|
<div class="col-lg-10 col-md-10 col-sm-10" style="text-align:right;">
|
||||||
<div id="task-search-form" class="search-form form-inline">
|
<div id="task-search-form" class="search-form form-inline">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Search</label>
|
|
||||||
<div class="search-name" style="display:inline-block; position:relative;">
|
<div class="search-name" style="display:inline-block; position:relative;">
|
||||||
<input type="text" class="input-xs form-control" id="search_host_summary_name" ng-model="search_host_summary_name"
|
<input type="text" class="input-xs form-control" id="search_host_summary_name" ng-model="search_host_summary_name"
|
||||||
placeholder="Host Name" ng-keypress="searchHostSummaryKeyPress($event)" >
|
placeholder="Host Name" ng-keypress="searchHostSummaryKeyPress($event)" >
|
||||||
@@ -288,7 +281,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Status</label>
|
|
||||||
<div class="btn-group" aw-toggle-button data-after-toggle="filterHostSummaryStatus">
|
<div class="btn-group" aw-toggle-button data-after-toggle="filterHostSummaryStatus">
|
||||||
<button class="btn btn-xs btn-primary active">All</button>
|
<button class="btn btn-xs btn-primary active">All</button>
|
||||||
<button class="btn btn-xs btn-default">Failed</button>
|
<button class="btn btn-xs btn-default">Failed</button>
|
||||||
@@ -385,9 +377,9 @@
|
|||||||
<table id="fixed-table-header" class="table">
|
<table id="fixed-table-header" class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th class="col-md-3">Status</th>
|
<tr><th class="col-md-3">Status</th>
|
||||||
|
<th class="col-md-3">Host</th>
|
||||||
<th class="col-md-3">Play</th>
|
<th class="col-md-3">Play</th>
|
||||||
<th class="col-md-3">Task</th>
|
<th class="col-md-3">Task</th>
|
||||||
<th class="col-md-3">Result</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
Reference in New Issue
Block a user