Commit cff5d8cd authored by nobodyiam's avatar nobodyiam

add admin page to delete app, cluster and app namespace

parent e9459a21
...@@ -52,9 +52,14 @@ public class ClusterController { ...@@ -52,9 +52,14 @@ public class ClusterController {
@RequestMapping(value = "apps/{appId}/envs/{env}/clusters/{clusterName:.+}", method = RequestMethod.DELETE) @RequestMapping(value = "apps/{appId}/envs/{env}/clusters/{clusterName:.+}", method = RequestMethod.DELETE)
public ResponseEntity<Void> deleteCluster(@PathVariable String appId, @PathVariable String env, public ResponseEntity<Void> deleteCluster(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName){ @PathVariable String clusterName){
clusterService.deleteCluster(Env.valueOf(env), appId, clusterName); clusterService.deleteCluster(Env.fromString(env), appId, clusterName);
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
@RequestMapping(value = "apps/{appId}/envs/{env}/clusters/{clusterName:.+}", method = RequestMethod.GET)
public ClusterDTO loadCluster(@PathVariable("appId") String appId, @PathVariable String env, @PathVariable("clusterName") String clusterName) {
return clusterService.loadCluster(appId, Env.fromString(env), clusterName);
}
} }
package com.ctrip.framework.apollo.portal.controller; package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.common.dto.AppNamespaceDTO;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.portal.listener.AppNamespaceDeletionEvent; import com.ctrip.framework.apollo.portal.listener.AppNamespaceDeletionEvent;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
...@@ -144,6 +146,18 @@ public class NamespaceController { ...@@ -144,6 +146,18 @@ public class NamespaceController {
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
@RequestMapping(value = "/apps/{appId}/appnamespaces/{namespaceName:.+}", method = RequestMethod.GET)
public AppNamespaceDTO findAppNamespace(@PathVariable String appId, @PathVariable String namespaceName) {
AppNamespace appNamespace = appNamespaceService.findByAppIdAndName(appId, namespaceName);
if (appNamespace == null) {
throw new BadRequestException(
String.format("AppNamespace not exists. AppId = %s, NamespaceName = %s", appId, namespaceName));
}
return BeanUtils.transfrom(AppNamespaceDTO.class, appNamespace);
}
@PreAuthorize(value = "@permissionValidator.hasCreateAppNamespacePermission(#appId, #appNamespace)") @PreAuthorize(value = "@permissionValidator.hasCreateAppNamespacePermission(#appId, #appNamespace)")
@RequestMapping(value = "/apps/{appId}/appnamespaces", method = RequestMethod.POST) @RequestMapping(value = "/apps/{appId}/appnamespaces", method = RequestMethod.POST)
public AppNamespace createAppNamespace(@PathVariable String appId, @RequestBody AppNamespace appNamespace) { public AppNamespace createAppNamespace(@PathVariable String appId, @RequestBody AppNamespace appNamespace) {
......
<!doctype html>
<html ng-app="delete_app_cluster_namespace">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="icon" href="../img/config.png">
<!-- styles -->
<link rel="stylesheet" type="text/css" href="../vendor/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="../vendor/angular/angular-toastr-1.4.1.min.css">
<link rel="stylesheet" type="text/css" media='all' href="../vendor/angular/loading-bar.min.css">
<link rel="stylesheet" type="text/css" href="../styles/common-style.css">
<link rel="stylesheet" type="text/css" href="../vendor/select2/select2.min.css">
<title>删除应用、集群、AppNamespace</title>
</head>
<body>
<apollonav></apollonav>
<div class="container-fluid" ng-controller="DeleteAppClusterNamespaceController">
<div class="col-md-10 col-md-offset-1 panel">
<section class="panel-body" ng-show="isRootUser">
<!-- delete app -->
<section class="row">
<h5>删除应用
<small>
(由于删除应用影响面较大,所以现在暂时只允许系统管理员删除,请确保没有客户端读取该应用的配置后再做删除动作)
</small>
</h5>
<hr>
<form class="form-horizontal">
<div class="form-group" valdr-form-group>
<label class="col-sm-2 control-label">
<apollorequiredfield></apollorequiredfield>
应用AppId</label>
<div class="col-sm-5">
<input type="text" class="form-control" ng-model="app.appId">
<small>(删除前请先查询应用信息)</small>
</div>
<div class="col-sm-1">
<button class="btn btn-info" ng-click="getAppInfo()">查询</button>
</div>
</div>
<div class="form-group" valdr-form-group>
<label class="col-sm-2 control-label">
应用信息</label>
<div class="col-sm-5">
<h5 ng-show="app.info" ng-bind="app.info"></h5>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-9">
<button type="submit" class="btn btn-primary"
ng-disabled="deleteAppBtnDisabled"
ng-click="deleteApp()">
删除应用
</button>
</div>
</div>
</form>
</section>
<!-- delete cluster -->
<section class="row">
<h5>删除集群
<small>
(由于删除集群影响面较大,所以现在暂时只允许系统管理员删除,请确保没有客户端读取该集群的配置后再做删除动作)
</small>
</h5>
<hr>
<form class="form-horizontal">
<div class="form-group" valdr-form-group>
<label class="col-sm-2 control-label">
<apollorequiredfield></apollorequiredfield>
应用AppId</label>
<div class="col-sm-5">
<input type="text" class="form-control" ng-model="cluster.appId">
</div>
</div>
<div class="form-group" valdr-form-group>
<label class="col-sm-2 control-label">
<apollorequiredfield></apollorequiredfield>
环境名称</label>
<div class="col-sm-5">
<input type="text" class="form-control" ng-model="cluster.env">
</div>
</div>
<div class="form-group" valdr-form-group>
<label class="col-sm-2 control-label">
<apollorequiredfield></apollorequiredfield>
集群名称</label>
<div class="col-sm-5">
<input type="text" class="form-control" ng-model="cluster.name">
<small>(删除前请先查询应用集群信息)</small>
</div>
<div class="col-sm-1">
<button class="btn btn-info" ng-click="getClusterInfo()">查询</button>
</div>
</div>
<div class="form-group" viv clform-group>
<label class="col-sm-2 control-label">
集群信息</label>
<div class="col-sm-5">
<h5 ng-show="cluster.info" ng-bind="cluster.info"></h5>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-9">
<button type="submit" class="btn btn-primary"
ng-disabled="deleteClusterBtnDisabled"
ng-click="deleteCluster()">
删除集群
</button>
</div>
</div>
</form>
</section>
<!-- delete app namespace -->
<section class="row">
<h5>删除AppNamespace
<small>(注意,所有环境的Namespace和AppNamespace都会被删除!如果只是要删除某个环境的Namespace,让用户到项目页面中自行删除!)</small>
</h5>
<small>
目前用户可以自行删除关联的Namespace和私有的Namespace,不过无法删除AppNamespace元信息,因为删除AppNamespace影响面较大,所以现在暂时只允许系统管理员删除,对于公共Namespace需要确保没有应用关联了该AppNamespace。
</small>
<hr>
<form class="form-horizontal">
<div class="form-group" valdr-form-group>
<label class="col-sm-2 control-label">
<apollorequiredfield></apollorequiredfield>
应用AppId</label>
<div class="col-sm-5">
<input type="text" class="form-control" ng-model="appNamespace.appId">
</div>
</div>
<div class="form-group" valdr-form-group>
<label class="col-sm-2 control-label">
<apollorequiredfield></apollorequiredfield>
AppNamespace名称</label>
<div class="col-sm-5">
<input type="text" class="form-control" ng-model="appNamespace.name">
<small>(非properties类型的namespace请加上类型后缀,例如apollo.xml)</small>
</div>
<div class="col-sm-1">
<button class="btn btn-info" ng-click="getAppNamespaceInfo()">查询</button>
</div>
</div>
<div class="form-group" viv clform-group>
<label class="col-sm-2 control-label">
集群信息</label>
<div class="col-sm-5">
<h5 ng-show="appNamespace.info" ng-bind="appNamespace.info"></h5>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-9">
<button type="submit" class="btn btn-primary"
ng-disabled="deleteAppNamespaceBtnDisabled"
ng-click="deleteAppNamespace()">
删除AppNamespace
</button>
</div>
</div>
</form>
</section>
</section>
<section class="panel-body text-center" ng-if="!isRootUser">
<h4>当前页面只对Apollo管理员开放</h4>
</section>
</div>
</div>
<div ng-include="'../views/common/footer.html'"></div>
<!-- jquery.js -->
<script src="../vendor/jquery.min.js" type="text/javascript"></script>
<!--angular-->
<script src="../vendor/angular/angular.min.js"></script>
<script src="../vendor/angular/angular-route.min.js"></script>
<script src="../vendor/angular/angular-resource.min.js"></script>
<script src="../vendor/angular/angular-toastr-1.4.1.tpls.min.js"></script>
<script src="../vendor/angular/loading-bar.min.js"></script>
<!--valdr-->
<script src="../vendor/valdr/valdr.min.js" type="text/javascript"></script>
<script src="../vendor/valdr/valdr-message.min.js" type="text/javascript"></script>
<!-- bootstrap.js -->
<script src="../vendor/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
<!--nicescroll-->
<script src="../vendor/jquery.nicescroll.min.js"></script>
<script src="../vendor/lodash.min.js"></script>
<script src="../vendor/select2/select2.min.js" type="text/javascript"></script>
<!--biz-->
<!--must import-->
<script type="application/javascript" src="../scripts/app.js"></script>
<script type="application/javascript" src="../scripts/services/AppService.js"></script>
<script type="application/javascript" src="../scripts/services/EnvService.js"></script>
<script type="application/javascript" src="../scripts/services/UserService.js"></script>
<script type="application/javascript" src="../scripts/services/CommonService.js"></script>
<script type="application/javascript" src="../scripts/services/PermissionService.js"></script>
<script type="application/javascript" src="../scripts/services/ClusterService.js"></script>
<script type="application/javascript" src="../scripts/services/NamespaceService.js"></script>
<script type="application/javascript" src="../scripts/AppUtils.js"></script>
<script type="application/javascript" src="../scripts/PageCommon.js"></script>
<script type="application/javascript" src="../scripts/directive/directive.js"></script>
<script type="application/javascript" src="../scripts/valdr.js"></script>
<script type="application/javascript" src="../scripts/controller/DeleteAppClusterNamespaceController.js"></script>
</body>
</html>
...@@ -34,6 +34,5 @@ var open_manage_module = angular.module('open_manage', ['app.service', 'apollo.d ...@@ -34,6 +34,5 @@ var open_manage_module = angular.module('open_manage', ['app.service', 'apollo.d
var user_module = angular.module('user', ['apollo.directive', 'toastr', 'app.service', 'app.util', 'angular-loading-bar', 'valdr']); var user_module = angular.module('user', ['apollo.directive', 'toastr', 'app.service', 'app.util', 'angular-loading-bar', 'valdr']);
//login //login
var login_module = angular.module('login', ['toastr', 'app.util']); var login_module = angular.module('login', ['toastr', 'app.util']);
//delete app cluster namespace
var delete_app_cluster_namespace_module = angular.module('delete_app_cluster_namespace', ['app.service', 'apollo.directive', 'app.util', 'toastr', 'angular-loading-bar']);
delete_app_cluster_namespace_module.controller('DeleteAppClusterNamespaceController',
['$scope', 'toastr', 'AppUtil', 'AppService', 'ClusterService', 'NamespaceService', 'PermissionService',
DeleteAppClusterNamespaceController]);
function DeleteAppClusterNamespaceController($scope, toastr, AppUtil, AppService, ClusterService, NamespaceService, PermissionService) {
$scope.app = {};
$scope.deleteAppBtnDisabled = true;
$scope.getAppInfo = getAppInfo;
$scope.deleteApp = deleteApp;
$scope.cluster = {};
$scope.deleteClusterBtnDisabled = true;
$scope.getClusterInfo = getClusterInfo;
$scope.deleteCluster = deleteCluster;
$scope.appNamespace = {};
$scope.deleteAppNamespaceBtnDisabled = true;
$scope.getAppNamespaceInfo = getAppNamespaceInfo;
$scope.deleteAppNamespace = deleteAppNamespace;
initPermission();
function initPermission() {
PermissionService.has_root_permission()
.then(function (result) {
$scope.isRootUser = result.hasPermission;
})
}
function getAppInfo() {
if (!$scope.app.appId) {
toastr.warning("请输入appId");
return;
}
$scope.app.info = "";
AppService.load($scope.app.appId).then(function (result) {
if (!result.appId) {
toastr.warning("AppId: " + $scope.app.appId + " 不存在!");
$scope.deleteAppBtnDisabled = true;
return;
}
$scope.app.info = "应用名:" + result.name + " 部门:" + result.orgName + '(' + result.orgId + ')' + " 负责人:" + result.ownerName;
$scope.deleteAppBtnDisabled = false;
}, function (result) {
AppUtil.showErrorMsg(result);
});
}
function deleteApp() {
if (!$scope.app.appId) {
toastr.warning("请输入appId");
return;
}
if (confirm("确认删除AppId: " + $scope.app.appId + "")) {
AppService.delete_app($scope.app.appId).then(function (result) {
toastr.success("删除成功");
$scope.deleteAppBtnDisabled = true;
}, function (result) {
AppUtil.showErrorMsg(result);
})
}
}
function getClusterInfo() {
if (!$scope.cluster.appId || !$scope.cluster.env || !$scope.cluster.name) {
toastr.warning("请输入appId、环境和集群名称");
return;
}
$scope.cluster.info = "";
ClusterService.load_cluster($scope.cluster.appId, $scope.cluster.env, $scope.cluster.name).then(function (result) {
$scope.cluster.info = "AppId:" + result.appId+ " 环境:" + $scope.cluster.env + " 集群名称:" + result.name;
$scope.deleteClusterBtnDisabled = false;
}, function (result) {
AppUtil.showErrorMsg(result);
});
}
function deleteCluster() {
if (!$scope.cluster.appId || !$scope.cluster.env || !$scope.cluster.name) {
toastr.warning("请输入appId、环境和集群名称");
return;
}
if (confirm("确认删除集群?appId: " + $scope.cluster.appId + " 环境:" + $scope.cluster.env + " 集群名称:" + $scope.cluster.name)) {
ClusterService.delete_cluster($scope.cluster.appId, $scope.cluster.env, $scope.cluster.name).then(function (result) {
toastr.success("删除成功");
$scope.deleteClusterBtnDisabled = true;
}, function (result) {
AppUtil.showErrorMsg(result);
})
}
}
function getAppNamespaceInfo() {
if (!$scope.appNamespace.appId || !$scope.appNamespace.name) {
toastr.warning("请输入appId和AppNamespace名称");
return;
}
$scope.appNamespace.info = "";
NamespaceService.loadAppNamespace($scope.appNamespace.appId, $scope.appNamespace.name).then(function (result) {
$scope.appNamespace.info = "AppId:" + result.appId+ " AppNamespace名称:" + result.name + " isPublic:" + result.isPublic;
$scope.deleteAppNamespaceBtnDisabled = false;
}, function (result) {
AppUtil.showErrorMsg(result);
});
}
function deleteAppNamespace() {
if (!$scope.appNamespace.appId || !$scope.appNamespace.name) {
toastr.warning("请输入appId和AppNamespace名称");
return;
}
if (confirm("确认删除所有环境的AppNamespace和Namespace?appId: " + $scope.appNamespace.appId + " 环境:所有环境" + " AppNamespace名称:" + $scope.appNamespace.name)) {
NamespaceService.deleteAppNamespace($scope.appNamespace.appId, $scope.appNamespace.name).then(function (result) {
toastr.success("删除成功");
$scope.deleteAppNamespaceBtnDisabled = true;
}, function (result) {
AppUtil.showErrorMsg(result);
})
}
}
}
...@@ -34,6 +34,10 @@ appService.service('AppService', ['$resource', '$q', function ($resource, $q) { ...@@ -34,6 +34,10 @@ appService.service('AppService', ['$resource', '$q', function ($resource, $q) {
find_miss_envs: { find_miss_envs: {
method: 'GET', method: 'GET',
url: '/apps/:appId/miss_envs' url: '/apps/:appId/miss_envs'
},
delete_app: {
method: 'DELETE',
isArray: false
} }
}); });
return { return {
...@@ -123,6 +127,17 @@ appService.service('AppService', ['$resource', '$q', function ($resource, $q) { ...@@ -123,6 +127,17 @@ appService.service('AppService', ['$resource', '$q', function ($resource, $q) {
d.reject(result); d.reject(result);
}); });
return d.promise; return d.promise;
},
delete_app: function (appId) {
var d = $q.defer();
app_resource.delete_app({
appId: appId
}, function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
} }
} }
}]); }]);
...@@ -3,6 +3,14 @@ appService.service('ClusterService', ['$resource', '$q', function ($resource, $q ...@@ -3,6 +3,14 @@ appService.service('ClusterService', ['$resource', '$q', function ($resource, $q
create_cluster: { create_cluster: {
method: 'POST', method: 'POST',
url: 'apps/:appId/envs/:env/clusters' url: 'apps/:appId/envs/:env/clusters'
},
load_cluster: {
method: 'GET',
url: 'apps/:appId/envs/:env/clusters/:clusterName'
},
delete_cluster: {
method: 'DELETE',
url: 'apps/:appId/envs/:env/clusters/:clusterName'
} }
}); });
return { return {
...@@ -18,6 +26,34 @@ appService.service('ClusterService', ['$resource', '$q', function ($resource, $q ...@@ -18,6 +26,34 @@ appService.service('ClusterService', ['$resource', '$q', function ($resource, $q
d.reject(result); d.reject(result);
}); });
return d.promise; return d.promise;
},
load_cluster: function (appId, env, clusterName) {
var d = $q.defer();
cluster_resource.load_cluster({
appId: appId,
env: env,
clusterName: clusterName
},
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
},
delete_cluster: function (appId, env, clusterName) {
var d = $q.defer();
cluster_resource.delete_cluster({
appId: appId,
env: env,
clusterName: clusterName
},
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
} }
} }
}]); }]);
...@@ -27,6 +27,14 @@ appService.service("NamespaceService", ['$resource', '$q', function ($resource, ...@@ -27,6 +27,14 @@ appService.service("NamespaceService", ['$resource', '$q', function ($resource,
method: 'GET', method: 'GET',
url: '/envs/:env/appnamespaces/:publicNamespaceName/namespaces', url: '/envs/:env/appnamespaces/:publicNamespaceName/namespaces',
isArray: true isArray: true
},
loadAppNamespace: {
method: 'GET',
url: '/apps/:appId/appnamespaces/:namespaceName'
},
deleteAppNamespace: {
method: 'DELETE',
url: '/apps/:appId/appnamespaces/:namespaceName'
} }
}); });
...@@ -112,13 +120,47 @@ appService.service("NamespaceService", ['$resource', '$q', function ($resource, ...@@ -112,13 +120,47 @@ appService.service("NamespaceService", ['$resource', '$q', function ($resource,
} }
function loadAppNamespace(appId, namespaceName) {
var d = $q.defer();
namespace_source.loadAppNamespace({
appId: appId,
namespaceName: namespaceName
},
function (result) {
d.resolve(result);
},
function (result) {
d.reject(result);
});
return d.promise;
}
function deleteAppNamespace(appId, namespaceName) {
var d = $q.defer();
namespace_source.deleteAppNamespace({
appId: appId,
namespaceName: namespaceName
},
function (result) {
d.resolve(result);
},
function (result) {
d.reject(result);
});
return d.promise;
}
return { return {
find_public_namespaces: find_public_namespaces, find_public_namespaces: find_public_namespaces,
createNamespace: createNamespace, createNamespace: createNamespace,
createAppNamespace: createAppNamespace, createAppNamespace: createAppNamespace,
getNamespacePublishInfo: getNamespacePublishInfo, getNamespacePublishInfo: getNamespacePublishInfo,
deleteNamespace: deleteNamespace, deleteNamespace: deleteNamespace,
getPublicAppNamespaceAllNamespaces: getPublicAppNamespaceAllNamespaces getPublicAppNamespaceAllNamespaces: getPublicAppNamespaceAllNamespaces,
loadAppNamespace: loadAppNamespace,
deleteAppNamespace: deleteAppNamespace
} }
}]); }]);
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
<li><a href="/user-manage.html" target="_blank">用户管理</a></li> <li><a href="/user-manage.html" target="_blank">用户管理</a></li>
<li><a href="/open/manage.html" target="_blank">开放平台授权管理</a></li> <li><a href="/open/manage.html" target="_blank">开放平台授权管理</a></li>
<li><a href="/server_config.html" target="_blank">系统参数</a></li> <li><a href="/server_config.html" target="_blank">系统参数</a></li>
<li><a href="/delete_app_cluster_namespace.html" target="_blank">删除应用、集群、AppNamespace</a></li>
</ul> </ul>
</li> </li>
<li class="dropdown"> <li class="dropdown">
......
...@@ -111,13 +111,18 @@ public class NamespaceServiceTest extends AbstractUnitTest { ...@@ -111,13 +111,18 @@ public class NamespaceServiceTest extends AbstractUnitTest {
} }
@Test(expected = BadRequestException.class) @Test
public void testDeletePrivateNamespace() { public void testDeletePrivateNamespace() {
String operator = "user";
AppNamespace privateNamespace = createAppNamespace(testAppId, testNamespaceName, false); AppNamespace privateNamespace = createAppNamespace(testAppId, testNamespaceName, false);
when(appNamespaceService.findByAppIdAndName(testAppId, testNamespaceName)).thenReturn(privateNamespace); when(appNamespaceService.findByAppIdAndName(testAppId, testNamespaceName)).thenReturn(privateNamespace);
when(userInfoHolder.getUser()).thenReturn(createUser(operator));
namespaceService.deleteNamespace(testAppId, testEnv, testClusterName, testNamespaceName); namespaceService.deleteNamespace(testAppId, testEnv, testClusterName, testNamespaceName);
verify(namespaceAPI, times(1)).deleteNamespace(testEnv, testAppId, testClusterName, testNamespaceName, operator);
} }
@Test(expected = BadRequestException.class) @Test(expected = BadRequestException.class)
......
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