implem
return new ArrayList<>(entities.values());
}
+ public void clearAll() {
+ allRules.clear();
+ machineRules.clear();
+ appRules.clear();
+ }
+
protected T preProcess(T entity) {
return entity;
}
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js
index fcc61610..bc3747af 100755
--- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js
+++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js
@@ -26,9 +26,9 @@ angular
.factory('AuthInterceptor', ['$window', '$state', function ($window, $state) {
var authInterceptor = {
'responseError' : function(response) {
- if (response.status == 401) {
+ if (response.status === 401) {
// If not auth, clear session in localStorage and jump to the login page
- $window.localStorage.removeItem("session_sentinel_admin");
+ $window.localStorage.removeItem('session_sentinel_admin');
$state.go('login');
}
@@ -123,21 +123,21 @@ angular
}
})
- .state('dashboard.flow', {
- templateUrl: 'app/views/flow_v2.html',
- url: '/v2/flow/:app',
- controller: 'FlowControllerV2',
- resolve: {
- loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
- return $ocLazyLoad.load({
- name: 'sentinelDashboardApp',
- files: [
- 'app/scripts/controllers/flow_v2.js',
- ]
- });
- }]
- }
- })
+ .state('dashboard.flow', {
+ templateUrl: 'app/views/flow_v2.html',
+ url: '/v2/flow/:app',
+ controller: 'FlowControllerV2',
+ resolve: {
+ loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
+ return $ocLazyLoad.load({
+ name: 'sentinelDashboardApp',
+ files: [
+ 'app/scripts/controllers/flow_v2.js',
+ ]
+ });
+ }]
+ }
+ })
.state('dashboard.paramFlow', {
templateUrl: 'app/views/param_flow.html',
@@ -155,69 +155,69 @@ angular
}
})
- .state('dashboard.clusterAppAssignManage', {
- templateUrl: 'app/views/cluster_app_assign_manage.html',
- url: '/cluster/assign_manage/:app',
- controller: 'SentinelClusterAppAssignManageController',
- resolve: {
- loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
- return $ocLazyLoad.load({
- name: 'sentinelDashboardApp',
- files: [
- 'app/scripts/controllers/cluster_app_assign_manage.js',
- ]
- });
- }]
- }
- })
+ .state('dashboard.clusterAppAssignManage', {
+ templateUrl: 'app/views/cluster_app_assign_manage.html',
+ url: '/cluster/assign_manage/:app',
+ controller: 'SentinelClusterAppAssignManageController',
+ resolve: {
+ loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
+ return $ocLazyLoad.load({
+ name: 'sentinelDashboardApp',
+ files: [
+ 'app/scripts/controllers/cluster_app_assign_manage.js',
+ ]
+ });
+ }]
+ }
+ })
- .state('dashboard.clusterAppServerList', {
- templateUrl: 'app/views/cluster_app_server_list.html',
- url: '/cluster/server/:app',
- controller: 'SentinelClusterAppServerListController',
- resolve: {
- loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
- return $ocLazyLoad.load({
- name: 'sentinelDashboardApp',
- files: [
- 'app/scripts/controllers/cluster_app_server_list.js',
- ]
- });
- }]
- }
- })
+ .state('dashboard.clusterAppServerList', {
+ templateUrl: 'app/views/cluster_app_server_list.html',
+ url: '/cluster/server/:app',
+ controller: 'SentinelClusterAppServerListController',
+ resolve: {
+ loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
+ return $ocLazyLoad.load({
+ name: 'sentinelDashboardApp',
+ files: [
+ 'app/scripts/controllers/cluster_app_server_list.js',
+ ]
+ });
+ }]
+ }
+ })
- .state('dashboard.clusterAppClientList', {
- templateUrl: 'app/views/cluster_app_client_list.html',
- url: '/cluster/client/:app',
- controller: 'SentinelClusterAppTokenClientListController',
- resolve: {
- loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
- return $ocLazyLoad.load({
- name: 'sentinelDashboardApp',
- files: [
- 'app/scripts/controllers/cluster_app_token_client_list.js',
- ]
- });
- }]
- }
- })
+ .state('dashboard.clusterAppClientList', {
+ templateUrl: 'app/views/cluster_app_client_list.html',
+ url: '/cluster/client/:app',
+ controller: 'SentinelClusterAppTokenClientListController',
+ resolve: {
+ loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
+ return $ocLazyLoad.load({
+ name: 'sentinelDashboardApp',
+ files: [
+ 'app/scripts/controllers/cluster_app_token_client_list.js',
+ ]
+ });
+ }]
+ }
+ })
- .state('dashboard.clusterSingle', {
- templateUrl: 'app/views/cluster_single_config.html',
- url: '/cluster/single/:app',
- controller: 'SentinelClusterSingleController',
- resolve: {
- loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
- return $ocLazyLoad.load({
- name: 'sentinelDashboardApp',
- files: [
- 'app/scripts/controllers/cluster_single.js',
- ]
- });
- }]
- }
- })
+ .state('dashboard.clusterSingle', {
+ templateUrl: 'app/views/cluster_single_config.html',
+ url: '/cluster/single/:app',
+ controller: 'SentinelClusterSingleController',
+ resolve: {
+ loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
+ return $ocLazyLoad.load({
+ name: 'sentinelDashboardApp',
+ files: [
+ 'app/scripts/controllers/cluster_single.js',
+ ]
+ });
+ }]
+ }
+ })
.state('dashboard.authority', {
templateUrl: 'app/views/authority.html',
@@ -298,6 +298,23 @@ angular
}]
}
})
+
+ .state('dashboard.gatewayIdentity', {
+ templateUrl: 'app/views/gateway/identity.html',
+ url: '/gateway/identity/:app',
+ controller: 'GatewayIdentityCtl',
+ resolve: {
+ loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
+ return $ocLazyLoad.load({
+ name: 'sentinelDashboardApp',
+ files: [
+ 'app/scripts/controllers/gateway/identity.js',
+ ]
+ });
+ }]
+ }
+ })
+
.state('dashboard.metric', {
templateUrl: 'app/views/metric.html',
url: '/metric/:app',
@@ -312,5 +329,37 @@ angular
});
}]
}
+ })
+
+ .state('dashboard.gatewayApi', {
+ templateUrl: 'app/views/gateway/api.html',
+ url: '/gateway/api/:app',
+ controller: 'GatewayApiCtl',
+ resolve: {
+ loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
+ return $ocLazyLoad.load({
+ name: 'sentinelDashboardApp',
+ files: [
+ 'app/scripts/controllers/gateway/api.js',
+ ]
+ });
+ }]
+ }
+ })
+
+ .state('dashboard.gatewayFlow', {
+ templateUrl: 'app/views/gateway/flow.html',
+ url: '/gateway/flow/:app',
+ controller: 'GatewayFlowCtl',
+ resolve: {
+ loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
+ return $ocLazyLoad.load({
+ name: 'sentinelDashboardApp',
+ files: [
+ 'app/scripts/controllers/gateway/flow.js',
+ ]
+ });
+ }]
+ }
});
}]);
\ No newline at end of file
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/gateway/api.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/gateway/api.js
new file mode 100644
index 00000000..8e66b113
--- /dev/null
+++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/gateway/api.js
@@ -0,0 +1,245 @@
+var app = angular.module('sentinelDashboardApp');
+
+app.controller('GatewayApiCtl', ['$scope', '$stateParams', 'GatewayApiService', 'ngDialog', 'MachineService',
+ function ($scope, $stateParams, GatewayApiService, ngDialog, MachineService) {
+ $scope.app = $stateParams.app;
+
+ $scope.apisPageConfig = {
+ pageSize: 10,
+ currentPageIndex: 1,
+ totalPage: 1,
+ totalCount: 0,
+ };
+
+ $scope.macsInputConfig = {
+ searchField: ['text', 'value'],
+ persist: true,
+ create: false,
+ maxItems: 1,
+ render: {
+ item: function (data, escape) {
+ return '' + escape(data.text) + '
';
+ }
+ },
+ onChange: function (value, oldValue) {
+ $scope.macInputModel = value;
+ }
+ };
+
+ getApis();
+ function getApis() {
+ if (!$scope.macInputModel) {
+ return;
+ }
+
+ var mac = $scope.macInputModel.split(':');
+ GatewayApiService.queryApis($scope.app, mac[0], mac[1]).success(
+ function (data) {
+ if (data.code == 0 && data.data) {
+ // To merge rows for api who has more than one predicateItems, here we build data manually
+ $scope.apis = [];
+
+ data.data.forEach(function(api) {
+ api["predicateItems"].forEach(function (item, index) {
+ var newItem = {};
+ newItem["id"] = api["id"];
+ newItem["app"] = api["app"];
+ newItem["ip"] = api["ip"];
+ newItem["port"] = api["port"];
+ newItem["apiName"] = api["apiName"];
+ newItem["pattern"] = item["pattern"];
+ newItem["matchStrategy"] = item["matchStrategy"];
+ // The itemSize indicates how many rows to merge, by using rowspan="{{api.itemSize}}" in tag
+ newItem["itemSize"] = api["predicateItems"].length;
+ // Mark the flag of first item to zero, indicates the start row to merge
+ newItem["firstFlag"] = index == 0 ? 0 : 1;
+ // Still hold the data of predicateItems, in order to bind data in edit dialog html
+ newItem["predicateItems"] = api["predicateItems"];
+ $scope.apis.push(newItem);
+ });
+ });
+
+ $scope.apisPageConfig.totalCount = data.data.length;
+ } else {
+ $scope.apis = [];
+ $scope.apisPageConfig.totalCount = 0;
+ }
+ });
+ };
+ $scope.getApis = getApis;
+
+ var gatewayApiDialog;
+ $scope.editApi = function (api) {
+ $scope.currentApi = angular.copy(api);
+ $scope.gatewayApiDialog = {
+ title: '编辑自定义API',
+ type: 'edit',
+ confirmBtnText: '保存'
+ };
+ gatewayApiDialog = ngDialog.open({
+ template: '/app/views/dialog/gateway/api-dialog.html',
+ width: 840,
+ overlay: true,
+ scope: $scope
+ });
+ };
+
+ $scope.addNewApi = function () {
+ var mac = $scope.macInputModel.split(':');
+ $scope.currentApi = {
+ grade: 0,
+ app: $scope.app,
+ ip: mac[0],
+ port: mac[1],
+ predicateItems: [{matchStrategy: 0, pattern: ''}]
+ };
+ $scope.gatewayApiDialog = {
+ title: '新增自定义API',
+ type: 'add',
+ confirmBtnText: '新增'
+ };
+ gatewayApiDialog = ngDialog.open({
+ template: '/app/views/dialog/gateway/api-dialog.html',
+ width: 840,
+ overlay: true,
+ scope: $scope
+ });
+ };
+
+ $scope.saveApi = function () {
+ var apiNames = [];
+ if ($scope.gatewayApiDialog.type === 'add') {
+ apiNames = $scope.apis.map(function (item, index, array) {
+ return item["apiName"];
+ }).filter(function (item, index, array) {
+ return array.indexOf(item) === index;
+ });
+ }
+
+ if (!GatewayApiService.checkApiValid($scope.currentApi, apiNames)) {
+ return;
+ }
+
+ if ($scope.gatewayApiDialog.type === 'add') {
+ addNewApi($scope.currentApi);
+ } else if ($scope.gatewayApiDialog.type === 'edit') {
+ saveApi($scope.currentApi, true);
+ }
+ };
+
+ function addNewApi(api) {
+ GatewayApiService.newApi(api).success(function (data) {
+ if (data.code == 0) {
+ getApis();
+ gatewayApiDialog.close();
+ } else {
+ alert('新增自定义API失败!' + data.msg);
+ }
+ });
+ };
+
+ function saveApi(api, edit) {
+ GatewayApiService.saveApi(api).success(function (data) {
+ if (data.code == 0) {
+ getApis();
+ if (edit) {
+ gatewayApiDialog.close();
+ } else {
+ confirmDialog.close();
+ }
+ } else {
+ alert('修改自定义API失败!' + data.msg);
+ }
+ });
+ };
+
+ var confirmDialog;
+ $scope.deleteApi = function (api) {
+ $scope.currentApi = api;
+ $scope.confirmDialog = {
+ title: '删除自定义API',
+ type: 'delete_api',
+ attentionTitle: '请确认是否删除如下自定义API',
+ attention: 'API名称: ' + api.apiName,
+ confirmBtnText: '删除',
+ };
+ confirmDialog = ngDialog.open({
+ template: '/app/views/dialog/confirm-dialog.html',
+ scope: $scope,
+ overlay: true
+ });
+ };
+
+ $scope.confirm = function () {
+ if ($scope.confirmDialog.type == 'delete_api') {
+ deleteApi($scope.currentApi);
+ } else {
+ console.error('error');
+ }
+ };
+
+ function deleteApi(api) {
+ GatewayApiService.deleteApi(api).success(function (data) {
+ if (data.code == 0) {
+ getApis();
+ confirmDialog.close();
+ } else {
+ alert('删除自定义API失败!' + data.msg);
+ }
+ });
+ };
+
+ $scope.addNewMatchPattern = function() {
+ var total;
+ if ($scope.currentApi.predicateItems == null) {
+ $scope.currentApi.predicateItems = [];
+ total = 0;
+ } else {
+ total = $scope.currentApi.predicateItems.length;
+ }
+ $scope.currentApi.predicateItems.splice(total + 1, 0, {matchStrategy: 0, pattern: ''});
+ };
+
+ $scope.removeMatchPattern = function($index) {
+ if ($scope.currentApi.predicateItems.length <= 1) {
+ // Should never happen since no remove button will display when only one predicateItem.
+ alert('至少有一个匹配规则');
+ return;
+ }
+ $scope.currentApi.predicateItems.splice($index, 1);
+ };
+
+ queryAppMachines();
+ function queryAppMachines() {
+ MachineService.getAppMachines($scope.app).success(
+ function (data) {
+ if (data.code == 0) {
+ // $scope.machines = data.data;
+ if (data.data) {
+ $scope.machines = [];
+ $scope.macsInputOptions = [];
+ data.data.forEach(function (item) {
+ if (item.healthy) {
+ $scope.macsInputOptions.push({
+ text: item.ip + ':' + item.port,
+ value: item.ip + ':' + item.port
+ });
+ }
+ });
+ }
+ if ($scope.macsInputOptions.length > 0) {
+ $scope.macInputModel = $scope.macsInputOptions[0].value;
+ }
+ } else {
+ $scope.macsInputOptions = [];
+ }
+ }
+ );
+ };
+ $scope.$watch('macInputModel', function () {
+ if ($scope.macInputModel) {
+ getApis();
+ }
+ });
+ }]
+);
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/gateway/flow.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/gateway/flow.js
new file mode 100644
index 00000000..c492cf9c
--- /dev/null
+++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/gateway/flow.js
@@ -0,0 +1,251 @@
+var app = angular.module('sentinelDashboardApp');
+
+app.controller('GatewayFlowCtl', ['$scope', '$stateParams', 'GatewayFlowService', 'GatewayApiService', 'ngDialog', 'MachineService',
+ function ($scope, $stateParams, GatewayFlowService, GatewayApiService, ngDialog, MachineService) {
+ $scope.app = $stateParams.app;
+
+ $scope.rulesPageConfig = {
+ pageSize: 10,
+ currentPageIndex: 1,
+ totalPage: 1,
+ totalCount: 0,
+ };
+
+ $scope.macsInputConfig = {
+ searchField: ['text', 'value'],
+ persist: true,
+ create: false,
+ maxItems: 1,
+ render: {
+ item: function (data, escape) {
+ return ' ' + escape(data.text) + ' ';
+ }
+ },
+ onChange: function (value, oldValue) {
+ $scope.macInputModel = value;
+ }
+ };
+
+ getMachineRules();
+ function getMachineRules() {
+ if (!$scope.macInputModel) {
+ return;
+ }
+
+ var mac = $scope.macInputModel.split(':');
+ GatewayFlowService.queryRules($scope.app, mac[0], mac[1]).success(
+ function (data) {
+ if (data.code == 0 && data.data) {
+ $scope.rules = data.data;
+ $scope.rulesPageConfig.totalCount = $scope.rules.length;
+ } else {
+ $scope.rules = [];
+ $scope.rulesPageConfig.totalCount = 0;
+ }
+ });
+ };
+ $scope.getMachineRules = getMachineRules;
+
+ getApiNames();
+ function getApiNames() {
+ if (!$scope.macInputModel) {
+ return;
+ }
+
+ var mac = $scope.macInputModel.split(':');
+ GatewayApiService.queryApis($scope.app, mac[0], mac[1]).success(
+ function (data) {
+ if (data.code == 0 && data.data) {
+ $scope.apiNames = [];
+
+ data.data.forEach(function (api) {
+ $scope.apiNames.push(api["apiName"]);
+ });
+ }
+ });
+ }
+
+ $scope.intervalUnits = [{val: 0, desc: '秒'}, {val: 1, desc: '分'}, {val: 2, desc: '时'}, {val: 3, desc: '天'}];
+
+ var gatewayFlowRuleDialog;
+ $scope.editRule = function (rule) {
+ $scope.currentRule = angular.copy(rule);
+ $scope.gatewayFlowRuleDialog = {
+ title: '编辑网关流控规则',
+ type: 'edit',
+ confirmBtnText: '保存'
+ };
+ gatewayFlowRuleDialog = ngDialog.open({
+ template: '/app/views/dialog/gateway/flow-rule-dialog.html',
+ width: 780,
+ overlay: true,
+ scope: $scope
+ });
+ };
+
+ $scope.addNewRule = function () {
+ var mac = $scope.macInputModel.split(':');
+ $scope.currentRule = {
+ grade: 1,
+ app: $scope.app,
+ ip: mac[0],
+ port: mac[1],
+ resourceMode: 0,
+ interval: 1,
+ intervalUnit: 0,
+ controlBehavior: 0,
+ burst: 0,
+ maxQueueingTimeoutMs: 0
+ };
+
+ $scope.gatewayFlowRuleDialog = {
+ title: '新增网关流控规则',
+ type: 'add',
+ confirmBtnText: '新增'
+ };
+
+ gatewayFlowRuleDialog = ngDialog.open({
+ template: '/app/views/dialog/gateway/flow-rule-dialog.html',
+ width: 780,
+ overlay: true,
+ scope: $scope
+ });
+ };
+
+ $scope.saveRule = function () {
+ if (!GatewayFlowService.checkRuleValid($scope.currentRule)) {
+ return;
+ }
+ if ($scope.gatewayFlowRuleDialog.type === 'add') {
+ addNewRule($scope.currentRule);
+ } else if ($scope.gatewayFlowRuleDialog.type === 'edit') {
+ saveRule($scope.currentRule, true);
+ }
+ };
+
+ $scope.useRouteID = function() {
+ $scope.currentRule.resource = '';
+ };
+
+ $scope.useCustormAPI = function() {
+ $scope.currentRule.resource = '';
+ };
+
+ $scope.useParamItem = function () {
+ $scope.currentRule.paramItem = {
+ parseStrategy: 0,
+ matchStrategy: 0
+ };
+ };
+
+ $scope.notUseParamItem = function () {
+ $scope.currentRule.paramItem = null;
+ };
+
+ $scope.useParamItemVal = function() {
+ $scope.currentRule.paramItem.pattern = "";
+ $scope.currentRule.paramItem.matchStrategy = 0;
+ };
+
+ $scope.notUseParamItemVal = function() {
+ $scope.currentRule.paramItem.pattern = null;
+ $scope.currentRule.paramItem.matchStrategy = null;
+ };
+
+ function addNewRule(rule) {
+ GatewayFlowService.newRule(rule).success(function (data) {
+ if (data.code == 0) {
+ getMachineRules();
+ gatewayFlowRuleDialog.close();
+ } else {
+ alert('新增网关流控规则失败!' + data.msg);
+ }
+ });
+ };
+
+ function saveRule(rule, edit) {
+ GatewayFlowService.saveRule(rule).success(function (data) {
+ if (data.code == 0) {
+ getMachineRules();
+ if (edit) {
+ gatewayFlowRuleDialog.close();
+ } else {
+ confirmDialog.close();
+ }
+ } else {
+ alert('修改网关流控规则失败!' + data.msg);
+ }
+ });
+ };
+
+ var confirmDialog;
+ $scope.deleteRule = function (rule) {
+ $scope.currentRule = rule;
+ $scope.confirmDialog = {
+ title: '删除网关流控规则',
+ type: 'delete_rule',
+ attentionTitle: '请确认是否删除如下规则',
+ attention: 'API名称: ' + rule.resource + ', ' + (rule.grade == 1 ? 'QPS阈值' : '线程数') + ': ' + rule.count,
+ confirmBtnText: '删除',
+ };
+ confirmDialog = ngDialog.open({
+ template: '/app/views/dialog/confirm-dialog.html',
+ scope: $scope,
+ overlay: true
+ });
+ };
+
+ $scope.confirm = function () {
+ if ($scope.confirmDialog.type == 'delete_rule') {
+ deleteRule($scope.currentRule);
+ } else {
+ console.error('error');
+ }
+ };
+
+ function deleteRule(rule) {
+ GatewayFlowService.deleteRule(rule).success(function (data) {
+ if (data.code == 0) {
+ getMachineRules();
+ confirmDialog.close();
+ } else {
+ alert('删除网关流控规则失败!' + data.msg);
+ }
+ });
+ };
+
+ queryAppMachines();
+
+ function queryAppMachines() {
+ MachineService.getAppMachines($scope.app).success(
+ function (data) {
+ if (data.code == 0) {
+ if (data.data) {
+ $scope.machines = [];
+ $scope.macsInputOptions = [];
+ data.data.forEach(function (item) {
+ if (item.healthy) {
+ $scope.macsInputOptions.push({
+ text: item.ip + ':' + item.port,
+ value: item.ip + ':' + item.port
+ });
+ }
+ });
+ }
+ if ($scope.macsInputOptions.length > 0) {
+ $scope.macInputModel = $scope.macsInputOptions[0].value;
+ }
+ } else {
+ $scope.macsInputOptions = [];
+ }
+ }
+ );
+ };
+ $scope.$watch('macInputModel', function () {
+ if ($scope.macInputModel) {
+ getMachineRules();
+ getApiNames();
+ }
+ });
+ }]
+);
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/gateway/identity.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/gateway/identity.js
new file mode 100644
index 00000000..52871b4a
--- /dev/null
+++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/gateway/identity.js
@@ -0,0 +1,299 @@
+var app = angular.module('sentinelDashboardApp');
+
+app.controller('GatewayIdentityCtl', ['$scope', '$stateParams', 'IdentityService',
+ 'ngDialog', 'GatewayFlowService', 'GatewayApiService', 'DegradeService', 'MachineService',
+ '$interval', '$location', '$timeout',
+ function ($scope, $stateParams, IdentityService, ngDialog,
+ GatewayFlowService, GatewayApiService, DegradeService, MachineService, $interval, $location, $timeout) {
+
+ $scope.app = $stateParams.app;
+
+ $scope.currentPage = 1;
+ $scope.pageSize = 16;
+ $scope.totalPage = 1;
+ $scope.totalCount = 0;
+ $scope.identities = [];
+
+ $scope.searchKey = '';
+
+ $scope.macsInputConfig = {
+ searchField: ['text', 'value'],
+ persist: true,
+ create: false,
+ maxItems: 1,
+ render: {
+ item: function (data, escape) {
+ return '' + escape(data.text) + ' ';
+ }
+ },
+ onChange: function (value, oldValue) {
+ $scope.macInputModel = value;
+ }
+ };
+ $scope.table = null;
+
+ getApiNames();
+ function getApiNames() {
+ if (!$scope.macInputModel) {
+ return;
+ }
+
+ var mac = $scope.macInputModel.split(':');
+ GatewayApiService.queryApis($scope.app, mac[0], mac[1]).success(
+ function (data) {
+ if (data.code == 0 && data.data) {
+ $scope.apiNames = [];
+
+ data.data.forEach(function (api) {
+ $scope.apiNames.push(api["apiName"]);
+ });
+ }
+ });
+ }
+
+ var gatewayFlowRuleDialog;
+ var gatewayFlowRuleDialogScope;
+ $scope.addNewGatewayFlowRule = function (resource) {
+ if (!$scope.macInputModel) {
+ return;
+ }
+ var mac = $scope.macInputModel.split(':');
+ gatewayFlowRuleDialogScope = $scope.$new(true);
+
+ gatewayFlowRuleDialogScope.apiNames = $scope.apiNames;
+
+ gatewayFlowRuleDialogScope.intervalUnits = [{val: 0, desc: '秒'}, {val: 1, desc: '分'}, {val: 2, desc: '时'}, {val: 3, desc: '天'}];
+
+ gatewayFlowRuleDialogScope.currentRule = {
+ grade: 1,
+ app: $scope.app,
+ ip: mac[0],
+ port: mac[1],
+ resourceMode: gatewayFlowRuleDialogScope.apiNames.indexOf(resource) == -1 ? 0 : 1,
+ resource: resource,
+ interval: 1,
+ intervalUnit: 0,
+ controlBehavior: 0,
+ burst: 0,
+ maxQueueingTimeoutMs: 0
+ };
+
+ gatewayFlowRuleDialogScope.gatewayFlowRuleDialog = {
+ title: '新增网关流控规则',
+ type: 'add',
+ confirmBtnText: '新增',
+ saveAndContinueBtnText: '新增并继续添加',
+ showAdvanceButton: true
+ };
+
+ gatewayFlowRuleDialogScope.useRouteID = function() {
+ gatewayFlowRuleDialogScope.currentRule.resource = '';
+ };
+
+ gatewayFlowRuleDialogScope.useCustormAPI = function() {
+ gatewayFlowRuleDialogScope.currentRule.resource = '';
+ };
+
+ gatewayFlowRuleDialogScope.useParamItem = function () {
+ gatewayFlowRuleDialogScope.currentRule.paramItem = {
+ parseStrategy: 0,
+ matchStrategy: 0
+ };
+ };
+
+ gatewayFlowRuleDialogScope.notUseParamItem = function () {
+ gatewayFlowRuleDialogScope.currentRule.paramItem = null;
+ };
+
+ gatewayFlowRuleDialogScope.useParamItemVal = function() {
+ gatewayFlowRuleDialogScope.currentRule.paramItem.pattern = "";
+ };
+
+ gatewayFlowRuleDialogScope.notUseParamItemVal = function() {
+ gatewayFlowRuleDialogScope.currentRule.paramItem.pattern = null;
+ };
+
+ gatewayFlowRuleDialogScope.saveRule = saveGatewayFlowRule;
+ gatewayFlowRuleDialogScope.saveRuleAndContinue = saveGatewayFlowRuleAndContinue;
+ gatewayFlowRuleDialogScope.onOpenAdvanceClick = function () {
+ gatewayFlowRuleDialogScope.gatewayFlowRuleDialog.showAdvanceButton = false;
+ };
+ gatewayFlowRuleDialogScope.onCloseAdvanceClick = function () {
+ gatewayFlowRuleDialogScope.gatewayFlowRuleDialog.showAdvanceButton = true;
+ };
+
+ gatewayFlowRuleDialog = ngDialog.open({
+ template: '/app/views/dialog/gateway/flow-rule-dialog.html',
+ width: 780,
+ overlay: true,
+ scope: gatewayFlowRuleDialogScope
+ });
+ };
+
+ function saveGatewayFlowRule() {
+ if (!GatewayFlowService.checkRuleValid(gatewayFlowRuleDialogScope.currentRule)) {
+ return;
+ }
+ GatewayFlowService.newRule(gatewayFlowRuleDialogScope.currentRule).success(function (data) {
+ if (data.code === 0) {
+ gatewayFlowRuleDialog.close();
+ let url = '/dashboard/gateway/flow/' + $scope.app;
+ $location.path(url);
+ } else {
+ alert('失败!');
+ }
+ }).error((data, header, config, status) => {
+ alert('未知错误');
+ });
+ }
+
+ function saveGatewayFlowRuleAndContinue() {
+ if (!GatewayFlowService.checkRuleValid(gatewayFlowRuleDialogScope.currentRule)) {
+ return;
+ }
+ GatewayFlowService.newRule(gatewayFlowRuleDialogScope.currentRule).success(function (data) {
+ if (data.code == 0) {
+ gatewayFlowRuleDialog.close();
+ } else {
+ alert('失败!');
+ }
+ });
+ }
+
+ var degradeRuleDialog;
+ $scope.addNewDegradeRule = function (resource) {
+ if (!$scope.macInputModel) {
+ return;
+ }
+ var mac = $scope.macInputModel.split(':');
+ degradeRuleDialogScope = $scope.$new(true);
+ degradeRuleDialogScope.currentRule = {
+ enable: false,
+ grade: 0,
+ strategy: 0,
+ resource: resource,
+ limitApp: 'default',
+ app: $scope.app,
+ ip: mac[0],
+ port: mac[1]
+ };
+
+ degradeRuleDialogScope.degradeRuleDialog = {
+ title: '新增降级规则',
+ type: 'add',
+ confirmBtnText: '新增',
+ saveAndContinueBtnText: '新增并继续添加'
+ };
+ degradeRuleDialogScope.saveRule = saveDegradeRule;
+ degradeRuleDialogScope.saveRuleAndContinue = saveDegradeRuleAndContinue;
+
+ degradeRuleDialog = ngDialog.open({
+ template: '/app/views/dialog/degrade-rule-dialog.html',
+ width: 680,
+ overlay: true,
+ scope: degradeRuleDialogScope
+ });
+ };
+
+ function saveDegradeRule() {
+ if (!DegradeService.checkRuleValid(degradeRuleDialogScope.currentRule)) {
+ return;
+ }
+ DegradeService.newRule(degradeRuleDialogScope.currentRule).success(function (data) {
+ if (data.code == 0) {
+ degradeRuleDialog.close();
+ var url = '/dashboard/degrade/' + $scope.app;
+ $location.path(url);
+ } else {
+ alert('失败!');
+ }
+ });
+ }
+
+ function saveDegradeRuleAndContinue() {
+ if (!DegradeService.checkRuleValid(degradeRuleDialogScope.currentRule)) {
+ return;
+ }
+ DegradeService.newRule(degradeRuleDialogScope.currentRule).success(function (data) {
+ if (data.code == 0) {
+ degradeRuleDialog.close();
+ } else {
+ alert('失败!');
+ }
+ });
+ }
+
+ var searchHandler;
+ $scope.searchChange = function (searchKey) {
+ $timeout.cancel(searchHandler);
+ searchHandler = $timeout(function () {
+ $scope.searchKey = searchKey;
+ reInitIdentityDatas();
+ }, 600);
+ };
+
+ function queryAppMachines() {
+ MachineService.getAppMachines($scope.app).success(
+ function (data) {
+ if (data.code === 0) {
+ if (data.data) {
+ $scope.machines = [];
+ $scope.macsInputOptions = [];
+ data.data.forEach(function (item) {
+ if (item.healthy) {
+ $scope.macsInputOptions.push({
+ text: item.ip + ':' + item.port,
+ value: item.ip + ':' + item.port
+ });
+ }
+ });
+ }
+ if ($scope.macsInputOptions.length > 0) {
+ $scope.macInputModel = $scope.macsInputOptions[0].value;
+ }
+ } else {
+ $scope.macsInputOptions = [];
+ }
+ }
+ );
+ }
+
+ // Fetch all machines by current app name.
+ queryAppMachines();
+
+ $scope.$watch('macInputModel', function () {
+ if ($scope.macInputModel) {
+ reInitIdentityDatas();
+ }
+ });
+
+ $scope.$on('$destroy', function () {
+ $interval.cancel(intervalId);
+ });
+
+ var intervalId;
+ function reInitIdentityDatas() {
+ getApiNames();
+ queryIdentities();
+ };
+
+ function queryIdentities() {
+ var mac = $scope.macInputModel.split(':');
+ if (mac == null || mac.length < 2) {
+ return;
+ }
+
+ IdentityService.fetchClusterNodeOfMachine(mac[0], mac[1], $scope.searchKey).success(
+ function (data) {
+ if (data.code == 0 && data.data) {
+ $scope.identities = data.data;
+ $scope.totalCount = $scope.identities.length;
+ } else {
+ $scope.identities = [];
+ $scope.totalCount = 0;
+ }
+ }
+ );
+ };
+ $scope.queryIdentities = queryIdentities;
+ }]);
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/login.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/login.js
index 70b6ffd3..1230129c 100644
--- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/login.js
+++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/login.js
@@ -1,8 +1,8 @@
var app = angular.module('sentinelDashboardApp');
app.controller('LoginCtl', ['$scope', '$state', '$window', 'AuthService',
- function ($scope, $state, $window, LoginService) {
- // If auth, jump to the index page directly
+ function ($scope, $state, $window, AuthService) {
+ // If auth passed, jump to the index page directly
if ($window.localStorage.getItem('session_sentinel_admin')) {
$state.go('dashboard');
}
@@ -20,7 +20,7 @@ app.controller('LoginCtl', ['$scope', '$state', '$window', 'AuthService',
var param = {"username": $scope.username, "password": $scope.password};
- LoginService.login(param).success(function (data) {
+ AuthService.login(param).success(function (data) {
if (data.code == 0) {
$window.localStorage.setItem('session_sentinel_admin', {
username: data.data
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html b/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html
index 0f5b82c6..623e889a 100755
--- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html
+++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html
@@ -28,24 +28,41 @@
实时监控
-
+
+
簇点链路
-
-
- 流控规则
+
+
+ 请求链路
-
+
+
+
+
+
+ API管理
+
+
+
+ 流控规则
+
+
+
+
+ 流控规则
+
+
降级规则
-
+
热点规则
@@ -53,19 +70,19 @@
系统规则
-
+
授权规则
-
+
集群流控
+
机器列表
-
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.js
index bfa6dd75..64ed684b 100755
--- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.js
+++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.js
@@ -20,7 +20,8 @@ angular.module('sentinelDashboardApp')
AppService.getApps().success(
function (data) {
if (data.code === 0) {
- let initHashApp = $location.path().split('/')[3];
+ var path = $location.path().split('/');
+ let initHashApp = path[path.length - 1];
$scope.apps = data.data;
$scope.apps = $scope.apps.map(function (item) {
if (item.app === initHashApp) {
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/auth_service.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/auth_service.js
index 0e3c04b4..337221c1 100644
--- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/auth_service.js
+++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/auth_service.js
@@ -6,13 +6,13 @@ app.service('AuthService', ['$http', function ($http) {
url: '/auth/login',
params: param,
method: 'POST'
- })
- }
+ });
+ };
this.logout = function () {
return $http({
url: '/auth/logout',
method: 'POST'
- })
- }
+ });
+ };
}]);
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/gateway/api_service.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/gateway/api_service.js
new file mode 100644
index 00000000..373f71db
--- /dev/null
+++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/gateway/api_service.js
@@ -0,0 +1,73 @@
+var app = angular.module('sentinelDashboardApp');
+
+app.service('GatewayApiService', ['$http', function ($http) {
+ this.queryApis = function (app, ip, port) {
+ var param = {
+ app: app,
+ ip: ip,
+ port: port
+ };
+ return $http({
+ url: '/gateway/api/list.json',
+ params: param,
+ method: 'GET'
+ });
+ };
+
+ this.newApi = function (api) {
+ return $http({
+ url: '/gateway/api/new.json',
+ data: api,
+ method: 'POST'
+ });
+ };
+
+ this.saveApi = function (api) {
+ return $http({
+ url: '/gateway/api/save.json',
+ data: api,
+ method: 'POST'
+ });
+ };
+
+ this.deleteApi = function (api) {
+ var param = {
+ id: api.id,
+ app: api.app
+ };
+ return $http({
+ url: '/gateway/api/delete.json',
+ params: param,
+ method: 'POST'
+ });
+ };
+
+ this.checkApiValid = function (api, apiNames) {
+ if (api.apiName === undefined || api.apiName === '') {
+ alert('API名称不能为空');
+ return false;
+ }
+
+ if (api.predicateItems == null || api.predicateItems.length === 0) {
+ // Should never happen since no remove button will display when only one predicateItem.
+ alert('至少有一个匹配规则');
+ return false;
+ }
+
+ for (var i = 0; i < api.predicateItems.length; i++) {
+ var predicateItem = api.predicateItems[i];
+ var pattern = predicateItem.pattern;
+ if (pattern === undefined || pattern === '') {
+ alert('匹配串不能为空,请检查');
+ return false;
+ }
+ }
+
+ if (apiNames.indexOf(api.apiName) !== -1) {
+ alert('API名称(' + api.apiName + ')已存在');
+ return false;
+ }
+
+ return true;
+ };
+}]);
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/gateway/flow_service.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/gateway/flow_service.js
new file mode 100644
index 00000000..b026b32f
--- /dev/null
+++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/gateway/flow_service.js
@@ -0,0 +1,76 @@
+var app = angular.module('sentinelDashboardApp');
+
+app.service('GatewayFlowService', ['$http', function ($http) {
+ this.queryRules = function (app, ip, port) {
+ var param = {
+ app: app,
+ ip: ip,
+ port: port
+ };
+
+ return $http({
+ url: '/gateway/flow/list.json',
+ params: param,
+ method: 'GET'
+ });
+ };
+
+ this.newRule = function (rule) {
+ return $http({
+ url: '/gateway/flow/new.json',
+ data: rule,
+ method: 'POST'
+ });
+ };
+
+ this.saveRule = function (rule) {
+ return $http({
+ url: '/gateway/flow/save.json',
+ data: rule,
+ method: 'POST'
+ });
+ };
+
+ this.deleteRule = function (rule) {
+ var param = {
+ id: rule.id,
+ app: rule.app
+ };
+
+ return $http({
+ url: '/gateway/flow/delete.json',
+ params: param,
+ method: 'POST'
+ });
+ };
+
+ this.checkRuleValid = function (rule) {
+ if (rule.resource === undefined || rule.resource === '') {
+ alert('API名称不能为空');
+ return false;
+ }
+
+ if (rule.paramItem != null) {
+ if (rule.paramItem.parseStrategy == 2 ||
+ rule.paramItem.parseStrategy == 3 ||
+ rule.paramItem.parseStrategy == 4) {
+ if (rule.paramItem.fieldName === undefined || rule.paramItem.fieldName === '') {
+ alert('当参数属性为Header、URL参数、Cookie时,参数名称不能为空');
+ return false;
+ }
+
+ if (rule.paramItem.pattern === '') {
+ alert('匹配串不能为空');
+ return false;
+ }
+ }
+ }
+
+ if (rule.count === undefined || rule.count < 0) {
+ alert((rule.grade === 1 ? 'QPS阈值' : '线程数') + '必须大于等于 0');
+ return false;
+ }
+
+ return true;
+ };
+}]);
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/machineservice.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/machineservice.js
index 64531d65..2d3b5e8b 100755
--- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/machineservice.js
+++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/machineservice.js
@@ -1,23 +1,25 @@
var app = angular.module('sentinelDashboardApp');
-app.service('MachineService', ['$http', '$httpParamSerializerJQLike', function ($http, $httpParamSerializerJQLike) {
- this.getAppMachines = function (app) {
- return $http({
- url: 'app/' + app + '/machines.json',
- method: 'GET'
- });
- };
- this.removeAppMachine = function (app, ip, port) {
- return $http({
- url: 'app/' + app + '/machine/remove.json',
- method: 'POST',
- headers: {
- "Content-type": 'application/x-www-form-urlencoded; charset=UTF-8'
- },
- data: $httpParamSerializerJQLike({
- ip: ip,
- port: port
- })
- });
- };
-}]);
+app.service('MachineService', ['$http', '$httpParamSerializerJQLike',
+ function ($http, $httpParamSerializerJQLike) {
+ this.getAppMachines = function (app) {
+ return $http({
+ url: 'app/' + app + '/machines.json',
+ method: 'GET'
+ });
+ };
+ this.removeAppMachine = function (app, ip, port) {
+ return $http({
+ url: 'app/' + app + '/machine/remove.json',
+ method: 'POST',
+ headers: {
+ 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
+ },
+ data: $httpParamSerializerJQLike({
+ ip: ip,
+ port: port
+ })
+ });
+ };
+ }]
+);
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/gateway/api-dialog.html b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/gateway/api-dialog.html
new file mode 100644
index 00000000..d58f48f9
--- /dev/null
+++ b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/gateway/api-dialog.html
@@ -0,0 +1,52 @@
+
+ {{gatewayApiDialog.title}}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/gateway/flow-rule-dialog.html b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/gateway/flow-rule-dialog.html
new file mode 100644
index 00000000..bbaeab48
--- /dev/null
+++ b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/gateway/flow-rule-dialog.html
@@ -0,0 +1,172 @@
+
+ {{gatewayFlowRuleDialog.title}}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/gateway/api.html b/sentinel-dashboard/src/main/webapp/resources/app/views/gateway/api.html
new file mode 100644
index 00000000..0316e689
--- /dev/null
+++ b/sentinel-dashboard/src/main/webapp/resources/app/views/gateway/api.html
@@ -0,0 +1,87 @@
+
+
+ {{app}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ API名称
+ |
+
+ 匹配模式
+ |
+
+ 匹配串
+ |
+
+ 操作
+ |
+
+
+
+
+ {{api.apiName}} |
+
+ 精确
+ 前缀
+ 正则
+ |
+ {{api.pattern}} |
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/gateway/flow.html b/sentinel-dashboard/src/main/webapp/resources/app/views/gateway/flow.html
new file mode 100644
index 00000000..9bd4c232
--- /dev/null
+++ b/sentinel-dashboard/src/main/webapp/resources/app/views/gateway/flow.html
@@ -0,0 +1,94 @@
+
+
+ {{app}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ API名称
+ |
+
+ API类型
+ |
+
+ 阈值类型
+ |
+
+ 单机阈值
+ |
+
+ 操作
+ |
+
+
+
+
+
+ {{rule.resource}} |
+
+ Route ID
+ API分组
+ |
+
+ QPS
+ 线程数
+ |
+ {{rule.count}} |
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/gateway/identity.html b/sentinel-dashboard/src/main/webapp/resources/app/views/gateway/identity.html
new file mode 100644
index 00000000..a36c39ec
--- /dev/null
+++ b/sentinel-dashboard/src/main/webapp/resources/app/views/gateway/identity.html
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 资源名
+ |
+
+ 资源类型
+ |
+ 通过QPS |
+ 拒绝QPS |
+ 线程数 |
+ 平均RT |
+ 分钟通过 |
+ 分钟拒绝 |
+ 操作 |
+
+
+
+
+
+
+ {{resource.resource}}
+ |
+
+ Route ID
+ 自定义API
+ |
+ {{resource.passQps}} |
+ {{resource.blockQps}} |
+ {{resource.threadNum}} |
+ {{resource.averageRt}} |
+ {{resource.oneMinutePass}} |
+
+ {{resource.oneMinuteBlock}} |
+ {{resource.oneMinuteBlock}} |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sentinel-dashboard/src/main/webapp/resources/dist/js/app.js b/sentinel-dashboard/src/main/webapp/resources/dist/js/app.js
index 98fbd02f..7b992400 100755
--- a/sentinel-dashboard/src/main/webapp/resources/dist/js/app.js
+++ b/sentinel-dashboard/src/main/webapp/resources/dist/js/app.js
@@ -1 +1 @@
-"use strict";var app;angular.module("sentinelDashboardApp",["oc.lazyLoad","ui.router","ui.bootstrap","angular-loading-bar","ngDialog","ui.bootstrap.datetimepicker","ui-notification","rzTable","angular-clipboard","selectize","angularUtils.directives.dirPagination"]).factory("AuthInterceptor",["$window","$state",function(r,t){return{responseError:function(e){return 401==e.status&&(r.localStorage.removeItem("session_sentinel_admin"),t.go("login")),e},response:function(e){return e},request:function(e){return e},requestError:function(e){return e}}}]).config(["$stateProvider","$urlRouterProvider","$ocLazyLoadProvider","$httpProvider",function(e,r,t,a){a.interceptors.push("AuthInterceptor"),t.config({debug:!1,events:!0}),r.otherwise("/dashboard/home"),e.state("login",{url:"/login",templateUrl:"app/views/login.html",controller:"LoginCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/login.js"]})}]}}).state("dashboard",{url:"/dashboard",templateUrl:"app/views/dashboard/main.html",resolve:{loadMyDirectives:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/directives/header/header.js","app/scripts/directives/sidebar/sidebar.js","app/scripts/directives/sidebar/sidebar-search/sidebar-search.js"]})}]}}).state("dashboard.home",{url:"/home",templateUrl:"app/views/dashboard/home.html",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/main.js"]})}]}}).state("dashboard.flowV1",{templateUrl:"app/views/flow_v1.html",url:"/flow/:app",controller:"FlowControllerV1",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/flow_v1.js"]})}]}}).state("dashboard.flow",{templateUrl:"app/views/flow_v2.html",url:"/v2/flow/:app",controller:"FlowControllerV2",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/flow_v2.js"]})}]}}).state("dashboard.paramFlow",{templateUrl:"app/views/param_flow.html",url:"/paramFlow/:app",controller:"ParamFlowController",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/param_flow.js"]})}]}}).state("dashboard.clusterAppAssignManage",{templateUrl:"app/views/cluster_app_assign_manage.html",url:"/cluster/assign_manage/:app",controller:"SentinelClusterAppAssignManageController",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/cluster_app_assign_manage.js"]})}]}}).state("dashboard.clusterAppServerList",{templateUrl:"app/views/cluster_app_server_list.html",url:"/cluster/server/:app",controller:"SentinelClusterAppServerListController",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/cluster_app_server_list.js"]})}]}}).state("dashboard.clusterAppClientList",{templateUrl:"app/views/cluster_app_client_list.html",url:"/cluster/client/:app",controller:"SentinelClusterAppTokenClientListController",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/cluster_app_token_client_list.js"]})}]}}).state("dashboard.clusterSingle",{templateUrl:"app/views/cluster_single_config.html",url:"/cluster/single/:app",controller:"SentinelClusterSingleController",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/cluster_single.js"]})}]}}).state("dashboard.authority",{templateUrl:"app/views/authority.html",url:"/authority/:app",controller:"AuthorityRuleController",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/authority.js"]})}]}}).state("dashboard.degrade",{templateUrl:"app/views/degrade.html",url:"/degrade/:app",controller:"DegradeCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/degrade.js"]})}]}}).state("dashboard.system",{templateUrl:"app/views/system.html",url:"/system/:app",controller:"SystemCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/system.js"]})}]}}).state("dashboard.machine",{templateUrl:"app/views/machine.html",url:"/app/:app",controller:"MachineCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/machine.js"]})}]}}).state("dashboard.identity",{templateUrl:"app/views/identity.html",url:"/identity/:app",controller:"IdentityCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/identity.js"]})}]}}).state("dashboard.metric",{templateUrl:"app/views/metric.html",url:"/metric/:app",controller:"MetricCtl",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/metric.js"]})}]}})}]),(app=angular.module("sentinelDashboardApp")).filter("range",[function(){return function(e,r){if(isNaN(r)||r<=0)return[];e=[];for(var t=1;t<=r;t++)e.push(t);return e}}]),(app=angular.module("sentinelDashboardApp")).service("AuthService",["$http",function(r){this.login=function(e){return r({url:"/auth/login",params:e,method:"POST"})},this.logout=function(){return r({url:"/auth/logout",method:"POST"})}}]),(app=angular.module("sentinelDashboardApp")).service("AppService",["$http",function(e){this.getApps=function(){return e({url:"app/briefinfos.json",method:"GET"})}}]),(app=angular.module("sentinelDashboardApp")).service("FlowServiceV1",["$http",function(a){function r(e){return void 0===e||""===e||isNaN(e)||e<=0}this.queryMachineRules=function(e,r,t){return a({url:"/v1/flow/rules",params:{app:e,ip:r,port:t},method:"GET"})},this.newRule=function(e){e.resource,e.limitApp,e.grade,e.count,e.strategy,e.refResource,e.controlBehavior,e.warmUpPeriodSec,e.maxQueueingTimeMs,e.app,e.ip,e.port;return a({url:"/v1/flow/rule",data:e,method:"POST"})},this.saveRule=function(e){var r={id:e.id,resource:e.resource,limitApp:e.limitApp,grade:e.grade,count:e.count,strategy:e.strategy,refResource:e.refResource,controlBehavior:e.controlBehavior,warmUpPeriodSec:e.warmUpPeriodSec,maxQueueingTimeMs:e.maxQueueingTimeMs};return a({url:"/v1/flow/save.json",params:r,method:"PUT"})},this.deleteRule=function(e){var r={id:e.id,app:e.app};return a({url:"/v1/flow/delete.json",params:r,method:"DELETE"})},this.checkRuleValid=function(e){return void 0===e.resource||""===e.resource?(alert("资源名称不能为空"),!1):void 0===e.count||e.count<0?(alert("限流阈值必须大于等于 0"),!1):void 0===e.strategy||e.strategy<0?(alert("无效的流控模式"),!1):1!=e.strategy&&2!=e.strategy||void 0!==e.refResource&&""!=e.refResource?void 0===e.controlBehavior||e.controlBehavior<0?(alert("无效的流控整形方式"),!1):1==e.controlBehavior&&r(e.warmUpPeriodSec)?(alert("预热时长必须大于 0"),!1):2==e.controlBehavior&&r(e.maxQueueingTimeMs)?(alert("排队超时时间必须大于 0"),!1):!e.clusterMode||void 0!==e.clusterConfig&&void 0!==e.clusterConfig.thresholdType||(alert("集群限流配置不正确"),!1):(alert("请填写关联资源或入口"),!1)}}]),(app=angular.module("sentinelDashboardApp")).service("FlowServiceV2",["$http",function(a){function r(e){return void 0===e||""===e||isNaN(e)||e<=0}this.queryMachineRules=function(e,r,t){return a({url:"/v2/flow/rules",params:{app:e,ip:r,port:t},method:"GET"})},this.newRule=function(e){return a({url:"/v2/flow/rule",data:e,method:"POST"})},this.saveRule=function(e){return a({url:"/v2/flow/rule/"+e.id,data:e,method:"PUT"})},this.deleteRule=function(e){return a({url:"/v2/flow/rule/"+e.id,method:"DELETE"})},this.checkRuleValid=function(e){return void 0===e.resource||""===e.resource?(alert("资源名称不能为空"),!1):void 0===e.count||e.count<0?(alert("限流阈值必须大于等于 0"),!1):void 0===e.strategy||e.strategy<0?(alert("无效的流控模式"),!1):1!=e.strategy&&2!=e.strategy||void 0!==e.refResource&&""!=e.refResource?void 0===e.controlBehavior||e.controlBehavior<0?(alert("无效的流控整形方式"),!1):1==e.controlBehavior&&r(e.warmUpPeriodSec)?(alert("预热时长必须大于 0"),!1):2==e.controlBehavior&&r(e.maxQueueingTimeMs)?(alert("排队超时时间必须大于 0"),!1):!e.clusterMode||void 0!==e.clusterConfig&&void 0!==e.clusterConfig.thresholdType||(alert("集群限流配置不正确"),!1):(alert("请填写关联资源或入口"),!1)}}]),(app=angular.module("sentinelDashboardApp")).service("DegradeService",["$http",function(a){this.queryMachineRules=function(e,r,t){return a({url:"degrade/rules.json",params:{app:e,ip:r,port:t},method:"GET"})},this.newRule=function(e){var r={id:e.id,resource:e.resource,limitApp:e.limitApp,count:e.count,timeWindow:e.timeWindow,grade:e.grade,app:e.app,ip:e.ip,port:e.port};return a({url:"/degrade/new.json",params:r,method:"GET"})},this.saveRule=function(e){var r={id:e.id,resource:e.resource,limitApp:e.limitApp,grade:e.grade,count:e.count,timeWindow:e.timeWindow};return a({url:"/degrade/save.json",params:r,method:"GET"})},this.deleteRule=function(e){var r={id:e.id,app:e.app};return a({url:"/degrade/delete.json",params:r,method:"GET"})},this.checkRuleValid=function(e){return void 0===e.resource||""===e.resource?(alert("资源名称不能为空"),!1):void 0===e.grade||e.grade<0?(alert("未知的降级策略"),!1):void 0===e.count||""===e.count||e.count<0?(alert("降级阈值不能为空或小于 0"),!1):void 0===e.timeWindow||""===e.timeWindow||e.timeWindow<=0?(alert("降级时间窗口必须大于 0"),!1):!(1==e.grade&&1 entities = new ArrayList<>();
+
+ // Mock two entities
+ ApiDefinitionEntity entity = new ApiDefinitionEntity();
+ entity.setId(1L);
+ entity.setApp(TEST_APP);
+ entity.setIp(TEST_IP);
+ entity.setPort(TEST_PORT);
+ entity.setApiName("foo");
+ Date date = new Date();
+ entity.setGmtCreate(date);
+ entity.setGmtModified(date);
+
+ Set itemEntities = new LinkedHashSet<>();
+ entity.setPredicateItems(itemEntities);
+ ApiPredicateItemEntity itemEntity = new ApiPredicateItemEntity();
+ itemEntity.setPattern("/aaa");
+ itemEntity.setMatchStrategy(URL_MATCH_STRATEGY_EXACT);
+
+ itemEntities.add(itemEntity);
+ entities.add(entity);
+
+ ApiDefinitionEntity entity2 = new ApiDefinitionEntity();
+ entity2.setId(2L);
+ entity2.setApp(TEST_APP);
+ entity2.setIp(TEST_IP);
+ entity2.setPort(TEST_PORT);
+ entity2.setApiName("biz");
+ entity.setGmtCreate(date);
+ entity.setGmtModified(date);
+
+ Set itemEntities2 = new LinkedHashSet<>();
+ entity2.setPredicateItems(itemEntities2);
+ ApiPredicateItemEntity itemEntity2 = new ApiPredicateItemEntity();
+ itemEntity2.setPattern("/bbb");
+ itemEntity2.setMatchStrategy(URL_MATCH_STRATEGY_PREFIX);
+
+ itemEntities2.add(itemEntity2);
+ entities.add(entity2);
+
+ CompletableFuture> completableFuture = mock(CompletableFuture.class);
+ given(completableFuture.get()).willReturn(entities);
+ given(sentinelApiClient.fetchApis(TEST_APP, TEST_IP, TEST_PORT)).willReturn(completableFuture);
+
+ MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(path);
+ requestBuilder.param("app", TEST_APP);
+ requestBuilder.param("ip", TEST_IP);
+ requestBuilder.param("port", String.valueOf(TEST_PORT));
+
+ // Do controller logic
+ MvcResult mvcResult = mockMvc.perform(requestBuilder)
+ .andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();
+
+ // Verify the fetchApis method has been called
+ verify(sentinelApiClient).fetchApis(TEST_APP, TEST_IP, TEST_PORT);
+
+ // Verify if two same entities are got
+ Result> result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>>(){});
+ assertTrue(result.isSuccess());
+
+ List data = result.getData();
+ assertEquals(2, data.size());
+ assertEquals(entities, data);
+
+ // Verify the entities are add into memory repository
+ List entitiesInMem = repository.findAllByApp(TEST_APP);
+ assertEquals(2, entitiesInMem.size());
+ assertEquals(entities, entitiesInMem);
+ }
+
+ @Test
+ public void testAddApi() throws Exception {
+ String path = "/gateway/api/new.json";
+
+ AddApiReqVo reqVo = new AddApiReqVo();
+ reqVo.setApp(TEST_APP);
+ reqVo.setIp(TEST_IP);
+ reqVo.setPort(TEST_PORT);
+
+ reqVo.setApiName("customized_api");
+
+ List itemVos = new ArrayList<>();
+ ApiPredicateItemVo itemVo = new ApiPredicateItemVo();
+ itemVo.setMatchStrategy(URL_MATCH_STRATEGY_EXACT);
+ itemVo.setPattern("/product");
+ itemVos.add(itemVo);
+ reqVo.setPredicateItems(itemVos);
+
+ given(sentinelApiClient.modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true);
+
+ MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path);
+ requestBuilder.content(JSON.toJSONString(reqVo)).contentType(MediaType.APPLICATION_JSON);
+
+ // Do controller logic
+ MvcResult mvcResult = mockMvc.perform(requestBuilder)
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print()).andReturn();
+
+ // Verify the modifyApis method has been called
+ verify(sentinelApiClient).modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any());
+
+ Result result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>() {});
+ assertTrue(result.isSuccess());
+
+ // Verify the result
+ ApiDefinitionEntity entity = result.getData();
+ assertNotNull(entity);
+ assertEquals(TEST_APP, entity.getApp());
+ assertEquals(TEST_IP, entity.getIp());
+ assertEquals(TEST_PORT, entity.getPort());
+ assertEquals("customized_api", entity.getApiName());
+ assertNotNull(entity.getId());
+ assertNotNull(entity.getGmtCreate());
+ assertNotNull(entity.getGmtModified());
+
+ Set predicateItemEntities = entity.getPredicateItems();
+ assertEquals(1, predicateItemEntities.size());
+ ApiPredicateItemEntity predicateItemEntity = predicateItemEntities.iterator().next();
+ assertEquals(URL_MATCH_STRATEGY_EXACT, predicateItemEntity.getMatchStrategy().intValue());
+ assertEquals("/product", predicateItemEntity.getPattern());
+
+ // Verify the entity which is add in memory repository
+ List entitiesInMem = repository.findAllByApp(TEST_APP);
+ assertEquals(1, entitiesInMem.size());
+ assertEquals(entity, entitiesInMem.get(0));
+ }
+
+ @Test
+ public void testUpdateApi() throws Exception {
+ String path = "/gateway/api/save.json";
+
+ // Add one entity to memory repository for update
+ ApiDefinitionEntity addEntity = new ApiDefinitionEntity();
+ addEntity.setApp(TEST_APP);
+ addEntity.setIp(TEST_IP);
+ addEntity.setPort(TEST_PORT);
+ addEntity.setApiName("bbb");
+ Date date = new Date();
+ // To make the gmtModified different when do update
+ date = DateUtils.addSeconds(date, -1);
+ addEntity.setGmtCreate(date);
+ addEntity.setGmtModified(date);
+ Set addRedicateItemEntities = new HashSet<>();
+ addEntity.setPredicateItems(addRedicateItemEntities);
+ ApiPredicateItemEntity addPredicateItemEntity = new ApiPredicateItemEntity();
+ addPredicateItemEntity.setMatchStrategy(URL_MATCH_STRATEGY_EXACT);
+ addPredicateItemEntity.setPattern("/order");
+ addEntity = repository.save(addEntity);
+
+ UpdateApiReqVo reqVo = new UpdateApiReqVo();
+ reqVo.setId(addEntity.getId());
+ reqVo.setApp(TEST_APP);
+ List itemVos = new ArrayList<>();
+ ApiPredicateItemVo itemVo = new ApiPredicateItemVo();
+ itemVo.setMatchStrategy(URL_MATCH_STRATEGY_PREFIX);
+ itemVo.setPattern("/my_order");
+ itemVos.add(itemVo);
+ reqVo.setPredicateItems(itemVos);
+
+ given(sentinelApiClient.modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true);
+
+ MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path);
+ requestBuilder.content(JSON.toJSONString(reqVo)).contentType(MediaType.APPLICATION_JSON);
+
+ // Do controller logic
+ MvcResult mvcResult = mockMvc.perform(requestBuilder)
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print()).andReturn();
+
+ // Verify the modifyApis method has been called
+ verify(sentinelApiClient).modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any());
+
+ Result result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>() {});
+ assertTrue(result.isSuccess());
+
+ ApiDefinitionEntity entity = result.getData();
+ assertNotNull(entity);
+ assertEquals("bbb", entity.getApiName());
+ assertEquals(date, entity.getGmtCreate());
+ // To make sure gmtModified has been set and it's different from gmtCreate
+ assertNotNull(entity.getGmtModified());
+ assertNotEquals(entity.getGmtCreate(), entity.getGmtModified());
+
+ Set predicateItemEntities = entity.getPredicateItems();
+ assertEquals(1, predicateItemEntities.size());
+ ApiPredicateItemEntity predicateItemEntity = predicateItemEntities.iterator().next();
+ assertEquals(URL_MATCH_STRATEGY_PREFIX, predicateItemEntity.getMatchStrategy().intValue());
+ assertEquals("/my_order", predicateItemEntity.getPattern());
+
+ // Verify the entity which is update in memory repository
+ List entitiesInMem = repository.findAllByApp(TEST_APP);
+ assertEquals(1, entitiesInMem.size());
+ assertEquals(entity, entitiesInMem.get(0));
+ }
+
+ @Test
+ public void testDeleteApi() throws Exception {
+ String path = "/gateway/api/delete.json";
+
+ // Add one entity into memory repository for delete
+ ApiDefinitionEntity addEntity = new ApiDefinitionEntity();
+ addEntity.setApp(TEST_APP);
+ addEntity.setIp(TEST_IP);
+ addEntity.setPort(TEST_PORT);
+ addEntity.setApiName("ccc");
+ Date date = new Date();
+ addEntity.setGmtCreate(date);
+ addEntity.setGmtModified(date);
+ Set addRedicateItemEntities = new HashSet<>();
+ addEntity.setPredicateItems(addRedicateItemEntities);
+ ApiPredicateItemEntity addPredicateItemEntity = new ApiPredicateItemEntity();
+ addPredicateItemEntity.setMatchStrategy(URL_MATCH_STRATEGY_EXACT);
+ addPredicateItemEntity.setPattern("/user/add");
+ addEntity = repository.save(addEntity);
+
+ given(sentinelApiClient.modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true);
+
+ MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path);
+ requestBuilder.param("id", String.valueOf(addEntity.getId()));
+
+ // Do controller logic
+ MvcResult mvcResult = mockMvc.perform(requestBuilder)
+ .andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();
+
+ // Verify the modifyApis method has been called
+ verify(sentinelApiClient).modifyApis(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any());
+
+ // Verify the result
+ Result result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>() {});
+ assertTrue(result.isSuccess());
+
+ assertEquals(addEntity.getId(), result.getData());
+
+ // Now no entities in memory
+ List entitiesInMem = repository.findAllByApp(TEST_APP);
+ assertEquals(0, entitiesInMem.size());
+ }
+}
diff --git a/sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayFlowRuleControllerTest.java b/sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayFlowRuleControllerTest.java
new file mode 100644
index 00000000..74b9bc6b
--- /dev/null
+++ b/sentinel-dashboard/src/test/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayFlowRuleControllerTest.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.csp.sentinel.dashboard.controller.gateway;
+
+import com.alibaba.csp.sentinel.dashboard.auth.FakeAuthServiceImpl;
+import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
+import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayFlowRuleEntity;
+import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.GatewayParamFlowItemEntity;
+import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
+import com.alibaba.csp.sentinel.dashboard.discovery.SimpleMachineDiscovery;
+import com.alibaba.csp.sentinel.dashboard.domain.Result;
+import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.AddFlowRuleReqVo;
+import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.GatewayParamFlowItemVo;
+import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.rule.UpdateFlowRuleReqVo;
+import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemGatewayFlowRuleStore;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.TypeReference;
+import org.apache.commons.lang3.time.DateUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import static com.alibaba.csp.sentinel.slots.block.RuleConstant.*;
+import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*;
+import static org.junit.Assert.*;
+import static org.mockito.BDDMockito.*;
+
+/**
+ * Test cases for {@link GatewayFlowRuleController}.
+ *
+ * @author cdfive
+ */
+@RunWith(SpringRunner.class)
+@WebMvcTest(GatewayFlowRuleController.class)
+@Import({FakeAuthServiceImpl.class, InMemGatewayFlowRuleStore.class, AppManagement.class, SimpleMachineDiscovery.class})
+public class GatewayFlowRuleControllerTest {
+
+ private static final String TEST_APP = "test_app";
+
+ private static final String TEST_IP = "localhost";
+
+ private static final Integer TEST_PORT = 8719;
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Autowired
+ private InMemGatewayFlowRuleStore repository;
+
+ @MockBean
+ private SentinelApiClient sentinelApiClient;
+
+ @Before
+ public void before() {
+ repository.clearAll();
+ }
+
+ @Test
+ public void testQueryFlowRules() throws Exception {
+ String path = "/gateway/flow/list.json";
+
+ List entities = new ArrayList<>();
+
+ // Mock two entities
+ GatewayFlowRuleEntity entity = new GatewayFlowRuleEntity();
+ entity.setId(1L);
+ entity.setApp(TEST_APP);
+ entity.setIp(TEST_IP);
+ entity.setPort(TEST_PORT);
+ entity.setResource("httpbin_route");
+ entity.setResourceMode(RESOURCE_MODE_ROUTE_ID);
+ entity.setGrade(FLOW_GRADE_QPS);
+ entity.setCount(5D);
+ entity.setInterval(30L);
+ entity.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_SECOND);
+ entity.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
+ entity.setBurst(0);
+ entity.setMaxQueueingTimeoutMs(0);
+
+ GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity();
+ entity.setParamItem(itemEntity);
+ itemEntity.setParseStrategy(PARAM_PARSE_STRATEGY_CLIENT_IP);
+ entities.add(entity);
+
+ GatewayFlowRuleEntity entity2 = new GatewayFlowRuleEntity();
+ entity2.setId(2L);
+ entity2.setApp(TEST_APP);
+ entity2.setIp(TEST_IP);
+ entity2.setPort(TEST_PORT);
+ entity2.setResource("some_customized_api");
+ entity2.setResourceMode(RESOURCE_MODE_CUSTOM_API_NAME);
+ entity2.setCount(30D);
+ entity2.setInterval(2L);
+ entity2.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_MINUTE);
+ entity2.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
+ entity2.setBurst(0);
+ entity2.setMaxQueueingTimeoutMs(0);
+
+ GatewayParamFlowItemEntity itemEntity2 = new GatewayParamFlowItemEntity();
+ entity2.setParamItem(itemEntity2);
+ itemEntity2.setParseStrategy(PARAM_PARSE_STRATEGY_CLIENT_IP);
+ entities.add(entity2);
+
+ CompletableFuture> completableFuture = mock(CompletableFuture.class);
+ given(completableFuture.get()).willReturn(entities);
+ given(sentinelApiClient.fetchGatewayFlowRules(TEST_APP, TEST_IP, TEST_PORT)).willReturn(completableFuture);
+
+ MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get(path);
+ requestBuilder.param("app", TEST_APP);
+ requestBuilder.param("ip", TEST_IP);
+ requestBuilder.param("port", String.valueOf(TEST_PORT));
+
+ // Do controller logic
+ MvcResult mvcResult = mockMvc.perform(requestBuilder)
+ .andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();
+
+ // Verify the fetchGatewayFlowRules method has been called
+ verify(sentinelApiClient).fetchGatewayFlowRules(TEST_APP, TEST_IP, TEST_PORT);
+
+ // Verify if two same entities are got
+ Result> result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>>(){});
+ assertTrue(result.isSuccess());
+
+ List data = result.getData();
+ assertEquals(2, data.size());
+ assertEquals(entities, data);
+
+ // Verify the entities are add into memory repository
+ List entitiesInMem = repository.findAllByApp(TEST_APP);
+ assertEquals(2, entitiesInMem.size());
+ assertEquals(entities, entitiesInMem);
+ }
+
+ @Test
+ public void testAddFlowRule() throws Exception {
+ String path = "/gateway/flow/new.json";
+
+ AddFlowRuleReqVo reqVo = new AddFlowRuleReqVo();
+ reqVo.setApp(TEST_APP);
+ reqVo.setIp(TEST_IP);
+ reqVo.setPort(TEST_PORT);
+
+ reqVo.setResourceMode(RESOURCE_MODE_ROUTE_ID);
+ reqVo.setResource("httpbin_route");
+
+ reqVo.setGrade(FLOW_GRADE_QPS);
+ reqVo.setCount(5D);
+ reqVo.setInterval(30L);
+ reqVo.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_SECOND);
+ reqVo.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
+ reqVo.setBurst(0);
+ reqVo.setMaxQueueingTimeoutMs(0);
+
+ given(sentinelApiClient.modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true);
+
+ MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path);
+ requestBuilder.content(JSON.toJSONString(reqVo)).contentType(MediaType.APPLICATION_JSON);
+
+ // Do controller logic
+ MvcResult mvcResult = mockMvc.perform(requestBuilder)
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print()).andReturn();
+
+ // Verify the modifyGatewayFlowRules method has been called
+ verify(sentinelApiClient).modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any());
+
+ Result result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>() {});
+ assertTrue(result.isSuccess());
+
+ // Verify the result
+ GatewayFlowRuleEntity entity = result.getData();
+ assertNotNull(entity);
+ assertEquals(TEST_APP, entity.getApp());
+ assertEquals(TEST_IP, entity.getIp());
+ assertEquals(TEST_PORT, entity.getPort());
+ assertEquals(RESOURCE_MODE_ROUTE_ID, entity.getResourceMode().intValue());
+ assertEquals("httpbin_route", entity.getResource());
+ assertNotNull(entity.getId());
+ assertNotNull(entity.getGmtCreate());
+ assertNotNull(entity.getGmtModified());
+
+ // Verify the entity which is add in memory repository
+ List entitiesInMem = repository.findAllByApp(TEST_APP);
+ assertEquals(1, entitiesInMem.size());
+ assertEquals(entity, entitiesInMem.get(0));
+ }
+
+ @Test
+ public void testUpdateFlowRule() throws Exception {
+ String path = "/gateway/flow/save.json";
+
+ // Add one entity into memory repository for update
+ GatewayFlowRuleEntity addEntity = new GatewayFlowRuleEntity();
+ addEntity.setId(1L);
+ addEntity.setApp(TEST_APP);
+ addEntity.setIp(TEST_IP);
+ addEntity.setPort(TEST_PORT);
+ addEntity.setResource("httpbin_route");
+ addEntity.setResourceMode(RESOURCE_MODE_ROUTE_ID);
+ addEntity.setGrade(FLOW_GRADE_QPS);
+ addEntity.setCount(5D);
+ addEntity.setInterval(30L);
+ addEntity.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_SECOND);
+ addEntity.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
+ addEntity.setBurst(0);
+ addEntity.setMaxQueueingTimeoutMs(0);
+ Date date = new Date();
+ // To make the gmtModified different when do update
+ date = DateUtils.addSeconds(date, -1);
+ addEntity.setGmtCreate(date);
+ addEntity.setGmtModified(date);
+
+ GatewayParamFlowItemEntity addItemEntity = new GatewayParamFlowItemEntity();
+ addEntity.setParamItem(addItemEntity);
+ addItemEntity.setParseStrategy(PARAM_PARSE_STRATEGY_CLIENT_IP);
+
+ repository.save(addEntity);
+
+ UpdateFlowRuleReqVo reqVo = new UpdateFlowRuleReqVo();
+ reqVo.setId(addEntity.getId());
+ reqVo.setApp(TEST_APP);
+ reqVo.setGrade(FLOW_GRADE_QPS);
+ reqVo.setCount(6D);
+ reqVo.setInterval(2L);
+ reqVo.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_MINUTE);
+ reqVo.setControlBehavior(CONTROL_BEHAVIOR_RATE_LIMITER);
+ reqVo.setMaxQueueingTimeoutMs(500);
+
+ GatewayParamFlowItemVo itemVo = new GatewayParamFlowItemVo();
+ reqVo.setParamItem(itemVo);
+ itemVo.setParseStrategy(PARAM_PARSE_STRATEGY_URL_PARAM);
+ itemVo.setFieldName("pa");
+
+ given(sentinelApiClient.modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true);
+
+ MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path);
+ requestBuilder.content(JSON.toJSONString(reqVo)).contentType(MediaType.APPLICATION_JSON);
+
+ // Do controller logic
+ MvcResult mvcResult = mockMvc.perform(requestBuilder)
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andDo(MockMvcResultHandlers.print()).andReturn();
+
+ // Verify the modifyGatewayFlowRules method has been called
+ verify(sentinelApiClient).modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any());
+
+ Result result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>() {
+ });
+ assertTrue(result.isSuccess());
+
+ GatewayFlowRuleEntity entity = result.getData();
+ assertNotNull(entity);
+ assertEquals(RESOURCE_MODE_ROUTE_ID, entity.getResourceMode().intValue());
+ assertEquals("httpbin_route", entity.getResource());
+ assertEquals(6D, entity.getCount().doubleValue(), 0);
+ assertEquals(2L, entity.getInterval().longValue());
+ assertEquals(GatewayFlowRuleEntity.INTERVAL_UNIT_MINUTE, entity.getIntervalUnit().intValue());
+ assertEquals(CONTROL_BEHAVIOR_RATE_LIMITER, entity.getControlBehavior().intValue());
+ assertEquals(0, entity.getBurst().intValue());
+ assertEquals(500, entity.getMaxQueueingTimeoutMs().intValue());
+ assertEquals(date, entity.getGmtCreate());
+ // To make sure gmtModified has been set and it's different from gmtCreate
+ assertNotNull(entity.getGmtModified());
+ assertNotEquals(entity.getGmtCreate(), entity.getGmtModified());
+
+ // Verify the entity which is update in memory repository
+ GatewayParamFlowItemEntity itemEntity = entity.getParamItem();
+ assertEquals(PARAM_PARSE_STRATEGY_URL_PARAM, itemEntity.getParseStrategy().intValue());
+ assertEquals("pa", itemEntity.getFieldName());
+ }
+
+ @Test
+ public void testDeleteFlowRule() throws Exception {
+ String path = "/gateway/flow/delete.json";
+
+ // Add one entity into memory repository for delete
+ GatewayFlowRuleEntity addEntity = new GatewayFlowRuleEntity();
+ addEntity.setId(1L);
+ addEntity.setApp(TEST_APP);
+ addEntity.setIp(TEST_IP);
+ addEntity.setPort(TEST_PORT);
+ addEntity.setResource("httpbin_route");
+ addEntity.setResourceMode(RESOURCE_MODE_ROUTE_ID);
+ addEntity.setGrade(FLOW_GRADE_QPS);
+ addEntity.setCount(5D);
+ addEntity.setInterval(30L);
+ addEntity.setIntervalUnit(GatewayFlowRuleEntity.INTERVAL_UNIT_SECOND);
+ addEntity.setControlBehavior(CONTROL_BEHAVIOR_DEFAULT);
+ addEntity.setBurst(0);
+ addEntity.setMaxQueueingTimeoutMs(0);
+ Date date = new Date();
+ date = DateUtils.addSeconds(date, -1);
+ addEntity.setGmtCreate(date);
+ addEntity.setGmtModified(date);
+
+ GatewayParamFlowItemEntity addItemEntity = new GatewayParamFlowItemEntity();
+ addEntity.setParamItem(addItemEntity);
+ addItemEntity.setParseStrategy(PARAM_PARSE_STRATEGY_CLIENT_IP);
+
+ repository.save(addEntity);
+
+ given(sentinelApiClient.modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any())).willReturn(true);
+
+ MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(path);
+ requestBuilder.param("id", String.valueOf(addEntity.getId()));
+
+ // Do controller logic
+ MvcResult mvcResult = mockMvc.perform(requestBuilder)
+ .andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();
+
+ // Verify the modifyGatewayFlowRules method has been called
+ verify(sentinelApiClient).modifyGatewayFlowRules(eq(TEST_APP), eq(TEST_IP), eq(TEST_PORT), any());
+
+ // Verify the result
+ Result result = JSONObject.parseObject(mvcResult.getResponse().getContentAsString(), new TypeReference>() {});
+ assertTrue(result.isSuccess());
+
+ assertEquals(addEntity.getId(), result.getData());
+
+ // Now no entities in memory
+ List entitiesInMem = repository.findAllByApp(TEST_APP);
+ assertEquals(0, entitiesInMem.size());
+ }
+}
|