Commit aad80f8a by lepdou

同步配置优化

parent 77c489d9
package com.ctrip.framework.apollo.adminservice.controller;
import java.util.LinkedList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -131,16 +130,7 @@ public class ItemController {
public List<ItemDTO> findItems(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName) {
List<Item> items = itemService.findItems(appId, clusterName, namespaceName);
List<ItemDTO> itemDTOs = new LinkedList<>();
for (Item item : items) {
ItemDTO itemDTO = BeanUtils.transfrom(ItemDTO.class, item);
itemDTO.setLastModifiedBy(item.getDataChangeLastModifiedBy());
itemDTO.setLastModifiedTime(item.getDataChangeLastModifiedTime());
itemDTOs.add(itemDTO);
}
return itemDTOs;
return BeanUtils.batchTransform(ItemDTO.class, itemService.findItems(appId, clusterName, namespaceName));
}
@RequestMapping("/items/{itemId}")
......
......@@ -13,4 +13,5 @@ public interface ItemRepository extends PagingAndSortingRepository<Item, Long> {
List<Item> findByNamespaceIdOrderByLineNumAsc(Long namespaceId);
Item findFirst1ByNamespaceIdOrderByLineNumDesc(Long namespaceId);
}
......@@ -83,10 +83,10 @@ public class ItemService {
}
public List<Item> findItems(String appId, String clusterName, String namespaceName) {
Namespace group = namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, clusterName,
Namespace namespace = namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, clusterName,
namespaceName);
if (group != null) {
return findItems(group.getId());
if (namespace != null) {
return findItems(namespace.getId());
} else {
return Collections.emptyList();
}
......
......@@ -12,6 +12,7 @@ import com.ctrip.framework.apollo.biz.utils.ConfigChangeContentBuilder;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.dto.ItemChangeSets;
import com.ctrip.framework.apollo.core.dto.ItemDTO;
import com.ctrip.framework.apollo.core.exception.NotFoundException;
import com.ctrip.framework.apollo.core.utils.StringUtils;
......@@ -50,9 +51,10 @@ public class ItemSetService {
Item entity = BeanUtils.transfrom(Item.class, item);
Item beforeUpdateItem = itemService.findOne(entity.getId());
if (beforeUpdateItem != null){
beforeUpdateItem = BeanUtils.transfrom(Item.class, beforeUpdateItem);
if (beforeUpdateItem == null) {
throw new NotFoundException(String.format("item not found.(key=%s)", entity.getKey()));
}
beforeUpdateItem = BeanUtils.transfrom(Item.class, beforeUpdateItem);
entity.setDataChangeLastModifiedBy(operator);
Item updatedItem = itemService.update(entity);
......
package com.ctrip.framework.apollo.core.dto;
import java.util.Date;
public class ItemDTO extends BaseDTO{
......@@ -16,10 +15,6 @@ public class ItemDTO extends BaseDTO{
private int lineNum;
private String lastModifiedBy;
private Date lastModifiedTime;
public ItemDTO() {
}
......@@ -79,33 +74,4 @@ public class ItemDTO extends BaseDTO{
this.lineNum = lineNum;
}
public String getLastModifiedBy() {
return lastModifiedBy;
}
public void setLastModifiedBy(String lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
public Date getLastModifiedTime() {
return lastModifiedTime;
}
public void setLastModifiedTime(Date lastModifiedTime) {
this.lastModifiedTime = lastModifiedTime;
}
@Override
public String toString() {
return "ItemDTO{" +
"id=" + id +
", namespaceId=" + namespaceId +
", key='" + key + '\'' +
", value='" + value + '\'' +
", comment='" + comment + '\'' +
", lineNum=" + lineNum +
", lastModifiedBy='" + lastModifiedBy + '\'' +
", lastModifiedTime=" + lastModifiedTime +
'}';
}
}
......@@ -17,15 +17,16 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import static com.ctrip.framework.apollo.common.utils.RequestPrecondition.checkModel;
@RestController
@RequestMapping("")
public class ConfigController {
public class ItemController {
@Autowired
private ConfigService configService;
......@@ -83,9 +84,22 @@ public class ConfigController {
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/items")
public List<ItemDTO> findItems(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName){
return configService.findItems(appId, Env.valueOf(env), clusterName, namespaceName);
@PathVariable String clusterName, @PathVariable String namespaceName,
@RequestParam(defaultValue = "lineNum") String orderBy){
List<ItemDTO> items = configService.findItems(appId, Env.valueOf(env), clusterName, namespaceName);
if ("lastModifyTime".equals(orderBy)){
Collections.sort(items, (o1, o2) -> {
if (o1.getDataChangeLastModifiedTime().after(o2.getDataChangeLastModifiedTime())){
return -1;
}
if (o1.getDataChangeLastModifiedTime().before(o2.getDataChangeLastModifiedTime())){
return 1;
}
return 0;
});
}
return items;
}
@RequestMapping(value = "/namespaces/{namespaceName}/diff", method = RequestMethod.POST, consumes = {
......
......@@ -11,6 +11,7 @@ public class NamespaceVO {
private String format;
private boolean isPublic;
private String parentAppId;
private String comment;
public NamespaceDTO getBaseInfo() {
......@@ -61,6 +62,14 @@ public class NamespaceVO {
this.parentAppId = parentAppId;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public static class ItemVO{
private ItemDTO item;
private boolean isModified;
......@@ -109,6 +118,7 @@ public class NamespaceVO {
}
}
}
......@@ -91,7 +91,7 @@ public class NamespaceService {
NamespaceVO namespaceVO = new NamespaceVO();
namespaceVO.setBaseInfo(namespace);
fillFormatAndIsPublicAndParentAppField(namespaceVO);
fillAppNamespaceProperties(namespaceVO);
List<NamespaceVO.ItemVO> itemVos = new LinkedList<>();
namespaceVO.setItems(itemVos);
......@@ -130,7 +130,7 @@ public class NamespaceService {
return namespaceVO;
}
private void fillFormatAndIsPublicAndParentAppField(NamespaceVO namespace) {
private void fillAppNamespaceProperties(NamespaceVO namespace) {
NamespaceDTO namespaceDTO = namespace.getBaseInfo();
//先从当前appId下面找,包含私有的和公共的
......@@ -140,6 +140,7 @@ public class NamespaceService {
if (appNamespace == null) {
appNamespace = appNamespaceService.findPublicAppNamespace(namespaceDTO.getNamespaceName());
}
String format;
boolean isPublic;
if (appNamespace == null) {
......@@ -149,10 +150,10 @@ public class NamespaceService {
format = appNamespace.getFormat();
isPublic = appNamespace.isPublic();
namespace.setParentAppId(appNamespace.getAppId());
namespace.setComment(appNamespace.getComment());
}
namespace.setFormat(format);
namespace.setPublic(isPublic);
}
private List<NamespaceVO.ItemVO> parseDeletedItems(List<ItemDTO> newItems, Map<String, String> releaseItems) {
......
......@@ -178,10 +178,10 @@
<span ng-bind="config.newValue | limitTo: 250"></span>
<span ng-bind="config.newValue.length > 250 ? '...': ''"></span>
</td>
<td width="15%" ng-bind="config.item.lastModifiedBy">
<td width="15%" ng-bind="config.item.dataChangeLastModifiedBy">
</td>
<td width="15%"
ng-bind="config.item.lastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'">
ng-bind="config.item.dataChangeLastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'">
</td>
</tr>
</tbody>
......
......@@ -105,59 +105,56 @@
</div>
<!--step 2-->
<div class="row" ng-show="syncItemStep == 2" ng-repeat="diff in diffs">
<div class="row" ng-show="syncItemStep == 2" ng-repeat="clusterDiff in clusterDiffs">
<h5 class="text-center">
环境:<span ng-bind="diff.namespace.env"></span>
集群:<span ng-bind="diff.namespace.clusterName"></span>
<span ng-show="!diff.extInfo">Namespace:{{pageContext.namespaceName}}</span>
环境:<span ng-bind="clusterDiff.namespace.env"></span>
集群:<span ng-bind="clusterDiff.namespace.clusterName"></span>
<span ng-show="!clusterDiff.extInfo">Namespace:{{pageContext.namespaceName}}</span>
</h5>
<div class="text-center" ng-show="diff.diffs.createItems.length + diff.diffs.updateItems.length == 0 || diff.extInfo">
<font ng-show="diff.diffs.createItems.length + diff.diffs.updateItems.length == 0 && !diff.extInfo">没有更新的配置</font>
<font ng-show="diff.extInfo" ng-bind="diff.extInfo"></font>
<div class="text-center"
ng-show="clusterDiff.diffs.createItems.length + clusterDiff.diffs.updateItems.length == 0 || clusterDiff.extInfo">
<span ng-show="clusterDiff.diffs.createItems.length + clusterDiff.diffs.updateItems.length == 0 && !clusterDiff.extInfo">没有更新的配置</span>
<span ng-show="clusterDiff.extInfo" ng-bind="clusterDiff.extInfo"></span>
,忽略同步
</div>
<div class="row" style="margin-top: 10px;" ng-show="diff.diffs.createItems.length > 0">
<div class="row" style="margin-top: 10px;"
ng-show="clusterDiff.diffs.updateItems.length + clusterDiff.diffs.createItems.length > 0">
<div class="form-horizontal">
<label class="col-sm-2 control-label">新增的配置</label>
<div class="col-sm-9">
<div class="col-sm-12">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<td>key</td>
<td>value</td>
<td>comment</td>
<td>Type</td>
<td>Key</td>
<td>同步前</td>
<td>同步后</td>
<td>Comment</td>
<td>操作</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="createItem in diff.diffs.createItems">
<td width="30%" ng-bind="createItem.key"></td>
<td width="40%" ng-bind="createItem.value"></td>
<td width="30%" ng-bind="createItem.comment"></td>
<tr ng-repeat="createItem in clusterDiff.diffs.createItems">
<td width="5%">新增</td>
<td width="15%" ng-bind="createItem.key"></td>
<td width="30%"></td>
<td width="30%" ng-bind="createItem.value"></td>
<td width="15%" ng-bind="createItem.comment"></td>
<td width="5%">
<a data-tooltip="tooltip" data-placement="bottom" title="不同步该配置"
ng-click="removeItem(clusterDiff.diffs, 'create', createItem)">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="row" style="margin-top: 10px;" ng-show="diff.diffs.updateItems.length > 0">
<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>
<td>comment</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="updateItem in diff.diffs.updateItems">
<td width="30%" ng-bind="updateItem.key"></td>
<td width="40%" ng-bind="updateItem.value"></td>
<td width="30%" ng-bind="updateItem.comment"></td>
<tr ng-repeat="updateItem in clusterDiff.diffs.updateItems">
<td width="5%">更新</td>
<td width="15%" ng-bind="updateItem.key"></td>
<td width="30%" ng-bind="updateItem.oldValue"></td>
<td width="30%" ng-bind="updateItem.value"></td>
<td width="15%" ng-bind="updateItem.comment"></td>
<td width="5%">
<a data-tooltip="tooltip" data-placement="bottom" title="不同步该配置"
ng-click="removeItem(clusterDiff.diffs, 'update', updateItem)">删除</a>
</td>
</tr>
</tbody>
</table>
......
$(document).ready(function () {
// nicescroll
$("html").niceScroll({
styler: "fb",
cursorcolor: "#e8403f",
......@@ -9,12 +11,24 @@ $(document).ready(function () {
cursorborder: '',
zindex: '1000'
});
});
$(function () {
$('[data-toggle="tooltip"]').tooltip()
// bootstrap tooltip
setInterval(function () {
$('[data-tooltip="tooltip"]').tooltip();
$('html').bind('mousewheel DOMMouseScroll',
function (e) {
var e0 = e.originalEvent,
delta = e0.wheelDelta
|| -e0.detail;
this.scrollTop +=
( delta < 0 ? 1 : -1 ) * 30;
e.preventDefault();
});
}, 2500);
});
// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
// (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
Date.prototype.Format = function (fmt) {
......
......@@ -73,26 +73,13 @@ application_module.controller("ConfigNamespaceController",
$rootScope.pageContext.clusterName,
viewType).then(
function (result) {
$scope.namespaces = result;
setInterval(function () {
$('[data-tooltip="tooltip"]').tooltip();
$('.namespace-view-table').bind('mousewheel DOMMouseScroll',
function (e) {
var e0 = e.originalEvent,
delta = e0.wheelDelta
|| -e0.detail;
this.scrollTop +=
( delta < 0 ? 1 : -1 ) * 30;
e.preventDefault();
});
}, 2500);
$scope.namespaces = result;
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "加载配置信息出错");
});
};
}
function commitChange(namespace) {
var model = {
......
sync_item_module.controller("SyncItemController",
['$scope', '$location', '$window', 'toastr', 'AppService', 'AppUtil', 'ConfigService',
function ($scope, $location, $window, toastr, AppService, AppUtil, ConfigService) {
var params = AppUtil.parseParams($location.$$url);
$scope.pageContext = {
appId: params.appid,
env: params.env,
clusterName: params.clusterName,
namespaceName: params.namespaceName
};
$scope.syncBtnDisabled = false;
////// load items //////
ConfigService.find_items($scope.pageContext.appId, $scope.pageContext.env,
$scope.pageContext.clusterName, $scope.pageContext.namespaceName).then(function (result) {
$scope.sourceItems = [];
result.forEach(function (item) {
if (item.key){
item.checked = false;
$scope.sourceItems.push(item);
}
})
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "加载配置出错");
});
var itemAllSelected = false;
$scope.toggleItemsCheckedStatus = function () {
itemAllSelected = !itemAllSelected;
$scope.sourceItems.forEach(function (item) {
item.checked = itemAllSelected;
})
};
$scope.diff = function () {
$scope.hasDiff = false;
ConfigService.diff($scope.pageContext.namespaceName, parseSyncSourceData()).then(function (result) {
$scope.diffs = result;
result.forEach(function (diff) {
if (!$scope.hasDiff) {
$scope.hasDiff = diff.diffs.createItems.length + diff.diffs.updateItems.length > 0;
}
});
$scope.syncItemNextStep(1);
}, function (result) {
toastr.error(AppUtil.errorMsg(result));
});
};
$scope.syncItems = function () {
$scope.syncBtnDisabled = true;
ConfigService.sync_items($scope.pageContext.appId,
$scope.pageContext.namespaceName,
parseSyncSourceData()).then(function (result) {
$scope.syncItemStep += 1;
$scope.syncSuccess = true;
$scope.syncBtnDisabled = false;
}, function (result) {
$scope.syncSuccess = false;
$scope.syncBtnDisabled = false;
toastr.error(AppUtil.errorMsg(result));
});
};
var selectedClusters = [];
$scope.collectSelectedClusters = function (data) {
selectedClusters = data;
};
function parseSyncSourceData() {
var sourceData = {
syncToNamespaces: [],
syncItems: []
};
var namespaceName = $scope.pageContext.namespaceName;
selectedClusters.forEach(function (cluster) {
if (cluster.checked){
cluster.clusterName = cluster.name;
cluster.namespaceName = namespaceName;
sourceData.syncToNamespaces.push(cluster);
}
});
$scope.sourceItems.forEach(function (item) {
if (item.checked) {
sourceData.syncItems.push(item);
}
});
return sourceData;
}
////// flow control ///////
$scope.syncItemStep = 1;
$scope.syncItemNextStep = function (offset) {
$scope.syncItemStep += offset;
};
$scope.backToAppHomePage = function () {
$window.location.href = '/config.html?#appid=' + $scope.pageContext.appId;
};
$scope.switchSelect = function (o) {
o.checked = !o.checked;
}
}]);
['$scope', '$location', '$window', 'toastr', 'AppService', 'AppUtil', 'ConfigService',
function ($scope, $location, $window, toastr, AppService, AppUtil, ConfigService) {
var params = AppUtil.parseParams($location.$$url);
$scope.pageContext = {
appId: params.appid,
env: params.env,
clusterName: params.clusterName,
namespaceName: params.namespaceName
};
$scope.syncBtnDisabled = false;
////// load items //////
ConfigService.find_items($scope.pageContext.appId, $scope.pageContext.env,
$scope.pageContext.clusterName,
$scope.pageContext.namespaceName,
"lastModifyTime")
.then(function (result) {
$scope.sourceItems = [];
result.forEach(function (item) {
if (item.key) {
item.checked = false;
$scope.sourceItems.push(item);
}
})
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "加载配置出错");
});
var itemAllSelected = false;
$scope.toggleItemsCheckedStatus = function () {
itemAllSelected = !itemAllSelected;
$scope.sourceItems.forEach(function (item) {
item.checked = itemAllSelected;
})
};
var syncData = {
syncToNamespaces: [],
syncItems: []
};
$scope.diff = function () {
parseSyncSourceData();
if (syncData.syncItems.length == 0) {
toastr.warning("请选择需要同步的配置");
return;
}
if (syncData.syncToNamespaces.length == 0) {
toastr.warning("请选择集群");
return;
}
$scope.hasDiff = false;
ConfigService.diff($scope.pageContext.namespaceName, syncData).then(
function (result) {
$scope.clusterDiffs = result;
$scope.clusterDiffs.forEach(function (clusterDiff) {
if (!$scope.hasDiff) {
$scope.hasDiff =
clusterDiff.diffs.createItems.length + clusterDiff.diffs.updateItems.length
> 0;
}
if (clusterDiff.diffs.updateItems.length > 0){
//赋予同步前的值
ConfigService.find_items(clusterDiff.namespace.appId,
clusterDiff.namespace.env,
clusterDiff.namespace.clusterName,
clusterDiff.namespace.namespaceName)
.then(function (result) {
var oldItemMap = {};
result.forEach(function (item) {
oldItemMap[item.key] = item.value;
});
clusterDiff.diffs.updateItems.forEach(function (item) {
item.oldValue = oldItemMap[item.key];
})
});
}
});
$scope.syncItemNextStep(1);
}, function (result) {
toastr.error(AppUtil.errorMsg(result));
});
};
$scope.removeItem = function (diff, type, toRemoveItem) {
var syncDataResult = [],
diffSetResult = [],
diffSet;
if (type == 'create') {
diffSet = diff.createItems;
} else {
diffSet = diff.updateItems;
}
diffSet.forEach(function (item) {
if (item.key != toRemoveItem.key) {
diffSetResult.push(item);
}
});
if (type == 'create') {
diff.createItems = diffSetResult;
} else {
diff.updateItems = diffSetResult;
}
syncData.syncItems.forEach(function (item) {
if (item.key != toRemoveItem.key) {
syncDataResult.push(item);
}
});
syncData.syncItems = syncDataResult;
};
$scope.syncItems = function () {
$scope.syncBtnDisabled = true;
ConfigService.sync_items($scope.pageContext.appId,
$scope.pageContext.namespaceName,
syncData).then(function (result) {
$scope.syncItemStep += 1;
$scope.syncSuccess = true;
$scope.syncBtnDisabled = false;
}, function (result) {
$scope.syncSuccess = false;
$scope.syncBtnDisabled = false;
toastr.error(AppUtil.errorMsg(result));
});
};
var selectedClusters = [];
$scope.collectSelectedClusters = function (data) {
selectedClusters = data;
};
function parseSyncSourceData() {
syncData = {
syncToNamespaces: [],
syncItems: []
};
var namespaceName = $scope.pageContext.namespaceName;
selectedClusters.forEach(function (cluster) {
if (cluster.checked) {
cluster.clusterName = cluster.name;
cluster.namespaceName = namespaceName;
syncData.syncToNamespaces.push(cluster);
}
});
$scope.sourceItems.forEach(function (item) {
if (item.checked) {
syncData.syncItems.push(item);
}
});
return syncData;
}
////// flow control ///////
$scope.syncItemStep = 1;
$scope.syncItemNextStep = function (offset) {
$scope.syncItemStep += offset;
};
$scope.backToAppHomePage = function () {
$window.location.href = '/config.html?#appid=' + $scope.pageContext.appId;
};
$scope.switchSelect = function (o) {
o.checked = !o.checked;
};
}]);
......@@ -215,7 +215,7 @@ directive_module.directive('apollonspanel',
var itemCnt = 0;
namespace.items.forEach(function (item) {
//deleted key
if (!item.item.lastModifiedBy) {
if (!item.item.dataChangeLastModifiedBy) {
return;
}
if (item.item.key) {
......
......@@ -53,13 +53,14 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
return d.promise;
},
find_items: function (appId, env, clusterName, namespaceName) {
find_items: function (appId, env, clusterName, namespaceName, orderBy) {
var d = $q.defer();
config_source.find_items({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName
namespaceName: namespaceName,
orderBy: orderBy
}, function (result) {
d.resolve(result);
}, function (result) {
......
......@@ -25,6 +25,10 @@ p, td, span {
word-break: break-all;
}
table{
text-align: center;
}
.no-radius{
border-radius: 0px;
}
......@@ -359,6 +363,7 @@ table th {
height: 200px;
position: absolute;
margin-left: 0px;
cursor: pointer;
background: #ffffff;
border: 1px solid #ddd;
overflow-y: scroll;
......
<div class="panel" style="border-top: 0px;">
<div class="row namespace-attribute-panel" ng-if="namespace.isPublic">
<div class="text-center namespace-attribute-public" data-tooltip="tooltip" data-placement="bottom"
title="点击跳转到公共Namespace" ng-click="goToParentAppConfigPage(namespace)">
title="点击跳转到公共Namespace">
<span ng-show="namespace.parentAppId == namespace.baseInfo.appId">公共</span>
<span ng-show="namespace.parentAppId != namespace.baseInfo.appId">关联</span>
<span ng-show="namespace.parentAppId != namespace.baseInfo.appId"
ng-click="goToParentAppConfigPage(namespace)">关联</span>
</div>
</div>
<header class="panel-heading">
<div class="row">
<div class="col-md-6">
<b ng-bind="namespace.viewName" style="font-size: 20px;"></b>
<b ng-bind="namespace.viewName" style="font-size: 20px;" data-tooltip="tooltip" data-placement="bottom"
title="{{namespace.comment}}"></b>
<span class="label label-info no-radius" ng-bind="namespace.format"></span>
<span class="label label-primary no-radius" ng-show="namespace.itemModifiedCnt > 0">有修改
<span class="badge label" ng-bind="namespace.itemModifiedCnt"></span></span>
......@@ -168,10 +170,10 @@
<span ng-bind="config.item.comment | limitTo: 250"></span>
<span ng-bind="config.item.comment.length > 250 ?'...' : ''"></span>
</td>
<td width="10%" ng-bind="config.item.lastModifiedBy">
<td width="10%" ng-bind="config.item.dataChangeLastModifiedBy">
</td>
<td width="15%"
ng-bind="config.item.lastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'">
ng-bind="config.item.dataChangeLastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'">
</td>
<td width="10%">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment