提交 e8150525 编写于 作者: L lepdou

sync items

上级 8e6ac7c1
package com.ctrip.apollo.portal.controller;
import com.ctrip.apollo.core.dto.ItemDTO;
import com.ctrip.apollo.core.enums.Env;
import com.ctrip.apollo.core.dto.ReleaseDTO;
import com.ctrip.apollo.core.exception.BadRequestException;
import com.ctrip.apollo.core.utils.StringUtils;
import com.ctrip.apollo.portal.entity.ItemDiffs;
import com.ctrip.apollo.portal.entity.form.NamespaceSyncModel;
import com.ctrip.apollo.portal.entity.form.NamespaceTextModel;
import com.ctrip.apollo.portal.entity.NamespaceVO;
import com.ctrip.apollo.portal.entity.form.NamespaceReleaseModel;
......@@ -78,4 +81,28 @@ public class ConfigController {
}
@RequestMapping(value = "/apps/{appId}/env/{env}/clusters/{clusterName}/namespaces/{namespaceName}/items")
public List<ItemDTO> findItems(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName){
if (StringUtils.isContainEmpty(appId, env, clusterName, namespaceName)){
throw new BadRequestException("appid,env,cluster name,namespace name can not be null");
}
return configService.findItems(appId, Env.valueOf(env), clusterName, namespaceName);
}
@RequestMapping(value = "/namespaces/{namespaceName}/diff", method = RequestMethod.POST, consumes = {
"application/json"})
public List<ItemDiffs> diff(@RequestBody NamespaceSyncModel model){
if (model == null){
throw new BadRequestException("request payload shoud not be null");
}
if (model.isInvalid()) {
throw new BadRequestException("request model is invalid");
}
return configService.compare(model.getSyncItems(), model.getSyncToNamespaces());
}
}
package com.ctrip.apollo.portal.entity;
import com.ctrip.apollo.core.dto.ItemChangeSets;
public class ItemDiffs {
private NamespaceIdentifer namespace;
private ItemChangeSets diffs;
public ItemDiffs(NamespaceIdentifer namespace){
this.namespace = namespace;
}
public NamespaceIdentifer getNamespace() {
return namespace;
}
public void setNamespace(NamespaceIdentifer namespace) {
this.namespace = namespace;
}
public ItemChangeSets getDiffs() {
return diffs;
}
public void setDiffs(ItemChangeSets diffs) {
this.diffs = diffs;
}
}
package com.ctrip.apollo.portal.entity;
import com.ctrip.apollo.core.enums.Env;
import com.ctrip.apollo.core.utils.StringUtils;
import com.ctrip.apollo.portal.entity.form.Verifiable;
public class NamespaceIdentifer implements Verifiable{
private String appId;
private String env;
private String clusterName;
private String namespaceName;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public Env getEnv() {
return Env.valueOf(env);
}
public void setEnv(String env) {
this.env = env;
}
public String getClusterName() {
return clusterName;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public String getNamespaceName() {
return namespaceName;
}
public void setNamespaceName(String namespaceName) {
this.namespaceName = namespaceName;
}
@Override
public boolean isInvalid() {
return StringUtils.isContainEmpty(env, clusterName, namespaceName);
}
}
package com.ctrip.apollo.portal.entity;
public class SimpleMsg {
private String msg;
public SimpleMsg(String msg){
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
......@@ -4,7 +4,7 @@ package com.ctrip.apollo.portal.entity.form;
import com.ctrip.apollo.core.enums.Env;
import com.ctrip.apollo.core.utils.StringUtils;
public class NamespaceReleaseModel implements FormModel{
public class NamespaceReleaseModel implements Verifiable {
private String appId;
private String env;
......
package com.ctrip.apollo.portal.entity.form;
import com.ctrip.apollo.core.dto.ItemDTO;
import com.ctrip.apollo.portal.entity.NamespaceIdentifer;
import org.springframework.util.CollectionUtils;
import java.util.List;
public class NamespaceSyncModel implements Verifiable {
private List<NamespaceIdentifer> syncToNamespaces;
private List<ItemDTO> syncItems;
@Override
public boolean isInvalid() {
if (CollectionUtils.isEmpty(syncToNamespaces) || CollectionUtils.isEmpty(syncItems)){
return true;
}
for (NamespaceIdentifer namespaceIdentifer: syncToNamespaces){
if (namespaceIdentifer.isInvalid()){
return true;
}
}
return false;
}
public List<NamespaceIdentifer> getSyncToNamespaces() {
return syncToNamespaces;
}
public void setSyncToNamespaces(List<NamespaceIdentifer> syncToNamespaces) {
this.syncToNamespaces = syncToNamespaces;
}
public List<ItemDTO> getSyncItems() {
return syncItems;
}
public void setSyncItems(List<ItemDTO> syncItems) {
this.syncItems = syncItems;
}
}
......@@ -4,7 +4,7 @@ package com.ctrip.apollo.portal.entity.form;
import com.ctrip.apollo.core.enums.Env;
import com.ctrip.apollo.core.utils.StringUtils;
public class NamespaceTextModel implements FormModel{
public class NamespaceTextModel implements Verifiable {
private String appId;
private String env;
......
package com.ctrip.apollo.portal.entity.form;
public interface FormModel {
public interface Verifiable {
boolean isInvalid();
......
......@@ -7,17 +7,23 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.HttpClientErrorException;
import com.ctrip.apollo.common.utils.BeanUtils;
import com.ctrip.apollo.common.utils.ExceptionUtils;
import com.ctrip.apollo.core.enums.Env;
import com.ctrip.apollo.core.dto.ItemChangeSets;
import com.ctrip.apollo.core.dto.ItemDTO;
import com.ctrip.apollo.core.dto.NamespaceDTO;
import com.ctrip.apollo.core.dto.ReleaseDTO;
import com.ctrip.apollo.core.exception.BadRequestException;
import com.ctrip.apollo.core.exception.NotFoundException;
import com.ctrip.apollo.core.exception.ServiceException;
import com.ctrip.apollo.core.utils.StringUtils;
import com.ctrip.apollo.portal.api.AdminServiceAPI;
import com.ctrip.apollo.portal.entity.ItemDiffs;
import com.ctrip.apollo.portal.entity.NamespaceIdentifer;
import com.ctrip.apollo.portal.entity.form.NamespaceTextModel;
import com.ctrip.apollo.portal.entity.NamespaceVO;
import com.ctrip.apollo.portal.entity.form.NamespaceReleaseModel;
......@@ -172,4 +178,82 @@ public class ConfigService {
return releaseAPI.release(model.getAppId(), model.getEnv(), model.getClusterName(),
model.getNamespaceName(), model.getReleaseBy(), model.getReleaseComment());
}
public List<ItemDTO> findItems(String appId, Env env, String clusterName, String namespaceName){
return itemAPI.findItems(appId, env, clusterName, namespaceName);
}
public List<ItemDiffs> compare(List<ItemDTO> sourceItems, List<NamespaceIdentifer> comparedNamespaces){
List<ItemDiffs> result = new LinkedList<>();
String appId, clusterName, namespaceName;
Env env;
for (NamespaceIdentifer namespace: comparedNamespaces){
appId = namespace.getAppId();
clusterName = namespace.getClusterName();
namespaceName = namespace.getNamespaceName();
env = namespace.getEnv();
NamespaceDTO namespaceDTO = null;
try {
namespaceDTO = namespaceAPI.loadNamespace(appId, env, clusterName, namespaceName);
} catch (NotFoundException e){
logger.warn("namespace not exist. appId:{}, env:{}, clusterName:{}, namespaceName:{}", appId, env, clusterName,
namespaceName);
throw new BadRequestException(String.format(
"namespace not exist. appId:%s, env:%s, clusterName:%s, namespaceName:%s", appId, env, clusterName,
namespaceName));
}
ItemDiffs itemDiffs = new ItemDiffs(namespace);
ItemChangeSets changeSets = new ItemChangeSets();
itemDiffs.setDiffs(changeSets);
List<ItemDTO>
targetItems =
itemAPI.findItems(namespace.getAppId(), namespace.getEnv(),
namespace.getClusterName(), namespace.getNamespaceName());
long namespaceId = namespaceDTO.getId();
if (CollectionUtils.isEmpty(targetItems)){//all source items is added
int lineNum = 1;
for (ItemDTO sourceItem: sourceItems){
changeSets.addCreateItem(buildItem(namespaceId, lineNum++, sourceItem));
}
}else {
Map<String, ItemDTO> keyMapItem = BeanUtils.mapByKey("key", targetItems);
String key,sourceValue,sourceComment;
ItemDTO targetItem = null;
int maxLineNum = targetItems.size();//append to last
for (ItemDTO sourceItem: sourceItems){
key = sourceItem.getKey();
sourceValue = sourceItem.getValue();
sourceComment = sourceItem.getComment();
targetItem = keyMapItem.get(key);
if (targetItem == null) {//added items
changeSets.addCreateItem(buildItem(namespaceId, ++maxLineNum, sourceItem));
}else if (!sourceValue.equals(targetItem.getValue()) || !sourceComment.equals(targetItem.getComment())){//modified items
targetItem.setValue(sourceValue);
targetItem.setComment(sourceComment);
changeSets.addUpdateItem(targetItem);
}
}
}
result.add(itemDiffs);
}
return result;
}
private ItemDTO buildItem(long namespaceId, int lineNum, ItemDTO sourceItem){
ItemDTO createdItem = new ItemDTO();
BeanUtils.copyEntityProperties(sourceItem, createdItem);
createdItem.setLineNum(lineNum++);
createdItem.setNamespaceId(namespaceId);
return createdItem;
}
}
......@@ -17,4 +17,4 @@ ctrip:
apollo:
portal:
env: local,dev
env: local
......@@ -76,10 +76,14 @@
<!-- bootstrap.js -->
<script src="vendor/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
<!--nicescroll-->
<script src="vendor/jquery.nicescroll.min.js"></script>
<script type="application/javascript" src="scripts/app.js"></script>
<script type="application/javascript" src="scripts/services/AppService.js"></script>
<script type="application/javascript" src="scripts/services/EnvService.js"></script>
<script type="application/javascript" src="scripts/AppUtils.js"></script>
<script type="application/javascript" src="scripts/controller/IndexController.js"></script>
</body>
</html>
appUtil.service('AppUtil', [function () {
return {
errorMsg: function (response) {
var msg = "Code:" + response.status;
if (response.data.message != null){
var msg = "Code:" + response.status;
if (response.data.message != null) {
msg += " Msg:" + response.data.message;
}
return msg;
},
parseParams: function (path) {
if (!path) {
return {};
}
if (path.startsWith("/")) {
path = path.substring(1, path.length);
}
var params = path.split("&");
var result = {};
params.forEach(function (param) {
var kv = param.split("=");
result[kv[0]] = kv[1];
});
return result;
},
cutOffString: function (str, maxLength) {
if (!str || maxLength <= 0) {
return '';
}
return str.length > maxLength ? str.substr(0, maxLength) : str;
}
}
}]);
$(document).ready(function () {
$("html").niceScroll({
styler: "fb",
cursorcolor: "#e8403f",
cursorwidth: '6',
cursorborderradius: '10px',
background: '#404040',
spacebarenabled: false,
cursorborder: '',
zindex: '1000'
});
});
......@@ -11,6 +11,8 @@ var index_module = angular.module('index', ['toastr', 'app.service', 'app.util',
var application_module = angular.module('application', ['app.service', 'app.util', 'toastr', 'angular-loading-bar']);
//创建项目页面
var create_app_module = angular.module('create_app', ['ngResource', 'toastr', 'app.service', 'app.util', 'angular-loading-bar']);
//配置同步页面
var sync_item_module = angular.module('sync_item', ['app.service', 'app.util', 'toastr', 'angular-loading-bar']);
......
......@@ -2,7 +2,8 @@ application_module.controller("AppConfigController",
['$scope', '$location', 'toastr', 'AppService', 'AppUtil', 'ConfigService',
function ($scope, $location, toastr, AppService, AppUtil, ConfigService) {
var appId = $location.$$url.split("=")[1];
var appId = AppUtil.parseParams($location.$$url).appid;
var currentUser = 'test_user';
var pageContext = {
appId: appId,
......@@ -92,16 +93,17 @@ application_module.controller("AppConfigController",
//初始化视图
if ($scope.namespaces) {
$scope.namespaces.forEach(function (item) {
item.isModify = false;
if (!viewType){//default text view
if (!viewType) {//default text view
$scope.switchView(item, namespace_view_type.TEXT);
}else if (viewType == namespace_view_type.TABLE){
} else if (viewType == namespace_view_type.TABLE) {
item.viewType = namespace_view_type.TABLE;
}
item.isTextEditing = false;
})
});
}
}, function (result) {
......@@ -122,10 +124,12 @@ application_module.controller("AppConfigController",
//把表格内容解析成文本
function parseModel2Text(namespace) {
if (!namespace.items) {
return "无配置信息";
}
var result = "";
var itemCnt = 0;
namespace.items.forEach(function (item) {
if (item.item.key) {
result +=
......@@ -133,9 +137,12 @@ application_module.controller("AppConfigController",
} else {
result += item.item.comment + "\n";
}
itemCnt ++;
});
itemCnt > 30 ? 30 : itemCnt;
itemCnt < 9 ? 8 : itemCnt;
namespace.itemCnt = itemCnt + 3;
return result;
}
......@@ -183,14 +190,12 @@ application_module.controller("AppConfigController",
////// table view oper //////
//查看旧值
$scope.queryOldValue = function (key, oldValue) {
$scope.queryKey = key;
if (oldValue == '') {
$scope.OldValue = key + "是新添加的key";
} else {
$scope.OldValue = oldValue;
}
$scope.watch = {};
//查看配置
$scope.watchItem = function (key, value, oldValue) {
$scope.watch.key = key;
$scope.watch.value = value;
$scope.watch.oldValue = oldValue;
};
/////// release ///////
......@@ -251,7 +256,6 @@ application_module.controller("AppConfigController",
});
});
};
}]);
sync_item_module.controller("SyncItemController",
['$scope', '$location', 'toastr', 'AppService', 'AppUtil', 'ConfigService',
function ($scope, $location, toastr, AppService, AppUtil, ConfigService) {
var params = AppUtil.parseParams($location.$$url);
var currentUser = 'test_user';
$scope.pageContext = {
appId: params.appid,
env: params.env,
clusterName: params.clusterName,
namespaceName: params.namespaceName
};
////// load env //////
AppService.load_nav_tree($scope.pageContext.appId).then(function (result) {
$scope.clusters = result.nodes;
$scope.clusters = [];
result.nodes.forEach(function (node) {
var env = node.env;
node.clusters.forEach(function (cluster) {
cluster.env = env;
cluster.checked = false;
$scope.clusters.push(cluster);
})
});
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "加载环境出错");
});
var envAllSelected = false;
$scope.toggleEnvsCheckedStatus = function () {
envAllSelected = !envAllSelected;
$scope.clusters.forEach(function (cluster) {
cluster.checked = envAllSelected;
})
};
////// load items //////
ConfigService.find_items($scope.pageContext.appId, $scope.pageContext.env,
$scope.pageContext.clusterName, $scope.pageContext.namespaceName).then(function (result) {
$scope.sourceItems = result;
$scope.sourceItems.forEach(function (item) {
item.checked = false;
})
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "加载配置出错");
});
var itemAllSelected = false;
$scope.toggleItemsCheckedStatus = function () {
itemAllSelected = !itemAllSelected;
$scope.sourceItems.forEach(function (item) {
item.checked = itemAllSelected;
})
};
////// flow control ///////
$scope.syncItemStep = 1;
$scope.syncItemNextStep = function (offset) {
$scope.syncItemStep += offset;
};
$scope.syncItems = function () {
$scope.syncItemStep += 1;
};
$scope.destorySync = function () {
$scope.syncItemStep = 1;
}
}]);
appService.service("ConfigService", ['$resource', '$q', function ($resource, $q) {
var config_source = $resource("", {}, {
load_all_groups: {
load_all_namespaces: {
method: 'GET',
isArray: true,
url: '/apps/:appId/env/:env/clusters/:clusterName/namespaces'
},
find_items:{
method:'GET',
isArray: true,
url:'/apps/:appId/env/:env/clusters/:clusterName/namespaces/:namespaceName/items'
},
modify_items: {
method: 'PUT',
url: '/apps/:appId/env/:env/clusters/:clusterName/namespaces/:namespaceName/items'
......@@ -18,7 +23,7 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
return {
load_all_namespaces: function (appId, env, clusterName) {
var d = $q.defer();
config_source.load_all_groups({
config_source.load_all_namespaces({
appId: appId,
env: env,
clusterName: clusterName
......@@ -29,6 +34,20 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
});
return d.promise;
},
find_items: function (appId, env, clusterName, namespaceName) {
var d = $q.defer();
config_source.find_items({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName
}, function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
},
modify_items: function (appId, env, clusterName, namespaceName, configText, namespaceId, comment, modifyBy) {
var d = $q.defer();
......
......@@ -197,6 +197,10 @@ table th {
table-layout: fixed;
}
.namespace-view-table tr{
cursor: pointer;
}
.namespace-view-table td {
word-wrap: break-word;
}
......
......@@ -65,7 +65,16 @@
</div>
</section>
<a class="list-group-item" data-toggle="modal" data-target="#createEnvModal" ng-show="missEnvs.length > 0">
<a class="list-group-item" data-toggle="modal" data-target="#syncItems">
<div class="row">
<div class="col-md-2"><img src="../img/sync.png" class="i-20"></div>
<div class="col-md-7 hidden-xs">
<p class="apps-description">配置同步</p>
</div>
</div>
</a>
<a class="list-group-item" data-toggle="modal" data-target="#createEnvModal"
ng-show="missEnvs.length > 0">
<div class="row">
<div class="col-md-2"><img src="../img/plus.png" class="i-20"></div>
<div class="col-md-7 hidden-xs">
......@@ -126,9 +135,10 @@
<button type="button"
class="btn btn-default btn-sm J_tableview_btn">授权
</button>
<button type="button"
class="btn btn-default btn-sm J_tableview_btn">克隆
</button>
<a type="button" target="_blank"
href="sync.html?#/appid={{pageContext.appId}}&env={{pageContext.env}}&clusterName={{pageContext.clusterName}}&namespaceName={{namespace.namespace.namespaceName}}"
class="btn btn-default btn-sm J_tableview_btn">同步
</a>
</div>
</div>
<div class="btn-group" role="group" aria-label="...">
......@@ -169,7 +179,7 @@
</header>
<!--text view-->
<textarea class="form-control" rows="30"
<textarea class="form-control" rows="{{namespace.itemCnt}}"
ng-show="namespace.viewType == 'text'"
ng-disabled="!namespace.isTextEditing" ng-model="namespace.text">
{{namespace.text}}
......@@ -177,7 +187,7 @@
<!--table view-->
<div class="namespace-view-table">
<table class="table table-bordered text-center table-hover"
<table class="table table-bordered table-striped text-center table-hover"
ng-show="namespace.viewType == 'table'">
<thead>
<tr>
......@@ -200,21 +210,16 @@
</thead>
<tbody>
<tr ng-repeat="config in namespace.items" ng-class="{warning:config.modified}"
ng-if="config.item.key">
<tr title="点击查看" data-toggle="modal" data-target="#oldValueModal" ng-repeat="config in namespace.items" ng-class="{warning:config.modified}"
ng-if="config.item.key" ng-click="watchItem(config.item.key, config.item.value, config.oldValue)">
<td width="25%">
{{config.item.key}}
{{config.item.key | limitTo: 20}} {{config.item.key.length > 20 ? '...' : ''}}
</td>
<td width="30%">
<button data-placement="top" title="查看旧值"
class="glyphicon glyphicon-eye-open"
aria-hidden="true" data-toggle="modal" data-target="#oldValueModal"
ng-show="config.modified"
ng-click="queryOldValue(config.item.key, config.oldValue)"></button>
{{config.item.value}}
{{config.item.value | limitTo: 20}} {{config.item.value.length > 20 ? '...' : ''}}
</td>
<td width="20%">
{{config.item.comment}}
{{config.item.comment | limitTo: 20}} {{config.item.comment.length > 20 ? '...' : ''}}
</td>
<td width="10%">
{{config.item.lastModifiedBy}}
......@@ -282,19 +287,22 @@
</div>
<!-- view old value Modal -->
<div class="modal fade " id="oldValueModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-sm" role="document">
<div class="modal fade" id="oldValueModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title">旧值</h4>
<h4 class="modal-title">{{watch.key}}</h4>
</div>
<div class="modal-body">
{{OldValue}}
<div class="modal-body" style="word-wrap: break-word;">
{{watch.value}}
<div ng-show="watch.oldValue">
<hr>
老的值:{{watch.oldValue}}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
......@@ -375,6 +383,8 @@
</div>
</div>
</div>
</div>
</div>
</div>
......@@ -414,24 +424,7 @@
<!--controller-->
<script type="application/javascript" src="../scripts/controller/app/AppConfigController.js"></script>
<script type="application/javascript">
$(document).ready(function () {
$("html").niceScroll({
styler: "fb",
cursorcolor: "#e8403f",
cursorwidth: '6',
cursorborderradius: '10px',
background: '#404040',
spacebarenabled: false,
cursorborder: '',
zindex: '1000'
});
});
$(function () {
$('[data-toggle="tooltip"]').tooltip()
});
</script>
<script type="application/javascript" src="../scripts/PageCommon.js"></script>
</body>
</html>
<!doctype html>
<html ng-app="sync_item">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<!-- styles -->
<link rel="stylesheet" type="text/css" href="../vendor/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="../vendor/angular/angular-toastr-1.4.1.min.css">
<link rel="stylesheet" type="text/css" media='all' href="../vendor/angular/loading-bar.min.css">
<link rel="stylesheet" type="text/css" href="../styles/common-style.css">
<title>同步配置</title>
</head>
<body ng-controller="SyncItemController">
<div ng-include="'common/nav.html'"></div>
<div class="container-fluid apollo-container">
<section class="panel col-md-offset-1 col-md-10">
<header class="panel-heading">
<div class="row">
<div class="col-md-3">
<h4 class="modal-title">同步配置
<small ng-show="syncItemStep == 1">(第一步:选择同步信息)</small>
<small ng-show="syncItemStep == 2">(第二步:检查Diff)</small>
</h4>
</div>
<div class="col-md-9 text-right">
<button type="button" class="btn btn-primary" ng-show="syncItemStep > 1 && syncItemStep < 3"
ng-click="syncItemNextStep(-1)">上一步
</button>
<button type="button" class="btn btn-primary" ng-show="syncItemStep < 2"
ng-click="syncItemNextStep(1)">下一步
</button>
<button type="button" class="btn btn-success" ng-show="syncItemStep == 2" ng-click="syncItems()">同步
</button>
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-show="syncItemStep == 3"
ng-click="destorySync()">返回
</button>
</div>
</div>
</header>
<div class="panel-body">
<div class="row" ng-show="syncItemStep == 1">
<div class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">同步到那个集群</label>
<div class="col-sm-6">
<table class="table table-hover">
<thead>
<tr>
<td><input type="checkbox" ng-click="toggleEnvsCheckedStatus()"></td>
</td>
<td>环境</td>
<td>集群</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="cluster in clusters">
<td width="10%"><input type="checkbox" ng-checked="cluster.checked"></td>
<td width="30%">{{cluster.env}}</td>
<td width="60%">{{cluster.name}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<hr>
</div>
<div class="row" ng-show="syncItemStep == 1" style="margin-top: 10px;">
<div class="form-horizontal">
<div class="col-sm-2 text-right">
<label class="control-label">需要同步的配置</label>
</div>
<div class="col-sm-10">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<td><input type="checkbox" ng-click="toggleItemsCheckedStatus()"></td>
<td>key</td>
<td>value</td>
<td>comment</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in sourceItems">
<td width="10%"><input type="checkbox" ng-checked="item.checked"></td>
<td width="20%">{{item.key}}</td>
<td width="50%">{{item.value | limitTo: 36}} {{item.value.length > 36 ? '...' : ''}}</td>
<td width="20%">{{item.comment | limitTo: 15}}{{item.comment.length > 15 ? '...' : ''}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!--step 2-->
<div class="row" ng-show="syncItemStep == 2">
<h4 class="text-center">环境:fat 集群:default</h4>
<hr>
<div class="row" style="margin-top: 10px;">
<div class="form-horizontal">
<label class="col-sm-2 control-label">新增的配置</label>
<div class="col-sm-9">
<table class="table table-bordered table-hover">
<thead>
<tr>
<td>key</td>
<td>value</td>
</tr>
</thead>
<tbody>
<tr>
<td width="30%">k1</td>
<td width="60%">v1</td>
</tr>
<tr>
<td width="30%">k1</td>
<td width="60%">v1</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="row" ng-show="syncItemStep == 2">
<div class="row" style="margin-top: 10px;">
<div class="form-horizontal">
<label class="col-sm-2 control-label">更新的配置</label>
<div class="col-sm-9">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<td>key</td>
<td>value</td>
</tr>
</thead>
<tbody>
<tr>
<td width="30%">k1</td>
<td width="60%">v1</td>
</tr>
<tr>
<td width="30%">k1</td>
<td width="60%">v1</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!--step 3-->
<div class="row text-center" ng-show="syncItemStep == 3">
<img src="../img/sync-succ.png" style="height: 100px; width: 100px">
<h3>同步成功!</h3>
</div>
</div>
</section>
</div>
<div ng-include="'common/footer.html'"></div>
<!--angular-->
<script src="../vendor/angular/angular.min.js"></script>
<script src="../vendor/angular/angular-route.min.js"></script>
<script src="../vendor/angular/angular-resource.min.js"></script>
<script src="../vendor/angular/angular-toastr-1.4.1.tpls.min.js"></script>
<script src="../vendor/angular/loading-bar.min.js"></script>
<!-- jquery.js -->
<script src="../vendor/jquery.js" type="text/javascript"></script>
<!-- bootstrap.js -->
<script src="../vendor/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
<!--nicescroll-->
<script src="../vendor/jquery.nicescroll.min.js"></script>
<!--biz-->
<script type="application/javascript" src="../scripts/app.js"></script>
<script type="application/javascript" src="../scripts/services/AppService.js"></script>
<script type="application/javascript" src="../scripts/services/ConfigService.js"></script>
<script type="application/javascript" src="../scripts/AppUtils.js"></script>
<script type="application/javascript" src="../scripts/controller/app/SyncConfigController.js"></script>
<script type="application/javascript" src="../scripts/PageCommon.js"></script>
</body>
</html>
package com.ctrip.apollo.portal;
import com.ctrip.apollo.core.ConfigConsts;
import com.ctrip.apollo.core.dto.ItemChangeSets;
import com.ctrip.apollo.core.dto.ItemDTO;
import com.ctrip.apollo.core.dto.NamespaceDTO;
import com.ctrip.apollo.core.dto.ReleaseDTO;
import com.ctrip.apollo.core.enums.Env;
import com.ctrip.apollo.portal.api.AdminServiceAPI;
import com.ctrip.apollo.portal.entity.ItemDiffs;
import com.ctrip.apollo.portal.entity.NamespaceIdentifer;
import com.ctrip.apollo.portal.entity.NamespaceVO;
import com.ctrip.apollo.portal.entity.form.NamespaceTextModel;
import com.ctrip.apollo.portal.service.ConfigService;
......@@ -127,4 +130,110 @@ public class ConfigServiceTest {
return Arrays.asList(item1, item2, item3);
}
@Test
public void testCompareTargetNamespaceHasNoItems(){
ItemDTO sourceItem1 = new ItemDTO("a","b","comment",1);
List<ItemDTO> sourceItems = Arrays.asList(sourceItem1);
String appId = "6666", env = "LOCAL", clusterName = ConfigConsts.CLUSTER_NAME_DEFAULT,
namespaceName = ConfigConsts.NAMESPACE_DEFAULT;
List<NamespaceIdentifer> namespaceIdentifers = generateNamespaceIdentifer(appId, env, clusterName, namespaceName);
NamespaceDTO namespaceDTO = generateNamespaceDTO(appId, clusterName, namespaceName);
when(namespaceAPI.loadNamespace(appId, Env.valueOf(env), clusterName, namespaceName)).thenReturn(namespaceDTO);
when(itemAPI.findItems(appId, Env.valueOf(env), clusterName, namespaceName)).thenReturn(null);
List<ItemDiffs> itemDiffses = configService.compare(sourceItems, namespaceIdentifers);
assertEquals(1,itemDiffses.size());
ItemDiffs itemDiffs = itemDiffses.get(0);
ItemChangeSets changeSets = itemDiffs.getDiffs();
assertEquals(0, changeSets.getUpdateItems().size());
assertEquals(0, changeSets.getDeleteItems().size());
List<ItemDTO> createItems = changeSets.getCreateItems();
ItemDTO createItem = createItems.get(0);
assertEquals(1, createItem.getLineNum());
assertEquals("a", createItem.getKey());
assertEquals("b", createItem.getValue());
assertEquals("comment", createItem.getComment());
}
@Test
public void testCompare(){
ItemDTO sourceItem1 = new ItemDTO("a","b","comment",1);//not modified
ItemDTO sourceItem2 = new ItemDTO("newKey","c","comment",2);//new item
ItemDTO sourceItem3 = new ItemDTO("c","newValue","comment",3);// update value
ItemDTO sourceItem4 = new ItemDTO("d","b","newComment",4);// update comment
List<ItemDTO> sourceItems = Arrays.asList(sourceItem1, sourceItem2, sourceItem3, sourceItem4);
ItemDTO targetItem1 = new ItemDTO("a","b","comment",1);
ItemDTO targetItem2 = new ItemDTO("c","oldValue","comment",2);
ItemDTO targetItem3 = new ItemDTO("d","b","oldComment",3);
List<ItemDTO> targetItems = Arrays.asList(targetItem1, targetItem2, targetItem3);
String appId = "6666", env = "LOCAL", clusterName = ConfigConsts.CLUSTER_NAME_DEFAULT,
namespaceName = ConfigConsts.NAMESPACE_DEFAULT;
List<NamespaceIdentifer> namespaceIdentifers = generateNamespaceIdentifer(appId, env, clusterName, namespaceName);
NamespaceDTO namespaceDTO = generateNamespaceDTO(appId, clusterName, namespaceName);
when(namespaceAPI.loadNamespace(appId, Env.valueOf(env), clusterName, namespaceName)).thenReturn(namespaceDTO);
when(itemAPI.findItems(appId, Env.valueOf(env), clusterName, namespaceName)).thenReturn(targetItems);
List<ItemDiffs> itemDiffses = configService.compare(sourceItems, namespaceIdentifers);
assertEquals(1, itemDiffses.size());
ItemDiffs itemDiffs = itemDiffses.get(0);
ItemChangeSets changeSets = itemDiffs.getDiffs();
assertEquals(0, changeSets.getDeleteItems().size());
assertEquals(2, changeSets.getUpdateItems().size());
assertEquals(1, changeSets.getCreateItems().size());
NamespaceIdentifer namespaceIdentifer = itemDiffs.getNamespace();
assertEquals(appId, namespaceIdentifer.getAppId());
assertEquals(Env.valueOf("LOCAL"), namespaceIdentifer.getEnv());
assertEquals(clusterName, namespaceIdentifer.getClusterName());
assertEquals(namespaceName, namespaceIdentifer.getNamespaceName());
ItemDTO createdItem = changeSets.getCreateItems().get(0);
assertEquals("newKey", createdItem.getKey());
assertEquals("c", createdItem.getValue());
assertEquals("comment", createdItem.getComment());
assertEquals(4, createdItem.getLineNum());
List<ItemDTO> updateItems = changeSets.getUpdateItems();
ItemDTO updateItem1 = updateItems.get(0);
ItemDTO updateItem2 = updateItems.get(1);
assertEquals("c", updateItem1.getKey());
assertEquals("newValue", updateItem1.getValue());
assertEquals("comment", updateItem1.getComment());
assertEquals(2, updateItem1.getLineNum());
assertEquals("d", updateItem2.getKey());
assertEquals("b", updateItem2.getValue());
assertEquals("newComment", updateItem2.getComment());
assertEquals(3, updateItem2.getLineNum());
}
private NamespaceDTO generateNamespaceDTO(String appId, String clusterName, String namespaceName){
NamespaceDTO namespaceDTO = new NamespaceDTO();
namespaceDTO.setAppId(appId);
namespaceDTO.setId(1);
namespaceDTO.setClusterName(clusterName);
namespaceDTO.setNamespaceName(namespaceName);
return namespaceDTO;
}
private List<NamespaceIdentifer> generateNamespaceIdentifer(String appId, String env, String clusterName, String namespaceName){
NamespaceIdentifer targetNamespace = new NamespaceIdentifer();
targetNamespace.setAppId(appId);
targetNamespace.setEnv(env);
targetNamespace.setClusterName(clusterName);
targetNamespace.setNamespaceName(namespaceName);
return Arrays.asList(targetNamespace);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册