提交 60a5a27f 编写于 作者: L lepdou

commit history & bugfix empty value

上级 57ddd524
package com.ctrip.framework.apollo.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.Commit;
import com.ctrip.framework.apollo.biz.service.CommitService;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.dto.CommitDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class CommitController {
@Autowired
private CommitService commitService;
@RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/commit")
public List<CommitDTO> find(@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespaceName, Pageable pageable){
List<Commit> commits = commitService.find(appId, clusterName, namespaceName, pageable);
return BeanUtils.batchTransform(CommitDTO.class, commits);
}
}
...@@ -11,8 +11,13 @@ import org.springframework.web.bind.annotation.RequestMethod; ...@@ -11,8 +11,13 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ctrip.framework.apollo.biz.entity.Commit;
import com.ctrip.framework.apollo.biz.entity.Item; import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.service.CommitService;
import com.ctrip.framework.apollo.biz.service.ItemService; import com.ctrip.framework.apollo.biz.service.ItemService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
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.ItemDTO; import com.ctrip.framework.apollo.core.dto.ItemDTO;
import com.ctrip.framework.apollo.core.exception.NotFoundException; import com.ctrip.framework.apollo.core.exception.NotFoundException;
...@@ -22,16 +27,24 @@ public class ItemController { ...@@ -22,16 +27,24 @@ public class ItemController {
@Autowired @Autowired
private ItemService itemService; private ItemService itemService;
@Autowired
private NamespaceService namespaceService;
@Autowired
private CommitService commitService;
@RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items", method = RequestMethod.POST) @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items", method = RequestMethod.POST)
public ItemDTO createOrUpdate(@PathVariable("appId") String appId, public ItemDTO createOrUpdate(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName, @PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName, @RequestBody ItemDTO dto) { @PathVariable("namespaceName") String namespaceName, @RequestBody ItemDTO dto) {
Item entity = BeanUtils.transfrom(Item.class, dto); Item entity = BeanUtils.transfrom(Item.class, dto);
ConfigChangeContentBuilder builder = new ConfigChangeContentBuilder();
Item managedEntity = itemService.findOne(appId, clusterName, namespaceName, entity.getKey()); Item managedEntity = itemService.findOne(appId, clusterName, namespaceName, entity.getKey());
if (managedEntity != null) { if (managedEntity != null) {
Item beforeUpdateItem = BeanUtils.transfrom(Item.class, managedEntity);
BeanUtils.copyEntityProperties(entity, managedEntity); BeanUtils.copyEntityProperties(entity, managedEntity);
entity = itemService.update(managedEntity); entity = itemService.update(managedEntity);
builder.updateItem(beforeUpdateItem, entity);
} else { } else {
Item lastItem = itemService.findLastOne(appId, clusterName, namespaceName); Item lastItem = itemService.findLastOne(appId, clusterName, namespaceName);
int lineNum = 1; int lineNum = 1;
...@@ -41,9 +54,19 @@ public class ItemController { ...@@ -41,9 +54,19 @@ public class ItemController {
} }
entity.setLineNum(lineNum); entity.setLineNum(lineNum);
entity = itemService.save(entity); entity = itemService.save(entity);
builder.createItem(entity);
} }
dto = BeanUtils.transfrom(ItemDTO.class, entity); dto = BeanUtils.transfrom(ItemDTO.class, entity);
Commit commit = new Commit();
commit.setAppId(appId);
commit.setClusterName(clusterName);
commit.setNamespaceName(namespaceName);
commit.setChangeSets(builder.build());
commit.setDataChangeCreatedBy(dto.getDataChangeLastModifiedBy());
commit.setDataChangeLastModifiedBy(dto.getDataChangeLastModifiedBy());
commitService.save(commit);
return dto; return dto;
} }
...@@ -54,6 +77,17 @@ public class ItemController { ...@@ -54,6 +77,17 @@ public class ItemController {
throw new NotFoundException("item not found for itemId " + itemId); throw new NotFoundException("item not found for itemId " + itemId);
} }
itemService.delete(entity.getId(), operator); itemService.delete(entity.getId(), operator);
Namespace namespace = namespaceService.findOne(entity.getNamespaceId());
Commit commit = new Commit();
commit.setAppId(namespace.getAppId());
commit.setClusterName(namespace.getClusterName());
commit.setNamespaceName(namespace.getNamespaceName());
commit.setChangeSets(new ConfigChangeContentBuilder().deleteItem(entity).build());
commit.setDataChangeCreatedBy(operator);
commit.setDataChangeLastModifiedBy(operator);
commitService.save(commit);
} }
@RequestMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items") @RequestMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items")
......
...@@ -3,6 +3,7 @@ package com.ctrip.framework.apollo.adminservice.controller; ...@@ -3,6 +3,7 @@ package com.ctrip.framework.apollo.adminservice.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
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;
...@@ -18,8 +19,13 @@ public class ItemSetController { ...@@ -18,8 +19,13 @@ public class ItemSetController {
private ItemSetService itemSetService; private ItemSetService itemSetService;
@RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/itemset", method = RequestMethod.POST) @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/itemset", method = RequestMethod.POST)
public ResponseEntity<Void> create(@RequestBody ItemChangeSets changeSet) { public ResponseEntity<Void> create(@PathVariable String appId, @PathVariable String clusterName,
itemSetService.updateSet(changeSet); @PathVariable String namespaceName, @RequestBody ItemChangeSets changeSet) {
itemSetService.updateSet(appId, clusterName, namespaceName, changeSet);
return ResponseEntity.status(HttpStatus.OK).build(); return ResponseEntity.status(HttpStatus.OK).build();
} }
} }
...@@ -15,7 +15,7 @@ import javax.persistence.Table; ...@@ -15,7 +15,7 @@ import javax.persistence.Table;
@Where(clause = "isDeleted = 0") @Where(clause = "isDeleted = 0")
public class Commit extends BaseEntity { public class Commit extends BaseEntity {
@Column(name = "ChangeSets", nullable = false) @Column(name = "ChangeSets", length = 4048, nullable = false)
private String changeSets; private String changeSets;
@Column(name = "AppId", nullable = false) @Column(name = "AppId", nullable = false)
......
...@@ -2,13 +2,14 @@ package com.ctrip.framework.apollo.biz.repository; ...@@ -2,13 +2,14 @@ package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.Commit; import com.ctrip.framework.apollo.biz.entity.Commit;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List; import java.util.List;
public interface CommitRepository extends PagingAndSortingRepository<Commit, Long> { public interface CommitRepository extends PagingAndSortingRepository<Commit, Long> {
List<Commit> findByAppIdAndClusterNameAndNamespaceName(String appId, String clusterName, List<Commit> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String clusterName,
String namespaceName); String namespaceName, Pageable pageable);
} }
...@@ -4,9 +4,11 @@ import com.ctrip.framework.apollo.biz.entity.Commit; ...@@ -4,9 +4,11 @@ import com.ctrip.framework.apollo.biz.entity.Commit;
import com.ctrip.framework.apollo.biz.repository.CommitRepository; import com.ctrip.framework.apollo.biz.repository.CommitRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date; import java.util.List;
@Service @Service
public class CommitService { public class CommitService {
...@@ -14,12 +16,14 @@ public class CommitService { ...@@ -14,12 +16,14 @@ public class CommitService {
@Autowired @Autowired
private CommitRepository commitRepository; private CommitRepository commitRepository;
public void save(Commit commit, String user){ @Transactional
public Commit save(Commit commit){
commit.setId(0);//protection commit.setId(0);//protection
commit.setDataChangeCreatedBy(user); return commitRepository.save(commit);
commit.setDataChangeCreatedTime(new Date()); }
commitRepository.save(commit);
public List<Commit> find(String appId, String clusterName, String namespaceName, Pageable page){
return commitRepository.findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(appId, clusterName, namespaceName, page);
} }
} }
...@@ -6,12 +6,15 @@ import org.springframework.transaction.annotation.Transactional; ...@@ -6,12 +6,15 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import com.ctrip.framework.apollo.biz.entity.Audit; import com.ctrip.framework.apollo.biz.entity.Audit;
import com.ctrip.framework.apollo.biz.entity.Commit;
import com.ctrip.framework.apollo.biz.entity.Item; import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.repository.ItemRepository; import com.ctrip.framework.apollo.biz.repository.ItemRepository;
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;
@Service @Service
public class ItemSetService { public class ItemSetService {
...@@ -21,16 +24,24 @@ public class ItemSetService { ...@@ -21,16 +24,24 @@ public class ItemSetService {
@Autowired @Autowired
private AuditService auditService; private AuditService auditService;
@Autowired
private CommitService commitService;
@Transactional @Transactional
public void updateSet(ItemChangeSets changeSet) { public ItemChangeSets updateSet(String appId, String clusterName,
String namespaceName, ItemChangeSets changeSet) {
String operator = changeSet.getDataChangeLastModifiedBy(); String operator = changeSet.getDataChangeLastModifiedBy();
ConfigChangeContentBuilder configChangeContentBuilder = new ConfigChangeContentBuilder();
if (!CollectionUtils.isEmpty(changeSet.getCreateItems())) { if (!CollectionUtils.isEmpty(changeSet.getCreateItems())) {
for (ItemDTO item : changeSet.getCreateItems()) { for (ItemDTO item : changeSet.getCreateItems()) {
Item entity = BeanUtils.transfrom(Item.class, item); Item entity = BeanUtils.transfrom(Item.class, item);
entity.setId(0);//protection entity.setId(0);//protection
entity.setDataChangeCreatedBy(operator); entity.setDataChangeCreatedBy(operator);
entity.setDataChangeLastModifiedBy(operator); entity.setDataChangeLastModifiedBy(operator);
itemRepository.save(entity); Item createdItem = itemRepository.save(entity);
configChangeContentBuilder.createItem(createdItem);
} }
auditService.audit("ItemSet", null, Audit.OP.INSERT, operator); auditService.audit("ItemSet", null, Audit.OP.INSERT, operator);
} }
...@@ -39,9 +50,12 @@ public class ItemSetService { ...@@ -39,9 +50,12 @@ public class ItemSetService {
for (ItemDTO item : changeSet.getUpdateItems()) { for (ItemDTO item : changeSet.getUpdateItems()) {
Item entity = BeanUtils.transfrom(Item.class, item); Item entity = BeanUtils.transfrom(Item.class, item);
Item managedItem = itemRepository.findOne(entity.getId()); Item managedItem = itemRepository.findOne(entity.getId());
Item beforeUpdateItem = BeanUtils.transfrom(Item.class, managedItem);
BeanUtils.copyEntityProperties(entity, managedItem); BeanUtils.copyEntityProperties(entity, managedItem);
managedItem.setDataChangeLastModifiedBy(operator); managedItem.setDataChangeLastModifiedBy(operator);
itemRepository.save(managedItem); Item updatedItem = itemRepository.save(managedItem);
configChangeContentBuilder.updateItem(beforeUpdateItem, updatedItem);
} }
auditService.audit("ItemSet", null, Audit.OP.UPDATE, operator); auditService.audit("ItemSet", null, Audit.OP.UPDATE, operator);
} }
...@@ -51,9 +65,27 @@ public class ItemSetService { ...@@ -51,9 +65,27 @@ public class ItemSetService {
Item entity = BeanUtils.transfrom(Item.class, item); Item entity = BeanUtils.transfrom(Item.class, item);
entity.setDeleted(true); entity.setDeleted(true);
entity.setDataChangeLastModifiedBy(operator); entity.setDataChangeLastModifiedBy(operator);
itemRepository.save(entity); Item deletedItem = itemRepository.save(entity);
configChangeContentBuilder.deleteItem(deletedItem);
} }
auditService.audit("ItemSet", null, Audit.OP.DELETE, operator); auditService.audit("ItemSet", null, Audit.OP.DELETE, operator);
} }
createCommit(appId, clusterName, namespaceName, configChangeContentBuilder.build(), changeSet.getDataChangeLastModifiedBy());
return changeSet;
}
private void createCommit(String appId, String clusterName, String namespaceName, String configChangeContent, String operator){
Commit commit = new Commit();
commit.setAppId(appId);
commit.setClusterName(clusterName);
commit.setNamespaceName(namespaceName);
commit.setChangeSets(configChangeContent);
commit.setDataChangeCreatedBy(operator);
commit.setDataChangeLastModifiedBy(operator);
commitService.save(commit);
} }
} }
package com.ctrip.framework.apollo.biz.utils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.ctrip.framework.apollo.biz.entity.Item;
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 java.util.Date;
import java.util.LinkedList;
import java.util.List;
public class ConfigChangeContentBuilder {
private static final Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
private List<Item> createItems = new LinkedList<>();
private List<ItemPair> updateItems = new LinkedList<>();
private List<Item> deleteItems = new LinkedList<>();
public ConfigChangeContentBuilder createItem(Item item) {
createItems.add(item);
return this;
}
public ConfigChangeContentBuilder updateItem(Item oldItem, Item newItem) {
ItemPair itemPair = new ItemPair(oldItem, newItem);
updateItems.add(itemPair);
return this;
}
public ConfigChangeContentBuilder deleteItem(Item item) {
deleteItems.add(item);
return this;
}
public ConfigChangeContentBuilder changeSet(ItemChangeSets changeSets) {
for (ItemDTO itemDTO : changeSets.getCreateItems()) {
createItems.add(BeanUtils.transfrom(Item.class, itemDTO));
}
for (ItemDTO itemDTO : changeSets.getDeleteItems()) {
deleteItems.add(BeanUtils.transfrom(Item.class, itemDTO));
}
return this;
}
public String build() {
//因为事务第一段提交并没有更新时间,所以build时统一更新
for (Item item : createItems) {
item.setDataChangeLastModifiedTime(new Date());
}
for (ItemPair item : updateItems) {
item.newItem.setDataChangeLastModifiedTime(new Date());
}
for (Item item : deleteItems) {
item.setDataChangeLastModifiedTime(new Date());
}
return gson.toJson(this);
}
class ItemPair {
Item oldItem;
Item newItem;
public ItemPair(Item oldItem, Item newItem) {
this.oldItem = oldItem;
this.newItem = newItem;
}
}
}
package com.ctrip.framework.apollo.portal.util; package com.ctrip.framework.apollo.common.utils;
import com.ctrip.framework.apollo.core.exception.BadRequestException; import com.ctrip.framework.apollo.core.exception.BadRequestException;
...@@ -10,6 +10,8 @@ public class RequestPrecondition { ...@@ -10,6 +10,8 @@ public class RequestPrecondition {
private static String ILLEGAL_MODEL = "request model is invalid"; private static String ILLEGAL_MODEL = "request model is invalid";
private static String ILLEGAL_NUMBER = "number should be positive";
public static void checkArgument(String... args) { public static void checkArgument(String... args) {
checkArgument(!StringUtils.isContainEmpty(args), CONTAIN_EMPTY_ARGUMENT); checkArgument(!StringUtils.isContainEmpty(args), CONTAIN_EMPTY_ARGUMENT);
} }
...@@ -24,6 +26,14 @@ public class RequestPrecondition { ...@@ -24,6 +26,14 @@ public class RequestPrecondition {
} }
} }
public static void checkNumberPositive(int... args){
for (int num: args){
if (num <= 0){
throw new BadRequestException(ILLEGAL_NUMBER);
}
}
}
} }
package com.ctrip.framework.apollo.core.dto; package com.ctrip.framework.apollo.core.dto;
import java.util.Date;
public class BaseDTO { public class BaseDTO {
protected String dataChangeCreatedBy; protected String dataChangeCreatedBy;
protected String dataChangeLastModifiedBy; protected String dataChangeLastModifiedBy;
protected Date dataChangeCreatedTime;
protected Date dataChangeLastModifiedTime;
public String getDataChangeCreatedBy() { public String getDataChangeCreatedBy() {
return dataChangeCreatedBy; return dataChangeCreatedBy;
} }
...@@ -22,4 +28,20 @@ public class BaseDTO { ...@@ -22,4 +28,20 @@ public class BaseDTO {
public void setDataChangeLastModifiedBy(String dataChangeLastModifiedBy) { public void setDataChangeLastModifiedBy(String dataChangeLastModifiedBy) {
this.dataChangeLastModifiedBy = dataChangeLastModifiedBy; this.dataChangeLastModifiedBy = dataChangeLastModifiedBy;
} }
public Date getDataChangeCreatedTime() {
return dataChangeCreatedTime;
}
public void setDataChangeCreatedTime(Date dataChangeCreatedTime) {
this.dataChangeCreatedTime = dataChangeCreatedTime;
}
public Date getDataChangeLastModifiedTime() {
return dataChangeLastModifiedTime;
}
public void setDataChangeLastModifiedTime(Date dataChangeLastModifiedTime) {
this.dataChangeLastModifiedTime = dataChangeLastModifiedTime;
}
} }
package com.ctrip.framework.apollo.core.dto;
public class CommitDTO extends BaseDTO{
private String changeSets;
private String appId;
private String clusterName;
private String namespaceName;
private String comment;
public String getChangeSets() {
return changeSets;
}
public void setChangeSets(String changeSets) {
this.changeSets = changeSets;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
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;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
}
...@@ -2,6 +2,7 @@ package com.ctrip.framework.apollo.portal.api; ...@@ -2,6 +2,7 @@ package com.ctrip.framework.apollo.portal.api;
import com.ctrip.framework.apollo.core.dto.AppNamespaceDTO; import com.ctrip.framework.apollo.core.dto.AppNamespaceDTO;
import com.ctrip.framework.apollo.core.dto.CommitDTO;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.core.dto.AppDTO; import com.ctrip.framework.apollo.core.dto.AppDTO;
import com.ctrip.framework.apollo.core.dto.ClusterDTO; import com.ctrip.framework.apollo.core.dto.ClusterDTO;
...@@ -66,8 +67,10 @@ public class AdminServiceAPI { ...@@ -66,8 +67,10 @@ public class AdminServiceAPI {
public NamespaceDTO loadNamespace(String appId, Env env, String clusterName, public NamespaceDTO loadNamespace(String appId, Env env, String clusterName,
String namespaceName) { String namespaceName) {
NamespaceDTO dto = restTemplate.getForObject("{host}/apps/{appId}/clusters/{clusterName}/namespaces/" + namespaceName, NamespaceDTO
NamespaceDTO.class, getAdminServiceHost(env), appId, clusterName); dto =
restTemplate.getForObject("{host}/apps/{appId}/clusters/{clusterName}/namespaces/" + namespaceName,
NamespaceDTO.class, getAdminServiceHost(env), appId, clusterName);
return dto; return dto;
} }
...@@ -157,4 +160,19 @@ public class AdminServiceAPI { ...@@ -157,4 +160,19 @@ public class AdminServiceAPI {
} }
} }
@Service
public static class CommitAPI extends API {
public List<CommitDTO> find(String appId, Env env, String clusterName, String namespaceName, int page, int size) {
CommitDTO[]
commitDTOs =
restTemplate.getForObject("{host}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/commit?page={page}&size={size}",
CommitDTO[].class,
getAdminServiceHost(env), appId, clusterName, namespaceName, page, size);
return Arrays.asList(commitDTOs);
}
}
} }
...@@ -25,7 +25,7 @@ import org.springframework.web.client.HttpClientErrorException; ...@@ -25,7 +25,7 @@ import org.springframework.web.client.HttpClientErrorException;
import java.util.List; import java.util.List;
import static com.ctrip.framework.apollo.portal.util.RequestPrecondition.checkArgument; import static com.ctrip.framework.apollo.common.utils.RequestPrecondition.checkArgument;
@RestController @RestController
@RequestMapping("/apps") @RequestMapping("/apps")
...@@ -40,6 +40,7 @@ public class AppController { ...@@ -40,6 +40,7 @@ public class AppController {
@Autowired @Autowired
private ApplicationEventPublisher publisher; private ApplicationEventPublisher publisher;
@RequestMapping("") @RequestMapping("")
public List<App> findAllApp() { public List<App> findAllApp() {
return appService.findAll(); return appService.findAll();
......
package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.core.dto.CommitDTO;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.service.CommitService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class CommitController {
private final static int COMMIT_HISTORY_PAGE_SIZE = 10;
@Autowired
private CommitService commitService;
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/commits")
public List<CommitDTO> find(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName,
@RequestParam int page){
if (page < 0){
page = 0;
}
return commitService.find(appId, Env.valueOf(env), clusterName, namespaceName, page, COMMIT_HISTORY_PAGE_SIZE);
}
}
...@@ -25,7 +25,7 @@ import org.springframework.web.bind.annotation.RestController; ...@@ -25,7 +25,7 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List; import java.util.List;
import static com.ctrip.framework.apollo.portal.util.RequestPrecondition.checkModel; import static com.ctrip.framework.apollo.common.utils.RequestPrecondition.checkModel;
@RestController @RestController
@RequestMapping("") @RequestMapping("")
...@@ -128,7 +128,7 @@ public class ConfigController { ...@@ -128,7 +128,7 @@ public class ConfigController {
} }
private boolean isValidItem(ItemDTO item){ private boolean isValidItem(ItemDTO item){
return item != null && !StringUtils.isContainEmpty(item.getKey(), item.getValue()); return item != null && !StringUtils.isContainEmpty(item.getKey());
} }
} }
...@@ -29,8 +29,8 @@ import org.springframework.web.bind.annotation.RestController; ...@@ -29,8 +29,8 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List; import java.util.List;
import static com.ctrip.framework.apollo.portal.util.RequestPrecondition.checkArgument; import static com.ctrip.framework.apollo.common.utils.RequestPrecondition.checkArgument;
import static com.ctrip.framework.apollo.portal.util.RequestPrecondition.checkModel; import static com.ctrip.framework.apollo.common.utils.RequestPrecondition.checkModel;
@RestController @RestController
public class NamespaceController { public class NamespaceController {
......
...@@ -24,7 +24,7 @@ import org.springframework.web.bind.annotation.RestController; ...@@ -24,7 +24,7 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.Set; import java.util.Set;
import static com.ctrip.framework.apollo.portal.util.RequestPrecondition.checkArgument; import static com.ctrip.framework.apollo.common.utils.RequestPrecondition.checkArgument;
@RestController @RestController
......
...@@ -12,8 +12,8 @@ import org.springframework.web.bind.annotation.RequestMapping; ...@@ -12,8 +12,8 @@ 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.RestController; import org.springframework.web.bind.annotation.RestController;
import static com.ctrip.framework.apollo.portal.util.RequestPrecondition.checkArgument; import static com.ctrip.framework.apollo.common.utils.RequestPrecondition.checkArgument;
import static com.ctrip.framework.apollo.portal.util.RequestPrecondition.checkModel; import static com.ctrip.framework.apollo.common.utils.RequestPrecondition.checkModel;
/** /**
* 配置中心本身需要一些配置,这些配置放在数据库里面 * 配置中心本身需要一些配置,这些配置放在数据库里面
......
...@@ -12,7 +12,6 @@ public class NamespaceTextModel implements Verifiable { ...@@ -12,7 +12,6 @@ public class NamespaceTextModel implements Verifiable {
private String namespaceName; private String namespaceName;
private int namespaceId; private int namespaceId;
private String configText; private String configText;
private String comment;
@Override @Override
public boolean isInvalid(){ public boolean isInvalid(){
...@@ -66,11 +65,4 @@ public class NamespaceTextModel implements Verifiable { ...@@ -66,11 +65,4 @@ public class NamespaceTextModel implements Verifiable {
this.configText = configText; this.configText = configText;
} }
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
} }
package com.ctrip.framework.apollo.portal.service;
import com.ctrip.framework.apollo.core.dto.CommitDTO;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CommitService {
@Autowired
private AdminServiceAPI.CommitAPI commitAPI;
public List<CommitDTO> find(String appId, Env env, String clusterName, String namespaceName, int page, int size){
return commitAPI.find(appId, env, clusterName, namespaceName, page, size);
}
}
...@@ -120,7 +120,7 @@ ...@@ -120,7 +120,7 @@
</div> </div>
<div class="col-md-8 text-right"> <div class="col-md-8 text-right">
<button type="button" <button type="button"
class="btn btn-default btn-sm J_tableview_btn" class="btn btn-success btn-sm J_tableview_btn"
ng-disabled="namespace.isTextEditing" ng-disabled="namespace.isTextEditing"
ng-click="prepareReleaseNamespace(namespace)"> ng-click="prepareReleaseNamespace(namespace)">
<img src="img/release.png"> <img src="img/release.png">
...@@ -195,11 +195,11 @@ ...@@ -195,11 +195,11 @@
<a data-tooltip="tooltip" data-placement="bottom" title="提交修改" <a data-tooltip="tooltip" data-placement="bottom" title="提交修改"
data-toggle="modal" data-target="#commitModal" data-toggle="modal" data-target="#commitModal"
ng-show="namespace.isTextEditing && namespace.viewType == 'text'" ng-show="namespace.isTextEditing && namespace.viewType == 'text'"
ng-click="setCommitNamespace(namespace)"> ng-click="commitChange(namespace)">
<img src="img/submit.png" class="ns_btn"> <img src="img/submit.png" class="ns_btn">
</a> </a>
<button type="button" class="btn btn-default btn-sm" <button type="button" class="btn btn-primary btn-sm"
ng-show="namespace.viewType == 'table' && namespace.hasModifyPermission" ng-show="namespace.viewType == 'table' && namespace.hasModifyPermission"
data-toggle="modal" data-target="#itemModal" data-toggle="modal" data-target="#itemModal"
ng-click="createItem(namespace)"> ng-click="createItem(namespace)">
...@@ -277,7 +277,8 @@ ...@@ -277,7 +277,8 @@
data-toggle="modal" data-target="#itemModal" data-toggle="modal" data-target="#itemModal"
ng-click="editItem(namespace, config.item)" ng-click="editItem(namespace, config.item)"
ng-show="namespace.hasModifyPermission"> ng-show="namespace.hasModifyPermission">
<img style="margin-left: 5px;" src="img/cancel.png" data-toggle="modal" data-target="#deleteConfirmDialog" <img style="margin-left: 5px;" src="img/cancel.png" data-toggle="modal"
data-target="#deleteConfirmDialog"
data-tooltip="tooltip" data-placement="bottom" title="删除" data-tooltip="tooltip" data-placement="bottom" title="删除"
ng-click="preDeleteItem(namespace, config.item.id)" ng-click="preDeleteItem(namespace, config.item.id)"
ng-show="namespace.hasModifyPermission"> ng-show="namespace.hasModifyPermission">
...@@ -289,72 +290,114 @@ ...@@ -289,72 +290,114 @@
</div> </div>
<!--历史修改视图--> <!--历史修改视图-->
<div class="J_historyview history-view" <div class="J_historyview history-view" ng-show="namespace.viewType == 'history'">
ng-show="namespace.viewType == 'history'"> <div class="text-right">
<div class="row"> <span class="label label-primary change-type-mark">&nbsp;</span>
<div class="col-md-11 col-md-offset-1 list" style=""> <small>新增&nbsp;</small>
<div class="media"> <span class="label label-info change-type-mark">&nbsp;</span>
<img src="img/history.png"/> <small>更新&nbsp;</small>
<span class="label label-danger change-type-mark">&nbsp;</span>
<div class="row"> <small>删除&nbsp;</small>
<div class="col-md-10"><h5 class="media-heading">2016-02-23
12:23
王玉</h5>
<p>
修改comment
</p></div>
<div class="col-md-2 text-right">
<button class="btn btn-default" type="submit">查看修改内容
</button>
</div>
</div>
<hr>
</div>
<div class="media">
<img src="img/history.png"/>
<div class="row">
<div class="col-md-10"><h5 class="media-heading">2016-02-23
12:23
王玉</h5>
<p>
修改comment
</p></div>
<div class="col-md-2 text-right">
<button class="btn btn-default" type="submit">查看修改内容
</button>
</div>
</div>
</div>
</div>
</div> </div>
</div> <div class="media" ng-repeat="commits in namespace.commits">
</div> <div class="media-left">
</div> <h4 class="media-heading" ng-bind="commits.dataChangeCreatedBy"></h4>
</div>
<!-- commit modify config modal--> <div class="media-body">
<div class="modal fade" id="commitModal" tabindex="-1" role="dialog"> <h5 class="media-heading"
<div class="modal-dialog" role="document"> ng-bind="commits.dataChangeCreatedTime | date: 'yyyy-MM-dd HH:mm:ss'"></h5>
<div class="modal-content"> <table class="table table-bordered table-striped text-center table-hover">
<div class="modal-header panel-primary"> <thead>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span <tr>
aria-hidden="true">&times;</span></button> <th>
<h4 class="modal-title">Commit changes</h4> Type
</div> </th>
<div class="modal-body"> <th>
<textarea rows="4" class="form-control" style="width:570px;" Key
placeholder="Add an optional extended description..." </th>
ng-model="commitComment"></textarea> <th>
Old Value
</th>
<th>
New Value
</th>
<th>
Comment
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in commits.changeSets.createItems" ng-show="item.key || item.comment">
<td width="2%">
<span class="label label-primary change-type-mark">&nbsp;</span>
</td>
<td width="20%" title="{{item.key}}">
<span ng-bind="item.key | limitTo: 250"></span>
<span ng-bind="item.key.length > 250 ? '...' :''"></span>
</td>
<td width="30%">
</td>
<td width="30%" title="{{item.value}}">
<span ng-bind="item.value | limitTo: 250"></span>
<span ng-bind="item.value.length > 250 ? '...': ''"></span>
</td>
<td width="18%" title="{{item.comment}}">
<span ng-bind="item.comment | limitTo: 250"></span>
<span ng-bind="item.comment.length > 250 ?'...' : ''"></span>
</td>
</tr>
<tr ng-repeat="item in commits.changeSets.updateItems">
<td width="2%">
<span class="label label-info change-type-mark">&nbsp;</span>
</td>
<td width="20%" title="{{item.newItem.key}}">
<span ng-bind="item.newItem.key | limitTo: 250"></span>
<span ng-bind="item.newItem.key.length > 250 ? '...' :''"></span>
</td>
<td width="30%" title="{{item.oldItem.value}}">
<span ng-bind="item.oldItem.value | limitTo: 250"></span>
<span ng-bind="item.oldItem.value.length > 250 ? '...': ''"></span>
</td>
<td width="30%" title="{{item.newItem.value}}">
<span ng-bind="item.newItem.value | limitTo: 250"></span>
<span ng-bind="item.newItem.value.length > 250 ? '...': ''"></span>
</td>
<td width="18%" title="{{item.newItem.comment}}">
<span ng-bind="item.newItem.comment | limitTo: 250"></span>
<span ng-bind="item.newItem.comment.length > 250 ?'...' : ''"></span>
</td>
</tr>
<tr ng-repeat="item in commits.changeSets.deleteItems" ng-show="item.key || item.comment">
<td width="2%">
<span class="label label-danger change-type-mark">&nbsp;</span>
</td>
<td width="20%" title="{{item.key}}">
<span ng-bind="item.key | limitTo: 250"></span>
<span ng-bind="item.key.length > 250 ? '...' :''"></span>
</td>
<td width="30%">
</td>
<td width="30%" title="{{item.value}}">
<span ng-bind="item.value | limitTo: 250"></span>
<span ng-bind="item.value.length > 250 ? '...': ''"></span>
</td>
<td width="18%" title="{{item.comment}}">
<span ng-bind="item.comment | limitTo: 250"></span>
<span ng-bind="item.comment.length > 250 ?'...' : ''"></span>
</td>
</tr>
</tbody>
</table>
</div>
<hr>
</div> </div>
<div class="modal-footer"> <div class="text-center">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button> <button type="button" class="btn btn-default" ng-show="!namespace.hasLoadAllCommit"
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="loadCommitHistory(namespace)">加载更多
ng-click="commitChange()"> <span class="glyphicon glyphicon-menu-down"></span></button>
提交
</button>
</div> </div>
</div> </div>
</div> </div>
...@@ -410,7 +453,7 @@ ...@@ -410,7 +453,7 @@
ng-if="config.item.key && config.isModified"> ng-if="config.item.key && config.isModified">
<td width="20%" title="{{config.item.key}}"> <td width="20%" title="{{config.item.key}}">
<span class="label label-danger" <span class="label label-danger"
ng-show="!config.newValue">deleted</span> ng-show="!config.item.lastModifiedBy">deleted</span>
<span ng-bind="config.item.key | limitTo: 250"></span> <span ng-bind="config.item.key | limitTo: 250"></span>
<span ng-bind="config.item.key.length > 250 ? '...' :''"></span> <span ng-bind="config.item.key.length > 250 ? '...' :''"></span>
</td> </td>
...@@ -586,6 +629,7 @@ ...@@ -586,6 +629,7 @@
<script type="application/javascript" src="scripts/services/UserService.js"></script> <script type="application/javascript" src="scripts/services/UserService.js"></script>
<script type="application/javascript" src="scripts/services/ConfigService.js"></script> <script type="application/javascript" src="scripts/services/ConfigService.js"></script>
<script type="application/javascript" src="scripts/services/PermissionService.js"></script> <script type="application/javascript" src="scripts/services/PermissionService.js"></script>
<script type="application/javascript" src="scripts/services/CommitService.js"></script>
<script type="application/javascript" src="scripts/AppUtils.js"></script> <script type="application/javascript" src="scripts/AppUtils.js"></script>
......
application_module.controller("ConfigNamespaceController", application_module.controller("ConfigNamespaceController",
['$rootScope', '$scope', '$location', 'toastr', 'AppUtil', 'ConfigService', 'PermissionService', ['$rootScope', '$scope', '$location', 'toastr', 'AppUtil', 'ConfigService', 'PermissionService',
function ($rootScope, $scope, $location, toastr, AppUtil, ConfigService, PermissionService) { 'CommitService',
function ($rootScope, $scope, $location, toastr, AppUtil, ConfigService, PermissionService, CommitService) {
var namespace_view_type = { var namespace_view_type = {
TEXT: 'text', TEXT: 'text',
...@@ -78,14 +79,41 @@ application_module.controller("ConfigNamespaceController", ...@@ -78,14 +79,41 @@ application_module.controller("ConfigNamespaceController",
}); });
$scope.switchView = function (namespace, viewType) { $scope.switchView = function (namespace, viewType) {
namespace.viewType = viewType;
if (namespace_view_type.TEXT == viewType) { if (namespace_view_type.TEXT == viewType) {
namespace.text = parseModel2Text(namespace); namespace.text = parseModel2Text(namespace);
} else if (namespace_view_type.TABLE == viewType) { } else if (namespace_view_type.TABLE == viewType) {
} else {
$scope.loadCommitHistory(namespace);
} }
namespace.viewType = viewType;
}; };
$scope.loadCommitHistory = function(namespace) {
if (!namespace.commits){
namespace.commits = [];
namespace.commitPage = 0;
}
CommitService.find_commits($rootScope.pageContext.appId,
$rootScope.pageContext.env,
$rootScope.pageContext.clusterName,
namespace.namespace.namespaceName,
namespace.commitPage)
.then(function (result) {
if (result.length == 0){
namespace.hasLoadAllCommit = true;
}
for (var i = 0; i < result.length; i++) {
//to json
result[i].changeSets = JSON.parse(result[i].changeSets);
namespace.commits.push(result[i]);
}
namespace.commitPage += 1;
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "加载提交历史记录出错");
});
}
var MAX_ROW_SIZE = 30; var MAX_ROW_SIZE = 30;
var APPEND_ROW_SIZE = 8; var APPEND_ROW_SIZE = 8;
//把表格内容解析成文本 //把表格内容解析成文本
...@@ -118,28 +146,20 @@ application_module.controller("ConfigNamespaceController", ...@@ -118,28 +146,20 @@ application_module.controller("ConfigNamespaceController",
return result; return result;
} }
$scope.toCommitNamespace = {};
$scope.setCommitNamespace = function (namespace) {
$scope.toCommitNamespace = namespace;
};
$scope.commitComment = '';
//更新配置 //更新配置
$scope.commitChange = function () { $scope.commitChange = function (namespace) {
ConfigService.modify_items($scope.pageContext.appId, $scope.pageContext.env, ConfigService.modify_items($scope.pageContext.appId, $scope.pageContext.env,
$scope.pageContext.clusterName, $scope.pageContext.clusterName,
$scope.toCommitNamespace.namespace.namespaceName, namespace.namespace.namespaceName,
$scope.toCommitNamespace.editText, namespace.editText,
$scope.toCommitNamespace.namespace.id, namespace.namespace.id).then(
$scope.commitComment).then(
function (result) { function (result) {
toastr.success("更新成功"); toastr.success("更新成功");
//refresh all namespace items //refresh all namespace items
$rootScope.refreshNamespaces(); $rootScope.refreshNamespaces();
$scope.toCommitNamespace.commited = true; namespace.commited = true;
$scope.toggleTextEditStatus($scope.toCommitNamespace); $scope.toggleTextEditStatus(namespace);
}, function (result) { }, function (result) {
toastr.error(AppUtil.errorMsg(result), "更新失败"); toastr.error(AppUtil.errorMsg(result), "更新失败");
...@@ -151,12 +171,12 @@ application_module.controller("ConfigNamespaceController", ...@@ -151,12 +171,12 @@ application_module.controller("ConfigNamespaceController",
$scope.toggleTextEditStatus = function (namespace) { $scope.toggleTextEditStatus = function (namespace) {
namespace.isTextEditing = !namespace.isTextEditing; namespace.isTextEditing = !namespace.isTextEditing;
if (namespace.isTextEditing) {//切换为编辑状态 if (namespace.isTextEditing) {//切换为编辑状态
$scope.toCommitNamespace.commited = false; namespace.commited = false;
namespace.backupText = namespace.text; namespace.backupText = namespace.text;
namespace.editText = parseModel2Text(namespace, 'edit'); namespace.editText = parseModel2Text(namespace, 'edit');
} else { } else {
if (!$scope.toCommitNamespace.commited) {//取消编辑,则复原 if (!namespace.commited) {//取消编辑,则复原
namespace.text = namespace.backupText; namespace.text = namespace.backupText;
} }
} }
...@@ -286,15 +306,19 @@ application_module.controller("ConfigNamespaceController", ...@@ -286,15 +306,19 @@ application_module.controller("ConfigNamespaceController",
}); });
} else if ($scope.tableViewOperType == TABLE_VIEW_OPER_TYPE.UPDATE) { } else if ($scope.tableViewOperType == TABLE_VIEW_OPER_TYPE.UPDATE) {
if (!$scope.item.value){
$scope.item.value = "";
}
if (!$scope.item.comment){
$scope.item.comment = "";
}
ConfigService.update_item($rootScope.pageContext.appId, ConfigService.update_item($rootScope.pageContext.appId,
cluster.env, cluster.env,
cluster.name, cluster.name,
toOperationNamespaceName, toOperationNamespaceName,
$scope.item).then( $scope.item).then(
function (result) { function (result) {
toastr.success("[" + cluster.env + "," + cluster.name + "]", toastr.success("更新成功, 如需生效请发布");
"更新成功");
itemModal.modal('hide'); itemModal.modal('hide');
$rootScope.refreshNamespaces(namespace_view_type.TABLE); $rootScope.refreshNamespaces(namespace_view_type.TABLE);
}, function (result) { }, function (result) {
......
appService.service('CommitService', ['$resource', '$q', function ($resource, $q) {
var commit_resource = $resource('', {}, {
find_commits: {
method: 'GET',
isArray: true,
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/commits?page=:page'
}
});
return {
find_commits: function (appId, env, clusterName, namespaceName, page) {
var d = $q.defer();
commit_resource.find_commits({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName,
page: page
},
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
}
}
}]);
...@@ -174,7 +174,6 @@ table th { ...@@ -174,7 +174,6 @@ table th {
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
} }
.tocify-header { .tocify-header {
font-size: 14px; font-size: 14px;
} }
...@@ -206,12 +205,12 @@ table th { ...@@ -206,12 +205,12 @@ table th {
border: 0px; border: 0px;
} }
.config-item-container .second-panel-heading .ns_btn{ .config-item-container .second-panel-heading .ns_btn {
width: 25px; width: 25px;
height: 25px; height: 25px;
} }
.config-item-container .second-panel-heading .nav-tabs .node_active{ .config-item-container .second-panel-heading .nav-tabs .node_active {
border-bottom: 3px #1b6d85 solid; border-bottom: 3px #1b6d85 solid;
} }
...@@ -221,26 +220,26 @@ table th { ...@@ -221,26 +220,26 @@ table th {
overflow: scroll; overflow: scroll;
} }
.config-item-container .panel-heading button img{ .config-item-container .panel-heading button img {
width: 12px; width: 12px;
height: 12px; height: 12px;
} }
.config-item-container .panel-heading a img{ .config-item-container .panel-heading a img {
width: 12px; width: 12px;
height: 12px; height: 12px;
} }
.config-item-container .panel-heading li img{ .config-item-container .panel-heading li img {
width: 12px; width: 12px;
height: 12px; height: 12px;
} }
.config-item-container .second-panel-heading{ .config-item-container .second-panel-heading {
height: 45px; height: 45px;
} }
.config-item-container .second-panel-heading a{ .config-item-container .second-panel-heading a {
height: 35px; height: 35px;
color: #555; color: #555;
font-size: 13px; font-size: 13px;
...@@ -248,11 +247,11 @@ table th { ...@@ -248,11 +247,11 @@ table th {
} }
.second-panel-heading .nav-tabs{ .second-panel-heading .nav-tabs {
border-bottom: 0px; border-bottom: 0px;
} }
.namespace-view-table td img{ .namespace-view-table td img {
cursor: pointer; cursor: pointer;
width: 23px; width: 23px;
height: 23px; height: 23px;
...@@ -272,27 +271,8 @@ table th { ...@@ -272,27 +271,8 @@ table th {
} }
.history-view { .history-view {
padding: 50px 20px; padding: 10px 20px;
}
.history-view .commit {
padding: 5px 15px;
border: 1px solid #ddd;
}
.history-view img {
position: absolute;
left: -28px;
}
.history-view .media .row {
padding-left: 35px;
}
.history-view .list {
position: relative;
border-left: 3px solid #ddd;
} }
.line { .line {
...@@ -380,7 +360,12 @@ table th { ...@@ -380,7 +360,12 @@ table th {
padding: 20px 15px padding: 20px 15px
} }
.user-container .user-info{ .user-container .user-info {
margin-left: 5px; margin-left: 5px;
} }
.change-type-mark {
width: 5px;
height: 5px;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册