提交 4f3dd644 编写于 作者: J Jason Song 提交者: GitHub

Merge pull request #520 from lepdou/emergency_publish

emergency publish
......@@ -33,6 +33,7 @@ import java.util.stream.Collectors;
@RestController
public class ReleaseController {
private static final Splitter RELEASES_SPLITTER = Splitter.on(",").omitEmptyStrings()
.trimResults();
......@@ -57,7 +58,7 @@ public class ReleaseController {
}
@RequestMapping("/releases")
public List<ReleaseDTO> findReleaseByIds(@RequestParam("releaseIds") String releaseIds){
public List<ReleaseDTO> findReleaseByIds(@RequestParam("releaseIds") String releaseIds) {
Set<Long> releaseIdSet = RELEASES_SPLITTER.splitToList(releaseIds).stream().map(Long::parseLong)
.collect(Collectors.toSet());
......@@ -99,13 +100,14 @@ public class ReleaseController {
@PathVariable("namespaceName") String namespaceName,
@RequestParam("name") String releaseName,
@RequestParam(name = "comment", required = false) String releaseComment,
@RequestParam("operator") String operator) {
@RequestParam("operator") String operator,
@RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish) {
Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
if (namespace == null) {
throw new NotFoundException(String.format("Could not find namespace for %s %s %s", appId,
clusterName, namespaceName));
}
Release release = releaseService.publish(namespace, releaseName, releaseComment, operator);
Release release = releaseService.publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish);
//send release message
Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
......@@ -122,31 +124,33 @@ public class ReleaseController {
/**
*merge branch items to master and publish master
* merge branch items to master and publish master
*
* @return published result
*/
@Transactional
@RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/updateAndPublish", method = RequestMethod.POST)
public ReleaseDTO updateAndPublish(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName,
@RequestParam("releaseName") String releaseName,
@RequestParam("branchName") String branchName,
@RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch,
@RequestParam(name = "releaseComment", required = false) String releaseComment,
@RequestBody ItemChangeSets changeSets) {
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName,
@RequestParam("releaseName") String releaseName,
@RequestParam("branchName") String branchName,
@RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch,
@RequestParam(name = "releaseComment", required = false) String releaseComment,
@RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish,
@RequestBody ItemChangeSets changeSets) {
Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
if (namespace == null) {
throw new NotFoundException(String.format("Could not find namespace for %s %s %s", appId,
clusterName, namespaceName));
}
Release release = releaseService.mergeBranchChangeSetsAndRelease(namespace, branchName,
releaseName, releaseComment, changeSets);
Release release = releaseService.mergeBranchChangeSetsAndRelease(namespace, branchName, releaseName,
releaseComment, isEmergencyPublish, changeSets);
if (deleteBranch) {
namespaceBranchService.deleteBranch(appId, clusterName, namespaceName, branchName,
NamespaceBranchStatus.MERGED, changeSets.getDataChangeLastModifiedBy());
NamespaceBranchStatus.MERGED, changeSets.getDataChangeLastModifiedBy());
}
messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
......
......@@ -121,7 +121,7 @@ public class ReleaseControllerTest extends AbstractControllerTest {
.thenReturn(someNamespace);
releaseController
.publish(someAppId, someCluster, someNamespaceName, someName, someComment, "test");
.publish(someAppId, someCluster, someNamespaceName, someName, someComment, "test", false);
verify(someMessageSender, times(1))
.sendMessage(Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR)
......
......@@ -13,6 +13,7 @@ import com.ctrip.framework.apollo.biz.entity.NamespaceLock;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.repository.ReleaseRepository;
import com.ctrip.framework.apollo.biz.utils.ReleaseKeyGenerator;
import com.ctrip.framework.apollo.common.constants.GsonType;
import com.ctrip.framework.apollo.common.constants.ReleaseOperation;
import com.ctrip.framework.apollo.common.constants.ReleaseOperationContext;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
......@@ -43,12 +44,10 @@ import java.util.Set;
*/
@Service
public class ReleaseService {
private static final FastDateFormat TIMESTAMP_FORMAT = FastDateFormat.getInstance("yyyyMMddHHmmss");
private Gson gson = new Gson();
private Type configurationTypeReference = new TypeToken<Map<String, String>>() {
}.getType();
@Autowired
private ReleaseRepository releaseRepository;
@Autowired
......@@ -123,12 +122,11 @@ public class ReleaseService {
}
@Transactional
public Release mergeBranchChangeSetsAndRelease(Namespace namespace, String branchName, String
releaseName, String releaseComment, ItemChangeSets changeSets) {
NamespaceLock lock = namespaceLockService.findLock(namespace.getId());
if (lock != null && lock.getDataChangeCreatedBy().equals(changeSets.getDataChangeLastModifiedBy())) {
throw new BadRequestException("config can not be published by yourself.");
}
public Release mergeBranchChangeSetsAndRelease(Namespace namespace, String branchName, String releaseName,
String releaseComment, boolean isEmergencyPublish,
ItemChangeSets changeSets) {
checkLock(namespace, isEmergencyPublish, changeSets.getDataChangeLastModifiedBy());
itemSetService.updateSet(namespace, changeSets);
......@@ -141,22 +139,19 @@ public class ReleaseService {
Map<String, Object> operationContext = Maps.newHashMap();
operationContext.put(ReleaseOperationContext.SOURCE_BRANCH, branchName);
operationContext.put(ReleaseOperationContext.BASE_RELEASE_ID, branchReleaseId);
operationContext.put(ReleaseOperationContext.IS_EMERGENCY_PUBLISH, isEmergencyPublish);
Release release = masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems,
changeSets.getDataChangeLastModifiedBy(),
ReleaseOperation.GRAY_RELEASE_MERGE_TO_MASTER, operationContext);
return masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems,
changeSets.getDataChangeLastModifiedBy(),
ReleaseOperation.GRAY_RELEASE_MERGE_TO_MASTER, operationContext);
return release;
}
@Transactional
public Release publish(Namespace namespace, String releaseName, String releaseComment,
String operator) {
String operator, boolean isEmergencyPublish) {
NamespaceLock lock = namespaceLockService.findLock(namespace.getId());
if (lock != null && lock.getDataChangeCreatedBy().equals(operator)) {
throw new BadRequestException("config can not be published by yourself.");
}
checkLock(namespace, isEmergencyPublish, operator);
Map<String, String> operateNamespaceItems = getNamespaceItems(namespace);
......@@ -165,7 +160,7 @@ public class ReleaseService {
//branch release
if (parentNamespace != null) {
return publishBranchNamespace(parentNamespace, namespace, operateNamespaceItems,
releaseName, releaseComment, operator);
releaseName, releaseComment, operator, isEmergencyPublish);
}
Namespace childNamespace = namespaceService.findChildNamespace(namespace);
......@@ -176,31 +171,43 @@ public class ReleaseService {
}
//master release
Map<String, Object> operationContext = Maps.newHashMap();
operationContext.put(ReleaseOperationContext.IS_EMERGENCY_PUBLISH, isEmergencyPublish);
Release release = masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems,
operator, ReleaseOperation.NORMAL_RELEASE, null);
operator, ReleaseOperation.NORMAL_RELEASE, operationContext);
//merge to branch and auto release
if (childNamespace != null) {
mergeFromMasterAndPublishBranch(namespace, childNamespace, operateNamespaceItems,
releaseName, releaseComment, operator, previousRelease, release);
releaseName, releaseComment, operator, previousRelease,
release, isEmergencyPublish);
}
return release;
}
private void checkLock(Namespace namespace, boolean isEmergencyPublish, String operator) {
if (!isEmergencyPublish) {
NamespaceLock lock = namespaceLockService.findLock(namespace.getId());
if (lock != null && lock.getDataChangeCreatedBy().equals(operator)) {
throw new BadRequestException("Config can not be published by yourself.");
}
}
}
private void mergeFromMasterAndPublishBranch(Namespace parentNamespace, Namespace childNamespace,
Map<String, String> parentNamespaceItems,
String releaseName, String releaseComment,
String operator, Release masterPreviousRelease, Release parentRelease) {
// //create release for child namespace
String operator, Release masterPreviousRelease,
Release parentRelease, boolean isEmergencyPublish) {
//create release for child namespace
Map<String, String> childReleaseConfiguration = getNamespaceReleaseConfiguration(childNamespace);
Map<String, String> parentNamespaceOldConfiguration = masterPreviousRelease == null ? null :
gson.fromJson(
masterPreviousRelease.getConfigurations(),
configurationTypeReference);
Map<String, String> parentNamespaceOldConfiguration = masterPreviousRelease == null ?
null : gson.fromJson(masterPreviousRelease.getConfigurations(),
GsonType.CONFIG);
Map<String, String>
childNamespaceToPublishConfigs =
Map<String, String> childNamespaceToPublishConfigs =
calculateChildNamespaceToPublishConfiguration(parentNamespaceOldConfiguration,
parentNamespaceItems,
childNamespace);
......@@ -208,27 +215,27 @@ public class ReleaseService {
if (!childNamespaceToPublishConfigs.equals(childReleaseConfiguration)) {
branchRelease(parentNamespace, childNamespace, releaseName, releaseComment,
childNamespaceToPublishConfigs, parentRelease.getId(), operator,
ReleaseOperation.MASTER_NORMAL_RELEASE_MERGE_TO_GRAY);
ReleaseOperation.MASTER_NORMAL_RELEASE_MERGE_TO_GRAY, isEmergencyPublish);
}
}
private Release publishBranchNamespace(Namespace parentNamespace, Namespace childNamespace,
Map<String, String> childNamespaceItems,
String releaseName, String releaseComment, String operator) {
String releaseName, String releaseComment,
String operator, boolean isEmergencyPublish) {
Release parentLatestRelease = findLatestActiveRelease(parentNamespace);
Map<String, String> parentConfigurations = parentLatestRelease != null ?
gson.fromJson(parentLatestRelease.getConfigurations(),
configurationTypeReference) : new HashMap<>();
GsonType.CONFIG) : new HashMap<>();
long baseReleaseId = parentLatestRelease == null ? 0 : parentLatestRelease.getId();
Map<String, String> childNamespaceToPublishConfigs = mergeConfiguration(parentConfigurations, childNamespaceItems);
Release release =
branchRelease(parentNamespace, childNamespace, releaseName, releaseComment,
childNamespaceToPublishConfigs, baseReleaseId, operator,
ReleaseOperation.GRAY_RELEASE);
return release;
return branchRelease(parentNamespace, childNamespace, releaseName, releaseComment,
childNamespaceToPublishConfigs, baseReleaseId, operator,
ReleaseOperation.GRAY_RELEASE, isEmergencyPublish);
}
private Release masterRelease(Namespace namespace, String releaseName, String releaseComment,
......@@ -241,8 +248,8 @@ public class ReleaseService {
releaseHistoryService.createReleaseHistory(namespace.getAppId(), namespace.getClusterName(),
namespace.getNamespaceName(), namespace.getClusterName(),
release.getId(),
previousReleaseId, releaseOperation, operationContext, operator);
release.getId(), previousReleaseId, releaseOperation,
operationContext, operator);
return release;
}
......@@ -250,7 +257,7 @@ public class ReleaseService {
private Release branchRelease(Namespace parentNamespace, Namespace childNamespace,
String releaseName, String releaseComment,
Map<String, String> configurations, long baseReleaseId,
String operator, int releaseOperation) {
String operator, int releaseOperation, boolean isEmergencyPublish) {
Release previousRelease = findLatestActiveRelease(childNamespace.getAppId(),
childNamespace.getClusterName(),
childNamespace.getNamespaceName());
......@@ -258,6 +265,7 @@ public class ReleaseService {
Map<String, Object> releaseOperationContext = Maps.newHashMap();
releaseOperationContext.put(ReleaseOperationContext.BASE_RELEASE_ID, baseReleaseId);
releaseOperationContext.put(ReleaseOperationContext.IS_EMERGENCY_PUBLISH, isEmergencyPublish);
Release release =
createRelease(childNamespace, releaseName, releaseComment, configurations, operator);
......@@ -316,8 +324,7 @@ public class ReleaseService {
Release release = findLatestActiveRelease(namespace);
Map<String, String> configuration = new HashMap<>();
if (release != null) {
configuration = new Gson().fromJson(release.getConfigurations(),
configurationTypeReference);
configuration = new Gson().fromJson(release.getConfigurations(), GsonType.CONFIG);
}
return configuration;
}
......@@ -395,12 +402,11 @@ public class ReleaseService {
Release parentNamespaceNewLatestRelease = parentNamespaceTwoLatestActiveRelease.get(1);
Map<String, String> parentNamespaceAbandonedConfiguration = gson.fromJson(abandonedRelease.getConfigurations(),
configurationTypeReference);
GsonType.CONFIG);
Map<String, String>
parentNamespaceNewLatestConfiguration =
gson.fromJson(parentNamespaceNewLatestRelease.getConfigurations(),
configurationTypeReference);
gson.fromJson(parentNamespaceNewLatestRelease.getConfigurations(), GsonType.CONFIG);
Map<String, String>
childNamespaceNewConfiguration =
......@@ -408,9 +414,10 @@ public class ReleaseService {
parentNamespaceNewLatestConfiguration,
childNamespace);
branchRelease(parentNamespace, childNamespace, TIMESTAMP_FORMAT.format(new Date()) + "-master-rollback-merge-to-gray", "",
branchRelease(parentNamespace, childNamespace,
TIMESTAMP_FORMAT.format(new Date()) + "-master-rollback-merge-to-gray", "",
childNamespaceNewConfiguration, parentNamespaceNewLatestRelease.getId(), operator,
ReleaseOperation.MATER_ROLLBACK_MERGE_TO_GRAY);
ReleaseOperation.MATER_ROLLBACK_MERGE_TO_GRAY, false);
}
private Map<String, String> calculateChildNamespaceToPublishConfiguration(
......@@ -423,7 +430,7 @@ public class ReleaseService {
Map<String, String> childNamespaceLatestActiveConfiguration = childNamespaceLatestActiveRelease == null ? null :
gson.fromJson(childNamespaceLatestActiveRelease
.getConfigurations(),
configurationTypeReference);
GsonType.CONFIG);
Map<String, String> childNamespaceModifiedConfiguration = calculateBranchModifiedItemsAccordingToRelease(
parentNamespaceOldConfiguration, childNamespaceLatestActiveConfiguration);
......
......@@ -8,6 +8,7 @@ import com.ctrip.framework.apollo.biz.entity.GrayReleaseRule;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.entity.ReleaseHistory;
import com.ctrip.framework.apollo.common.constants.GsonType;
import com.ctrip.framework.apollo.common.constants.ReleaseOperation;
import org.junit.Assert;
......@@ -24,8 +25,6 @@ import java.util.Map;
public class ReleaseCreationTest extends AbstractIntegrationTest {
private Gson gson = new Gson();
private Type configurationTypeReference = new TypeToken<Map<String, String>>() {
}.getType();
@Autowired
private ReleaseService releaseService;
......@@ -47,7 +46,7 @@ public class ReleaseCreationTest extends AbstractIntegrationTest {
String clusterName = "only-master";
Namespace namespace = instanceNamespace(namespaceId, clusterName);
releaseService.publish(namespace, "", "", operator);
releaseService.publish(namespace, "", "", operator, false);
Release latestRelease = releaseService.findLatestActiveRelease(namespace);
......@@ -92,7 +91,7 @@ public class ReleaseCreationTest extends AbstractIntegrationTest {
String childClusterName = "child-cluster1";
Namespace parentNamespace = instanceNamespace(parentNamespaceId, parentClusterName);
releaseService.publish(parentNamespace, "", "", operator);
releaseService.publish(parentNamespace, "", "", operator, false);
Release latestParentNamespaceRelease = releaseService.findLatestActiveRelease(parentNamespace);
......@@ -166,7 +165,7 @@ public class ReleaseCreationTest extends AbstractIntegrationTest {
String childClusterName = "child-cluster2";
Namespace parentNamespace = instanceNamespace(parentNamespaceId, parentClusterName);
releaseService.publish(parentNamespace, "", "", operator);
releaseService.publish(parentNamespace, "", "", operator, false);
Release latestParentNamespaceRelease = releaseService.findLatestActiveRelease(parentNamespace);
......@@ -237,7 +236,7 @@ public class ReleaseCreationTest extends AbstractIntegrationTest {
String childClusterName = "child-cluster3";
Namespace parentNamespace = instanceNamespace(parentNamespaceId, parentClusterName);
releaseService.publish(parentNamespace, "", "", operator);
releaseService.publish(parentNamespace, "", "", operator, false);
Release latestParentNamespaceRelease = releaseService.findLatestActiveRelease(parentNamespace);
......@@ -306,7 +305,7 @@ public class ReleaseCreationTest extends AbstractIntegrationTest {
String childClusterName = "child-cluster4";
//assert child namespace
Namespace childNamespace = instanceNamespace(childNamespaceId, childClusterName);
releaseService.publish(childNamespace, "", "", operator);
releaseService.publish(childNamespace, "", "", operator, false);
Release latestChildNamespaceRelease = releaseService.findLatestActiveRelease(childNamespace);
......@@ -377,7 +376,7 @@ public class ReleaseCreationTest extends AbstractIntegrationTest {
String childClusterName = "child-cluster5";
//assert child namespace
Namespace childNamespace = instanceNamespace(childNamespaceId, childClusterName);
releaseService.publish(childNamespace, "", "", operator);
releaseService.publish(childNamespace, "", "", operator, false);
Release latestChildNamespaceRelease = releaseService.findLatestActiveRelease(childNamespace);
......@@ -495,7 +494,7 @@ public class ReleaseCreationTest extends AbstractIntegrationTest {
}
private Map<String, String> parseConfiguration(String configuration) {
return gson.fromJson(configuration, configurationTypeReference);
return gson.fromJson(configuration, GsonType.CONFIG);
}
}
......@@ -8,4 +8,5 @@ public interface ReleaseOperationContext {
String RULES = "rules";
String OLD_RULES = "oldRules";
String BASE_RELEASE_ID = "baseReleaseId";
String IS_EMERGENCY_PUBLISH = "isEmergencyPublish";
}
......@@ -230,31 +230,32 @@ public class AdminServiceAPI {
}
public ReleaseDTO createRelease(String appId, Env env, String clusterName, String namespace,
String releaseName, String releaseComment, String operator) {
String releaseName, String releaseComment, String operator, boolean isEmergencyPublish) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.add("name", releaseName);
parameters.add("comment", releaseComment);
parameters.add("operator", operator);
parameters.add("isEmergencyPublish", String.valueOf(isEmergencyPublish));
HttpEntity<MultiValueMap<String, String>> entity =
new HttpEntity<>(parameters, headers);
ReleaseDTO response = restTemplate.post(
env, "apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases", entity,
ReleaseDTO.class,
appId, clusterName, namespace);
ReleaseDTO.class, appId, clusterName, namespace);
return response;
}
public ReleaseDTO updateAndPublish(String appId, Env env, String clusterName, String namespace,
String releaseName, String releaseComment, String branchName,
boolean deleteBranch, ItemChangeSets changeSets) {
boolean isEmergencyPublish, boolean deleteBranch, ItemChangeSets changeSets) {
return restTemplate.post(env,
"apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/updateAndPublish?"
+ "releaseName={releaseName}&releaseComment={releaseComment}&branchName={branchName}&deleteBranch={deleteBranch}",
changeSets, ReleaseDTO.class,
appId, clusterName, namespace, releaseName, releaseComment, branchName, deleteBranch);
+ "releaseName={releaseName}&releaseComment={releaseComment}&branchName={branchName}"
+ "&deleteBranch={deleteBranch}&isEmergencyPublish={isEmergencyPublish}",
changeSets, ReleaseDTO.class, appId, clusterName, namespace,
releaseName, releaseComment, branchName, deleteBranch, isEmergencyPublish);
}
......
......@@ -20,6 +20,7 @@ import org.springframework.util.StringUtils;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@Component
......@@ -97,6 +98,19 @@ public class PortalConfig extends RefreshableConfig {
return getValue("apollo.portal.address");
}
public boolean isEmergencyPublishAllowed(Env env) {
String targetEnv = env.name();
String[] emergencyPublishSupportedEnvs = getArrayProperty("emergencyPublish.supported.envs", new String[0]);
for (String supportedEnv: emergencyPublishSupportedEnvs) {
if (Objects.equals(targetEnv, supportedEnv.toUpperCase().trim())) {
return true;
}
}
return false;
}
/***
* Level: low
......
......@@ -4,6 +4,7 @@ package com.ctrip.framework.apollo.portal.components.emailbuilder;
import com.google.common.collect.Lists;
import com.ctrip.framework.apollo.common.constants.ReleaseOperation;
import com.ctrip.framework.apollo.common.constants.ReleaseOperationContext;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
......@@ -29,12 +30,15 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
public abstract class ConfigPublishEmailBuilder {
private static final String EMERGENCY_PUBLISH_TAG = "<span style='color:red'>(紧急发布)</span>";
//email content common field placeholder
private static final String EMAIL_CONTENT_FIELD_APPID = "#\\{appId\\}";
private static final String EMAIL_CONTENT_FIELD_ENV = "#\\{env}";
......@@ -48,6 +52,7 @@ public abstract class ConfigPublishEmailBuilder {
private static final String EMAIL_CONTENT_FIELD_RELEASE_COMMENT = "#\\{releaseComment}";
private static final String EMAIL_CONTENT_FIELD_APOLLO_SERVER_ADDRESS = "#\\{apollo.portal.address}";
private static final String EMAIL_CONTENT_FIELD_DIFF_CONTENT = "#\\{diffContent}";
private static final String EMAIL_CONTENT_FIELD_EMERGENCY_PUBLISH = "#\\{emergencyPublish}";
private static final String EMAIL_CONTENT_DIFF_MODULE = "#\\{diffModule}";
protected static final String EMAIL_CONTENT_GRAY_RULES_MODULE = "#\\{rulesModule}";
......@@ -118,7 +123,18 @@ public abstract class ConfigPublishEmailBuilder {
}
private String renderReleaseBasicInfo(String template, Env env, ReleaseHistoryBO releaseHistory) {
String renderResult = template.replaceAll(EMAIL_CONTENT_FIELD_APPID, Matcher.quoteReplacement(releaseHistory.getAppId()));
String renderResult = template;
Map<String, Object> operationContext = releaseHistory.getOperationContext();
boolean isEmergencyPublish = operationContext.containsKey(ReleaseOperationContext.IS_EMERGENCY_PUBLISH) &&
(boolean) operationContext.get(ReleaseOperationContext.IS_EMERGENCY_PUBLISH);
if (isEmergencyPublish) {
renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_EMERGENCY_PUBLISH, Matcher.quoteReplacement(EMERGENCY_PUBLISH_TAG));
} else {
renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_EMERGENCY_PUBLISH, "");
}
renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_APPID, Matcher.quoteReplacement(releaseHistory.getAppId()));
renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_ENV, Matcher.quoteReplacement(env.toString()));
renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_CLUSTER, Matcher.quoteReplacement(releaseHistory.getClusterName()));
renderResult = renderResult.replaceAll(EMAIL_CONTENT_FIELD_NAMESPACE, Matcher.quoteReplacement(releaseHistory.getNamespaceName()));
......
......@@ -3,8 +3,10 @@ package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.components.PermissionValidator;
import com.ctrip.framework.apollo.portal.components.config.PortalConfig;
import com.ctrip.framework.apollo.portal.entity.model.NamespaceReleaseModel;
import com.ctrip.framework.apollo.portal.entity.bo.NamespaceBO;
import com.ctrip.framework.apollo.portal.listener.ConfigPublishEvent;
......@@ -33,6 +35,8 @@ public class NamespaceBranchController {
private NamespaceBranchService namespaceBranchService;
@Autowired
private ApplicationEventPublisher publisher;
@Autowired
private PortalConfig portalConfig;
@RequestMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches")
public NamespaceBO findBranch(@PathVariable String appId,
......@@ -84,8 +88,13 @@ public class NamespaceBranchController {
@PathVariable String branchName, @RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch,
@RequestBody NamespaceReleaseModel model) {
if (model.isEmergencyPublish() && !portalConfig.isEmergencyPublishAllowed(Env.fromString(env))) {
throw new BadRequestException(String.format("Env: %s is not supported emergency publish now", env));
}
ReleaseDTO createdRelease = namespaceBranchService.merge(appId, Env.valueOf(env), clusterName, namespaceName, branchName,
model.getReleaseTitle(), model.getReleaseComment(), deleteBranch);
model.getReleaseTitle(), model.getReleaseComment(),
model.isEmergencyPublish(), deleteBranch);
ConfigPublishEvent event = ConfigPublishEvent.instance();
event.withAppId(appId)
......
......@@ -2,6 +2,7 @@ package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.common.dto.NamespaceLockDTO;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.entity.vo.LockInfo;
import com.ctrip.framework.apollo.portal.service.NamespaceLockService;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -15,6 +16,7 @@ public class NamespaceLockController {
@Autowired
private NamespaceLockService namespaceLockService;
@Deprecated
@RequestMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/lock")
public NamespaceLockDTO getNamespaceLock(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName) {
......@@ -22,4 +24,13 @@ public class NamespaceLockController {
return namespaceLockService.getNamespaceLock(appId, Env.valueOf(env), clusterName, namespaceName);
}
@RequestMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/lock-info")
public LockInfo getNamespaceLockInfo(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName) {
return namespaceLockService.getNamespaceLockInfo(appId, Env.fromString(env), clusterName, namespaceName);
}
}
package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.utils.RequestPrecondition;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.components.config.PortalConfig;
import com.ctrip.framework.apollo.portal.entity.model.NamespaceReleaseModel;
import com.ctrip.framework.apollo.portal.entity.vo.ReleaseCompareResult;
import com.ctrip.framework.apollo.portal.entity.bo.ReleaseBO;
......@@ -30,6 +32,8 @@ public class ReleaseController {
private ReleaseService releaseService;
@Autowired
private ApplicationEventPublisher publisher;
@Autowired
private PortalConfig portalConfig;
@PreAuthorize(value = "@permissionValidator.hasReleaseNamespacePermission(#appId, #namespaceName)")
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/releases", method = RequestMethod.POST)
......@@ -43,6 +47,10 @@ public class ReleaseController {
model.setClusterName(clusterName);
model.setNamespaceName(namespaceName);
if (model.isEmergencyPublish() && !portalConfig.isEmergencyPublishAllowed(Env.valueOf(env))) {
throw new BadRequestException(String.format("Env: %s is not supported emergency publish now", env));
}
ReleaseDTO createdRelease = releaseService.publish(model);
ConfigPublishEvent event = ConfigPublishEvent.instance();
......@@ -72,6 +80,10 @@ public class ReleaseController {
model.setClusterName(branchName);
model.setNamespaceName(namespaceName);
if (model.isEmergencyPublish() && !portalConfig.isEmergencyPublishAllowed(Env.valueOf(env))) {
throw new BadRequestException(String.format("Env: %s is not supported emergency publish now", env));
}
ReleaseDTO createdRelease = releaseService.publish(model);
ConfigPublishEvent event = ConfigPublishEvent.instance();
......
......@@ -13,6 +13,7 @@ public class NamespaceReleaseModel implements Verifiable {
private String releaseTitle;
private String releaseComment;
private String releasedBy;
private boolean isEmergencyPublish;
@Override
public boolean isInvalid() {
......@@ -74,4 +75,12 @@ public class NamespaceReleaseModel implements Verifiable {
public void setReleasedBy(String releasedBy) {
this.releasedBy = releasedBy;
}
public boolean isEmergencyPublish() {
return isEmergencyPublish;
}
public void setEmergencyPublish(boolean emergencyPublish) {
isEmergencyPublish = emergencyPublish;
}
}
package com.ctrip.framework.apollo.portal.entity.vo;
public class LockInfo {
private String lockOwner;
private boolean isEmergencyPublishAllowed;
public String getLockOwner() {
return lockOwner;
}
public void setLockOwner(String lockOwner) {
this.lockOwner = lockOwner;
}
public boolean isEmergencyPublishAllowed() {
return isEmergencyPublishAllowed;
}
public void setEmergencyPublishAllowed(boolean emergencyPublishAllowed) {
isEmergencyPublishAllowed = emergencyPublishAllowed;
}
}
......@@ -84,13 +84,14 @@ public class NamespaceBranchService {
public ReleaseDTO merge(String appId, Env env, String clusterName, String namespaceName,
String branchName, String title, String comment, boolean deleteBranch) {
String branchName, String title, String comment,
boolean isEmergencyPublish, boolean deleteBranch) {
ItemChangeSets changeSets = calculateBranchChangeSet(appId, env, clusterName, namespaceName, branchName);
ReleaseDTO
mergedResult =
releaseService.updateAndPublish(appId, env, clusterName, namespaceName, title, comment, branchName, deleteBranch, changeSets);
ReleaseDTO mergedResult =
releaseService.updateAndPublish(appId, env, clusterName, namespaceName, title, comment,
branchName, isEmergencyPublish, deleteBranch, changeSets);
Tracer.logEvent(CatEventType.MERGE_GRAY_RELEASE,
String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName));
......
......@@ -3,6 +3,8 @@ package com.ctrip.framework.apollo.portal.service;
import com.ctrip.framework.apollo.common.dto.NamespaceLockDTO;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.components.config.PortalConfig;
import com.ctrip.framework.apollo.portal.entity.vo.LockInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
......@@ -12,10 +14,24 @@ public class NamespaceLockService {
@Autowired
private AdminServiceAPI.NamespaceLockAPI namespaceLockAPI;
@Autowired
private PortalConfig portalConfig;
public NamespaceLockDTO getNamespaceLock(String appId, Env env, String clusterName, String namespaceName) {
return namespaceLockAPI.getNamespaceLockOwner(appId, env, clusterName, namespaceName);
}
public LockInfo getNamespaceLockInfo(String appId, Env env, String clusterName, String namespaceName) {
LockInfo lockInfo = new LockInfo();
NamespaceLockDTO namespaceLockDTO = namespaceLockAPI.getNamespaceLockOwner(appId, env, clusterName, namespaceName);
String lockOwner = namespaceLockDTO == null ? "" : namespaceLockDTO.getDataChangeCreatedBy();
lockInfo.setLockOwner(lockOwner);
lockInfo.setEmergencyPublishAllowed(portalConfig.isEmergencyPublishAllowed(env));
return lockInfo;
}
}
......@@ -42,27 +42,30 @@ public class ReleaseService {
private AdminServiceAPI.ReleaseAPI releaseAPI;
public ReleaseDTO publish(NamespaceReleaseModel model) {
String appId = model.getAppId();
Env env = model.getEnv();
boolean isEmergencyPublish = model.isEmergencyPublish();
String appId = model.getAppId();
String clusterName = model.getClusterName();
String namespaceName = model.getNamespaceName();
String releaseBy =
StringUtils.isEmpty(model.getReleasedBy()) ? userInfoHolder.getUser().getUserId() : model.getReleasedBy();
String releaseBy = StringUtils.isEmpty(model.getReleasedBy()) ?
userInfoHolder.getUser().getUserId() : model.getReleasedBy();
ReleaseDTO releaseDTO = releaseAPI.createRelease(appId, env, clusterName, namespaceName,
model.getReleaseTitle(), model.getReleaseComment(),
releaseBy, isEmergencyPublish);
ReleaseDTO releaseDTO = releaseAPI
.createRelease(appId, env, clusterName, namespaceName, model.getReleaseTitle(), model.getReleaseComment()
, releaseBy);
Tracer.logEvent(CatEventType.RELEASE_NAMESPACE,
String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName));
Tracer.logEvent(CatEventType.RELEASE_NAMESPACE, String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName));
return releaseDTO;
}
public ReleaseDTO updateAndPublish(String appId, Env env, String clusterName, String namespaceName,
String releaseTitle, String releaseComment, String branchName,
boolean deleteBranch, ItemChangeSets changeSets) {
boolean isEmergencyPublish, boolean deleteBranch, ItemChangeSets changeSets) {
return releaseAPI.updateAndPublish(appId, env, clusterName, namespaceName, releaseTitle, releaseComment, branchName,
deleteBranch, changeSets);
isEmergencyPublish, deleteBranch, changeSets);
}
public List<ReleaseBO> findAllReleases(String appId, Env env, String clusterName, String namespaceName, int page,
......
......@@ -229,6 +229,7 @@
cluster="pageContext.clusterName">
</mergeandpublishmodal>
<publishdenymodal></publishdenymodal>
<apolloconfirmdialog apollo-dialog-id="'deleteConfirmDialog'" apollo-title="'删除配置'"
apollo-detail="'确定要删除配置吗?'"
......@@ -251,10 +252,6 @@
apollo-detail="'当前namespace正在被 ' + lockOwner + ' 编辑,一次发布只能被一个人修改.'"
apollo-show-cancel-btn="false"></apolloconfirmdialog>
<apolloconfirmdialog apollo-dialog-id="'releaseDenyDialog'" apollo-title="'发布受限'"
apollo-detail="'您不能发布哟~ ' + pageContext.env + '环境配置的编辑和发布必须为不同的人,请找另一个具有当前namespace发布权的人操作发布~'"
apollo-show-cancel-btn="false"></apolloconfirmdialog>
<apolloconfirmdialog apollo-dialog-id="'rollbackAlertDialog'" apollo-title="'回滚'"
apollo-detail="'确定要回滚吗?'"
apollo-show-cancel-btn="true" apollo-confirm="rollback"></apolloconfirmdialog>
......@@ -373,6 +370,7 @@
<script type="application/javascript" src="scripts/directive/rollback-modal-directive.js"></script>
<script type="application/javascript" src="scripts/directive/gray-release-rules-modal-directive.js"></script>
<script type="application/javascript" src="scripts/directive/merge-and-publish-modal-directive.js"></script>
<script type="application/javascript" src="scripts/directive/publish-deny-modal-directive.js"></script>
<!--controller-->
<script type="application/javascript" src="scripts/controller/config/ConfigNamespaceController.js"></script>
......
......@@ -75,7 +75,8 @@
<h5 class="col-md-7 word-break" ng-show="releaseHistory.operation == 8">删除灰度(全量发布)</h5>
<h6 class="col-md-5 text-right" ng-bind="releaseHistory.releaseTimeFormatted"></h6>
<span class="label label-warning no-radius emergency-publish"
ng-if="releaseHistory.operationContext.isEmergencyPublish">紧急发布</span>
</div>
</div>
......
......@@ -20,17 +20,22 @@ function mergeAndPublishDirective(AppUtil, EventManager) {
var branch = context.branch;
scope.toReleaseNamespace = branch;
scope.toDeleteBranch = branch;
scope.isEmergencyPublish =
context.isEmergencyPublish ? context.isEmergencyPublish : false;
var branchStatusMerge = 2;
branch.branchStatus = branchStatusMerge;
branch.mergeAndPublish = true;
AppUtil.showModal('#mergeAndPublishModal');
});
function showReleaseModal() {
EventManager.emit(EventManager.EventType.PUBLISH_NAMESPACE, {namespace: scope.toReleaseNamespace});
EventManager.emit(EventManager.EventType.PUBLISH_NAMESPACE,
{
namespace: scope.toReleaseNamespace,
isEmergencyPublish: scope.isEmergencyPublish
});
}
}
......
......@@ -279,16 +279,12 @@ function directive($window, toastr, AppUtil, EventManager, PermissionService, Na
}
function initNamespaceLock(namespace) {
NamespaceLockService.get_namespace_lock(
scope.appId, scope.env,
namespace.baseInfo.clusterName,
namespace.baseInfo.namespaceName)
NamespaceLockService.get_namespace_lock(scope.appId, scope.env,
namespace.baseInfo.clusterName,
namespace.baseInfo.namespaceName)
.then(function (result) {
if (result.dataChangeCreatedBy) {
namespace.lockOwner = result.dataChangeCreatedBy;
} else {
namespace.lockOwner = "";
}
namespace.lockOwner = result.lockOwner;
namespace.isEmergencyPublishAllowed = result.isEmergencyPublishAllowed;
});
}
......@@ -748,7 +744,10 @@ function directive($window, toastr, AppUtil, EventManager, PermissionService, Na
return;
} else if (namespace.lockOwner && scope.user == namespace.lockOwner) {
//can not publish if config modified by himself
AppUtil.showModal('#releaseDenyDialog');
EventManager.emit(EventManager.EventType.PUBLISH_DENY, {
namespace: namespace,
mergeAndPublish: false
});
return;
}
......@@ -766,12 +765,14 @@ function directive($window, toastr, AppUtil, EventManager, PermissionService, Na
var parentNamespace = branch.parentNamespace;
if (!parentNamespace.hasReleasePermission) {
AppUtil.showModal('#releaseNoPermissionDialog');
} else if (branch.lockOwner && scope.user == branch.lockOwner) {
AppUtil.showModal('#releaseDenyDialog');
} else if (parentNamespace.itemModifiedCnt > 0) {
AppUtil.showModal('#mergeAndReleaseDenyDialog');
}
else {
} else if (branch.lockOwner && scope.user == branch.lockOwner) {
EventManager.emit(EventManager.EventType.PUBLISH_DENY, {
namespace: branch,
mergeAndPublish: true
});
} else {
EventManager.emit(EventManager.EventType.MERGE_AND_PUBLISH_NAMESPACE, {branch: branch});
}
}
......
directive_module.directive('publishdenymodal', publishDenyDirective);
function publishDenyDirective(AppUtil, EventManager) {
return {
restrict: 'E',
templateUrl: '../../views/component/publish-deny-modal.html',
transclude: true,
replace: true,
scope: {
},
link: function (scope) {
var MODAL_ID = "#publishDenyModal";
EventManager.subscribe(EventManager.EventType.PUBLISH_DENY, function (context) {
scope.toReleaseNamespace = context.namespace;
scope.mergeAndPublish = !!context.mergeAndPublish;
AppUtil.showModal(MODAL_ID);
});
scope.emergencyPublish = emergencyPublish;
function emergencyPublish() {
AppUtil.hideModal(MODAL_ID);
if (scope.mergeAndPublish) {
EventManager.emit(EventManager.EventType.MERGE_AND_PUBLISH_NAMESPACE,
{
branch: scope.toReleaseNamespace,
isEmergencyPublish: true
});
} else {
EventManager.emit(EventManager.EventType.PUBLISH_NAMESPACE,
{
namespace: scope.toReleaseNamespace,
isEmergencyPublish: true
});
}
}
}
}
}
......@@ -19,12 +19,14 @@ function releaseModalDirective(toastr, AppUtil, EventManager, ReleaseService, Na
scope.releaseBtnDisabled = false;
scope.releaseChangeViewType = 'change';
scope.releaseComment = '';
scope.isEmergencyPublish = false;
EventManager.subscribe(EventManager.EventType.PUBLISH_NAMESPACE,
function (context) {
var namespace = context.namespace;
scope.toReleaseNamespace = context.namespace;
scope.isEmergencyPublish = !!context.isEmergencyPublish;
var date = new Date().Format("yyyyMMddhhmmss");
if (namespace.mergeAndPublish) {
......@@ -55,7 +57,8 @@ function releaseModalDirective(toastr, AppUtil, EventManager, ReleaseService, Na
scope.toReleaseNamespace.baseInfo.clusterName,
scope.toReleaseNamespace.baseInfo.namespaceName,
scope.toReleaseNamespace.releaseTitle,
scope.releaseComment).then(
scope.releaseComment,
scope.isEmergencyPublish).then(
function (result) {
AppUtil.hideModal('#releaseModal');
toastr.success("发布成功");
......@@ -83,7 +86,8 @@ function releaseModalDirective(toastr, AppUtil, EventManager, ReleaseService, Na
scope.toReleaseNamespace.baseInfo.namespaceName,
scope.toReleaseNamespace.baseInfo.clusterName,
scope.toReleaseNamespace.releaseTitle,
scope.releaseComment).then(
scope.releaseComment,
scope.isEmergencyPublish).then(
function (result) {
AppUtil.hideModal('#releaseModal');
toastr.success("灰度发布成功");
......@@ -98,7 +102,9 @@ function releaseModalDirective(toastr, AppUtil, EventManager, ReleaseService, Na
item.isModified = false;
}
});
//reset namespace status
scope.toReleaseNamespace.itemModifiedCnt = 0;
scope.toReleaseNamespace.lockOwner = undefined;
//check rules
if (!scope.toReleaseNamespace.rules
......@@ -125,6 +131,7 @@ function releaseModalDirective(toastr, AppUtil, EventManager, ReleaseService, Na
scope.toReleaseNamespace.baseInfo.clusterName,
scope.toReleaseNamespace.releaseTitle,
scope.releaseComment,
scope.isEmergencyPublish,
scope.toReleaseNamespace.mergeAfterDeleteBranch)
.then(function (result) {
......
......@@ -131,7 +131,8 @@ appService.service('EventManager', [function () {
PRE_ROLLBACK_NAMESPACE: 'pre_rollback_namespace',
ROLLBACK_NAMESPACE: 'rollback_namespace',
EDIT_GRAY_RELEASE_RULES: 'edit_gray_release_rules',
UPDATE_GRAY_RELEASE_RULES: 'update_gray_release_rules'
UPDATE_GRAY_RELEASE_RULES: 'update_gray_release_rules',
PUBLISH_DENY: 'publish_deny'
}
}
......
......@@ -83,7 +83,7 @@ appService.service('NamespaceBranchService', ['$resource', '$q', function ($reso
}
function merge_and_release_branch(appId, env, clusterName, namespaceName,
branchName, title, comment, deleteBranch) {
branchName, title, comment, isEmergencyPublish, deleteBranch) {
var d = $q.defer();
resource.merge_and_release_branch({
appId: appId,
......@@ -94,7 +94,8 @@ appService.service('NamespaceBranchService', ['$resource', '$q', function ($reso
deleteBranch:deleteBranch
}, {
releaseTitle: title,
releaseComment: comment
releaseComment: comment,
isEmergencyPublish: isEmergencyPublish
},
function (result) {
d.resolve(result);
......
......@@ -2,7 +2,7 @@ appService.service('NamespaceLockService', ['$resource', '$q', function ($resour
var resource = $resource('', {}, {
get_namespace_lock: {
method: 'GET',
url: 'apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/lock'
url: 'apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/lock-info'
}
});
......
......@@ -28,7 +28,7 @@ appService.service('ReleaseService', ['$resource', '$q', function ($resource, $q
}
});
function createRelease(appId, env, clusterName, namespaceName, releaseTitle, comment) {
function createRelease(appId, env, clusterName, namespaceName, releaseTitle, comment, isEmergencyPublish) {
var d = $q.defer();
resource.release({
appId: appId,
......@@ -37,7 +37,8 @@ appService.service('ReleaseService', ['$resource', '$q', function ($resource, $q
namespaceName: namespaceName
}, {
releaseTitle: releaseTitle,
releaseComment: comment
releaseComment: comment,
isEmergencyPublish: isEmergencyPublish
}, function (result) {
d.resolve(result);
}, function (result) {
......@@ -46,18 +47,19 @@ appService.service('ReleaseService', ['$resource', '$q', function ($resource, $q
return d.promise;
}
function createGrayRelease(appId, env, clusterName, namespaceName, branchName, releaseTitle, comment) {
function createGrayRelease(appId, env, clusterName, namespaceName, branchName, releaseTitle, comment, isEmergencyPublish) {
var d = $q.defer();
resource.gray_release({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName,
branchName:branchName
}, {
releaseTitle: releaseTitle,
releaseComment: comment
}, function (result) {
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName,
branchName: branchName
}, {
releaseTitle: releaseTitle,
releaseComment: comment,
isEmergencyPublish: isEmergencyPublish
}, function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
......@@ -98,7 +100,7 @@ appService.service('ReleaseService', ['$resource', '$q', function ($resource, $q
});
return d.promise;
}
function findLatestActiveRelease(appId, env, clusterName, namespaceName) {
var d = $q.defer();
resource.find_active_releases({
......@@ -109,7 +111,7 @@ appService.service('ReleaseService', ['$resource', '$q', function ($resource, $q
page: 0,
size: 1
}, function (result) {
if (result && result.length){
if (result && result.length) {
d.resolve(result[0]);
}
......
......@@ -2,7 +2,7 @@ appService.service('ServerConfigService', ['$resource', '$q', function ($resourc
var server_config_resource = $resource('', {}, {
create_server_config: {
method: 'POST',
url:'/server/config'
url: '/server/config'
}
});
return {
......
......@@ -607,6 +607,12 @@ table th {
padding: 0;
}
.release-history .release-history-list .emergency-publish {
position: absolute;
left: 0;
top: 0;
}
.release-history .release-history-list .load-more {
height: 45px;
background: #f5f5f5;
......
<div class="modal fade" id="publishDenyModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">发布受限</h4>
</div>
<div class="modal-body">
您不能发布哟~{{env}}环境配置的编辑和发布必须为不同的人,请找另一个具有当前namespace发布权的人操作发布~
<span ng-if="toReleaseNamespace.isEmergencyPublishAllowed">
<br><br><small>(如果是非工作时间或者特殊情况,您可以通过点击<mark>紧急发布</mark>按钮进行发布)</small>
</span>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal"
ng-if="toReleaseNamespace.isEmergencyPublishAllowed"
ng-click="emergencyPublish()">
紧急发布
</button>
<button type="button" class="btn btn-primary" data-dismiss="modal">
关闭
</button>
</div>
</div>
</div>
</div>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册