Add cluster overview page. Add async progressbars

Change-Id: I23e29aaf4d8dcea8ec367eee80e495ad5e213dba
This commit is contained in:
jiahuay 2014-07-18 11:49:40 -07:00
parent 52d7f8c159
commit be3cb8ac8c
12 changed files with 487 additions and 28 deletions

View File

@ -34,6 +34,10 @@ html {
.action i {
padding-top: 7px;
}
.action i.fa-trash-o:hover {
background-color: #d15b47 !important;
color: white;
}
.role-tag {
background-color: #6fb3e0 !important;
border-color: #6fb3e0 !important;
@ -88,15 +92,29 @@ select {
width: 200px;
}
.network-placeholder {
min-height: 200px;
min-height: 200px;
}
.interface-placeholder {
border: 1px solid #BED2DB;
box-sizing: border-box;
margin: 5px 0;
min-height: 50px;
min-height: 50px;
padding: 10px;
}
.border-radius-4 {
border-radius: 4px !important;
}
.cluster-progress {
margin-top: -40px;
margin-right: 20px;
margin-bottom: 18px;
}
.progressbar-number {
text-align: center;
position: absolute;
width: 100%;
margin-top: -1px;
}
.btn-clstr {
border: none !important;
background-color: inherit!important;

View File

@ -45,7 +45,7 @@
"language": "en",
"http_proxy": [""],
"https_proxy": [""],
"default_no_proxy":[""],
"default_no_proxy": [""],
"ntp_server": "",
"dns_servers": [""],
"search_path": ["compass.com"],

View File

@ -125,5 +125,34 @@
"title": "State",
"field": "state",
"visible": false
}],
"progress": [{
"title": "Host MAC Addr",
"field": "mac",
"visible": false
}, {
"title": "Switch IP",
"field": "switch_ip",
"visible": true
}, {
"title": "Port",
"field": "port",
"visible": true
}, {
"title": "VLAN",
"field": "vlan",
"visible": false
}, {
"title": "Hostname",
"field": "name",
"visible": true
}, {
"title": "Clusters",
"field": "clusters",
"visible": false
}, {
"title": "Roles",
"field": "roles",
"visible": false
}]
}

View File

@ -1,6 +1,7 @@
var compassAppDev = angular.module('compassAppDev', ['compass', 'ngMockE2E']);
compassAppDev.run(function($httpBackend, settings, $http) {
var progressPercent = 0;
// Allow all calls not to the API to pass through normally
$httpBackend.whenGET(new RegExp('src\/.*')).passThrough();
@ -112,7 +113,7 @@ compassAppDev.run(function($httpBackend, settings, $http) {
"vlan": "2",
"port": "2",
"name": "sv-2",
"clusters": ["cluster1"],
"clusters": [],
"os": "CentOS",
"adapter": "OpenStack",
"roles": [],
@ -223,7 +224,6 @@ compassAppDev.run(function($httpBackend, settings, $http) {
"network": {},
"state": "Successful"
}];
console.log(servers);
return [200, servers, {}];
});
@ -252,7 +252,7 @@ compassAppDev.run(function($httpBackend, settings, $http) {
var states = ["DEPLOYING", "SUCCESSFUL", "UNDEPLOYED", "FAILED"];
var progressData = {
"id": 1,
"state": states[Math.floor((Math.random() * 4))],
"state": states[Math.floor((Math.random() * 4))], //states[0],
"config_step": "deploy",
"status": {
"total_hosts": 4,
@ -265,7 +265,7 @@ compassAppDev.run(function($httpBackend, settings, $http) {
return [200, progressData, {}];
});
$httpBackend.whenGET(/\.*\/clusters/).respond(function(method, url, data) {
$httpBackend.whenGET(/\.*\/clusters$/).respond(function(method, url, data) {
console.log(method, url);
var clusters = [{
"id": 1,
@ -351,6 +351,30 @@ compassAppDev.run(function($httpBackend, settings, $http) {
return [200, clusters, {}];
});
$httpBackend.whenGET(/\.*\/clusters\/[1-9][0-9]*$/).respond(function(method, url, data) {
console.log(method, url);
var index = url.indexOf("clusters/");
var id = url.substring(index).split("/")[1];
var cluster = {
"id": id,
"name": "Cluster" + id,
"adapter_id": 1,
"os_id": 1,
"editable": true,
"create_by": "user@someemail.com",
"create_at": "2014-3-25 12:00:00",
"updated_at": "2014-3-26 13:00:00",
" links": [{
"href": "/clusters/" + id,
"rel": "self"
}, {
"href": "/clusters/" + id + "/hosts",
"rel": "hosts"
}]
};
return [200, cluster, {}];
});
$httpBackend.whenPUT(/\.*\/clusters\/[1-9][0-9]*\/config/).respond(function(method, url, data) {
console.log(method, url, data);
var config = JSON.parse(data);
@ -447,9 +471,82 @@ compassAppDev.run(function($httpBackend, settings, $http) {
return [200, network, {}];
});
$httpBackend.whenGET(/\.*\/clusters\/([0-9]|[1-9][0-9])\/hosts$/).respond(function(method, url, data) {
console.log(method, url, data);
var hosts = [];
var num = 20;
for (var i = 1; i <= num; i++) {
var host = {
"id": i,
"machine_id": i * 2 + 10,
"name": "host-" + i,
"mac": "28.e5.ee.47.14." + (i < 10 ? "a" : "") + i,
"switch_ip": "172.29.8.40",
"port": i,
"vlan": i,
"roles": [{
"display_name": "Network",
"name": "os-network"
}, {
"display_name": "Storage",
"name": "os-block-storage-worker"
}],
"clusters": ["cluster1", "cluster2"]
};
hosts.push(host);
}
return [200, hosts, {}];
});
$httpBackend.whenPUT(/\.*\/clusters\/[1-9][0-9]*\/hosts\/[1-9][0-9]*\/config/).respond(function(method, url, data) {
console.log(method, url, data);
var config = JSON.parse(data);
return [200, config, {}];
});
$httpBackend.whenGET(/\.*\/clusters\/([0-9]|[1-9][0-9])\/hosts\/([0-9]|[1-9][0-9])\/progress/).respond(function(method, url, data) {
//console.log(method, url, data);
var index = url.indexOf("clusters/");
var hostId = url.substring(index).split("/")[3];
var messages = ["Setting up kickstart configurations",
"Downloading installation images from server",
"Installing bootloaders",
"Installing packages",
"Chef run complete"
];
var message = "";
if (progressPercent < 0.1)
message = messages[0];
else if (progressPercent < 0.2)
message = messages[1];
else if (progressPercent < 0.5)
message = messages[2];
else if (progressPercent < 0.9)
message = messages[3];
else
message = messages[4];
var progress = {
"cluster_id": 1,
"host_id": hostId,
"state": "INSTALLING",
"percentage": progressPercent,
"severity": "INFO",
"message": message,
"updated_at": "---timestamp---"
}
progressPercent += 0.01;
if (progressPercent > 1) {
progressPercent = 1;
}
return [200, progress, {}];
});
$httpBackend.whenDELETE(/\.*\/hosts\/([0-9]|[1-9][0-9])/).respond(function(method, url, data) {
console.log(method, url, data);
var deleteHost = {};
return [200, deleteHost, {}];
})
});

View File

@ -1,7 +1,4 @@
<div class="sidebar" id="sidebar">
<div>
Cluster Name
</div>
<div class="sidebar responsive">
<ul class="nav nav-list">
<li ng-class="{active:state.includes('cluster.overview')}">
<a ui-sref="cluster.overview">

View File

@ -1 +1,147 @@
overview
<div class="page-header">
<h1>
{{clusterInfo.name}}
<small>
<span class="badge" ng-class="{'badge-warning':clusterProgress.state==='UNDEPLOYED',
'badge-info':clusterProgress.state==='DEPLOYING',
'badge-danger':clusterProgress.state==='FAILED',
'badge-success':clusterProgress.state==='SUCCESSFUL'}">
{{clusterProgress.state}}</span>
<!--<i class="ace-icon fa fa-angle-double-right"></i>&nbsp;
<a href="">OpenStack Dashboard Link</a>-->
</small>
</h1>
</div>
<div class="row">
<div class="col-xs-12">
<div class="row">
<div class="pull-right align-right">
<div class="cluster-progress">
<div>
<strong>{{clusterProgress.status.total_hosts}} Total Hosts</strong>
</div>
<div>{{clusterProgress.status.installing_hosts}} Installing Hosts</div>
<div>{{clusterProgress.status.completed_hosts}} Completed Hosts</div>
<div>{{clusterProgress.status.failed_hosts}} Failed Hosts</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div>
<div class="pull-left">
<span class="input-icon">{{search}}
<input type="text" placeholder="Search" ng-model="search">
<i class="ace-icon fa fa-search blue"></i>
</span>
<div class="btn-group" dropdown>
<button type="button" class="btn btn-white btn-default dropdown-toggle">
Column Show / Hide
<span class="ace-icon fa fa-caret-down icon-on-right"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="column in server_columns" ng-click="column.visible=!column.visible">
<a class="action">
<span ng-class="{'opacity-zero': !column.visible}">
<i class="ace-icon fa fa-check blue"></i>
</span>
{{column.title}}
</a>
</li>
</ul>
</div>
</div>
<div class="pull-right">
<button class="btn btn-white btn-info">
<i class="ace-icon fa fa-plus bigger-120"></i>
Add Host
</button>
<div class="btn-group" dropdown>
<button type="button" class="btn btn-primary btn-white dropdown-toggle">
Actions
<span class="ace-icon fa fa-caret-down icon-on-right"></span>
</button>
<ul class="dropdown-menu dropdown-info dropdown-menu-right" role="menu">
<li>
<a class="action" ng-click="assignRole(role)">
IPMI Power on
</a>
</li>
<li>
<a class="action" ng-click="assignRole(role)">
IPMI Power off
</a>
</li>
<li>
<a class="action" ng-click="assignRole(role)">
IPMI Reset
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="clearfix"></div>
<div class="table-responsive">
<table ng-table="tableParams" class="table table-hover table-striped">
<thead>
<tr>
<th>
<label>
<input type="checkbox" ng-model="selectall" ng-change="selectAllServers(selectall)" class="ace">
<span class="lbl"></span>
</label>
</th>
<th ng-repeat="column in server_columns" ng-show="column.visible" class="sortable" ng-class="{'sort-asc': tableParams.isSortBy(column.field, 'asc'),
'sort-desc': tableParams.isSortBy(column.field, 'desc')}" ng-click="tableParams.sorting(column.field, tableParams.isSortBy(column.field, 'asc') ? 'desc' : 'asc')">
{{column.title}}
</th>
<th>Progress</th>
<th class="align-right">Actions</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="host in $data | filter:search">
<td>
<label>
<input type="checkbox" ng-model="host.selected" class="ace">
<span class="lbl"></span>
</label>
</td>
<td ng-repeat="column in server_columns" ng-show="column.visible" sortable="column.field">
<span ng-if="column.field!='roles'">
{{host[column.field]}}
</span>
<span ng-if="column.field=='roles'">
<span ng-repeat="role in host['roles']" class="badge">
{{role.display_name}}
</span>
</span>
</td>
<td>
<hostprogressbar hostid="host.id" clusterid="clusterInfo.id" clusterstate="clusterProgress.state" progressdata="">
</hostprogressbar>
</td>
<td class="align-right">
<button class="btn btn-xs" ng-click="openDeleteHostModal($index)">
<i class="ace-icon fa fa-trash-o bigger-120"></i>
</button>
</td>
</tr>
</tbody>
</table>
<script type="text/ng-template" id="deleteHostConfirm.html">
<div class="modal-body">
Are you sure to delete
<strong>{{host.name}}</strong>?
</div>
<div class="modal-footer">
<button class="btn btn-default" ng-click="cancel()">Cancel</button>
<button class="btn btn-primary" ng-click="ok()">OK</button>
</div>
</script>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,18 +1,20 @@
angular.module('compass.cluster', [
'ui.router',
'ui.bootstrap',
'ngAnimate'
'ngAnimate',
'ngTable'
])
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('cluster', {
url: '/cluster/{id}',
//controller: 'clusterCtrl',
controller: 'clusterCtrl',
templateUrl: 'src/app/cluster/cluster.tpl.html'
})
.state('cluster.overview', {
url: '/overview',
controller: 'clusterProgressCtrl',
templateUrl: 'src/app/cluster/cluster-overview.tpl.html'
})
.state('cluster.config', {
@ -45,9 +47,14 @@ angular.module('compass.cluster', [
});
})
.controller('clusternavCtrl', function($scope, $state, dataService, stateService) {
.controller('clusterCtrl', function($scope, $state, dataService, stateService, $stateParams) {
var clusterId = $stateParams.id;
$scope.state = $state;
dataService.getClusterById(clusterId).success(function(data) {
$scope.clusterInfo = data;
});
dataService.getMonitoringNav().success(function(data) {
$scope.monitoringNav = data;
stateService.addStates($scope.monitoringNav);
@ -56,7 +63,87 @@ angular.module('compass.cluster', [
}).directive('clusternav', function() {
return {
templateUrl: 'src/app/cluster/cluster-nav.tpl.html'
}
})
.controller('clusterProgressCtrl', function($scope, dataService, $stateParams, $filter, ngTableParams, $timeout, $modal) {
var clusterId = $stateParams.id;
var progressTimer;
var getClusterProgress = function() {
dataService.getClusterProgress(clusterId).success(function(data) {
$scope.clusterProgress = data;
if ($scope.clusterProgress.state == "DEPLOYING") {
progressTimer = $timeout(getClusterProgress, 5000);
}
});
}
getClusterProgress();
dataService.getServerColumns().success(function(data) {
$scope.server_columns = data.progress;
});
dataService.getClusterHostMachines(clusterId).success(function(data) {
$scope.hosts = data;
$scope.tableParams = new ngTableParams({
page: 1, // show first page
count: $scope.hosts.length + 1 // count per page
}, {
counts: [], // hide count-per-page box
total: $scope.hosts.length, // length of data
getData: function($defer, params) {
// use build-in angular filter
var orderedData = params.sorting() ?
$filter('orderBy')($scope.hosts, params.orderBy()) : $scope.hosts;
$defer.resolve(orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()));
}
});
$scope.deleteHost = function(index) {
dataService.deleteHost($scope.hosts[index].id)
$scope.hosts.splice(index, 1);
$scope.tableParams.reload();
};
});
$scope.selectAllServers = function(flag) {
if (flag) {
angular.forEach($scope.hosts, function(sv) {
sv.selected = true;
})
} else {
angular.forEach($scope.hosts, function(sv) {
sv.selected = false;
})
}
};
$scope.openDeleteHostModal = function(index) {
var modalInstance = $modal.open({
templateUrl: 'deleteHostConfirm.html',
controller: deleteHostModalCtrl,
resolve: {
host: function() {
return $scope.hosts[index];
}
}
});
modalInstance.result.then(function() {
// ok
$scope.deleteHost(index);
}, function() {
// cancel
});
};
$scope.$on('$destroy', function() {
$timeout.cancel(progressTimer);
});
})
.controller('createClusterCtrl', ['$scope', '$state', '$modal', '$log', 'dataService', 'wizardFactory',
@ -85,7 +172,7 @@ angular.module('compass.cluster', [
dataService.createCluster(cluster).success(function(data, status) {
wizardFactory.setClusterInfo(data);
angular.forEach($scope.allAdapters, function(adapter) {
if(adapter.id == $scope.cluster.adapter_id) {
if (adapter.id == $scope.cluster.adapter_id) {
wizardFactory.setAdapter(adapter);
}
})
@ -122,3 +209,13 @@ var ModalInstanceCtrl = function($scope, $modalInstance, allAdapters, cluster) {
$scope.result = 'cancel';
};
};
var deleteHostModalCtrl = function($scope, $modalInstance, host) {
$scope.host = host;
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
}

View File

@ -1,8 +1,8 @@
<div ng-controller="clusternavCtrl">
<div clusternav></div>
<div clusternav></div>
<div class="main-content">
<div class="page-content">
<div ui-view></div>
</div>
</div>
<!-- might need this part for monitoring-->
<!--<iframe src="{{currentProjectUrl}}" style="width:500px;height:500px;"></iframe>-->
<div ui-view></div>

View File

@ -66,14 +66,18 @@ angular.module('compass.services', [])
return $http.post(settings.apiUrlBase + '/clusters', angular.toJson(cluster));
};
this.getClusterProgress = function(id) {
return $http.get(settings.apiUrlBase + '/clusters/' + id + '/progress');
};
this.getClusters = function() {
return $http.get(settings.apiUrlBase + '/clusters');
};
this.getClusterById = function(id) {
return $http.get(settings.apiUrlBase + '/clusters/' + id);
};
this.getClusterProgress = function(id) {
return $http.get(settings.apiUrlBase + '/clusters/' + id + '/progress');
};
this.updateClusterConfig = function(id, config) {
return $http.put(settings.apiUrlBase + '/clusters/' + id + '/config', angular.toJson(config));
};
@ -121,13 +125,24 @@ angular.module('compass.services', [])
return $http.put(settings.apiUrlBase + '/hosts/' + id + '/network/' + networkId, angular.toJson(network));
};
this.getClusterHostMachines = function(clusterId, hostId) {
return $http.get(settings.apiUrlBase + '/clusters/' + clusterId + '/hosts');
};
this.updateClusterHostConfig = function(clusterId, hostId, config) {
return $http.put(settings.apiUrlBase + '/hosts/' + clusterId + '/hosts/' + hostId + '/config', angular.toJson(config));
return $http.put(settings.apiUrlBase + '/clusters/' + clusterId + '/hosts/' + hostId + '/config', angular.toJson(config));
};
this.getClusterHostProgress = function(clusterId, hostId) {
return $http.get(settings.apiUrlBase + '/clusters/' + clusterId + '/hosts/' + hostId + '/progress');
};
this.deleteHost = function(id) {
return $http.delete(settings.apiUrlBase + '/hosts/' + id);
};
}
])
.factory('wizardFactory', [
function() {

View File

@ -93,7 +93,7 @@
<div class="widget-box transparent margin-top-minus10">
<div class="widget-header widget-header-flat">
<h4 class="widget-title lighter">
<i class="ace-icon fa fa-database"></i>
<i class="ace-icon fa fa-hdd-o"></i>
Partition
</h4>
<div class="widget-toolbar">

View File

@ -68,3 +68,49 @@ angular.module('compass.charts', [])
}
}
})
.directive('hostprogressbar', function(dataService, $timeout) {
return {
restrict: 'E',
scope: {
hostid: '=',
clusterid: '=',
clusterstate: '=',
progressdata: '@'
},
link: function(scope, element, attrs) {
var hostId = scope.hostid;
var clusterId = scope.clusterid;
var clusterState = scope.clusterstate;
var progress = 0;
var progressTimer;
scope.progressdata = 0;
scope.progressSeverity = "INFO";
var getProgress = function(num) {
dataService.getClusterHostProgress(clusterId, hostId).then(function(progressData) {
//success
progress = parseInt(eval(progressData.data.percentage * 100));
scope.progressdata = progress;
if (progress < 100 && num != 1) {
progressTimer = $timeout(getProgress, 5000);
}
scope.message = progressData.data.message;
scope.progressSeverity = progressData.data.severity;
}, function(response) {
})
};
if (clusterState == "DEPLOYING") {
$timeout(getProgress, 1000);
} else {
getProgress(1);
}
element.bind('$destroy', function() {
$timeout.cancel(progressTimer);
});
},
templateUrl: "src/common/progressbar.tpl.html"
}
})

View File

@ -0,0 +1,14 @@
<div class='progress progress-striped border-radius-4 no-margin-bottom' ng-class="{'active': progressdata!=100&&clusterstate=='DEPLOYING'}">
<div style='width: {{progressdata}}%;' class='progress-bar' ng-class="{'progress-bar-warning': progressSeverity=='WARNING', 'progress-bar-danger': progressSeverity=='ERROR', 'progress-bar-success': progressdata==100}"></div>
<div class='progressbar-number'>
<span ng-if="progressdata==0">
Waiting...
</span>
<span ng-if="progressdata!=0">
{{progressdata}}%
</span>
</div>
</div>
<div>
{{message}}
</div>