zStack 扩展开发流程-zstack-dashboard部分
本流程是基于开源项目zstack的扩展开发流程。以一个具体业务为例进行说明目标添加一个新的业务模块:公有云账户管理。实现效果为在界面的左边计算选项添加一个账户管理。在账户管理中可以添加、删除账户信息。
·
本流程是基于开源项目zstack的扩展开发流程。以一个具体业务为例进行说明
目标
添加一个新的业务模块:公有云账户管理。实现效果为在界面的左边计算选项添加一个账户管理。在账户管理中可以添加、删除账户信息。
流程
前端zstack-dashboard
在前端主要是界面UI的开发,以及相关API的设计。
一、创建html文件
编辑位置:[zStack-dashboard/zstack_dashboard/static/templates]
在此路径下添加需要添加的文件夹,命名为pubAccount。文件夹下面创建pubAccount.html文件。此文件为主界面的显示文件。createPubAccount.html,为创建账户信息的显示界面。
pubAccount.html
<div style="display: none">
<div z-create-pubaccount="winNewPubAccount" z-options="optionsCreateCluster"></div> //!!!!!这里的命名需要注意,z-create-pubaccount需要与js部分的命名一直
<div z-search="search" z-options="optionsSearch"></div>
</div>
<div>
<h3 class="z-h3">账户管理</h3>
<div class="btn-group-sm z-btn-bar">
<button type="button" class="btn btn-default btn-sm" ng-click="funcRefresh()">
<i class="fa fa-refresh"></i>
</button>
<button type="button" class="btn btn-default btn-sm" ng-click="funcSearch(search)">
<i class="fa fa-search"></i>
</button>
<div z-sort-by z-options="optionsSortBy"></div>
<button z-popover="popoverFilterBy" type="button" id="vmInstanceFilter" z-content-id="vmInstanceFilterContent" class="btn btn-default btn-sm" ng-click="filterBy.open(popoverFilterBy)">
<i class="fa fa-filter"></i><span> {{filterBy.getButtonName()}}</span>
</button>
<div class="btn-group" >
<button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown" >
添加账户信息<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href ng-click="funcCreatePubAccount(winNewPubAccount)" >添加云厂商信息</a></li> //!!!!功能函数在js部分实现,传入参数命名和上面的一样
<li class="divider"></li>
</ul>
</div>
<div id="vmInstanceFilterContent" style="display: none">
<form class="form">
<div class="form-group">
<label for="fieldList" class="z-block-label">{{"vm.vm.FILTER BY" | translate}}</label>
<select id="fieldList" kendo-drop-down-list k-options="filterBy.fieldList" ng-model="filterBy.field"></select>
</div>
<div class="form-group">
<label class="z-block-label" for="valueList">{{"vm.vm.VALUE" | translate}}</label>
<select kendo-drop-down-list id="valueList" ng-disabled="filterBy.isValueListDisabled()" k-options="filterBy.valueList" ng-model="filterBy.value"></select>
</div>
<button type="button" class="btn btn-sm btn-primary" ng-click="filterBy.confirm(popoverFilterBy)">{{"vm.vm.Confirm" | translate}}</button>
</form>
</div>
<div class="btn-group" ng-show="funcIsActionShow()">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" ng-disabled="funcIsActionDisabled()">
{{"vm.vm.Action" | translate}} <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href ng-click="action.stop()" ng-show="action.isActionShow('stop')">修改</a></li>
<li class="divider"></li>
<li><a href style="color:red" ng-click="funcDeletepubAccount(pubAccountDelete)" ng-show="action.isActionShow('delete')">{{"vm.vm.Delete" | translate}}</a></li>
</ul>
</div>
</div>
<div kendo-grid="pubAccountGrid" k-options="oPubAccountGrid.options" z-grid-double-click="funcGridDoubleClick" class="z-flat-table"></div> //!!!此处是js部分的,描述表格信息的,k-options需要和js部分一致
<p class="z-hint">{{"vm.vm.HINT1" | translate}}</p>
<p class="z-hint">{{"vm.vm.HINT2" | translate}}</p>
</div>
createPubAccount.html
<div kendo-window="winCreatePubAccount__" k-visible="false" k-options="winCreatePubAccountOptions__"> //修改window参数为js部分
<div class="row">
<div class="col-sm-15">
<div class="tab-content">
<div id="createClusterInfo" class="tab-pane active">
<div class="alert alert-warning col-sm-21" ng-show="!infoPage.hasPubCloudType()">
没有已经注册的云类型
</div>
<h4 class="z-win-h4">账户管理</h4>
<form class="form">
<div class="form-group col-sm-24">
<label for="PubCloudType">云厂商</label>
<select id="PubCloudType" kendo-drop-down-list k-options="PubCloudTypeList" class="z-win-dropdown" ng-model="infoPage.PubCloudType"></select>
</div>
<div class="form-group col-lg-18">
<p class="z-hint">请根据该类型资源所需的必填项进行填写</p>
<label for="name">用户名</label>
<input class="form-control" type="text" id="name" placeholder="(Required) max length of 255 characters" ng-model="modelCreateZone.name" required data-message="name is required">
</div>
<div class="form-group col-lg-18">
<label for="name">密码</label>
<input class="form-control" type="text" id="name" placeholder="(Required) max length of 255 characters" ng-model="modelCreateZone.name" required data-message="name is required">
</div>
<div class="form-group col-lg-18">
<label for="name">accesskeyID</label>
<input class="form-control" type="text" id="name" placeholder="(Required) max length of 255 characters" ng-model="modelCreateZone.name" required data-message="name is required">
</div>
<div class="form-group col-lg-18">
<label for="name">accesskeyKey</label>
<input class="form-control" type="text" id="name" placeholder="(Required) max length of 255 characters" ng-model="modelCreateZone.name" required data-message="name is required">
</div>
<div class="form-group col-lg-18">
<label for="name">token</label>
<input class="form-control" type="text" id="name" placeholder="(Required) max length of 255 characters" ng-model="modelCreateZone.name" required data-message="name is required">
</div>
<div class="form-group col-lg-18">
<label for="description">{{"zone.zone.DESCRIPTION" | translate}}</label>
<textarea class="form-control" rows="5" id="description" placeholder="(Optional) max length of 2048 characters" ng-model="modelCreateZone.description"></textarea>
</div>
</form>
</div>
<div class="form-group col-sm-18">
<button type="button" class="btn btn-default" ng-click="button.previousClick()" ng-disabled="!button.canPreviousProceed()">{{"cluster.createCluster.Previous" | translate}}</button>
<button type="button" class="btn btn-primary" ng-disabled="!button.canNextProceed()" ng-click="button.nextClick()">{{button.nextButtonName()}}</button>
</div>
</div>
</div>
<div class="col-sm-7 z-wizard-bar">
<ul class="nav">
<!-- 注意根据具体有几个页面进行修改-->
<li class="active"><a data-target="#createClusterInfo" ng-click="button.pageClick('createClusterInfo')">{{"cluster.createCluster.CLUSTER INFO" | translate}}</a></li>
</ul>
</div>
</div>
</div>
二、添加JS部分
编辑文件:[vStack-dashboard/zstack_dashboard/static/app/app.js]
js部分需要实现angularJS的代码.
var MPubAccount;
(function (MPubAccount) {
var PubAccount = (function (_super) {
__extends(PubAccount, _super);
function PubAccount() {
_super.apply(this, arguments);
}
PubAccount.prototype.progressOn = function () {
this.inProgress = true;
};
PubAccount.prototype.progressOff = function () {
this.inProgress = false;
};
PubAccount.prototype.isInProgress = function () {
return this.inProgress;
};
PubAccount.prototype.isEnableShow = function () {
return this.state == 'Disabled';
};
PubAccount.prototype.isDisableShow = function () {
return this.state == 'Enabled';
};
PubAccount.prototype.stateLabel = function () {
if (this.state == 'Enabled') {
return 'label label-success';
}
else if (this.state == 'Disabled') {
return 'label label-danger';
}
else {
return 'label label-default';
}
};
PubAccount.prototype.gridColumnLabel = function () {
if (this.state == 'Enabled') {
return 'z-color-box-green';
}
else if (this.state == 'Disabled') {
return 'z-color-box-red';
}
};
//描述在账户管理中有哪些数据
PubAccount.prototype.updateObservableObject = function (inv) {
// self : ObservableObject
var self = this;
self.set('name', inv.name);
self.set('description', inv.description);
self.set('hypervisorType', inv.hypervisorType);
self.set('state', inv.state);
self.set('createDate', inv.createDate);
self.set('lastOpDate', inv.lastOpDate);
self.set('accesskey', inv.accesskey);
};
return PubAccount;
}(ApiHeader.PubAccountInventory)); //!!!!PubAccountInventory需要在前面创建
MPubAccount.PubAccount = PubAccount;
var PubAccountManager = (function () {
function PubAccountManager(api, $rootScope) {
this.api = api;
this.$rootScope = $rootScope;
}
PubAccountManager.prototype.setSortBy = function (sortBy) {
this.sortBy = sortBy;
};
PubAccountManager.prototype.wrap = function (pubAccount) {
return new kendo.data.ObservableObject(pubAccount);
};
PubAccountManager.prototype.create = function (pubAccount, done) {
var _this = this;
var msg = new ApiHeader.APICreatePubAccountMsg();
msg.name = pubAccount.name;
msg.description = pubAccount.description;
msg.pubCloudTyoe = pubAccount.pubCloudTyoe;
this.api.asyncApi(msg, function (ret) {
var c = new PubAccount();
angular.extend(c, ret.inventory);
done(_this.wrap(c));
_this.$rootScope.$broadcast(MRoot.Events.NOTIFICATION, {
msg: Utils.sprintf('Created new pubAccount: {0}', c.name),
link: Utils.sprintf('/#/pubAccount', c.uuid)
});
});
};
PubAccountManager.prototype.query = function (qobj, callback) {
var _this = this;
var msg = new ApiHeader.APIQueryPubAccountMsg(); //!!!!APIQueryPubAccountMsg需要在前面创建
msg.count = qobj.count === true;
msg.start = qobj.start;
msg.limit = qobj.limit;
msg.replyWithCount = true;
msg.conditions = qobj.conditions ? qobj.conditions : [];
if (Utils.notNullnotUndefined(this.sortBy) && this.sortBy.isValid()) {
msg.sortBy = this.sortBy.field;
msg.sortDirection = this.sortBy.direction;
}
this.api.syncApi(msg, function (ret) {
var clusters = [];
ret.inventories.forEach(function (inv) {
var c = new PubAccount();
angular.extend(c, inv);
clusters.push(_this.wrap(c));
});
callback(clusters, ret.total);
});
};
PubAccountManager.prototype.delete = function (pubAccount, done) {
var _this = this;
pubAccount.progressOn();
var msg = new ApiHeader.APIDeletePubAccountMsg(); //!!!!APIDeletePubAccountMsg需要在前面创建
msg.uuid = pubAccount.uuid;
this.api.asyncApi(msg, function (ret) {
pubAccount.progressOff();
done(ret);
_this.$rootScope.$broadcast(MRoot.Events.NOTIFICATION, {
msg: Utils.sprintf('Deleted cluster: {0}', pubAccount.name)
});
});
};
PubAccountManager.$inject = ['Api', '$rootScope'];
return PubAccountManager;
}());
MPubAccount.PubAccountManager = PubAccountManager;
var PubAccountModel = (function () {
function PubAccountModel() {
this.current = new PubAccount();
}
PubAccountModel.prototype.resetCurrent = function () {
this.current = null;
};
PubAccountModel.prototype.setCurrent = function ($scope, PubAccount) {
this.current = PubAccount;
};
return PubAccountModel;
}());
MPubAccount.PubAccountModel = PubAccountModel;
var oPubAccountGrid = (function (_super) {
__extends(oPubAccountGrid, _super);
function oPubAccountGrid($scope, pubAccountMgr) {
_super.call(this);
this.pubAccountMgr = pubAccountMgr;
_super.prototype.init.call(this, $scope, $scope.pubAccountGrid);
//描述表格格式
this.options.columns = [
{
field: 'username',
title: 'username',
width: '15%',
template: '<a href="/\\#/cluster/{{dataItem.uuid}}">{{dataItem.name}}</a>'
},
{
field: 'accesskey',
title: 'accesskey',
width: '15%',
template: '<a href="/\\#/cluster/{{dataItem.uuid}}">{{dataItem.name}}</a>'
},
{
field: 'token',
title: 'token',
width: '15%'
},
{
field: 'description',
title: 'description',
width: '25%'
},
{
field: 'state',
title: 'state',
width: '15%',
template: '<span class="{{dataItem.stateLabel()}}">{{dataItem.state}}</span>'
},
{
field: 'uuid',
title: '{{"cluster.ts.UUID" | translate}}',
width: '30%'
}
];
this.options.dataSource.transport.read = function (options) {
var qobj = new ApiHeader.QueryObject();
qobj.limit = options.data.take;
qobj.start = options.data.pageSize * (options.data.page - 1);
pubAccountMgr.query(qobj, function (pubAccounts, total) {
options.success({
data: pubAccounts,
total: total
});
});
};
}
return oPubAccountGrid;
}(Utils.OGrid));
var Action = (function () {
function Action($scope, pubAccountMgr) {
this.$scope = $scope;
this.pubAccountMgr = pubAccountMgr;
}
Action.prototype.enable = function () {
this.pubAccountMgr.enable(this.$scope.model.current);
};
Action.prototype.disable = function () {
this.pubAccountMgr.disable(this.$scope.model.current);
};
return Action;
}());
var FilterBy = (function () {
function FilterBy($scope, hypervisorTypes) {
var _this = this;
this.$scope = $scope;
this.hypervisorTypes = hypervisorTypes;
this.fieldList = {
dataSource: new kendo.data.DataSource({
data: [
{
name: '{{"cluster.ts.None" | translate}}',
value: FilterBy.NONE
},
{
name: '{{"cluster.ts.State" | translate}}',
value: FilterBy.STATE
},
{
name: '{{"cluster.ts.Hypervisor" | translate}}',
value: FilterBy.HYPERVISOR
}
]
}),
dataTextField: 'name',
dataValueField: 'value'
};
this.valueList = {
dataSource: new kendo.data.DataSource({
data: []
})
};
this.field = FilterBy.NONE;
$scope.$watch(function () {
return _this.field;
}, function () {
if (_this.field == FilterBy.NONE) {
_this.valueList.dataSource.data([]);
_this.value = null;
}
else if (_this.field == FilterBy.STATE) {
_this.valueList.dataSource.data(['Enabled', 'Disabled']);
}
else if (_this.field == FilterBy.HYPERVISOR) {
_this.valueList.dataSource.data(_this.hypervisorTypes);
}
});
}
FilterBy.prototype.confirm = function (popover) {
console.log(JSON.stringify(this.toKendoFilter()));
this.$scope.oPubAccountGrid.setFilter(this.toKendoFilter());
this.name = !Utils.notNullnotUndefined(this.value) ? null : Utils.sprintf('{0}:{1}', this.field, this.value);
popover.toggle();
};
FilterBy.prototype.open = function (popover) {
popover.toggle();
};
FilterBy.prototype.isValueListDisabled = function () {
return !Utils.notNullnotUndefined(this.value);
};
FilterBy.prototype.getButtonName = function () {
return this.name;
};
FilterBy.prototype.toKendoFilter = function () {
if (!Utils.notNullnotUndefined(this.value)) {
return null;
}
return {
field: this.field,
operator: 'eq',
value: this.value
};
};
FilterBy.NONE = 'none';
FilterBy.STATE = 'state';
FilterBy.HYPERVISOR = 'hypervisorType';
return FilterBy;
}());
var Controller = (function () {
function Controller($scope, pubAccountMgr, $location) { //!!!根据具体的业务传入参数
this.$scope = $scope;
this.pubAccountMgr = pubAccountMgr;
this.$location = $location;
$scope.model = new PubAccountModel();
$scope.oPubAccountGrid = new oPubAccountGrid($scope, pubAccountMgr);
$scope.action = new Action($scope, pubAccountMgr);
$scope.optionsSortBy = {
fields: [
{
name: '{{"cluster.ts.Name" | translate}}',
value: 'name'
},
{
name: '{{"cluster.ts.Description" | translate}}',
value: 'Description'
},
{
name: '{{"cluster.ts.State" | translate}}',
value: 'state'
},
{
name: '{{"cluster.ts.Hypervisor" | translate}}',
value: 'hypervisorType'
},
{
name: '{{"cluster.ts.Created Date" | translate}}',
value: 'createDate'
},
{
name: '{{"cluster.ts.Last Updated Date" | translate}}',
value: 'lastOpDate'
}
],
done: function (ret) {
pubAccountMgr.setSortBy(ret);
$scope.oPubAccountGrid.refresh();
}
};
$scope.optionsSearch = {
fields: ApiHeader.ClusterInventoryQueryable,
name: 'CLUSTER',
schema: {
state: {
type: Directive.SearchBoxSchema.VALUE_TYPE_LIST,
list: ['Enabled', 'Disabled']
},
hypervisorType: {
type: Directive.SearchBoxSchema.VALUE_TYPE_LIST,
list: this.hypervisorTypes
},
createDate: {
type: Directive.SearchBoxSchema.VALUE_TYPE_TIMESTAMP
},
lastOpDate: {
type: Directive.SearchBoxSchema.VALUE_TYPE_TIMESTAMP
}
},
done: function (ret) {
var qobj = new ApiHeader.QueryObject();
qobj.conditions = ret;
pubAccountMgr.query(qobj, function (clusters, total) {
$scope.oPubAccountGrid.refresh(clusters);
});
}
};
$scope.filterBy = new FilterBy($scope, this.hypervisorTypes);
$scope.funcSearch = function (win) {
win.open();
};
$scope.funcCreatePubAccount = function (win) { //!!!!创建窗口函数,和html页面一致
win.open();
};
$scope.funcDeletePubAccount = function (win) {
$scope.deletePubAccount.open();
};
$scope.optionsDeletePubAccount = {
title: 'DELETE PubAccount',
description: 'Are you sure?',
confirm: function () {
pubAccountMgr.delete($scope.model.current, function (ret) {
$scope.oPubAccountGrid.deleteCurrent();
});
}
};
$scope.funcRefresh = function () {
$scope.oPubAccountGrid.refresh();
};
$scope.funcIsActionShow = function () {
return !Utils.isEmptyObject($scope.model.current);
};
$scope.funcIsActionDisabled = function () {
return Utils.notNullnotUndefined($scope.model.current) && $scope.model.current.isInProgress();
};
$scope.optionsCreatePubAccount= {
done: function (cluster) {
$scope.oPubAccountGrid.add(cluster);
}
};
}
Controller.$inject = ['$scope', 'PubAccountManager', '$location'];
return Controller;
}());
MPubAccount.Controller = Controller;
var CreatePubAccountOptions = (function () {
function CreatePubAccountOptions() {
}
return CreatePubAccountOptions;
}());
MPubAccount.CreatePubAccountOptions = CreatePubAccountOptions;
var CreatePubAccount = (function () {
function CreatePubAccount(api, pubAccountMgr) {
var _this = this;
this.api = api;
this.pubAccountMgr = pubAccountMgr;
this.scope = true;
this.link = function ($scope, $element, $attrs, $ctrl, $transclude) {
var instanceName = $attrs.zCreatePubaccount; //!!!!!与html部分一致
var parentScope = $scope.$parent;
parentScope[instanceName] = _this;
_this.options = new CreatePubAccountOptions(); //注意修改
var optionName = $attrs.zOptions;
if (angular.isDefined(optionName)) {
_this.options = parentScope[optionName];
$scope.$watch(function () {
return parentScope[optionName];
}, function () {
_this.options = parentScope[optionName];
});
}
var infoPage = $scope.infoPage = {
activeState: true,
name: null,
description: null,
accesskey: null,
canMoveToPrevious: function () {
return false;
},
hasPubCloudType: function () { //注意修改
return $scope.PubCloudTypeList.dataSource.data().length > 0;
},
canMoveToNext: function () {
return true;
},
show: function () {
this.getAnchorElement().tab('show');
},
getAnchorElement: function () {
return $('.nav a[data-target="#createPubAccountInfo"]');
},
active: function () {
this.activeState = true;
},
isActive: function () {
return this.activeState;
},
getPageName: function () {
return 'createPubAccountInfo';
},
reset: function () {
this.name = Utils.shortHashName("PubAccount");
this.PubCloudType = null;
this.description = null;
this.username = null;
this.password = null;
this.accesskey = null;
this.accessID = null;
this.token = null;
this.activeState = false;
}
};
var mediator = $scope.mediator = {
currentPage: infoPage,
movedToPage: function (page) {
$scope.mediator.currentPage = page;
},
finishButtonName: function () {
return "Create";
},
finish: function () {
var resultCluster;
var chain = new Utils.Chain();
chain.then(function () {
pubAccountMgr.create(infoPage, function (ret) {
resultCluster = ret;
chain.next();
});
}).done(function () {
if (Utils.notNullnotUndefined(_this.options.done)) {
_this.options.done(resultCluster);
}
$scope.winCreatePubAccount__.close(); //注意修改
}).start();
}
};
$scope.button = new Utils.WizardButton([
infoPage //注意修改
], mediator);
$scope.PubCloudTypeList = { //注意修改
dataSource: new kendo.data.DataSource({ data: [] }),
dataTextField: "type",
dataValueField: "type"
};
$scope.winCreatePubAccountOptions__ = { //注意修改
width: "700px",
//height: "518px",
animation: false,
modal: true,
draggable: false,
resizable: false
};
_this.$scope = $scope;
};
this.restrict = 'EA';
this.replace = true;
this.templateUrl = '/static/templates/account/createPubAccount.html'; //注意修改
}
CreatePubAccount.prototype.open = function () {
var _this = this;
var win = this.$scope.winCreatePubAccount__; //注意修改
this.$scope.button.reset();
var chain = new Utils.Chain();
chain.then(function () {
chain.next();
// _this.api.getPubCloudType(function (pubcloudTypes) {
// var types = [];
// angular.forEach(pubcloudTypes, function (item) {
// types.push({ type: item });
// });
// _this.$scope.PubCloudTypeList.dataSource.data(new kendo.data.ObservableArray(types));
// _this.$scope.model.pubCloudTypes = pubcloudTypes[0];
// chain.next();
// });
}).done(function () {
win.center();
win.open();
}).start();
};
return CreatePubAccount;
}());
MPubAccount.CreatePubAccount = CreatePubAccount;
})(MPubAccount || (MPubAccount = {}));
//注意修改
angular.module('root').factory('PubAccountManager', ['Api', '$rootScope', function (api, $rootScope) {
return new MPubAccount.PubAccountManager(api, $rootScope);
}]).directive('zCreatePubaccount', ['Api', 'PubAccountManager',
function (api, PubAccountManager) {
return new MPubAccount.CreatePubAccount(api, PubAccountManager);
}]).config(['$routeProvider', function (route) {
route.when('/pubAccount', {
templateUrl: '/static/templates/account/account.html',
controller: 'MPubAccount.Controller',
});
}]);
三、添加API
编辑文件:【zStack-dashboard/zstack_dashboard/api_messages.py】
在这个文件中将每个在js中所新添加的api放到这里
关于java部分请查看zStack 扩展开发流程-zstack-java部分
更多推荐
已为社区贡献2条内容
所有评论(0)