Commit daa18396 authored by nobodyiam's avatar nobodyiam Committed by Jason Song

add syntax check function for yaml namespace

parent 41450a51
...@@ -3,6 +3,7 @@ package com.ctrip.framework.apollo.portal.controller; ...@@ -3,6 +3,7 @@ package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets; import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ItemDTO; import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.portal.component.PermissionValidator; import com.ctrip.framework.apollo.portal.component.PermissionValidator;
...@@ -12,6 +13,8 @@ import com.ctrip.framework.apollo.portal.entity.vo.ItemDiffs; ...@@ -12,6 +13,8 @@ import com.ctrip.framework.apollo.portal.entity.vo.ItemDiffs;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceIdentifier; import com.ctrip.framework.apollo.portal.entity.vo.NamespaceIdentifier;
import com.ctrip.framework.apollo.portal.service.ItemService; import com.ctrip.framework.apollo.portal.service.ItemService;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder; import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
...@@ -185,6 +188,34 @@ public class ItemController { ...@@ -185,6 +188,34 @@ public class ItemController {
throw new AccessDeniedException(String.format("您没有修改环境%s的权限", envNoPermission)); throw new AccessDeniedException(String.format("您没有修改环境%s的权限", envNoPermission));
} }
@PreAuthorize(value = "@permissionValidator.hasModifyNamespacePermission(#appId, #namespaceName, #env)")
@PostMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/syntax-check", consumes = {
"application/json"})
public ResponseEntity<Void> syntaxCheckText(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName, @RequestBody NamespaceTextModel model) {
doSyntaxCheck(model);
return ResponseEntity.ok().build();
}
private void doSyntaxCheck(NamespaceTextModel model) {
if (StringUtils.isBlank(model.getConfigText())) {
return;
}
// only support yaml syntax check
if (model.getFormat() != ConfigFileFormat.YAML && model.getFormat() != ConfigFileFormat.YML) {
return;
}
// use YamlPropertiesFactoryBean to check the yaml syntax
YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
yamlPropertiesFactoryBean.setResources(new ByteArrayResource(model.getConfigText().getBytes()));
// this call converts yaml to properties and will throw exception if the conversion fails
yamlPropertiesFactoryBean.getObject();
}
private boolean isValidItem(ItemDTO item) { private boolean isValidItem(ItemDTO item) {
return Objects.nonNull(item) && !StringUtils.isContainEmpty(item.getKey()); return Objects.nonNull(item) && !StringUtils.isContainEmpty(item.getKey());
} }
......
...@@ -322,6 +322,12 @@ ...@@ -322,6 +322,12 @@
apollo-detail="deleteNamespaceContext.detailReason"> apollo-detail="deleteNamespaceContext.detailReason">
</apolloconfirmdialog> </apolloconfirmdialog>
<apolloconfirmdialog apollo-dialog-id="'syntaxCheckFailedDialog'"
apollo-title="'语法检查错误'"
apollo-detail="syntaxCheckContext.syntaxCheckMessage"
apollo-extra-class="'pre'">
</apolloconfirmdialog>
<div class="modal fade" id="createBranchTips" tabindex="-1" role="dialog"> <div class="modal fade" id="createBranchTips" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
......
...@@ -11,6 +11,16 @@ appUtil.service('AppUtil', ['toastr', '$window', '$q', function (toastr, $window ...@@ -11,6 +11,16 @@ appUtil.service('AppUtil', ['toastr', '$window', '$q', function (toastr, $window
return msg; return msg;
} }
function parsePureErrorMsg(response) {
if (response.status == -1) {
return "您的登录信息已过期,请刷新页面后重试";
}
if (response.data.message != null) {
return response.data.message;
}
return "";
}
function ajax(resource, requestParams, requestBody) { function ajax(resource, requestParams, requestBody) {
var d = $q.defer(); var d = $q.defer();
if (requestBody) { if (requestBody) {
...@@ -34,6 +44,7 @@ appUtil.service('AppUtil', ['toastr', '$window', '$q', function (toastr, $window ...@@ -34,6 +44,7 @@ appUtil.service('AppUtil', ['toastr', '$window', '$q', function (toastr, $window
return { return {
errorMsg: parseErrorMsg, errorMsg: parseErrorMsg,
pureErrorMsg: parsePureErrorMsg,
ajax: ajax, ajax: ajax,
showErrorMsg: function (response, title) { showErrorMsg: function (response, title) {
toastr.error(parseErrorMsg(response), title); toastr.error(parseErrorMsg(response), title);
......
...@@ -370,6 +370,11 @@ function controller($rootScope, $scope, toastr, AppUtil, EventManager, ConfigSer ...@@ -370,6 +370,11 @@ function controller($rootScope, $scope, toastr, AppUtil, EventManager, ConfigSer
}); });
EventManager.subscribe(EventManager.EventType.SYNTAX_CHECK_TEXT_FAILED, function (context) {
$scope.syntaxCheckContext = context;
AppUtil.showModal('#syntaxCheckFailedDialog');
});
new Clipboard('.clipboard'); new Clipboard('.clipboard');
......
...@@ -234,6 +234,7 @@ directive_module.directive('apolloconfirmdialog', function ($compile, $window, $ ...@@ -234,6 +234,7 @@ directive_module.directive('apolloconfirmdialog', function ($compile, $window, $
detail: '=apolloDetail', detail: '=apolloDetail',
showCancelBtn: '=apolloShowCancelBtn', showCancelBtn: '=apolloShowCancelBtn',
doConfirm: '=apolloConfirm', doConfirm: '=apolloConfirm',
extraClass: '=apolloExtraClass',
confirmBtnText: '=?', confirmBtnText: '=?',
cancel: '=' cancel: '='
}, },
......
...@@ -52,6 +52,7 @@ function directive($window, toastr, AppUtil, EventManager, PermissionService, Na ...@@ -52,6 +52,7 @@ function directive($window, toastr, AppUtil, EventManager, PermissionService, Na
scope.toggleTextEditStatus = toggleTextEditStatus; scope.toggleTextEditStatus = toggleTextEditStatus;
scope.goToSyncPage = goToSyncPage; scope.goToSyncPage = goToSyncPage;
scope.modifyByText = modifyByText; scope.modifyByText = modifyByText;
scope.syntaxCheck = syntaxCheck;
scope.goToParentAppConfigPage = goToParentAppConfigPage; scope.goToParentAppConfigPage = goToParentAppConfigPage;
scope.switchInstanceViewType = switchInstanceViewType; scope.switchInstanceViewType = switchInstanceViewType;
scope.switchBranch = switchBranch; scope.switchBranch = switchBranch;
...@@ -115,6 +116,7 @@ function directive($window, toastr, AppUtil, EventManager, PermissionService, Na ...@@ -115,6 +116,7 @@ function directive($window, toastr, AppUtil, EventManager, PermissionService, Na
scope.showNamespaceBody = namespace.showNamespaceBody ? true : scope.showBody; scope.showNamespaceBody = namespace.showNamespaceBody ? true : scope.showBody;
namespace.viewItems = namespace.items; namespace.viewItems = namespace.items;
namespace.isPropertiesFormat = namespace.format == 'properties'; namespace.isPropertiesFormat = namespace.format == 'properties';
namespace.isSyntaxCheckable = namespace.format == 'yaml' || namespace.format == 'yml';
namespace.isTextEditing = false; namespace.isTextEditing = false;
namespace.instanceViewType = namespace_instance_view_type.LATEST_RELEASE; namespace.instanceViewType = namespace_instance_view_type.LATEST_RELEASE;
namespace.latestReleaseInstancesPage = 0; namespace.latestReleaseInstancesPage = 0;
...@@ -735,6 +737,28 @@ function directive($window, toastr, AppUtil, EventManager, PermissionService, Na ...@@ -735,6 +737,28 @@ function directive($window, toastr, AppUtil, EventManager, PermissionService, Na
toggleTextEditStatus(namespace); toggleTextEditStatus(namespace);
} }
function syntaxCheck(namespace) {
var model = {
configText: namespace.editText,
namespaceId: namespace.baseInfo.id,
format: namespace.format
};
ConfigService.syntax_check_text(scope.appId,
scope.env,
scope.cluster,
namespace.baseInfo.namespaceName,
model).then(
function (result) {
toastr.success("语法正确!");
}, function (result) {
EventManager.emit(EventManager.EventType.SYNTAX_CHECK_TEXT_FAILED, {
syntaxCheckMessage: AppUtil.pureErrorMsg(result)
});
}
);
}
function goToParentAppConfigPage(namespace) { function goToParentAppConfigPage(namespace) {
$window.location.href = "/config.html?#/appid=" + namespace.parentAppId; $window.location.href = "/config.html?#/appid=" + namespace.parentAppId;
$window.location.reload(); $window.location.reload();
......
...@@ -45,6 +45,10 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q) ...@@ -45,6 +45,10 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
delete_item: { delete_item: {
method: 'DELETE', method: 'DELETE',
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/items/:itemId' url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/items/:itemId'
},
syntax_check_text: {
method: 'POST',
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/syntax-check'
} }
}); });
...@@ -193,6 +197,23 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q) ...@@ -193,6 +197,23 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
d.reject(result); d.reject(result);
}); });
return d.promise; return d.promise;
},
syntax_check_text: function (appId, env, clusterName, namespaceName, model) {
var d = $q.defer();
config_source.syntax_check_text({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName
},
model, function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
} }
} }
......
...@@ -137,7 +137,8 @@ appService.service('EventManager', [function () { ...@@ -137,7 +137,8 @@ appService.service('EventManager', [function () {
PRE_DELETE_NAMESPACE: 'pre_delete_namespace', PRE_DELETE_NAMESPACE: 'pre_delete_namespace',
DELETE_NAMESPACE: 'delete_namespace', DELETE_NAMESPACE: 'delete_namespace',
DELETE_NAMESPACE_FAILED: 'delete_namespace_failed', DELETE_NAMESPACE_FAILED: 'delete_namespace_failed',
CHANGE_ENV_CLUSTER: "change_env_cluster" CHANGE_ENV_CLUSTER: "change_env_cluster",
SYNTAX_CHECK_TEXT_FAILED: "syntax_check_text_failed"
} }
} }
......
...@@ -78,7 +78,7 @@ p, td, span { ...@@ -78,7 +78,7 @@ p, td, span {
color: #797979; color: #797979;
} }
pre { pre, .pre {
white-space: pre-wrap; /* Since CSS 2.1 */ white-space: pre-wrap; /* Since CSS 2.1 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */ white-space: -pre-wrap; /* Opera 4-6 */
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
aria-hidden="true">&times;</span></button> aria-hidden="true">&times;</span></button>
<h4 class="modal-title">{{title}}</h4> <h4 class="modal-title">{{title}}</h4>
</div> </div>
<div class="modal-body" ng-bind-html="detailAsHtml"> <div class="modal-body {{extraClass}}" ng-bind-html="detailAsHtml">
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" <button type="button" class="btn btn-default" data-dismiss="modal"
......
...@@ -150,6 +150,10 @@ ...@@ -150,6 +150,10 @@
data-clipboard-text="{{namespace.text}}" data-clipboard-text="{{namespace.text}}"
data-tooltip="tooltip" data-placement="bottom" title="复制文本" data-tooltip="tooltip" data-placement="bottom" title="复制文本"
ng-show="!namespace.isTextEditing && namespace.viewType == 'text' && namespace.hasModifyPermission"> ng-show="!namespace.isTextEditing && namespace.viewType == 'text' && namespace.hasModifyPermission">
<img src="img/syntax.png" class="ns_btn cursor-pointer"
data-tooltip="tooltip" data-placement="bottom" title="语法检查"
ng-show="namespace.isTextEditing && namespace.viewType == 'text' && namespace.isSyntaxCheckable"
ng-click="syntaxCheck(namespace)">
&nbsp; &nbsp;
<img src="img/cancel.png" class="ns_btn cursor-pointer" <img src="img/cancel.png" class="ns_btn cursor-pointer"
data-tooltip="tooltip" data-placement="bottom" title="取消修改" data-tooltip="tooltip" data-placement="bottom" title="取消修改"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment