Commit aad80f8a by lepdou

同步配置优化

parent 77c489d9
package com.ctrip.framework.apollo.adminservice.controller; package com.ctrip.framework.apollo.adminservice.controller;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -131,16 +130,7 @@ public class ItemController { ...@@ -131,16 +130,7 @@ public class ItemController {
public List<ItemDTO> findItems(@PathVariable("appId") String appId, public List<ItemDTO> findItems(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName, @PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName) { @PathVariable("namespaceName") String namespaceName) {
List<Item> items = itemService.findItems(appId, clusterName, namespaceName); return BeanUtils.batchTransform(ItemDTO.class, 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;
} }
@RequestMapping("/items/{itemId}") @RequestMapping("/items/{itemId}")
......
...@@ -13,4 +13,5 @@ public interface ItemRepository extends PagingAndSortingRepository<Item, Long> { ...@@ -13,4 +13,5 @@ public interface ItemRepository extends PagingAndSortingRepository<Item, Long> {
List<Item> findByNamespaceIdOrderByLineNumAsc(Long namespaceId); List<Item> findByNamespaceIdOrderByLineNumAsc(Long namespaceId);
Item findFirst1ByNamespaceIdOrderByLineNumDesc(Long namespaceId); Item findFirst1ByNamespaceIdOrderByLineNumDesc(Long namespaceId);
} }
...@@ -83,10 +83,10 @@ public class ItemService { ...@@ -83,10 +83,10 @@ public class ItemService {
} }
public List<Item> findItems(String appId, String clusterName, String namespaceName) { public List<Item> findItems(String appId, String clusterName, String namespaceName) {
Namespace group = namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, clusterName, Namespace namespace = namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, clusterName,
namespaceName); namespaceName);
if (group != null) { if (namespace != null) {
return findItems(group.getId()); return findItems(namespace.getId());
} else { } else {
return Collections.emptyList(); return Collections.emptyList();
} }
......
...@@ -12,6 +12,7 @@ import com.ctrip.framework.apollo.biz.utils.ConfigChangeContentBuilder; ...@@ -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.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.dto.ItemChangeSets; import com.ctrip.framework.apollo.core.dto.ItemChangeSets;
import com.ctrip.framework.apollo.core.dto.ItemDTO; import com.ctrip.framework.apollo.core.dto.ItemDTO;
import com.ctrip.framework.apollo.core.exception.NotFoundException;
import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.core.utils.StringUtils;
...@@ -50,9 +51,10 @@ public class ItemSetService { ...@@ -50,9 +51,10 @@ public class ItemSetService {
Item entity = BeanUtils.transfrom(Item.class, item); Item entity = BeanUtils.transfrom(Item.class, item);
Item beforeUpdateItem = itemService.findOne(entity.getId()); Item beforeUpdateItem = itemService.findOne(entity.getId());
if (beforeUpdateItem != null){ if (beforeUpdateItem == null) {
beforeUpdateItem = BeanUtils.transfrom(Item.class, beforeUpdateItem); throw new NotFoundException(String.format("item not found.(key=%s)", entity.getKey()));
} }
beforeUpdateItem = BeanUtils.transfrom(Item.class, beforeUpdateItem);
entity.setDataChangeLastModifiedBy(operator); entity.setDataChangeLastModifiedBy(operator);
Item updatedItem = itemService.update(entity); Item updatedItem = itemService.update(entity);
......
package com.ctrip.framework.apollo.core.dto; package com.ctrip.framework.apollo.core.dto;
import java.util.Date;
public class ItemDTO extends BaseDTO{ public class ItemDTO extends BaseDTO{
...@@ -16,10 +15,6 @@ public class ItemDTO extends BaseDTO{ ...@@ -16,10 +15,6 @@ public class ItemDTO extends BaseDTO{
private int lineNum; private int lineNum;
private String lastModifiedBy;
private Date lastModifiedTime;
public ItemDTO() { public ItemDTO() {
} }
...@@ -79,33 +74,4 @@ public class ItemDTO extends BaseDTO{ ...@@ -79,33 +74,4 @@ public class ItemDTO extends BaseDTO{
this.lineNum = lineNum; 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; ...@@ -17,15 +17,16 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List; import java.util.List;
import static com.ctrip.framework.apollo.common.utils.RequestPrecondition.checkModel; import static com.ctrip.framework.apollo.common.utils.RequestPrecondition.checkModel;
@RestController @RestController
@RequestMapping("") public class ItemController {
public class ConfigController {
@Autowired @Autowired
private ConfigService configService; private ConfigService configService;
...@@ -83,9 +84,22 @@ public class ConfigController { ...@@ -83,9 +84,22 @@ public class ConfigController {
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/items") @RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/items")
public List<ItemDTO> findItems(@PathVariable String appId, @PathVariable String env, public List<ItemDTO> findItems(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName){ @PathVariable String clusterName, @PathVariable String namespaceName,
@RequestParam(defaultValue = "lineNum") String orderBy){
return configService.findItems(appId, Env.valueOf(env), clusterName, namespaceName);
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 = { @RequestMapping(value = "/namespaces/{namespaceName}/diff", method = RequestMethod.POST, consumes = {
......
...@@ -11,6 +11,7 @@ public class NamespaceVO { ...@@ -11,6 +11,7 @@ public class NamespaceVO {
private String format; private String format;
private boolean isPublic; private boolean isPublic;
private String parentAppId; private String parentAppId;
private String comment;
public NamespaceDTO getBaseInfo() { public NamespaceDTO getBaseInfo() {
...@@ -61,6 +62,14 @@ public class NamespaceVO { ...@@ -61,6 +62,14 @@ public class NamespaceVO {
this.parentAppId = parentAppId; this.parentAppId = parentAppId;
} }
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public static class ItemVO{ public static class ItemVO{
private ItemDTO item; private ItemDTO item;
private boolean isModified; private boolean isModified;
...@@ -109,6 +118,7 @@ public class NamespaceVO { ...@@ -109,6 +118,7 @@ public class NamespaceVO {
} }
} }
} }
...@@ -91,7 +91,7 @@ public class NamespaceService { ...@@ -91,7 +91,7 @@ public class NamespaceService {
NamespaceVO namespaceVO = new NamespaceVO(); NamespaceVO namespaceVO = new NamespaceVO();
namespaceVO.setBaseInfo(namespace); namespaceVO.setBaseInfo(namespace);
fillFormatAndIsPublicAndParentAppField(namespaceVO); fillAppNamespaceProperties(namespaceVO);
List<NamespaceVO.ItemVO> itemVos = new LinkedList<>(); List<NamespaceVO.ItemVO> itemVos = new LinkedList<>();
namespaceVO.setItems(itemVos); namespaceVO.setItems(itemVos);
...@@ -130,7 +130,7 @@ public class NamespaceService { ...@@ -130,7 +130,7 @@ public class NamespaceService {
return namespaceVO; return namespaceVO;
} }
private void fillFormatAndIsPublicAndParentAppField(NamespaceVO namespace) { private void fillAppNamespaceProperties(NamespaceVO namespace) {
NamespaceDTO namespaceDTO = namespace.getBaseInfo(); NamespaceDTO namespaceDTO = namespace.getBaseInfo();
//先从当前appId下面找,包含私有的和公共的 //先从当前appId下面找,包含私有的和公共的
...@@ -140,6 +140,7 @@ public class NamespaceService { ...@@ -140,6 +140,7 @@ public class NamespaceService {
if (appNamespace == null) { if (appNamespace == null) {
appNamespace = appNamespaceService.findPublicAppNamespace(namespaceDTO.getNamespaceName()); appNamespace = appNamespaceService.findPublicAppNamespace(namespaceDTO.getNamespaceName());
} }
String format; String format;
boolean isPublic; boolean isPublic;
if (appNamespace == null) { if (appNamespace == null) {
...@@ -149,10 +150,10 @@ public class NamespaceService { ...@@ -149,10 +150,10 @@ public class NamespaceService {
format = appNamespace.getFormat(); format = appNamespace.getFormat();
isPublic = appNamespace.isPublic(); isPublic = appNamespace.isPublic();
namespace.setParentAppId(appNamespace.getAppId()); namespace.setParentAppId(appNamespace.getAppId());
namespace.setComment(appNamespace.getComment());
} }
namespace.setFormat(format); namespace.setFormat(format);
namespace.setPublic(isPublic); namespace.setPublic(isPublic);
} }
private List<NamespaceVO.ItemVO> parseDeletedItems(List<ItemDTO> newItems, Map<String, String> releaseItems) { private List<NamespaceVO.ItemVO> parseDeletedItems(List<ItemDTO> newItems, Map<String, String> releaseItems) {
......
...@@ -178,10 +178,10 @@ ...@@ -178,10 +178,10 @@
<span ng-bind="config.newValue | limitTo: 250"></span> <span ng-bind="config.newValue | limitTo: 250"></span>
<span ng-bind="config.newValue.length > 250 ? '...': ''"></span> <span ng-bind="config.newValue.length > 250 ? '...': ''"></span>
</td> </td>
<td width="15%" ng-bind="config.item.lastModifiedBy"> <td width="15%" ng-bind="config.item.dataChangeLastModifiedBy">
</td> </td>
<td width="15%" <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>
</tr> </tr>
</tbody> </tbody>
......
...@@ -105,59 +105,56 @@ ...@@ -105,59 +105,56 @@
</div> </div>
<!--step 2--> <!--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"> <h5 class="text-center">
环境:<span ng-bind="diff.namespace.env"></span> 环境:<span ng-bind="clusterDiff.namespace.env"></span>
集群:<span ng-bind="diff.namespace.clusterName"></span> 集群:<span ng-bind="clusterDiff.namespace.clusterName"></span>
<span ng-show="!diff.extInfo">Namespace:{{pageContext.namespaceName}}</span> <span ng-show="!clusterDiff.extInfo">Namespace:{{pageContext.namespaceName}}</span>
</h5> </h5>
<div class="text-center" ng-show="diff.diffs.createItems.length + diff.diffs.updateItems.length == 0 || diff.extInfo"> <div class="text-center"
<font ng-show="diff.diffs.createItems.length + diff.diffs.updateItems.length == 0 && !diff.extInfo">没有更新的配置</font> ng-show="clusterDiff.diffs.createItems.length + clusterDiff.diffs.updateItems.length == 0 || clusterDiff.extInfo">
<font ng-show="diff.extInfo" ng-bind="diff.extInfo"></font> <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>
<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"> <div class="form-horizontal">
<label class="col-sm-2 control-label">新增的配置</label> <div class="col-sm-12">
<div class="col-sm-9">
<table class="table table-bordered table-striped table-hover"> <table class="table table-bordered table-striped table-hover">
<thead> <thead>
<tr> <tr>
<td>key</td> <td>Type</td>
<td>value</td> <td>Key</td>
<td>comment</td> <td>同步前</td>
<td>同步后</td>
<td>Comment</td>
<td>操作</td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="createItem in diff.diffs.createItems"> <tr ng-repeat="createItem in clusterDiff.diffs.createItems">
<td width="30%" ng-bind="createItem.key"></td> <td width="5%">新增</td>
<td width="40%" ng-bind="createItem.value"></td> <td width="15%" ng-bind="createItem.key"></td>
<td width="30%" ng-bind="createItem.comment"></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> </tr>
</tbody> <tr ng-repeat="updateItem in clusterDiff.diffs.updateItems">
</table> <td width="5%">更新</td>
<td width="15%" ng-bind="updateItem.key"></td>
</div> <td width="30%" ng-bind="updateItem.oldValue"></td>
</div> <td width="30%" ng-bind="updateItem.value"></td>
</div> <td width="15%" ng-bind="updateItem.comment"></td>
<div class="row" style="margin-top: 10px;" ng-show="diff.diffs.updateItems.length > 0"> <td width="5%">
<div class="form-horizontal"> <a data-tooltip="tooltip" data-placement="bottom" title="不同步该配置"
<label class="col-sm-2 control-label">修改的配置</label> ng-click="removeItem(clusterDiff.diffs, 'update', updateItem)">删除</a>
<div class="col-sm-9"> </td>
<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> </tr>
</tbody> </tbody>
</table> </table>
......
$(document).ready(function () { $(document).ready(function () {
// nicescroll
$("html").niceScroll({ $("html").niceScroll({
styler: "fb", styler: "fb",
cursorcolor: "#e8403f", cursorcolor: "#e8403f",
...@@ -9,12 +11,24 @@ $(document).ready(function () { ...@@ -9,12 +11,24 @@ $(document).ready(function () {
cursorborder: '', cursorborder: '',
zindex: '1000' zindex: '1000'
}); });
});
$(function () { // bootstrap tooltip
$('[data-toggle="tooltip"]').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-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 // (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
Date.prototype.Format = function (fmt) { Date.prototype.Format = function (fmt) {
......
...@@ -73,26 +73,13 @@ application_module.controller("ConfigNamespaceController", ...@@ -73,26 +73,13 @@ application_module.controller("ConfigNamespaceController",
$rootScope.pageContext.clusterName, $rootScope.pageContext.clusterName,
viewType).then( viewType).then(
function (result) { 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 += $scope.namespaces = result;
( delta < 0 ? 1 : -1 ) * 30;
e.preventDefault();
});
}, 2500);
}, function (result) { }, function (result) {
toastr.error(AppUtil.errorMsg(result), "加载配置信息出错"); toastr.error(AppUtil.errorMsg(result), "加载配置信息出错");
}); });
}; }
function commitChange(namespace) { function commitChange(namespace) {
var model = { var model = {
......
...@@ -215,7 +215,7 @@ directive_module.directive('apollonspanel', ...@@ -215,7 +215,7 @@ directive_module.directive('apollonspanel',
var itemCnt = 0; var itemCnt = 0;
namespace.items.forEach(function (item) { namespace.items.forEach(function (item) {
//deleted key //deleted key
if (!item.item.lastModifiedBy) { if (!item.item.dataChangeLastModifiedBy) {
return; return;
} }
if (item.item.key) { if (item.item.key) {
......
...@@ -53,13 +53,14 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q) ...@@ -53,13 +53,14 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
return d.promise; return d.promise;
}, },
find_items: function (appId, env, clusterName, namespaceName) { find_items: function (appId, env, clusterName, namespaceName, orderBy) {
var d = $q.defer(); var d = $q.defer();
config_source.find_items({ config_source.find_items({
appId: appId, appId: appId,
env: env, env: env,
clusterName: clusterName, clusterName: clusterName,
namespaceName: namespaceName namespaceName: namespaceName,
orderBy: orderBy
}, function (result) { }, function (result) {
d.resolve(result); d.resolve(result);
}, function (result) { }, function (result) {
......
...@@ -25,6 +25,10 @@ p, td, span { ...@@ -25,6 +25,10 @@ p, td, span {
word-break: break-all; word-break: break-all;
} }
table{
text-align: center;
}
.no-radius{ .no-radius{
border-radius: 0px; border-radius: 0px;
} }
...@@ -359,6 +363,7 @@ table th { ...@@ -359,6 +363,7 @@ table th {
height: 200px; height: 200px;
position: absolute; position: absolute;
margin-left: 0px; margin-left: 0px;
cursor: pointer;
background: #ffffff; background: #ffffff;
border: 1px solid #ddd; border: 1px solid #ddd;
overflow-y: scroll; overflow-y: scroll;
......
<div class="panel" style="border-top: 0px;"> <div class="panel" style="border-top: 0px;">
<div class="row namespace-attribute-panel" ng-if="namespace.isPublic"> <div class="row namespace-attribute-panel" ng-if="namespace.isPublic">
<div class="text-center namespace-attribute-public" data-tooltip="tooltip" data-placement="bottom" <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">关联</span> <span ng-show="namespace.parentAppId != namespace.baseInfo.appId"
ng-click="goToParentAppConfigPage(namespace)">关联</span>
</div> </div>
</div> </div>
<header class="panel-heading"> <header class="panel-heading">
<div class="row"> <div class="row">
<div class="col-md-6"> <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-info no-radius" ng-bind="namespace.format"></span>
<span class="label label-primary no-radius" ng-show="namespace.itemModifiedCnt > 0">有修改 <span class="label label-primary no-radius" ng-show="namespace.itemModifiedCnt > 0">有修改
<span class="badge label" ng-bind="namespace.itemModifiedCnt"></span></span> <span class="badge label" ng-bind="namespace.itemModifiedCnt"></span></span>
...@@ -168,10 +170,10 @@ ...@@ -168,10 +170,10 @@
<span ng-bind="config.item.comment | limitTo: 250"></span> <span ng-bind="config.item.comment | limitTo: 250"></span>
<span ng-bind="config.item.comment.length > 250 ?'...' : ''"></span> <span ng-bind="config.item.comment.length > 250 ?'...' : ''"></span>
</td> </td>
<td width="10%" ng-bind="config.item.lastModifiedBy"> <td width="10%" ng-bind="config.item.dataChangeLastModifiedBy">
</td> </td>
<td width="15%" <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>
<td width="10%"> <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