提交 8720978c 编写于 作者: L lepdou

create app & release config

上级 f7191bfa
......@@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.ctrip.apollo.biz.entity.App;
import com.ctrip.apollo.biz.service.AdminService;
import com.ctrip.apollo.biz.service.AppService;
import com.ctrip.apollo.biz.utils.BeanUtils;
import com.ctrip.apollo.core.dto.AppDTO;
......@@ -25,11 +26,13 @@ public class AppController {
@Autowired
private AppService appService;
@Autowired
private AdminService adminService;
@RequestMapping(path = "/apps/", method = RequestMethod.POST)
@RequestMapping(path = "/apps", method = RequestMethod.POST)
public ResponseEntity<AppDTO> create(@RequestBody AppDTO dto) {
App entity = BeanUtils.transfrom(App.class, dto);
entity = appService.save(entity);
entity = adminService.createNewApp(entity);
dto = BeanUtils.transfrom(AppDTO.class, entity);
return ResponseEntity.status(HttpStatus.CREATED).body(dto);
}
......
package com.ctrip.apollo.biz.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.stereotype.Service;
import org.springframework.boot.actuate.metrics.CounterService;
import com.ctrip.apollo.biz.entity.App;
import com.ctrip.apollo.biz.entity.AppNamespace;
import com.ctrip.apollo.biz.entity.Cluster;
......@@ -12,6 +12,9 @@ import com.ctrip.apollo.biz.repository.AppNamespaceRepository;
import com.ctrip.apollo.biz.repository.AppRepository;
import com.ctrip.apollo.biz.repository.ClusterRepository;
import com.ctrip.apollo.biz.repository.NamespaceRepository;
import com.ctrip.apollo.core.ConfigConsts;
import java.util.Date;
@Service
public class AdminService {
......@@ -27,36 +30,56 @@ public class AdminService {
@Autowired
private ClusterRepository clusterRepository;
@Autowired
private CounterService counter;
public App createNewApp(String appId, String appName, String ownerName, String ownerEmail,
String namespace) {
public App createNewApp(App app) {
counter.increment("admin.createNewApp.start");
App app = new App();
app.setAppId(appId);
app.setName(appName);
app.setOwnerName(ownerName);
app.setOwnerEmail(ownerEmail);
appRepository.save(app);
String createBy = app.getDataChangeCreatedBy();
App createdApp = appRepository.save(app);
String appId = createdApp.getAppId();
createDefaultAppNamespace(appId, createBy);
createDefaultCluster(appId, createBy);
createDefaultNamespace(appId, createBy);
counter.increment("admin.createNewApp.success");
return app;
}
private void createDefaultAppNamespace(String appId, String createBy){
AppNamespace appNs = new AppNamespace();
appNs.setAppId(appId);
appNs.setName(namespace);
appNs.setName(ConfigConsts.NAMESPACE_APPLICATION);
appNs.setComment("default app namespace");
appNs.setDataChangeCreatedBy(createBy);
appNs.setDataChangeCreatedTime(new Date());
appNs.setDataChangeLastModifiedBy(createBy);
appNamespaceRepository.save(appNs);
}
private void createDefaultCluster(String appId, String createBy){
Cluster cluster = new Cluster();
cluster.setName("default");
cluster.setName(ConfigConsts.CLUSTER_NAME_DEFAULT);
cluster.setAppId(appId);
cluster.setDataChangeCreatedBy(createBy);
cluster.setDataChangeCreatedTime(new Date());
cluster.setDataChangeLastModifiedBy(createBy);
clusterRepository.save(cluster);
}
private void createDefaultNamespace(String appId, String createBy){
Namespace ns = new Namespace();
ns.setAppId(appId);
ns.setClusterName(cluster.getName());
ns.setNamespaceName(namespace);
ns.setClusterName(ConfigConsts.CLUSTER_NAME_DEFAULT);
ns.setNamespaceName(ConfigConsts.NAMESPACE_APPLICATION);
ns.setDataChangeCreatedBy(createBy);
ns.setDataChangeCreatedTime(new Date());
ns.setDataChangeLastModifiedBy(createBy);
namespaceRepository.save(ns);
counter.increment("admin.createNewApp.success");
return app;
}
}
......@@ -20,6 +20,7 @@ public class ItemSetService {
for (ItemDTO item : changeSet.getCreateItems()) {
Item entity = BeanUtils.transfrom(Item.class, item);
entity.setDataChangeCreatedBy(changeSet.getModifyBy());
entity.setDataChangeLastModifiedBy(changeSet.getModifyBy());
itemRepository.save(entity);
}
}
......
package com.ctrip.apollo.biz.service;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
......@@ -53,6 +54,9 @@ public class ReleaseService {
}
Release release = new Release();
release.setDataChangeCreatedTime(new Date());
release.setDataChangeCreatedBy(name);
release.setDataChangeLastModifiedBy(name);
release.setName(name);
release.setComment(comment);
release.setAppId(appId);
......
package com.ctrip.apollo.biz.service;
import java.util.Date;
import java.util.List;
import org.junit.Assert;
......@@ -27,12 +28,17 @@ public class AdminServiceTest {
@Test
public void testCreateNewApp() {
String appId = "someAppId";
String appName = "someAppName";
String ownerName = "someOwnerName";
String ownerEmail = "someOwnerName@ctrip.com";
String namespace = "someNamespace";
App app = adminService.createNewApp(appId, appName, ownerName, ownerEmail, namespace);
App app = new App();
app.setAppId(appId);
app.setName("someAppName");
String owner = "someOwnerName";
app.setOwnerName(owner);
app.setOwnerEmail("someOwnerName@ctrip.com");
app.setDataChangeCreatedBy(owner);
app.setDataChangeLastModifiedBy(owner);
app.setDataChangeCreatedTime(new Date());
app = adminService.createNewApp(app);
Assert.assertEquals(appId, app.getAppId());
List<Cluster> clusters = viewService.findClusters(app.getAppId());
......@@ -41,6 +47,6 @@ public class AdminServiceTest {
List<Namespace> namespaces = viewService.findNamespaces(appId, clusters.get(0).getName());
Assert.assertEquals(1, namespaces.size());
Assert.assertEquals(namespace, namespaces.get(0).getNamespaceName());
Assert.assertEquals("application", namespaces.get(0).getNamespaceName());
}
}
package com.ctrip.apollo.biz.service;
import java.util.Date;
import java.util.List;
import org.junit.Assert;
......@@ -30,10 +31,16 @@ public class PrivilegeServiceTest {
@Test
public void testAddAndRemovePrivilege() {
App newApp = adminService.createNewApp(String.valueOf(System.currentTimeMillis()),
"new app " + System.currentTimeMillis(), "owner " + System.currentTimeMillis(),
"owner " + System.currentTimeMillis() + "@ctrip.com",
"namespace " + System.currentTimeMillis());
App app = new App();
app.setAppId(String.valueOf(System.currentTimeMillis()));
app.setName("new app " + System.currentTimeMillis());
String owner = "owner " + System.currentTimeMillis();
app.setOwnerName(owner);
app.setOwnerEmail("owner " + System.currentTimeMillis() + "@ctrip.com");
app.setDataChangeCreatedBy(owner);
app.setDataChangeLastModifiedBy(owner);
app.setDataChangeCreatedTime(new Date());
App newApp = adminService.createNewApp(app);
List<Cluster> clusters = viewService.findClusters(newApp.getAppId());
List<Namespace> namespaces =
......@@ -55,11 +62,16 @@ public class PrivilegeServiceTest {
@Test
public void testCheckPrivilege() {
App newApp = adminService.createNewApp(String.valueOf(System.currentTimeMillis()),
"new app " + System.currentTimeMillis(), "owner " + System.currentTimeMillis(),
"owner " + System.currentTimeMillis() + "@ctrip.com",
"namespace " + System.currentTimeMillis());
App app = new App();
app.setAppId(String.valueOf(System.currentTimeMillis()));
app.setName("new app " + System.currentTimeMillis());
String owner = "owner " + System.currentTimeMillis();
app.setOwnerName(owner);
app.setOwnerEmail("owner " + System.currentTimeMillis() + "@ctrip.com");
app.setDataChangeCreatedBy(owner);
app.setDataChangeLastModifiedBy(owner);
app.setDataChangeCreatedTime(new Date());
App newApp = adminService.createNewApp(app);
List<Cluster> clusters = viewService.findClusters(newApp.getAppId());
List<Namespace> namespaces =
viewService.findNamespaces(newApp.getAppId(), clusters.get(0).getName());
......
package com.ctrip.apollo.core.dto;
import java.util.Date;
public class AppDTO {
private long id;
......@@ -12,6 +14,12 @@ public class AppDTO {
private String ownerEmail;
private String dataChangeCreatedBy;
private Date dataChangeCreatedTime;
private String dataChangeLastModifiedBy;
public String getAppId() {
return appId;
}
......@@ -51,4 +59,28 @@ public class AppDTO {
public void setOwnerName(String ownerName) {
this.ownerName = ownerName;
}
public String getDataChangeCreatedBy() {
return dataChangeCreatedBy;
}
public void setDataChangeCreatedBy(String dataChangeCreatedBy) {
this.dataChangeCreatedBy = dataChangeCreatedBy;
}
public Date getDataChangeCreatedTime() {
return dataChangeCreatedTime;
}
public void setDataChangeCreatedTime(Date dataChangeCreatedTime) {
this.dataChangeCreatedTime = dataChangeCreatedTime;
}
public String getDataChangeLastModifiedBy() {
return dataChangeLastModifiedBy;
}
public void setDataChangeLastModifiedBy(String dataChangeLastModifiedBy) {
this.dataChangeLastModifiedBy = dataChangeLastModifiedBy;
}
}
......@@ -16,6 +16,10 @@ public class ItemDTO{
private int lineNum;
private String dataChangeCreatedBy;
private Date dataChangeCreatedTime;
private String dataChangeLastModifiedBy;
private Date dataChangeLastModifiedTime;
......@@ -93,6 +97,22 @@ public class ItemDTO{
this.dataChangeLastModifiedTime = dataChangeLastModifiedTime;
}
public String getDataChangeCreatedBy() {
return dataChangeCreatedBy;
}
public void setDataChangeCreatedBy(String dataChangeCreatedBy) {
this.dataChangeCreatedBy = dataChangeCreatedBy;
}
public Date getDataChangeCreatedTime() {
return dataChangeCreatedTime;
}
public void setDataChangeCreatedTime(Date dataChangeCreatedTime) {
this.dataChangeCreatedTime = dataChangeCreatedTime;
}
@Override
public String toString() {
return "ItemDTO{" +
......@@ -102,9 +122,10 @@ public class ItemDTO{
", value='" + value + '\'' +
", comment='" + comment + '\'' +
", lineNum=" + lineNum +
", dataChangeCreatedBy='" + dataChangeCreatedBy + '\'' +
", dataChangeCreatedTime=" + dataChangeCreatedTime +
", dataChangeLastModifiedBy='" + dataChangeLastModifiedBy + '\'' +
", dataChangeLastModifiedTime=" + dataChangeLastModifiedTime +
'}';
}
}
......@@ -12,9 +12,13 @@ import com.ctrip.apollo.core.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import java.util.Arrays;
import java.util.List;
......@@ -22,6 +26,7 @@ import java.util.List;
@Service
public class AdminServiceAPI {
private static final Logger logger = LoggerFactory.getLogger(AdminServiceAPI.class);
@Service
......@@ -32,6 +37,10 @@ public class AdminServiceAPI {
public List<AppDTO> getApps(Apollo.Env env) {
return Arrays.asList(restTemplate.getForObject(getAdminServiceHost(env) + APP_API, AppDTO[].class));
}
public AppDTO save(Apollo.Env env, AppDTO app) {
return restTemplate.postForEntity(getAdminServiceHost(env) + APP_API, app, AppDTO.class).getBody();
}
}
......@@ -76,11 +85,12 @@ public class AdminServiceAPI {
public void updateItems(String appId, Apollo.Env env, String clusterName, String namespace,
ItemChangeSets changeSets) {
if (StringUtils.isContainEmpty(appId, clusterName, namespace)){
if (StringUtils.isContainEmpty(appId, clusterName, namespace)) {
return;
}
restTemplate.postForEntity(getAdminServiceHost(env) + String.format("apps/%s/clusters/%s/namespaces/%s/itemset",
appId,clusterName, namespace), changeSets, Void.class);
appId, clusterName, namespace), changeSets,
Void.class);
}
......@@ -107,14 +117,33 @@ public class AdminServiceAPI {
if (StringUtils.isContainEmpty(appId, clusterName, namespace)) {
return null;
}
try {
ReleaseDTO releaseDTO = restTemplate.getForObject(getAdminServiceHost(env) + String
.format("apps/%s/clusters/%s/namespaces/%s/releases/latest", appId,
clusterName, namespace), ReleaseDTO.class);
return releaseDTO;
}catch (HttpClientErrorException e){
logger.warn(" call [ReleaseAPI.loadLatestRelease] and return not fount exception.app id:{}, env:{}, clusterName:{}, namespace:{}",
appId, env, clusterName, namespace);
try {
ReleaseDTO releaseDTO = restTemplate.getForObject(getAdminServiceHost(env) + String
.format("apps/%s/clusters/%s/namespaces/%s/releases/latest", appId,
clusterName, namespace), ReleaseDTO.class);
return releaseDTO;
} catch (HttpClientErrorException e) {
logger.warn(
" call [ReleaseAPI.loadLatestRelease] and return not fount exception.app id:{}, env:{}, clusterName:{}, namespace:{}",
appId, env, clusterName, namespace);
return null;
}
}
public ReleaseDTO release(String appId, Apollo.Env env, String clusterName, String namespace, String releaseBy,
String comment) {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
parameters.add("name", releaseBy);
parameters.add("comment", comment);
HttpEntity<MultiValueMap<String, String>> entity =
new HttpEntity<MultiValueMap<String, String>>(parameters, null);
ResponseEntity<ReleaseDTO> response = restTemplate.postForEntity(getAdminServiceHost(env) + String.
format("apps/%s/clusters/%s/namespaces/%s/releases", appId, clusterName, namespace),
entity, ReleaseDTO.class);
if (response.getStatusCode() == HttpStatus.OK){
return response.getBody();
}else {
logger.error("release fail.id:{}, env:{}, clusterName:{}, namespace:{},releaseBy{}",appId, env, clusterName, namespace, releaseBy);
return null;
}
}
......
......@@ -2,12 +2,18 @@ package com.ctrip.apollo.portal.controller;
import com.google.common.base.Strings;
import com.ctrip.apollo.core.dto.AppDTO;
import com.ctrip.apollo.core.utils.StringUtils;
import com.ctrip.apollo.portal.entity.ClusterNavTree;
import com.ctrip.apollo.portal.service.AppService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
......@@ -26,6 +32,23 @@ public class AppController {
return appService.buildClusterNavTree(appId);
}
@RequestMapping(value = "", method = RequestMethod.POST, consumes = {"application/json"})
public ResponseEntity<AppDTO> create(@RequestBody AppDTO app) {
if (isInvalidApp(app)){
return ResponseEntity.badRequest().body(null);
}
AppDTO createdApp = appService.save(app);
if (createdApp != null){
return ResponseEntity.ok().body(createdApp);
}else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
}
private boolean isInvalidApp(AppDTO app) {
return StringUtils.isContainEmpty(app.getName(), app.getAppId(), app.getOwnerEmail(), app.getOwnerName());
}
}
......
......@@ -2,15 +2,20 @@ package com.ctrip.apollo.portal.controller;
import com.ctrip.apollo.Apollo;
import com.ctrip.apollo.core.dto.ReleaseDTO;
import com.ctrip.apollo.core.utils.StringUtils;
import com.ctrip.apollo.portal.entity.form.NamespaceModifyModel;
import com.ctrip.apollo.portal.entity.NamespaceVO;
import com.ctrip.apollo.portal.entity.SimpleMsg;
import com.ctrip.apollo.portal.entity.form.NamespaceReleaseModel;
import com.ctrip.apollo.portal.service.ConfigService;
import com.ctrip.apollo.portal.service.txtresolver.TextResolverResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
......@@ -34,13 +39,22 @@ public class ConfigController {
return configService.findNampspaces(appId, Apollo.Env.valueOf(env), clusterName);
}
@RequestMapping(value = "/apps/{appId}/env/{env}/clusters/{clusterName}/namespaces/{namespaceName}/modify", method = RequestMethod.GET)
public ResponseEntity<SimpleMsg> modifyConfigs(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName,
@PathVariable String namespaceName,
String configText) {
TextResolverResult result =
configService.resolve(appId, Apollo.Env.valueOf(env), clusterName, namespaceName, configText);
@RequestMapping(value = "/apps/{appId}/env/{env}/clusters/{clusterName}/namespaces/{namespaceName}/items",method = RequestMethod.PUT, consumes = {"application/json"})
public ResponseEntity<SimpleMsg> modifyItems(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName,
@RequestBody NamespaceModifyModel model) {
model.setAppId(appId);
model.setClusterName(clusterName);
model.setEnv(env);
model.setNamespaceName(namespaceName);
if (model == null || model.isInvalid()){
return ResponseEntity.badRequest().body(new SimpleMsg("form data exception."));
}
TextResolverResult result = configService.resolveConfigText(model);
if (result.isResolveSuccess()) {
return ResponseEntity.ok().body(new SimpleMsg("success"));
} else {
......@@ -48,4 +62,26 @@ public class ConfigController {
}
}
@RequestMapping(value = "/apps/{appId}/env/{env}/clusters/{clusterName}/namespaces/{namespaceName}/release", method = RequestMethod.POST, consumes = {"application/json"})
public ResponseEntity<SimpleMsg> createRelease(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName,
@RequestBody NamespaceReleaseModel model){
model.setAppId(appId);
model.setClusterName(clusterName);
model.setEnv(env);
model.setNamespaceName(namespaceName);
if (model == null || model.isInvalid()){
return ResponseEntity.badRequest().body(new SimpleMsg("form data exception."));
}
ReleaseDTO release = configService.release(model);
if (release == null){
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new SimpleMsg("oops! some error in server."));
}else {
return ResponseEntity.ok().body(new SimpleMsg("success"));
}
}
}
package com.ctrip.apollo.portal.entity.form;
public interface FormModel {
boolean isInvalid();
}
package com.ctrip.apollo.portal.entity.form;
import com.ctrip.apollo.Apollo;
import com.ctrip.apollo.core.utils.StringUtils;
public class NamespaceModifyModel implements FormModel{
private String appId;
private String env;
private String clusterName;
private String namespaceName;
private int namespaceId;
private String configText;
private String modifyBy;
@Override
public boolean isInvalid(){
return StringUtils.isContainEmpty(appId, env, clusterName, namespaceName, configText, modifyBy) || namespaceId <= 0;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public Apollo.Env getEnv() {
return Apollo.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;
}
public int getNamespaceId() {
return namespaceId;
}
public void setNamespaceId(int namespaceId) {
this.namespaceId = namespaceId;
}
public String getConfigText() {
return configText;
}
public void setConfigText(String configText) {
this.configText = configText;
}
public String getModifyBy() {
return modifyBy;
}
public void setModifyBy(String modifyBy) {
this.modifyBy = modifyBy;
}
}
package com.ctrip.apollo.portal.entity.form;
import com.ctrip.apollo.Apollo;
import com.ctrip.apollo.core.utils.StringUtils;
public class NamespaceReleaseModel implements FormModel{
private String appId;
private String env;
private String clusterName;
private String namespaceName;
private String releaseBy;
private String releaseComment;
@Override
public boolean isInvalid() {
return StringUtils.isContainEmpty(appId, env, clusterName, namespaceName, releaseBy);
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public Apollo.Env getEnv() {
return Apollo.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;
}
public String getReleaseBy() {
return releaseBy;
}
public void setReleaseBy(String releaseBy) {
this.releaseBy = releaseBy;
}
public String getReleaseComment() {
return releaseComment;
}
public void setReleaseComment(String releaseComment) {
this.releaseComment = releaseComment;
}
}
package com.ctrip.apollo.portal.service;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ctrip.apollo.Apollo.Env;
import com.ctrip.apollo.core.dto.AppDTO;
import com.ctrip.apollo.core.utils.StringUtils;
import com.ctrip.apollo.portal.PortalSettings;
import com.ctrip.apollo.portal.api.AdminServiceAPI;
import com.ctrip.apollo.portal.entity.ClusterNavTree;
import com.ctrip.apollo.portal.entity.SimpleMsg;
@Service
public class AppService {
private Logger logger = LoggerFactory.getLogger(AppService.class);
@Autowired
private ClusterService clusterService;
@Autowired
private PortalSettings portalSettings;
@Autowired
private AdminServiceAPI.AppAPI appAPI;
public ClusterNavTree buildClusterNavTree(String appId) {
ClusterNavTree tree = new ClusterNavTree();
......@@ -28,12 +40,20 @@ public class AppService {
clusterNode.setClusters(clusterService.findClusters(env, appId));
tree.addNode(clusterNode);
}
// ClusterNavTree.Node uatNode = new ClusterNavTree.Node(Apollo.Env.UAT);
// List<ClusterDTO> uatClusters = new LinkedList<>();
// uatClusters.add(defaultCluster);
// uatNode.setClusters(uatClusters);
// tree.addNode(uatNode);
return tree;
}
public AppDTO save(AppDTO app) {
String createBy = app.getOwnerName();
try {
app.setDataChangeCreatedBy(createBy);
app.setDataChangeCreatedTime(new Date());
app.setDataChangeLastModifiedBy(createBy);
return appAPI.save(Env.LOCAL, app);
} catch (Exception e) {
logger.error("oops! save app error. app id:{}", app.getAppId(), e);
return null;
}
}
}
......@@ -11,14 +11,18 @@ 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.utils.StringUtils;
import com.ctrip.apollo.portal.api.AdminServiceAPI;
import com.ctrip.apollo.portal.entity.form.NamespaceModifyModel;
import com.ctrip.apollo.portal.entity.NamespaceVO;
import com.ctrip.apollo.portal.entity.form.NamespaceReleaseModel;
import com.ctrip.apollo.portal.service.txtresolver.ConfigTextResolver;
import com.ctrip.apollo.portal.service.txtresolver.TextResolverResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
......@@ -41,6 +45,13 @@ public class ConfigService {
private ObjectMapper objectMapper = new ObjectMapper();
/**
* load cluster all namespace info with items
* @param appId
* @param env
* @param clusterName
* @return
*/
public List<NamespaceVO> findNampspaces(String appId, Apollo.Env env, String clusterName) {
List<NamespaceDTO> namespaces = groupAPI.findGroupsByAppAndCluster(appId, env, clusterName);
......@@ -65,39 +76,6 @@ public class ConfigService {
return namespaceVOs;
}
public TextResolverResult resolve(String appId, Apollo.Env env, String clusterName, String namespaceName,
String configText) {
TextResolverResult result = new TextResolverResult();
try {
result = resolver.resolve(configText, itemAPI.findItems(appId, env, clusterName, namespaceName));
} catch (Exception e) {
logger
.error("resolve config text error. app id:{}, env:{}, clusterName:{}, namespace:{}", appId, env, clusterName,
namespaceName, e);
result.setResolveSuccess(false);
result.setMsg("oops! server resolve config text error.");
return result;
}
if (result.isResolveSuccess()) {
try {
// TODO: 16/4/13
result.getChangeSets().setModifyBy("lepdou");
itemAPI.updateItems(appId, env, clusterName, namespaceName, result.getChangeSets());
} catch (Exception e) {
logger.error("resolve config text error. app id:{}, env:{}, clusterName:{}, namespace:{}", appId, env,
clusterName, namespaceName, e);
result.setResolveSuccess(false);
result.setMsg("oops! server update config error.");
return result;
}
} else {
logger.warn("resolve config text error by format error. app id:{}, env:{}, clusterName:{}, namespace:{},cause:{}",
appId,env, clusterName, namespaceName, result.getMsg());
}
return result;
}
private NamespaceVO parseNamespace(String appId, Apollo.Env env, String clusterName, NamespaceDTO namespace) {
NamespaceVO namespaceVO = new NamespaceVO();
......@@ -140,16 +118,75 @@ public class ConfigService {
}
private NamespaceVO.ItemVO parseItemVO(ItemDTO itemDTO, Map<String, String> releaseItems) {
String key = itemDTO.getKey();
NamespaceVO.ItemVO itemVO = new NamespaceVO.ItemVO();
itemVO.setItem(itemDTO);
String key = itemDTO.getKey();
String newValue = itemDTO.getValue();
String oldValue = releaseItems.get(key);
if (oldValue == null || !newValue.equals(oldValue)) {
if (!StringUtils.isEmpty(key) && (oldValue == null || !newValue.equals(oldValue))) {
itemVO.setModified(true);
itemVO.setOldValue(oldValue == null ? "" : oldValue);
itemVO.setNewValue(newValue);
}
return itemVO;
}
/**
* parse config text and update config items
* @return parse result
*/
public TextResolverResult resolveConfigText(NamespaceModifyModel model) {
String appId = model.getAppId();
Apollo.Env env = model.getEnv();
String clusterName = model.getClusterName();
String namespaceName = model.getNamespaceName();
long namespaceId = model.getNamespaceId();
String configText = model.getConfigText();
TextResolverResult result = new TextResolverResult();
try {
result = resolver.resolve( namespaceId, configText, itemAPI.findItems(appId, env, clusterName, namespaceName));
} catch (Exception e) {
logger
.error("resolve config text error. app id:{}, env:{}, clusterName:{}, namespace:{}", appId, env, clusterName,
namespaceName, e);
result.setResolveSuccess(false);
result.setMsg("oops! server resolveConfigText config text error.");
return result;
}
if (result.isResolveSuccess()) {
try {
ItemChangeSets changeSets = result.getChangeSets();
changeSets.setModifyBy(model.getModifyBy());
enrichChangeSetBaseInfo(changeSets);
itemAPI.updateItems(appId, env, clusterName, namespaceName, changeSets);
} catch (Exception e) {
logger.error("resolve config text error. app id:{}, env:{}, clusterName:{}, namespace:{}", appId, env,
clusterName, namespaceName, e);
result.setResolveSuccess(false);
result.setMsg("oops! server update config error.");
return result;
}
} else {
logger.warn("resolve config text error by format error. app id:{}, env:{}, clusterName:{}, namespace:{},cause:{}",
appId,env, clusterName, namespaceName, result.getMsg());
}
return result;
}
private void enrichChangeSetBaseInfo(ItemChangeSets changeSets){
for (ItemDTO item: changeSets.getCreateItems()){
item.setDataChangeCreatedTime(new Date());
}
}
/**
* release config items
* @return
*/
public ReleaseDTO release(NamespaceReleaseModel model){
return releaseAPI.release(model.getAppId(), model.getEnv(), model.getClusterName(), model.getNamespaceName(),
model.getReleaseBy(), model.getReleaseComment());
}
}
......@@ -5,10 +5,10 @@ import com.ctrip.apollo.core.dto.ItemDTO;
import java.util.List;
/**
* users can modify config in text mode.so need resolve text.
* users can modify config in text mode.so need resolveConfigText text.
*/
public interface ConfigTextResolver {
TextResolverResult resolve(String configText, List<ItemDTO> baseItems);
TextResolverResult resolve(long namespaceId, String configText, List<ItemDTO> baseItems);
}
......@@ -8,8 +8,10 @@ import com.ctrip.apollo.portal.util.BeanUtils;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* normal property file resolver.
......@@ -23,7 +25,7 @@ public class PropertyResolver implements ConfigTextResolver {
private static final String ITEM_SEPARATOR = "\n";
@Override
public TextResolverResult resolve(String configText, List<ItemDTO> baseItems) {
public TextResolverResult resolve(long namespaceId, String configText, List<ItemDTO> baseItems) {
TextResolverResult result = new TextResolverResult();
if (StringUtils.isEmpty(configText)){
......@@ -40,6 +42,12 @@ public class PropertyResolver implements ConfigTextResolver {
String[] newItems = configText.split(ITEM_SEPARATOR);
if (isHasRepeatKey(newItems)){
result.setResolveSuccess(false);
result.setMsg("config text has repeat key please check.");
return result;
}
ItemChangeSets changeSets = new ItemChangeSets();
result.setChangeSets(changeSets);
Map<Integer, String> newLineNumMapItem = new HashMap();//use for delete blank and comment item
......@@ -52,16 +60,16 @@ public class PropertyResolver implements ConfigTextResolver {
//comment item
if (isCommentItem(newItem)) {
handleCommentLine(oldItemByLine, newItem, lineCounter, changeSets);
handleCommentLine(namespaceId, oldItemByLine, newItem, lineCounter, changeSets);
//blank item
} else if (isBlankItem(newItem)) {
handleBlankLine(oldItemByLine, lineCounter, changeSets);
handleBlankLine(namespaceId, oldItemByLine, lineCounter, changeSets);
//normal line
//normal item
} else {
if (!handleNormalLine(oldKeyMapItem, newItem, lineCounter, result)) {
if (!handleNormalLine(namespaceId, oldKeyMapItem, newItem, lineCounter, result)) {
return result;
}
}
......@@ -77,42 +85,68 @@ public class PropertyResolver implements ConfigTextResolver {
return result;
}
private void handleCommentLine(ItemDTO oldItemByLine, String newItem, int lineCounter, ItemChangeSets changeSets) {
private boolean isHasRepeatKey(String[] newItems){
Set<String> keys = new HashSet<>();
int keyCount = 0;
for (String item: newItems){
if (!isCommentItem(item) && !isBlankItem(item)){
keyCount ++;
keys.add(parseKeyValueFromItem(item)[0]);
}
}
return keyCount > keys.size();
}
private String[] parseKeyValueFromItem(String item){
int kvSeparator = item.indexOf(KV_SEPARATOR);
if (kvSeparator == -1) {
return null;
}
String[] kv = new String[2];
kv[0] = item.substring(0, kvSeparator).trim();
kv[1] = item.substring(kvSeparator + 1, item.length()).trim();
return kv;
}
private void handleCommentLine(Long namespaceId, ItemDTO oldItemByLine, String newItem, int lineCounter, ItemChangeSets changeSets) {
String oldComment = oldItemByLine == null ? "" : oldItemByLine.getComment();
//create comment. implement update comment by delete old comment and create new comment
if (!(isCommentItem(oldItemByLine) && newItem.equals(oldComment))) {
changeSets.addCreateItem(buildCommentItem(0l, newItem, lineCounter));
changeSets.addCreateItem(buildCommentItem(0l, namespaceId, newItem, lineCounter));
}
}
private void handleBlankLine(ItemDTO oldItem, int lineCounter, ItemChangeSets changeSets) {
private void handleBlankLine(Long namespaceId, ItemDTO oldItem, int lineCounter, ItemChangeSets changeSets) {
if (!isBlankItem(oldItem)) {
changeSets.addCreateItem(buildBlankItem(0l, lineCounter));
changeSets.addCreateItem(buildBlankItem(0l, namespaceId, lineCounter));
}
}
private boolean handleNormalLine(Map<String, ItemDTO> keyMapOldItem, String newItem,
private boolean handleNormalLine(Long namespaceId, Map<String, ItemDTO> keyMapOldItem, String newItem,
int lineCounter, TextResolverResult result) {
ItemChangeSets changeSets = result.getChangeSets();
int kvSeparator = newItem.indexOf(KV_SEPARATOR);
if (kvSeparator == -1) {
String[] kv = parseKeyValueFromItem(newItem);
if (kv == null) {
result.setResolveSuccess(false);
result.setMsg(" line:" + lineCounter + " key value must separate by '='");
return false;
}
String newKey = newItem.substring(0, kvSeparator).trim();
String newValue = newItem.substring(kvSeparator + 1, newItem.length()).trim();
String newKey = kv[0];
String newValue = kv[1];
ItemDTO oldItem = keyMapOldItem.get(newKey);
if (oldItem == null) {//new item
changeSets.addCreateItem(buildNormalItem(0l, newKey, newValue, "", lineCounter));
} else if (!newValue.equals(oldItem.getValue())){//update item
changeSets.addCreateItem(buildNormalItem(0l, namespaceId, newKey, newValue, "", lineCounter));
} else if (!newValue.equals(oldItem.getValue()) || lineCounter != oldItem.getLineNum()){//update item
changeSets.addUpdateItem(
buildNormalItem(oldItem.getId(), newKey, newValue, oldItem.getComment(),
buildNormalItem(oldItem.getId(), namespaceId, newKey, newValue, oldItem.getComment(),
lineCounter));
}
keyMapOldItem.remove(newKey);
......@@ -154,23 +188,24 @@ public class PropertyResolver implements ConfigTextResolver {
//1. old is blank by now is not
//2.old is comment by now is not exist or modified
if ((isBlankItem(oldItem) && !isBlankItem(newItem))
|| isCommentItem(oldItem) && (newItem == null || !newItem.equals(oldItem))) {
|| isCommentItem(oldItem) && (newItem == null || !newItem.equals(oldItem.getComment()))) {
changeSets.addDeleteItem(oldItem);
}
}
}
private ItemDTO buildCommentItem(Long id, String comment, int lineNum) {
return buildNormalItem(id, "", "", comment, lineNum);
private ItemDTO buildCommentItem(Long id, Long namespaceId, String comment, int lineNum) {
return buildNormalItem(id, namespaceId, "", "", comment, lineNum);
}
private ItemDTO buildBlankItem(Long id, int lineNum) {
return buildNormalItem(id, "", "", "", lineNum);
private ItemDTO buildBlankItem(Long id, Long namespaceId, int lineNum) {
return buildNormalItem(id, namespaceId, "", "", "", lineNum);
}
private ItemDTO buildNormalItem(Long id, String key, String value, String comment, int lineNum) {
private ItemDTO buildNormalItem(Long id, Long namespaceId, String key, String value, String comment, int lineNum) {
ItemDTO item = new ItemDTO();
item.setId(id);
item.setNamespaceId(namespaceId);
item.setKey(key);
item.setValue(value);
item.setComment(comment);
......
......@@ -15,4 +15,4 @@ ctrip:
apollo:
portal:
env: local,dev
env: local
......@@ -5,9 +5,8 @@ create_app_module.controller('CreateAppController', ['$scope', '$window', 'toast
$scope.app = {
appId: 1001,
name: 'lepdou',
ownerPhone: '1111',
ownerMail: 'qqq@qq.com',
owner: 'le'
ownerEmail: 'qqq@qq.com',
ownerName: 'le'
};
$scope.save = function () {
......@@ -17,7 +16,11 @@ create_app_module.controller('CreateAppController', ['$scope', '$window', 'toast
$window.location.href = '/views/app.html?#appid=' + result.appId;
}, 1000);
}, function (result) {
toastr.error('添加失败!');
if (result.status == 400){
toastr.error('params error','添加失败!');
}else {
toastr.error('server error','添加失败!');
}
});
};
......
......@@ -3,29 +3,34 @@ application_module.controller("AppConfigController",
function ($scope, $location, toastr, AppService, ConfigService) {
var appId = $location.$$url.split("=")[1];
var currentUser = 'lepdou';
var pageContext = {
appId: appId,
env: 'LOCAL',
clusterName: 'default'
};
$scope.pageEnv = pageContext;
/////////////
$scope.pageContext = pageContext;
///////////// load cluster nav tree /////////
AppService.load_nav_tree($scope.pageEnv.appId).then(function (result) {
AppService.load_nav_tree($scope.pageContext.appId).then(function (result) {
var navTree = [];
var nodes = result.nodes;
nodes.forEach(function (item) {
var node = {};
//first nav
node.text = item.env;
node.selectable = false;
//second nav
var clusterNodes = [];
item.clusters.forEach(function (item) {
var clusterNode = {};
var clusterNode = {},
parentNode = [];
clusterNode.text = item.name;
parentNode.push(node.text);
clusterNode.tags = parentNode;
clusterNodes.push(clusterNode);
});
......@@ -36,33 +41,86 @@ application_module.controller("AppConfigController",
color: "#428bca",
showBorder: true,
data: navTree,
levels: 99
levels: 99,
onNodeSelected: function (event, data) {
$scope.pageContext.env = data.tags[0];
$scope.pageContext.clusterName = data.text;
refreshNamespaces();
}
});
}, function (result) {
toastr.error("加载导航出错:" + result);
});
///////////
/////////// namespace ////////////
var namespace_view_type = {
TEXT:'text',
TABLE: 'table',
LOG: 'log'
};
refreshNamespaces();
function refreshNamespaces(viewType) {
ConfigService.load_all_namespaces($scope.pageContext.appId, $scope.pageContext.env,
$scope.pageContext.clusterName, viewType).then(
function (result) {
$scope.namespaces = result;
//初始化视图
if ($scope.namespaces) {
$scope.namespaces.forEach(function (item) {
item.isModify = false;
if (!viewType){//default text view
$scope.switchView(item, namespace_view_type.TEXT);
}else if (viewType == namespace_view_type.TABLE){
item.viewType = namespace_view_type.TABLE;
}
item.isTextEditing = false;
})
}
}, function (result) {
toastr.error("加载配置信息出错");
});
}
////////////global view oper /////////////
$scope.switchView = function (namespace, viewType) {
ConfigService.load_all_namespaces($scope.pageEnv.appId, $scope.pageEnv.env,
$scope.pageEnv.clusterName).then(
function (result) {
$scope.namespaces = result;
if (namespace_view_type.TEXT == viewType) {
namespace.text = parseModel2Text(namespace);
} else if (namespace_view_type.TABLE == viewType) {
//初始化视图
if ($scope.namespaces) {
$scope.namespaces.forEach(function (item) {
item.isModify = false;
item.viewType = 'table';
item.isTextEditing = false;
})
}
namespace.viewType = viewType;
};
//把表格内容解析成文本
function parseModel2Text(namespace) {
if (!namespace.items) {
return "无配置信息";
}
var result = "";
namespace.items.forEach(function (item) {
if (item.item.key) {
result +=
item.item.key + " = " + item.item.value + "\n";
} else {
result += item.item.comment + "\n";
}
}, function (result) {
toastr.error("加载配置信息出错");
});
return result;
}
////////// text view oper /////////
$scope.draft = {};
//保存草稿
$scope.saveDraft = function (namespace) {
......@@ -71,12 +129,17 @@ application_module.controller("AppConfigController",
//更新配置
$scope.commitChange = function () {
ConfigService.modify_items($scope.pageEnv.appId, $scope.pageEnv.env, $scope.pageEnv.clusterName,
$scope.draft.namespace.namespaceName, $scope.draft.text).then(
ConfigService.modify_items($scope.pageContext.appId, $scope.pageContext.env, $scope.pageContext.clusterName,
$scope.draft.namespace.namespaceName, $scope.draft.text,
$scope.draft.namespace.id, 'lepdou').then(
function (result) {
toastr.success("更新成功");
//refresh all namespace items
refreshNamespaces();
$scope.draft.backupText = '';//清空备份文本
$scope.toggleTextEditStatus($scope.draft);
}, function (result) {
toastr.error(result.data.msg, "更新失败");
......@@ -84,7 +147,6 @@ application_module.controller("AppConfigController",
);
};
/////////
//文本编辑框状态切换
$scope.toggleTextEditStatus = function (namespace) {
namespace.isTextEditing = !namespace.isTextEditing;
......@@ -96,7 +158,10 @@ application_module.controller("AppConfigController",
}
}
};
////////// table view oper /////////
//查看旧值
$scope.queryOldValue = function (key, oldValue) {
$scope.queryKey = key;
if (oldValue == '') {
......@@ -105,40 +170,30 @@ application_module.controller("AppConfigController",
$scope.OldValue = oldValue;
}
};
$scope.switchView = function (namespace, viewType) {
if ('textarea' == viewType) {
namespace.text = parseTableModel2Text(namespace);
} else if ('table' == viewType) {
}
namespace.viewType = viewType;
/////// release ///////
var releaseNamespace = {};
$scope.prepareReleaseNamespace = function (namespace) {
releaseNamespace = namespace;
};
$scope.releaseComment = '';
$scope.release = function () {
ConfigService.release($scope.pageContext.appId, $scope.pageContext.env,
$scope.pageContext.clusterName,
releaseNamespace.namespace.namespaceName, currentUser,
$scope.releaseComment).then(
function (result) {
toastr.success("发布成功");
//refresh all namespace items
refreshNamespaces();
//把表格内容解析成文本
function parseTableModel2Text(namespace) {
if (!namespace.items) {
return "无配置信息";
}
var result = "";
namespace.items.forEach(function (item) {
// if (item.modified) {
// result += "**";
// }
if (item.item.key) {
result +=
item.item.key + " = " + item.item.value + "\n";
} else {
result += item.item.comment + "\n";
}
});
}, function (result) {
toastr.error(result.data.msg, "发布失败");
return result;
}
);
}
//把文本内容解析成表格
}]);
......@@ -6,12 +6,12 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
url: '/apps/:appId/env/:env/clusters/:clusterName/namespaces'
},
modify_items: {
method: 'GET',
isArray: false,
url: '/apps/:appId/env/:env/clusters/:clusterName/namespaces/:namespaceName/modify',
params: {
configText: '@configText'
}
method: 'PUT',
url: '/apps/:appId/env/:env/clusters/:clusterName/namespaces/:namespaceName/items'
},
release: {
method: 'POST',
url:'/apps/:appId/env/:env/clusters/:clusterName/namespaces/:namespaceName/release'
}
});
......@@ -29,17 +29,40 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
});
return d.promise;
},
modify_items: function (appId, env, clusterName, namespaceName, configText) {
modify_items: function (appId, env, clusterName, namespaceName, configText, namespaceId, modifyBy) {
var d = $q.defer();
config_source.modify_items({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName,
configText: configText
namespaceName: namespaceName
},
{
configText: configText,
namespaceId: namespaceId,
modifyBy: modifyBy
}, function (result) {
d.resolve(result);
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
},
release: function (appId, env, clusterName, namespaceName, releaseBy, comment) {
var d = $q.defer();
config_source.release({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName
}, {
releaseBy: releaseBy,
releaseComment: comment
}, function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
......
......@@ -72,27 +72,31 @@ table th {
height: 500px;
overflow: scroll;
}
.namespace-view-table{
max-height: 700px;
overflow: scroll;
}
.historyview {
.history-view {
padding: 50px 20px;
}
.historyview .commit {
.history-view .commit {
padding: 5px 15px;
border: 1px solid #ddd;
}
.historyview img {
.history-view img {
position: absolute;
left: -28px;
}
.historyview .media .row {
.history-view .media .row {
padding-left: 35px;
}
.historyview .list {
.history-view .list {
position: relative;
border-left: 3px solid #ddd;
}
......
......@@ -41,8 +41,10 @@
<div class="col-md-7">
<div class="btn-toolbar" role="toolbar" aria-label="...">
<div class="btn-group" role="group" aria-label="...">
<button type="button"
class="btn btn-default btn-sm J_tableview_btn">发布
<button type="button" data-toggle="modal" data-target="#releaseModal"
class="btn btn-default btn-sm J_tableview_btn"
ng-disabled="namespace.isTextEditing"
ng-click="prepareReleaseNamespace(namespace)">发布
</button>
<button type="button"
class="btn btn-default btn-sm J_tableview_btn">回滚
......@@ -65,7 +67,7 @@
<div class="btn-group" role="group" aria-label="...">
<button type="button"
class="btn btn-default btn-sm J_tableview_btn"
ng-click="switchView(namespace, 'textarea')">文本
ng-click="switchView(namespace, 'text')">文本
</button>
<button type="button"
class="btn btn-default btn-sm J_tableview_btn"
......@@ -86,7 +88,7 @@
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</a>
<a data-toggle="tooltip" data-placement="top" title="修改配置"
ng-show="!namespace.isTextEditing"
ng-show="!namespace.isTextEditing && namespace.viewType == 'text'"
ng-click="toggleTextEditStatus(namespace)">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
</a>
......@@ -99,65 +101,66 @@
</div>
</header>
<div ng-show="namespace.viewType == 'textarea'">
<textarea class="form-control" rows="20" ng-model="namespace.text"
<div ng-show="namespace.viewType == 'text'">
<textarea class="form-control" rows="30" ng-model="namespace.text"
ng-disabled="!namespace.isTextEditing">
{{namespace.text}}
</textarea>
</div>
<table class="table table-bordered text-center table-hover"
ng-show="namespace.viewType == 'table'">
<thead>
<tr>
<th>
Key
</th>
<th>
value
</th>
<th>
备注
</th>
<th>
最后修改人
</th>
<th>
最后修改时间
</th>
</tr>
</thead>
<tbody ng-repeat="config in namespace.items">
<tr ng-class="{warning:config.modified}" ng-if="config.item.key">
<td>
{{config.item.key}}
</td>
<td>
<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}}
</td>
<td>
{{config.item.comment}}
</td>
<td>
{{config.item.dataChangeLastModifiedBy}}
</td>
<td>
{{config.item.dataChangeLastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'}}
</td>
</tr>
</tbody>
</table>
<div class="namespace-view-table">
<table class="table table-bordered text-center table-hover"
ng-show="namespace.viewType == 'table'">
<thead>
<tr>
<th>
Key
</th>
<th>
value
</th>
<th>
备注
</th>
<th>
最后修改人
</th>
<th>
最后修改时间
</th>
</tr>
</thead>
<tbody ng-repeat="config in namespace.items">
<tr ng-class="{warning:config.modified}" ng-if="config.item.key">
<td>
{{config.item.key}}
</td>
<td>
<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}}
</td>
<td>
{{config.item.comment}}
</td>
<td>
{{config.item.dataChangeLastModifiedBy}}
</td>
<td>
{{config.item.dataChangeLastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'}}
</td>
</tr>
</tbody>
</table>
</div>
<!--历史修改视图-->
<div class="J_historyview historyview" ng-show="namespace.viewType == 'history'">
<div class="J_historyview history-view" ng-show="namespace.viewType == 'history'">
<div class="row">
<div class="col-md-11 col-md-offset-1 list" style="">
<div class="media">
......@@ -208,7 +211,7 @@
</div>
<!-- Modal -->
<!-- view old value Modal -->
<div class="modal fade " id="oldValueModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
......@@ -228,7 +231,7 @@
</div>
<!-- commint modify config -->
<!-- commit modify config modal-->
<div class="modal fade" id="commitModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel2">
<div class="modal-dialog" role="document">
<div class="modal-content">
......@@ -238,10 +241,29 @@
<h4 class="modal-title" id="myModalLabel2">Commit changes</h4>
</div>
<div class="modal-body">
<textarea rows="4" style="width:570px;" placeholder="input change log...."></textarea>
<textarea rows="4" class="form-control" style="width:570px;" placeholder="input change log...."></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="commitChange()">提交</button>
</div>
</div>
</div>
</div>
<!--release modal-->
<div class="modal fade" id="releaseModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel3">
<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" id="myModalLabel3">发布</h4>
</div>
<div class="modal-body">
<textarea rows="4" class="form-control" style="width:570px;" ng-model = "releaseComment" placeholder="input release log...."></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" ng-click="commitChange()">提交</button>
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="release()">提交</button>
</div>
</div>
</div>
......
......@@ -43,20 +43,13 @@
<div class="form-group">
<label class="col-sm-2 control-label">Owner</label>
<div class="col-sm-3">
<input type="text" class="form-control" name="appOwner" ng-model="app.owner">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">手机号</label>
<div class="col-sm-3">
<input type="text" class="form-control" name="appOwnerPhone"
ng-model="app.ownerPhone">
<input type="text" class="form-control" name="appOwner" ng-model="app.ownerName">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label"> 邮箱地址</label>
<div class="col-sm-4">
<input type="email" class="form-control" ng-model="app.ownerMail">
<input type="email" class="form-control" ng-model="app.ownerEmail">
</div>
</div>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册