zStack 扩展开发流程-zstack-dashboard部分

本流程是基于开源项目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>&nbsp; {{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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值