report UI

Change-Id: Iccb042fb542d8dea2b1fe2826bc0bfbac77d83a7
This commit is contained in:
sharonlucong 2015-01-15 13:55:07 -08:00
parent 0d41c806be
commit 4290e28fab
8 changed files with 619 additions and 23 deletions

View File

@ -1056,3 +1056,15 @@ h4.widget-title:hover {
.loadingMask .mask{
opacity: 0.5;
}
.centerLoading{
padding-top: 20%;
padding-left: 45%;
}
.popover-title{
background: #F2DEDE;
}
.yellow{
color: #FEE188 !important;
}
.red-tooltip + .tooltip > .tooltip-inner {background-color: #f00;}
.red-tooltip + .tooltip > .tooltip-arrow { border-bottom-color:#f00; }

View File

@ -40,17 +40,16 @@ define([
compassModule.stateProvider = $stateProvider;
$urlRouterProvider.otherwise('/clusterlist');
});
compassModule.config(function($httpProvider){
compassModule.config(function($httpProvider) {
$httpProvider.interceptors.push('authenticationInterceptor');
});
compassModule.run(function($rootScope, $state, authService, rememberMe) {
$rootScope.Object = Object; //Object can be used in anywhere. e.g. : Object.key({}).length == 0
$rootScope.Object = Object; //Object can be used in anywhere. e.g. : Object.key({}).length == 0
$rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams) {
if (toState.authenticate && !authService.isAuthenticated) {
if (rememberMe.getCookie("isAuthenticated")) {
authService.isAuthenticated = true
}
else {
} else {
// User isn't authenticated
$state.transitionTo("login");
event.preventDefault();
@ -79,11 +78,11 @@ define([
})
}
});
compassModule.controller('errorHandlingModalController',function($scope,$modalInstance,message){
compassModule.controller('errorHandlingModalController', function($scope, $modalInstance, message) {
$scope.warning = message.data;
$scope.status = message.status;
$scope.ok =function(){
$scope.ok = function() {
$modalInstance.close();
}
});

View File

@ -24,6 +24,302 @@ compassAppDev.run(function($httpBackend, settings, $http) {
return [200, message, {}];
});
$httpBackend.whenPOST(/\.*\/clusters\/([0-9]|[1-9][0-9])\/action$/).respond(function(method, url, data) {
console.log(method, url, data);
var healthrequest = {
"status": "start to check cluster health.",
"cluster_id": 1
};
return [200, healthrequest, {}];
});
$httpBackend.whenGET(/\.*\/clusters\/([0-9]|[1-9][0-9])*\/healthreports$/).respond(function(method, url, data) {
console.log(method, url, data);
var healthStates = ["verifying", "finished", "error", "success"];
var reports = [{
"name": "name1",
"category": "neutron",
"state": "verifying",
"error_message": "GetResourceErrorStatus",
"cluster_id": 1,
"report": {
"results": {
"total_errors": 2,
"actions": {
"nova.boot_server": {
"duration": {
"data": [
83.646584987640381,
48.459306001663208
],
"summary": {
"errors": 1,
"success": "100.0%",
"min (sec)": 48.459000000000003,
"avg (sec)": 66.052999999999997,
"max (sec)": 83.647000000000006,
"total": 2,
"errors_info": ["title", "error"]
}
}
},
"nova.delete_server": {
"duration": {
"data": [
2.3977420330047607,
2.6214451789855957
],
"summary": {
"errors": 0,
"success": "100.0%",
"min (sec)": 2.3980000000000001,
"avg (sec)": 2.5099999999999998,
"max (sec)": 2.621,
"total": 2,
"errors_info": ["title", "error"]
}
}
}
},
},
"raw_output": "output1"
}
}, {
"name": "name3",
"category": "neutron",
"state": "verifying",
"error_message": "GetResourceErrorStatus",
"cluster_id": 1,
"report": {
"results": {
"total_errors": 2,
"actions": {
"nova.boot_server": {
"duration": {
"data": [
83.646584987640381,
48.459306001663208
],
"summary": {
"errors": 1,
"success": "100.0%",
"min (sec)": 48.459000000000003,
"avg (sec)": 66.052999999999997,
"max (sec)": 83.647000000000006,
"total": 2,
"errors_info": ["title", "error"]
}
}
},
"nova.delete_server": {
"duration": {
"data": [
2.3977420330047607,
2.6214451789855957
],
"summary": {
"errors": 0,
"success": "100.0%",
"min (sec)": 2.3980000000000001,
"avg (sec)": 2.5099999999999998,
"max (sec)": 2.621,
"total": 2,
"errors_info": ["title", "error"]
}
}
}
},
},
"raw_output": "output1"
}
}, {
"name": "name2",
"category": "nova",
"state": "verifying",
"error_message": "GetResourceErrorStatus",
"cluster_id": 2,
"report": {
"results": {
"total_errors": 2,
"actions": {
"neutron.create_network": {
"duration": {
"data": [
0.87460207939147949,
0.97025418281555176
],
"summary": {
"errors": 1,
"success": "100.0%",
"min (sec)": 0.875,
"avg (sec)": 0.92200000000000004,
"max (sec)": 0.96999999999999997,
"total": 2,
"errors_info": ["title", "error"]
}
}
},
"neutron.delete_network": {
"duration": {
"data": [
0.58310413360595703,
0.6661829948425293
],
"summary": {
"errors": 0,
"success": "100.0%",
"min (sec)": 0.58299999999999996,
"avg (sec)": 0.625,
"max (sec)": 0.66600000000000004,
"total": 2,
"errors_info": []
}
}
}
},
},
"raw_output": "output2"
}
}];
return [200, reports, {}];
});
$httpBackend.whenGET(/\.*\/clusters\/([0-9]|[1-9][0-9])\/healthreports\/.*/).respond(function(method, url, data){
console.log(method, url, data);
var index = url.indexOf("healthreports/");
var name = url.substring(index).split("/")[1];
var healthStates = ["verifying","finished","error","success"];
var indireports =
{
"name": name,
"category": "neutron",
"state": healthStates[Math.floor((Math.random()*4))],
"error_message": "error",
"cluster_id": 1,
"report": {
"results": {
"total_errors": 2,
"actions": {
"nova.boot_server": {
"duration": {
"data": [
83.646584987640381,
48.459306001663208
],
"summary": {
"errors": {
"count": 2,
"details": [
[
"GetResourceErrorStatus",
"Resource <Server: rally_novaserver_mmlywuugchuxrjth> has ERROR status: No valid host was found. ",
"Traceback (most recent call last):\n File \"/opt/rally/lib/python2.6/site-packages/rally/benchmark/runners/base.py\", line 77, in _run_scenario_once\n method_name)(**kwargs) or scenario_output\n File \"/opt/rally/lib/python2.6/site-packages/rally/benchmark/scenarios/nova/servers.py\", line 104, in boot_and_delete_server\n self._generate_random_name(), image, flavor, **kwargs)\n File \"/opt/rally/lib/python2.6/site-packages/rally/benchmark/scenarios/base.py\", line 261, in func_atomic_actions\n f = func(self, *args, **kwargs)\n File \"/opt/rally/lib/python2.6/site-packages/rally/benchmark/scenarios/nova/utils.py\", line 126, in _boot_server\n check_interval=CONF.benchmark.nova_server_boot_poll_interval\n File \"/opt/rally/lib/python2.6/site-packages/rally/benchmark/utils.py\", line 104, in wait_for\n resource = update_resource(resource)\n File \"/opt/rally/lib/python2.6/site-packages/rally/benchmark/utils.py\", line 67, in _get_from_manager\n status=status, fault=msg)\nGetResourceErrorStatus: Resource <Server: rally_novaserver_mmlywuugchuxrjth> has ERROR status: No valid host was found. \n"
],
[
"GetResourceErrorStatus",
"Resource <Server: rally_novaserver_tydvigmxvdrwdgir> has ERROR status: No valid host was found. ",
"Traceback (most recent call last):\n File \"/opt/rally/lib/python2.6/site-packages/rally/benchmark/runners/base.py\", line 77, in _run_scenario_once\n method_name)(**kwargs) or scenario_output\n File \"/opt/rally/lib/python2.6/site-packages/rally/benchmark/scenarios/nova/servers.py\", line 104, in boot_and_delete_server\n self._generate_random_name(), image, flavor, **kwargs)\n File \"/opt/rally/lib/python2.6/site-packages/rally/benchmark/scenarios/base.py\", line 261, in func_atomic_actions\n f = func(self, *args, **kwargs)\n File \"/opt/rally/lib/python2.6/site-packages/rally/benchmark/scenarios/nova/utils.py\", line 126, in _boot_server\n check_interval=CONF.benchmark.nova_server_boot_poll_interval\n File \"/opt/rally/lib/python2.6/site-packages/rally/benchmark/utils.py\", line 104, in wait_for\n resource = update_resource(resource)\n File \"/opt/rally/lib/python2.6/site-packages/rally/benchmark/utils.py\", line 67, in _get_from_manager\n status=status, fault=msg)\nGetResourceErrorStatus: Resource <Server: rally_novaserver_tydvigmxvdrwdgir> has ERROR status: No valid host was found. \n"
]
]
},
"success": "100.0%",
"min (sec)": 48.459000000000003,
"avg (sec)": 66.052999999999997,
"max (sec)": 83.647000000000006,
"total": 2,
}
}
},
"nova.delete_server": {
"duration": {
"data": [
2.3977420330047607,
2.6214451789855957
],
"summary": {
"errors": 0,
"success": "100.0%",
"min (sec)": 2.3980000000000001,
"avg (sec)": 2.5099999999999998,
"max (sec)": 2.621,
"total": 2,
"errors": {
"count": 0,
"details": []
}
}
}
}
},
},
"raw_output": {
"result": [
{
"duration": 86.044774055480957,
"scenario_output": {
"errors": "",
"data": {}
},
"idle_duration": 0.0,
"atomic_actions": {
"nova.boot_server": 83.646584987640381,
"nova.delete_server": 2.3977420330047607
},
"error": []
},
{
"duration": 51.081114053726196,
"scenario_output": {
"errors": "",
"data": {}
},
"idle_duration": 0.0,
"atomic_actions": {
"nova.boot_server": 48.459306001663208,
"nova.delete_server": 2.6214451789855957
},
"error": []
}
],
"key": {
"kw": {
"runner": {
"type": "constant",
"concurrency": 2,
"times": 2
},
"args": {
"flavor": {
"name": "m1.tiny"
},
"image": {
"name": "cirros"
}
},
"context": {
"users": {
"project_domain": "default",
"users_per_tenant": 2,
"tenants": 3,
"resource_management_workers": 30,
"user_domain": "default"
}
}
},
"name": "NovaServers.boot_and_delete_server",
"pos": 0
},
"sla": []
}
}
};
return [200, indireports, {}];
});
$httpBackend.whenGET(settings.apiUrlBase + '/adapters').respond(function(method, url, data) {
console.log(method, url);
var adapters = [{
@ -42,18 +338,8 @@ compassAppDev.run(function($httpBackend, settings, $http) {
}],
"id": 2
}, {
"flavors": [{
"roles": [{
"display_name": "Ceph Cluster (Firefly)",
"description": "Ceph Cluster (Firefly)",
"name": "ceph_firefly"
}],
"display_name": "Ceph Cluster (Firefly)",
"id": 1,
"template": "cephfirefly.tmpl",
"name": "ceph_firefly"
}],
"name": "ceph_firefly",
"flavors": [],
"name": "ceph(chef)",
"roles": [],
"distributed_system_id": 2,
"supported_oses": [{
@ -66,7 +352,7 @@ compassAppDev.run(function($httpBackend, settings, $http) {
"name": "Ubuntu-12.04-x86_64"
}],
"distributed_system_name": "ceph",
"display_name": "ceph_firefly",
"display_name": "ceph(chef)",
"id": 4
}, {
"flavors": [{
@ -719,8 +1005,8 @@ compassAppDev.run(function($httpBackend, settings, $http) {
var config = {
"os_config": {
"server_credentials": {
"username": "root",
"password": "huawei"
"username": "",
"password": ""
},
"partition": {
"/var": {

View File

@ -84,6 +84,13 @@
</a>
</li>
<li ng-class="{active:state.includes('cluster.report')}">
<a ui-sref="cluster.report">
<i class="menu-icon glyphicon glyphicon-list-alt glyphicon-list-alt"></i>
<span class="menu-text">Report</span>
</a>
</li>
</ul>
<div class="sidebar-toggle sidebar-collapse" ng-click="sidebarCollapse = !sidebarCollapse">

View File

@ -82,6 +82,10 @@
</div>
<div class="pull-right">
<div class="btn-group" dropdown>
<button type="button" class="btn btn-info" ng-click="startChecking()" ng-disabled="clusterProgress.state == 'INSTALLING'">
Report
</button>
<button type="button" class="btn btn-info dropdown-toggle" ng-disabled="clusterProgress.state == 'INSTALLING'">
Actions
<span class="ace-icon fa fa-caret-down icon-on-right"></span>

View File

@ -0,0 +1,145 @@
<div ng-if="!showData && !isTimeout" class="centerLoading">
<i class="ace-icon fa fa-spinner fa-spin bigger-300 orange"></i>
<span>loading...</span>
</div>
<div ng-if="isTimeout" class="centerLoading">
<i class="ce-icon fa fa-times bigger-300 red"></i>
<span>Timeout Error</span>
</div>
<div ng-if="showData && !isTimeout" class="widget-box transparent" style="border-radius: 7px;" ng-repeat="categoryName in categories">
<div class="widget-header">
<h4 class="widget-title">{{categoryName}}</h4>
</div>
<div class="widget-body">
<div class="widget-main">
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<span ng-if="showData && !isTimeout">
<div class="panel panel-default" ng-repeat = "report in reports | FilterByCategory: categoryName">
<div class="panel-heading" role="tab" id="headingOne">
<h4 class="panel-title">
<span>{{report.name}}</span>
<span ng-if="reportStates[report.name] =='success'">
<i class="ace-icon fa fa fa-check bigger-160 green"></i>
</span>
<span ng-if="reportStates[report.name] =='finished'">
<i class="ace-icon fa fa-exclamation bigger-160 yellow"></i>
</span>
<span ng-if="reportStates[report.name] =='verifying'">
<i class="ace-icon fa fa-spinner fa-spin bigger-160 orange"></i>
</span>
<span ng-if="reportStates[report.name] =='error'">
<i class="ace-icon fa fa-times bigger-160 red tooltip-error" data-toggle="tooltip" data-placement="right" title="{{errorMessage[report.name]}}"></i>
</span>
<button class="pull-right btn btn-default btn-xs" data-toggle="collapse" data-parent="#accordion" href="#{{report.name}}" aria-expanded="true" aria-controls="{{report.name}}" ng-disabled="reportStates[report.name] == 'verifying'" ng-style="{color: notFinished ? 'white' : 'grey'}">
<i class="glyphicon glyphicon-chevron-down"></i>
</button>
</h4>
</div>
<div id="{{report.name}}" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingOne">
<div class="panel-body">
<table class="table" style="font-size:13px">
<thead>
<tr class="info">
<th>Actions</th>
<th>Avg(sec)</th>
<th>Max(sec)</th>
<th>Min(sec)</th>
<th>Errors</th>
<th>Success</th>
<th>Total</th>
</tr>
</thead>
<tr ng-repeat="(key,detail) in details[report.name]">
<td>{{key}}</td>
<td>{{detail.duration.summary["avg (sec)"]}}</td>
<td>{{detail.duration.summary["max (sec)"]}}</td>
<td>{{detail.duration.summary["min (sec)"]}}</td>
<td>
<span ng-if="detail.duration.summary.errors.count == 0">{{detail.duration.summary.errors.count}}</span>
<span ng-if="detail.duration.summary.errors.count!= 0">
<button id="popover" data-trigger="hover" style = "border-radius:20px!important" class="btn btn-xs btn-danger popover-hide"
title='{{detail.duration.summary.errors.details[0][0]}}'
type="button"
data-toggle="modal"
data-placement="right"
data-target="#{{createModalId(key,report.name)}}"
data-content='{{detail.duration.summary.errors.details[0][1]}}'>
{{detail.duration.summary.errors.count}}
</button>
<!-- Modal -->
<div class="modal fade" id="{{createModalId(key,report.name)}}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header" style="background:#B74635">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="myModalLabel">Error Details</h4>
</div>
<div class="modal-body">
<table class="table">
<tr>
<td><strong>Exception Type</strong>
</td>
<td>{{detail.duration.summary.errors.details[0][0]}}</td>
</tr>
<tr>
<td><strong>Exception Message</strong>
</td>
<td>{{detail.duration.summary.errors.details[0][1]}}</td>
</tr>
<tr>
<td></td>
<td>
<p ng-bind-html="detail.duration.summary.errors.details[0][2] | nl2br"></p>
</td>
</tr>
</table>
</div>
<div class="modal-footer" style="background:#F2DEDE">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script>
$(function() {
$('.popover-show').popover('show');
});
$(function() {
$('.popover-hide').popover('hide');
});
$(function() {
$('.popover-destroy').popover('destroy');
});
$(function() {
$('.popover-toggle').popover('toggle');
});
$(function() {
$(".popover-options a").popover({
html: true
});
});
$("#popover").popover({
trigger: "hover"
});
$(document).ready(function() {
$('[data-toggle="tooltip"]').tooltip({
placement: 'right'
});
});
</script>
</span>
</td>
<td>{{detail.duration.summary["success"]}}</td>
<td>{{detail.duration.summary["total"]}}</td>
</tr>
</table>
</div>
</div>
</div>
</span>
</div>
</div>
</div>
</div>

View File

@ -62,8 +62,126 @@ define(['angular'], function() {
controller: "clusterLogCtrl",
templateUrl: 'src/app/cluster/cluster-log.tpl.html',
authenticate: true
})
.state('cluster.report', {
url: '/report',
controller: "clusterReportCtrl",
templateUrl: 'src/app/cluster/cluster-report.tpl.html',
authenticate: true
});
});
clusterModule.controller('clusterReportCtrl', function($scope, $state, dataService, $stateParams, $timeout, $modal) {
var clusterId = $stateParams.id;
var dtLength = -1;
var progressTimer;
$scope.categories = {};
$scope.errorMessage = {};
$scope.showData = false;
$scope.modalId = {};
$scope.showData = false;
$scope.showDetails = false;
$scope.isTimeout = false;
$scope.promise = $timeout(function() {
$scope.isTimeout = true;
}, 1200000);
$scope.details = {};
$scope.reportStates = {};
function isEmpty(obj) {
for (var prop in obj) {
if (obj.hasOwnProperty(prop))
return false;
}
return true;
}
var getAllReports = function() {
if (!$scope.isTimeout) {
dataService.getHealthReports(clusterId).success(function(reportsData) {
if (!isEmpty(reportsData)) {
if (reportsData.length != dtLength) {
dtLength = reportsData.length;
$timeout(getAllReports, 3000);
} else {
$scope.reports = reportsData;
$scope.showData = true;
$timeout.cancel($scope.promise);
angular.forEach(reportsData, function(reportdt) {
$scope.categories[reportdt.category] = reportdt.category;
});
getIndividualReport();
}
} else {
$timeout(getAllReports, 2000);
$scope.showData = false;
}
});
}
}
var getIndividualReport = function() {
var finishedNumbers = 0;
angular.forEach($scope.reports, function(individualdt) {
if (($scope.reportStates[individualdt.name] === "verifying") || isEmpty($scope.reportStates[individualdt.name])) {
getIndividualDetails(individualdt);
} else {
finishedNumbers++;
}
});
if (finishedNumbers != $scope.reports.length) {
progressTimer = $timeout(getIndividualReport, 3000);
} else {
$timeout.cancel(progressTimer);
}
}
var getIndividualDetails = function(individualdt) {
dataService.getIndividualReports(individualdt.cluster_id, individualdt.name).success(function(indidetail) {
if (!isEmpty(indidetail.report)) {
$scope.details[individualdt.name] = indidetail.report.results.actions;
for (var i in indidetail.report.results.actions) {
var str = i + individualdt.name;
$scope.modalId[i + individualdt.name] = str.replace(".", "-");
$scope.createModalId = function(action, name) {
return $scope.modalId[action + name];
};
}
$scope.reportStates[individualdt.name] = indidetail.state;
} else {
$scope.reportStates[individualdt.name] = indidetail.state;
$scope.showDetails = false;
$timeout(getIndividualDetails, 2000);
}
if (indidetail.state === "error") {
$scope.errorMessage[individualdt.name] = indidetail.error_message;
}
});
};
getAllReports();
});
clusterModule.filter('FilterByCategory', function() {
return function(items, categoryName) {
var filtered = [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (item.category == categoryName) {
filtered.push(item);
}
}
return filtered;
}
});
clusterModule.filter('nl2br', function($sce) {
return function(text) {
return text ? $sce.trustAsHtml(text.replace(/\n/g, '<br/>')) : '';
};
});
clusterModule.controller('clusterCtrl', function($scope, $state, dataService, $stateParams) {
$scope.clusterId = $stateParams.id;
@ -72,7 +190,6 @@ define(['angular'], function() {
dataService.getClusterById($scope.clusterId).success(function(data) {
$scope.clusterInfo = data;
});
});
clusterModule.directive('clusternav', function($timeout) {
return {
@ -98,11 +215,20 @@ define(['angular'], function() {
});
clusterModule.controller('clusterProgressCtrl', function($scope, dataService, $stateParams, $filter, ngTableParams, $timeout, $modal, clusterhostsData) {
clusterModule.controller('clusterProgressCtrl', function($scope, dataService, $state, $stateParams, $filter, ngTableParams, $timeout, $modal, clusterhostsData, $rootScope) {
var clusterId = $stateParams.id;
var progressTimer;
var fireTimer = true;
$scope.hosts = clusterhostsData;
var request = {
"check_health": null
}
$scope.startChecking = function() {
dataService.startHealthCheck($scope.clusterId, request).success(function(data) {
$state.go('cluster.report', data);
});
};
var getClusterProgress = function() {
dataService.getClusterProgress(clusterId).success(function(data) {

View File

@ -126,6 +126,23 @@ define(['angular','uiBootstrap'], function(ng, uiBootstrap) {
this.getUserLog = function() {
return $http.get(settings.apiUrlBase + '/users/logs');
}
this.getHealthReports = function(id){
return $http.get(settings.apiUrlBase + '/clusters/' + id + '/healthreports');
};
this.getIndividualReports = function(id, name){
return $http.get(settings.apiUrlBase + '/clusters/' + id + '/healthreports/' + name);
};
this.postHealthCheck = function(id, checkHealth){
return $http.post(settings.apiUrlBase + '/clusters/' + id + '/action', angular.toJason(checkHealth));
};
this.startHealthCheck = function(id, request){
return $http.post(settings.apiUrlBase + '/clusters/' + id + '/action',angular.toJson(request));
};
this.getClusters = function() {
return $http.get(settings.apiUrlBase + '/clusters');
};