From bf34f8b3be912282dbca2a82d1150795e90157b1 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Fri, 4 Jan 2019 14:32:38 +0800 Subject: [PATCH] Polish Sentinel dashboard frontend for cluster flow control enhancement - Add cluster server list and assign page and client list page (for a specific app) Signed-off-by: Eric Zhao --- .../main/webapp/resources/app/scripts/app.js | 74 ++- .../controllers/cluster_app_assign_manage.js | 283 +++++++++ .../controllers/cluster_app_server_list.js | 543 ++++++++++++++++++ .../controllers/cluster_app_server_manage.js | 283 +++++++++ .../controllers/cluster_app_server_monitor.js | 97 ++++ .../cluster_app_token_client_list.js | 121 ++++ .../{cluster.js => cluster_single.js} | 41 +- .../controllers/{flow.js => flow_v1.js} | 15 +- .../controllers/{flow_old.js => flow_v2.js} | 21 +- .../app/scripts/controllers/identity.js | 10 +- .../app/scripts/controllers/param_flow.js | 6 +- .../scripts/directives/sidebar/sidebar.html | 4 +- .../scripts/services/cluster_state_service.js | 53 +- .../app/scripts/services/flow_service_v1.js | 126 ++-- .../app/scripts/services/flow_service_v2.js | 72 +-- .../main/webapp/resources/app/styles/main.css | 4 + .../resources/app/views/cluster/client.html | 8 + .../resources/app/views/cluster/server.html | 15 +- .../app/views/cluster_app_assign_manage.html | 118 ++++ .../app/views/cluster_app_client_list.html | 73 +++ .../app/views/cluster_app_server_list.html | 87 +++ .../views/cluster_app_server_overview.html | 88 +++ ...luster.html => cluster_single_config.html} | 9 +- .../cluster/cluster-client-config-dialog.html | 40 ++ .../cluster/cluster-server-assign-dialog.html | 139 +++++ ...uster-server-connection-detail-dialog.html | 37 ++ .../app/views/dialog/flow-rule-dialog.html | 4 +- .../views/dialog/param-flow-rule-dialog.html | 36 +- .../app/views/dialog/system-rule-dialog.html | 4 +- .../app/views/{flow_old.html => flow_v1.html} | 26 +- .../app/views/{flow.html => flow_v2.html} | 12 +- .../resources/app/views/param_flow.html | 9 +- .../main/webapp/resources/dist/css/app.css | 2 +- .../src/main/webapp/resources/dist/js/app.js | 2 +- 34 files changed, 2299 insertions(+), 163 deletions(-) create mode 100644 sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_assign_manage.js create mode 100644 sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_list.js create mode 100644 sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_manage.js create mode 100644 sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_monitor.js create mode 100644 sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_token_client_list.js rename sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/{cluster.js => cluster_single.js} (85%) rename sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/{flow.js => flow_v1.js} (92%) rename sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/{flow_old.js => flow_v2.js} (91%) create mode 100644 sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_assign_manage.html create mode 100644 sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_client_list.html create mode 100644 sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_server_list.html create mode 100644 sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_server_overview.html rename sentinel-dashboard/src/main/webapp/resources/app/views/{cluster.html => cluster_single_config.html} (89%) create mode 100755 sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-client-config-dialog.html create mode 100644 sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-server-assign-dialog.html create mode 100644 sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-server-connection-detail-dialog.html rename sentinel-dashboard/src/main/webapp/resources/app/views/{flow_old.html => flow_v1.html} (87%) rename sentinel-dashboard/src/main/webapp/resources/app/views/{flow.html => flow_v2.html} (94%) 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 c4cdcb65..e6c2abf9 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js @@ -67,15 +67,15 @@ angular }) .state('dashboard.flowV1', { - templateUrl: 'app/views/flow_old.html', - url: '/v1/flow/:app', + templateUrl: 'app/views/flow_v1.html', + url: '/flow/:app', controller: 'FlowControllerV1', resolve: { loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) { return $ocLazyLoad.load({ name: 'sentinelDashboardApp', files: [ - 'app/scripts/controllers/flow_old.js', + 'app/scripts/controllers/flow_v1.js', ] }); }] @@ -83,15 +83,15 @@ angular }) .state('dashboard.flow', { - templateUrl: 'app/views/flow.html', - url: '/flow/:app', - controller: 'FlowController', + 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.js', + 'app/scripts/controllers/flow_v2.js', ] }); }] @@ -114,16 +114,64 @@ angular } }) - .state('dashboard.clusterAll', { - templateUrl: 'app/views/cluster.html', - url: '/cluster/:app', - controller: 'SentinelClusterController', + .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.js', + '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.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', ] }); }] @@ -224,4 +272,4 @@ angular }] } }); - }]); + }]); \ No newline at end of file diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_assign_manage.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_assign_manage.js new file mode 100644 index 00000000..6f9367d6 --- /dev/null +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_assign_manage.js @@ -0,0 +1,283 @@ +var app = angular.module('sentinelDashboardApp'); + +app.controller('SentinelClusterAppAssignManageController', ['$scope', '$stateParams', 'ngDialog', + 'MachineService', 'ClusterStateService', + function ($scope, $stateParams, ngDialog, MachineService, ClusterStateService) { + $scope.app = $stateParams.app; + const UNSUPPORTED_CODE = 4041; + + const CLUSTER_MODE_CLIENT = 0; + const CLUSTER_MODE_SERVER = 1; + const DEFAULT_CLUSTER_SERVER_PORT = 18730; + + $scope.tmp = { + curClientChosen: [], + curRemainingClientChosen: [], + curChosenServer: {}, + }; + + function convertSetToString(set) { + if (set === undefined) { + return ''; + } + let s = ''; + for (let i = 0; i < set.length; i++) { + s = s + set[i]; + if (i < set.length - 1) { + s = s + ','; + } + } + return s; + } + + function convertStrToNamespaceSet(str) { + if (str === undefined || str === '') { + return []; + } + let arr = []; + let spliced = str.split(','); + spliced.forEach((v) => { + arr.push(v.trim()); + }); + return arr; + } + + function processAppSingleData(data) { + if (data.state.server && data.state.server.namespaceSet) { + data.state.server.namespaceSetStr = convertSetToString(data.state.server.namespaceSet); + data.mode = data.state.stateInfo.mode; + } + } + + function removeFromArr(arr, v) { + for (let i = 0; i < arr.length; i++) { + if (arr[i] === v) { + arr.splice(i, 1); + break; + } + } + } + + function resetChosen() { + $scope.tmp.curClientChosen = []; + $scope.tmp.curRemainingClientChosen = []; + } + + function generateMachineId(e) { + return e.ip + '@' + e.commandPort; + } + + function applyClusterMap(appClusterMachineList) { + if (!appClusterMachineList) { + return; + } + let tmpMap = new Map(); + $scope.clusterMap = []; + $scope.remainingClientAddressList = []; + let tmpServerList = []; + let tmpClientList = []; + appClusterMachineList.forEach((e) => { + if (e.mode === CLUSTER_MODE_CLIENT) { + tmpClientList.push(e); + } else if (e.mode === CLUSTER_MODE_SERVER) { + tmpServerList.push(e); + } else { + $scope.remainingClientAddressList.push(generateMachineId(e)); + } + }); + tmpServerList.forEach((e) => { + let ip = e.ip; + let machineId = ip + '@' + e.commandPort; + let group = { + ip: ip, + machineId: machineId, + port: e.state.server.port, + clientSet: [], + namespaceSetStr: e.state.server.namespaceSetStr, + belongToApp: true, + }; + if (!tmpMap.has(ip)) { + tmpMap.set(ip, group); + } + }); + tmpClientList.forEach((e) => { + let ip = e.ip; + let machineId = ip + '@' + e.commandPort; + + let targetServer = e.state.client.clientConfig.serverHost; + let targetPort = e.state.client.clientConfig.serverPort; + if (targetServer === undefined || targetServer === '' || + targetPort === undefined || targetPort <= 0) { + $scope.remainingClientAddressList.push(generateMachineId(e)); + return; + } + + if (!tmpMap.has(targetServer)) { + let group = { + ip: targetServer, + machineId: targetServer, + port: targetPort, + clientSet: [machineId], + belongToApp: false, + }; + tmpMap.set(targetServer, group); + } else { + let g = tmpMap.get(targetServer); + g.clientSet.push(machineId); + } + }); + tmpMap.forEach((v) => { + if (v !== undefined) { + $scope.clusterMap.push(v); + } + }); + } + + $scope.onCurrentServerChange = () => { + resetChosen(); + }; + + $scope.remainingClientAddressList = []; + + $scope.moveToServerGroup = () => { + let chosenServer = $scope.tmp.curChosenServer; + if (!chosenServer || !chosenServer.machineId) { + return; + } + $scope.tmp.curRemainingClientChosen.forEach(e => { + chosenServer.clientSet.push(e); + removeFromArr($scope.remainingClientAddressList, e); + }); + resetChosen(); + }; + + $scope.moveToRemainingSharePool = () => { + $scope.tmp.curClientChosen.forEach(e => { + $scope.remainingClientAddressList.push(e); + removeFromArr($scope.tmp.curChosenServer.clientSet, e); + }); + resetChosen(); + }; + + function parseIpFromMachineId(machineId) { + if (machineId.indexOf('@') === -1) { + return machineId; + } + let arr = machineId.split('@'); + return arr[0]; + } + + $scope.addToServerList = () => { + let group; + $scope.tmp.curRemainingClientChosen.forEach(e => { + group = { + machineId: e, + ip: parseIpFromMachineId(e), + port: DEFAULT_CLUSTER_SERVER_PORT, + clientSet: [], + namespaceSetStr: 'default,' + $scope.app, + belongToApp: true, + }; + $scope.clusterMap.push(group); + removeFromArr($scope.remainingClientAddressList, e); + $scope.tmp.curChosenServer = group; + }); + resetChosen(); + }; + + $scope.removeFromServerList = () => { + let chosenServer = $scope.tmp.curChosenServer; + if (!chosenServer || !chosenServer.machineId) { + return; + } + chosenServer.clientSet.forEach((e) => { + if (e !== undefined) { + $scope.remainingClientAddressList.push(e); + } + }); + + if (chosenServer.belongToApp || chosenServer.machineId.indexOf('@') !== -1) { + $scope.remainingClientAddressList.push(chosenServer.machineId); + } else { + alert('提示:非本应用内机器将不会置回空闲列表中'); + } + + removeFromArr($scope.clusterMap, chosenServer); + + resetChosen(); + + if ($scope.clusterMap.length > 0) { + $scope.tmp.curChosenServer = $scope.clusterMap[0]; + $scope.onCurrentServerChange(); + } else { + $scope.tmp.curChosenServer = {}; + } + }; + + function retrieveClusterAppInfo() { + ClusterStateService.fetchClusterUniversalStateOfApp($scope.app).success(function (data) { + if (data.code === 0 && data.data) { + $scope.loadError = undefined; + $scope.appClusterMachineList = data.data; + $scope.appClusterMachineList.forEach(processAppSingleData); + applyClusterMap($scope.appClusterMachineList); + if ($scope.clusterMap.length > 0) { + $scope.tmp.curChosenServer = $scope.clusterMap[0]; + $scope.onCurrentServerChange(); + } + } else { + $scope.appClusterMachineList = {}; + if (data.code === UNSUPPORTED_CODE) { + $scope.loadError = {message: '该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} + } else { + $scope.loadError = {message: data.msg}; + } + } + }).error(() => { + $scope.loadError = {message: '未知错误'}; + }); + } + + retrieveClusterAppInfo(); + + $scope.saveAndApplyAssign = () => { + let ok = confirm('是否确认执行变更?'); + if (!ok) { + return; + } + let cm = $scope.clusterMap; + if (!cm) { + cm = []; + } + cm.forEach((e) => { + e.namespaceSet = convertStrToNamespaceSet(e.namespaceSetStr); + }); + cm.namespaceSet = convertStrToNamespaceSet(cm.namespaceSetStr); + let request = { + clusterMap: cm, + remainingList: $scope.remainingClientAddressList, + }; + ClusterStateService.applyClusterFullAssignOfApp($scope.app, request).success((data) => { + if (data.code === 0 && data.data) { + let failedServerSet = data.data.failedServerSet; + let failedClientSet = data.data.failedClientSet; + if (failedClientSet.length === 0 && failedServerSet.length === 0) { + alert('全部推送成功'); + } else { + alert('推送完毕。token server 失败列表:' + JSON.stringify(failedServerSet) + + '; token client 失败列表:' + JSON.stringify(failedClientSet)); + } + + retrieveClusterAppInfo(); + } else { + if (data.code === UNSUPPORTED_CODE) { + alert('该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'); + } else { + alert('推送失败:' + data.msg); + } + } + }).error(() => { + alert('未知错误'); + }); + }; + }]); diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_list.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_list.js new file mode 100644 index 00000000..fc0a31de --- /dev/null +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_list.js @@ -0,0 +1,543 @@ +var app = angular.module('sentinelDashboardApp'); + +app.controller('SentinelClusterAppServerListController', ['$scope', '$stateParams', 'ngDialog', + 'MachineService', 'ClusterStateService', + function ($scope, $stateParams, ngDialog, MachineService, ClusterStateService) { + $scope.app = $stateParams.app; + const UNSUPPORTED_CODE = 4041; + + const CLUSTER_MODE_CLIENT = 0; + const CLUSTER_MODE_SERVER = 1; + const DEFAULT_CLUSTER_SERVER_PORT = 18730; + const DEFAULT_NAMESPACE = 'default'; + const DEFAULT_MAX_ALLOWED_QPS = 20000; + + // tmp for dialog temporary data. + $scope.tmp = { + curClientChosen: [], + curRemainingClientChosen: [], + curChosenServer: {}, + }; + + $scope.remainingMachineList = []; + + function convertSetToString(set) { + if (set === undefined) { + return ''; + } + if (set.length === 1 && set[0] === DEFAULT_NAMESPACE) { + return DEFAULT_NAMESPACE; + } + let s = ''; + for (let i = 0; i < set.length; i++) { + let ns = set[i]; + if (ns !== DEFAULT_NAMESPACE) { + s = s + ns; + if (i < set.length - 1) { + s = s + ','; + } + } + } + return s; + } + + function convertStrToNamespaceSet(str) { + if (str === undefined || str === '') { + return []; + } + let arr = []; + let spliced = str.split(','); + spliced.forEach((v) => { + arr.push(v.trim()); + }); + return arr; + } + + function processAppSingleData(data) { + if (data.state.server && data.state.server.namespaceSet) { + data.state.server.namespaceSetStr = convertSetToString(data.state.server.namespaceSet); + data.mode = data.state.stateInfo.mode; + } + } + + function removeFromArr(arr, v) { + for (let i = 0; i < arr.length; i++) { + if (arr[i] === v) { + arr.splice(i, 1); + break; + } + } + } + + function removeFromArrIf(arr, f) { + for (let i = 0; i < arr.length; i++) { + if (f(arr[i]) === true) { + arr.splice(i, 1); + break; + } + } + } + + function resetAssignDialogChosen() { + $scope.tmp.curClientChosen = []; + $scope.tmp.curRemainingClientChosen = []; + } + + function generateMachineId(e) { + return e.ip + '@' + e.commandPort; + } + + function applyClusterMap(appClusterMachineList) { + if (!appClusterMachineList) { + return; + } + let tmpMap = new Map(); + $scope.clusterMap = []; + $scope.remainingMachineList = []; + let tmpServerList = []; + let tmpClientList = []; + appClusterMachineList.forEach((e) => { + if (e.mode === CLUSTER_MODE_CLIENT) { + tmpClientList.push(e); + } else if (e.mode === CLUSTER_MODE_SERVER) { + tmpServerList.push(e); + } else { + $scope.remainingMachineList.push(generateMachineId(e)); + } + }); + tmpServerList.forEach((e) => { + let ip = e.ip; + let machineId = ip + '@' + e.commandPort; + let group = { + ip: ip, + machineId: machineId, + port: e.state.server.port, + clientSet: [], + namespaceSetStr: e.state.server.namespaceSetStr, + maxAllowedQps: e.state.server.flow.maxAllowedQps, + belongToApp: true, + }; + if (!tmpMap.has(ip)) { + tmpMap.set(ip, group); + } + }); + tmpClientList.forEach((e) => { + let ip = e.ip; + let machineId = ip + '@' + e.commandPort; + + let targetServer = e.state.client.clientConfig.serverHost; + let targetPort = e.state.client.clientConfig.serverPort; + if (targetServer === undefined || targetServer === '' || + targetPort === undefined || targetPort <= 0) { + $scope.remainingMachineList.push(generateMachineId(e)); + return; + } + + if (!tmpMap.has(targetServer)) { + let group = { + ip: targetServer, + machineId: targetServer, + port: targetPort, + clientSet: [machineId], + belongToApp: false, + }; + tmpMap.set(targetServer, group); + } else { + let g = tmpMap.get(targetServer); + g.clientSet.push(machineId); + } + }); + tmpMap.forEach((v) => { + if (v !== undefined) { + $scope.clusterMap.push(v); + } + }); + } + + $scope.notChosenServer = (id) => { + return id !== $scope.serverAssignDialogData.serverData.currentServer; + }; + + $scope.onCurrentServerChange = () => { + resetAssignDialogChosen(); + }; + + $scope.moveToServerGroup = () => { + $scope.tmp.curRemainingClientChosen.forEach(e => { + $scope.serverAssignDialogData.serverData.clientSet.push(e); + removeFromArr($scope.remainingMachineList, e); + }); + resetAssignDialogChosen(); + }; + + $scope.moveToRemainingSharePool = () => { + $scope.tmp.curClientChosen.forEach(e => { + $scope.remainingMachineList.push(e); + removeFromArr($scope.serverAssignDialogData.serverData.clientSet, e); + }); + resetAssignDialogChosen(); + }; + + function parseIpFromMachineId(machineId) { + if (machineId.indexOf('@') === -1) { + return machineId; + } + let arr = machineId.split('@'); + return arr[0]; + } + + function retrieveClusterAssignInfoOfApp() { + ClusterStateService.fetchClusterUniversalStateOfApp($scope.app).success(function (data) { + if (data.code === 0 && data.data) { + $scope.loadError = undefined; + $scope.appClusterMachineList = data.data; + $scope.appClusterMachineList.forEach(processAppSingleData); + applyClusterMap($scope.appClusterMachineList); + } else { + $scope.appClusterMachineList = {}; + if (data.code === UNSUPPORTED_CODE) { + $scope.loadError = {message: '该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} + } else { + $scope.loadError = {message: data.msg}; + } + } + }).error(() => { + $scope.loadError = {message: '未知错误'}; + }); + } + + + $scope.newServerDialog = () => { + retrieveClusterAssignInfoOfApp(); + $scope.serverAssignDialogData = { + title: '新增 Token Server', + type: 'add', + confirmBtnText: '保存', + serverData: { + serverType: 0, + clientSet: [], + serverPort: DEFAULT_CLUSTER_SERVER_PORT, + maxAllowedQps: DEFAULT_MAX_ALLOWED_QPS, + } + }; + $scope.serverAssignDialog = ngDialog.open({ + template: '/app/views/dialog/cluster/cluster-server-assign-dialog.html', + width: 1000, + overlay: true, + scope: $scope + }); + }; + + $scope.modifyServerAssignConfig = (id) => { + ClusterStateService.fetchClusterUniversalStateOfApp($scope.app).success(function (data) { + if (data.code === 0 && data.data) { + $scope.loadError = undefined; + $scope.appClusterMachineList = data.data; + $scope.appClusterMachineList.forEach(processAppSingleData); + applyClusterMap($scope.appClusterMachineList); + let clusterMap = $scope.clusterMap; + let d; + for (let i = 0; i < clusterMap.length; i++) { + if (clusterMap[i].machineId === id) { + d = clusterMap[i]; + } + } + if (!d) { + alert('状态错误'); + return; + } + $scope.serverAssignDialogData = { + title: 'Token Server 分配编辑', + type: 'edit', + confirmBtnText: '保存', + serverData: { + currentServer: d.machineId, + belongToApp: true, + serverPort: d.port, + clientSet: d.clientSet, + } + }; + if (d.maxAllowedQps !== undefined) { + $scope.serverAssignDialogData.serverData.maxAllowedQps = d.maxAllowedQps; + } + $scope.serverAssignDialog = ngDialog.open({ + template: '/app/views/dialog/cluster/cluster-server-assign-dialog.html', + width: 1000, + overlay: true, + scope: $scope + }); + } else { + if (data.code === UNSUPPORTED_CODE) { + $scope.loadError = {message: '该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} + } else { + $scope.loadError = {message: data.msg}; + } + } + }).error(() => { + $scope.loadError = {message: '未知错误'}; + }); + }; + + function getRemainingMachineList() { + return $scope.remainingMachineList.filter((e) => $scope.notChosenServer(e)); + } + + function doApplyNewSingleServerAssign() { + let ok = confirm('是否确认执行变更?'); + if (!ok) { + return; + } + let serverData = $scope.serverAssignDialogData.serverData; + let belongToApp = serverData.serverType == 0; // don't modify here! + let machineId = serverData.currentServer; + let request = { + clusterMap: { + machineId: machineId, + ip: parseIpFromMachineId(machineId), + port: serverData.serverPort, + clientSet: serverData.clientSet, + belongToApp: belongToApp, + maxAllowedQps: serverData.maxAllowedQps, + }, + remainingList: getRemainingMachineList(), + }; + ClusterStateService.applyClusterSingleServerAssignOfApp($scope.app, request).success((data) => { + if (data.code === 0 && data.data) { + let failedServerSet = data.data.failedServerSet; + let failedClientSet = data.data.failedClientSet; + if (failedClientSet.length === 0 && failedServerSet.length === 0) { + alert('全部推送成功'); + } else { + let failedSet = []; + if (failedServerSet) { + failedServerSet.forEach((e) => { + failedSet.push(e); + }); + } + if (failedClientSet) { + failedClientSet.forEach((e) => { + failedSet.push(e); + }); + } + + alert('推送完毕。失败机器列表:' + JSON.stringify(failedSet)); + } + + location.reload(); + } else { + if (data.code === UNSUPPORTED_CODE) { + alert('该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'); + } else { + alert('推送失败:' + data.msg); + } + } + }).error(() => { + alert('未知错误'); + }); + } + + function doApplySingleServerAssignEdit() { + let ok = confirm('是否确认执行变更?'); + if (!ok) { + return; + } + let serverData = $scope.serverAssignDialogData.serverData; + let machineId = serverData.currentServer; + let request = { + clusterMap: { + machineId: machineId, + ip: parseIpFromMachineId(machineId), + port: serverData.serverPort, + clientSet: serverData.clientSet, + belongToApp: serverData.belongToApp, + }, + remainingList: $scope.remainingMachineList, + }; + if (serverData.maxAllowedQps !== undefined) { + request.clusterMap.maxAllowedQps = serverData.maxAllowedQps; + } + ClusterStateService.applyClusterSingleServerAssignOfApp($scope.app, request).success((data) => { + if (data.code === 0 && data.data) { + let failedServerSet = data.data.failedServerSet; + let failedClientSet = data.data.failedClientSet; + if (failedClientSet.length === 0 && failedServerSet.length === 0) { + alert('全部推送成功'); + } else { + let failedSet = []; + failedServerSet.forEach(failedSet.push); + failedClientSet.forEach(failedSet.push); + alert('推送完毕。失败机器列表:' + JSON.stringify(failedSet)); + } + + location.reload(); + } else { + if (data.code === UNSUPPORTED_CODE) { + alert('该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'); + } else { + alert('推送失败:' + data.msg); + } + } + }).error(() => { + alert('未知错误'); + }); + } + + $scope.saveAssignForDialog = () => { + if (!checkAssignDialogValid()) { + return; + } + if ($scope.serverAssignDialogData.type === 'add') { + doApplyNewSingleServerAssign(); + } else if ($scope.serverAssignDialogData.type === 'edit') { + doApplySingleServerAssignEdit(); + } else { + alert('未知的操作'); + } + }; + + function checkAssignDialogValid() { + let serverData = $scope.serverAssignDialogData.serverData; + if (serverData.currentServer === undefined || serverData.currentServer === '') { + alert('请指定有效的 Token Server'); + return false; + } + if (serverData.serverPort === undefined || serverData.serverPort <= 0 || serverData.serverPort > 65535) { + alert('请输入合法的端口值'); + return false; + } + if (serverData.maxAllowedQps !== undefined && serverData.maxAllowedQps < 0) { + alert('请输入合法的最大允许 QPS'); + return false; + } + return true; + } + + $scope.viewConnectionDetail = (serverVO) => { + $scope.connectionDetailDialogData = { + serverData: serverVO + }; + $scope.connectionDetailDialog = ngDialog.open({ + template: '/app/views/dialog/cluster/cluster-server-connection-detail-dialog.html', + width: 700, + overlay: true, + scope: $scope + }); + }; + + function generateRequestLimitDataStr(limitData) { + if (limitData.length === 1 && limitData[0].namespace === DEFAULT_NAMESPACE) { + return 'default: ' + limitData[0].currentQps + ' / ' + limitData[0].maxAllowedQps; + } + for (let i = 0; i < limitData.length; i++) { + let crl = limitData[i]; + if (crl.namespace === $scope.app) { + return '' + crl.currentQps + ' / ' + crl.maxAllowedQps; + } + } + return '0'; + } + + function processServerListData(serverVO) { + if (serverVO.state && serverVO.state.namespaceSet) { + serverVO.state.namespaceSetStr = convertSetToString(serverVO.state.namespaceSet); + } + if (serverVO.state && serverVO.state.requestLimitData) { + serverVO.state.requestLimitDataStr = generateRequestLimitDataStr(serverVO.state.requestLimitData); + } + } + + $scope.generateConnectionSet = (data) => { + let connectionSet = data; + let s = ''; + if (connectionSet) { + s = s + '['; + for (let i = 0; i < connectionSet.length; i++) { + s = s + connectionSet[i].address; + if (i < connectionSet.length - 1) { + s = s + ', '; + } + } + s = s + ']'; + } else { + s = '[]'; + } + return s; + }; + + function retrieveClusterServerInfo() { + ClusterStateService.fetchClusterServerStateOfApp($scope.app).success(function (data) { + if (data.code === 0 && data.data) { + $scope.loadError = undefined; + $scope.serverVOList = data.data; + $scope.serverVOList.forEach(processServerListData); + + // if ($scope.serverVOList.length > 0) { + // $scope.tmp.curChosenServer = $scope.serverVOList[0]; + // $scope.onChosenServerChange(); + // } + } else { + $scope.serverVOList = {}; + if (data.code === UNSUPPORTED_CODE) { + $scope.loadError = {message: '该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} + } else { + $scope.loadError = {message: data.msg}; + } + } + }).error(() => { + $scope.loadError = {message: '未知错误'}; + }); + } + + retrieveClusterServerInfo(); + + let confirmUnbindServerDialog; + $scope.unbindServer = (id) => { + $scope.pendingUnbindIds = [id]; + $scope.confirmDialog = { + title: '移除 Token Server', + type: 'unbind_token_server', + attentionTitle: '请确认是否移除以下 Token Server(该 server 下的 client 也会解除分配)', + attention: id + '', + confirmBtnText: '移除', + }; + confirmUnbindServerDialog = ngDialog.open({ + template: '/app/views/dialog/confirm-dialog.html', + scope: $scope, + overlay: true + }); + }; + + function apiUnbindServerAssign(ids) { + ClusterStateService.applyClusterServerBatchUnbind($scope.app, ids).success((data) => { + if (data.code === 0 && data.data) { + let failedServerSet = data.data.failedServerSet; + let failedClientSet = data.data.failedClientSet; + if (failedClientSet.length === 0 && failedServerSet.length === 0) { + alert('成功'); + } else { + alert('操作推送完毕,部分失败机器列表:' + JSON.stringify(failedClientSet)); + } + + location.reload(); + } else { + if (data.code === UNSUPPORTED_CODE) { + alert('该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'); + } else { + alert('推送失败:' + data.msg); + } + } + }).error(() => { + alert('未知错误'); + }); + // confirmUnbindServerDialog.close(); + } + + // Confirm function for confirm dialog. + $scope.confirm = () => { + if ($scope.confirmDialog.type === 'unbind_token_server') { + apiUnbindServerAssign($scope.pendingUnbindIds); + } else { + console.error('Error dialog when unbinding token server'); + } + }; + }]); diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_manage.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_manage.js new file mode 100644 index 00000000..6f9367d6 --- /dev/null +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_manage.js @@ -0,0 +1,283 @@ +var app = angular.module('sentinelDashboardApp'); + +app.controller('SentinelClusterAppAssignManageController', ['$scope', '$stateParams', 'ngDialog', + 'MachineService', 'ClusterStateService', + function ($scope, $stateParams, ngDialog, MachineService, ClusterStateService) { + $scope.app = $stateParams.app; + const UNSUPPORTED_CODE = 4041; + + const CLUSTER_MODE_CLIENT = 0; + const CLUSTER_MODE_SERVER = 1; + const DEFAULT_CLUSTER_SERVER_PORT = 18730; + + $scope.tmp = { + curClientChosen: [], + curRemainingClientChosen: [], + curChosenServer: {}, + }; + + function convertSetToString(set) { + if (set === undefined) { + return ''; + } + let s = ''; + for (let i = 0; i < set.length; i++) { + s = s + set[i]; + if (i < set.length - 1) { + s = s + ','; + } + } + return s; + } + + function convertStrToNamespaceSet(str) { + if (str === undefined || str === '') { + return []; + } + let arr = []; + let spliced = str.split(','); + spliced.forEach((v) => { + arr.push(v.trim()); + }); + return arr; + } + + function processAppSingleData(data) { + if (data.state.server && data.state.server.namespaceSet) { + data.state.server.namespaceSetStr = convertSetToString(data.state.server.namespaceSet); + data.mode = data.state.stateInfo.mode; + } + } + + function removeFromArr(arr, v) { + for (let i = 0; i < arr.length; i++) { + if (arr[i] === v) { + arr.splice(i, 1); + break; + } + } + } + + function resetChosen() { + $scope.tmp.curClientChosen = []; + $scope.tmp.curRemainingClientChosen = []; + } + + function generateMachineId(e) { + return e.ip + '@' + e.commandPort; + } + + function applyClusterMap(appClusterMachineList) { + if (!appClusterMachineList) { + return; + } + let tmpMap = new Map(); + $scope.clusterMap = []; + $scope.remainingClientAddressList = []; + let tmpServerList = []; + let tmpClientList = []; + appClusterMachineList.forEach((e) => { + if (e.mode === CLUSTER_MODE_CLIENT) { + tmpClientList.push(e); + } else if (e.mode === CLUSTER_MODE_SERVER) { + tmpServerList.push(e); + } else { + $scope.remainingClientAddressList.push(generateMachineId(e)); + } + }); + tmpServerList.forEach((e) => { + let ip = e.ip; + let machineId = ip + '@' + e.commandPort; + let group = { + ip: ip, + machineId: machineId, + port: e.state.server.port, + clientSet: [], + namespaceSetStr: e.state.server.namespaceSetStr, + belongToApp: true, + }; + if (!tmpMap.has(ip)) { + tmpMap.set(ip, group); + } + }); + tmpClientList.forEach((e) => { + let ip = e.ip; + let machineId = ip + '@' + e.commandPort; + + let targetServer = e.state.client.clientConfig.serverHost; + let targetPort = e.state.client.clientConfig.serverPort; + if (targetServer === undefined || targetServer === '' || + targetPort === undefined || targetPort <= 0) { + $scope.remainingClientAddressList.push(generateMachineId(e)); + return; + } + + if (!tmpMap.has(targetServer)) { + let group = { + ip: targetServer, + machineId: targetServer, + port: targetPort, + clientSet: [machineId], + belongToApp: false, + }; + tmpMap.set(targetServer, group); + } else { + let g = tmpMap.get(targetServer); + g.clientSet.push(machineId); + } + }); + tmpMap.forEach((v) => { + if (v !== undefined) { + $scope.clusterMap.push(v); + } + }); + } + + $scope.onCurrentServerChange = () => { + resetChosen(); + }; + + $scope.remainingClientAddressList = []; + + $scope.moveToServerGroup = () => { + let chosenServer = $scope.tmp.curChosenServer; + if (!chosenServer || !chosenServer.machineId) { + return; + } + $scope.tmp.curRemainingClientChosen.forEach(e => { + chosenServer.clientSet.push(e); + removeFromArr($scope.remainingClientAddressList, e); + }); + resetChosen(); + }; + + $scope.moveToRemainingSharePool = () => { + $scope.tmp.curClientChosen.forEach(e => { + $scope.remainingClientAddressList.push(e); + removeFromArr($scope.tmp.curChosenServer.clientSet, e); + }); + resetChosen(); + }; + + function parseIpFromMachineId(machineId) { + if (machineId.indexOf('@') === -1) { + return machineId; + } + let arr = machineId.split('@'); + return arr[0]; + } + + $scope.addToServerList = () => { + let group; + $scope.tmp.curRemainingClientChosen.forEach(e => { + group = { + machineId: e, + ip: parseIpFromMachineId(e), + port: DEFAULT_CLUSTER_SERVER_PORT, + clientSet: [], + namespaceSetStr: 'default,' + $scope.app, + belongToApp: true, + }; + $scope.clusterMap.push(group); + removeFromArr($scope.remainingClientAddressList, e); + $scope.tmp.curChosenServer = group; + }); + resetChosen(); + }; + + $scope.removeFromServerList = () => { + let chosenServer = $scope.tmp.curChosenServer; + if (!chosenServer || !chosenServer.machineId) { + return; + } + chosenServer.clientSet.forEach((e) => { + if (e !== undefined) { + $scope.remainingClientAddressList.push(e); + } + }); + + if (chosenServer.belongToApp || chosenServer.machineId.indexOf('@') !== -1) { + $scope.remainingClientAddressList.push(chosenServer.machineId); + } else { + alert('提示:非本应用内机器将不会置回空闲列表中'); + } + + removeFromArr($scope.clusterMap, chosenServer); + + resetChosen(); + + if ($scope.clusterMap.length > 0) { + $scope.tmp.curChosenServer = $scope.clusterMap[0]; + $scope.onCurrentServerChange(); + } else { + $scope.tmp.curChosenServer = {}; + } + }; + + function retrieveClusterAppInfo() { + ClusterStateService.fetchClusterUniversalStateOfApp($scope.app).success(function (data) { + if (data.code === 0 && data.data) { + $scope.loadError = undefined; + $scope.appClusterMachineList = data.data; + $scope.appClusterMachineList.forEach(processAppSingleData); + applyClusterMap($scope.appClusterMachineList); + if ($scope.clusterMap.length > 0) { + $scope.tmp.curChosenServer = $scope.clusterMap[0]; + $scope.onCurrentServerChange(); + } + } else { + $scope.appClusterMachineList = {}; + if (data.code === UNSUPPORTED_CODE) { + $scope.loadError = {message: '该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} + } else { + $scope.loadError = {message: data.msg}; + } + } + }).error(() => { + $scope.loadError = {message: '未知错误'}; + }); + } + + retrieveClusterAppInfo(); + + $scope.saveAndApplyAssign = () => { + let ok = confirm('是否确认执行变更?'); + if (!ok) { + return; + } + let cm = $scope.clusterMap; + if (!cm) { + cm = []; + } + cm.forEach((e) => { + e.namespaceSet = convertStrToNamespaceSet(e.namespaceSetStr); + }); + cm.namespaceSet = convertStrToNamespaceSet(cm.namespaceSetStr); + let request = { + clusterMap: cm, + remainingList: $scope.remainingClientAddressList, + }; + ClusterStateService.applyClusterFullAssignOfApp($scope.app, request).success((data) => { + if (data.code === 0 && data.data) { + let failedServerSet = data.data.failedServerSet; + let failedClientSet = data.data.failedClientSet; + if (failedClientSet.length === 0 && failedServerSet.length === 0) { + alert('全部推送成功'); + } else { + alert('推送完毕。token server 失败列表:' + JSON.stringify(failedServerSet) + + '; token client 失败列表:' + JSON.stringify(failedClientSet)); + } + + retrieveClusterAppInfo(); + } else { + if (data.code === UNSUPPORTED_CODE) { + alert('该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'); + } else { + alert('推送失败:' + data.msg); + } + } + }).error(() => { + alert('未知错误'); + }); + }; + }]); diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_monitor.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_monitor.js new file mode 100644 index 00000000..202fca1b --- /dev/null +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_monitor.js @@ -0,0 +1,97 @@ +var app = angular.module('sentinelDashboardApp'); + +app.controller('SentinelClusterAppServerMonitorController', ['$scope', '$stateParams', 'ngDialog', + 'MachineService', 'ClusterStateService', + function ($scope, $stateParams, ngDialog, MachineService, ClusterStateService) { + $scope.app = $stateParams.app; + const UNSUPPORTED_CODE = 4041; + + const CLUSTER_MODE_SERVER = 1; + + $scope.tmp = { + curChosenServer: {}, + }; + + function convertSetToString(set) { + if (set === undefined) { + return ''; + } + let s = ''; + for (let i = 0; i < set.length; i++) { + s = s + set[i]; + if (i < set.length - 1) { + s = s + ','; + } + } + return s; + } + + function processServerData(serverVO) { + if (serverVO.state && serverVO.state.namespaceSet) { + serverVO.state.namespaceSetStr = convertSetToString(serverVO.state.namespaceSet); + } + } + + $scope.generateConnectionSet = (data) => { + let connectionSet = data; + let s = ''; + if (connectionSet) { + s = s + '['; + for (let i = 0; i < connectionSet.length; i++) { + s = s + connectionSet[i].address; + if (i < connectionSet.length - 1) { + s = s + ', '; + } + } + s = s + ']'; + } else { + s = '[]'; + } + return s; + }; + + $scope.onChosenServerChange = () => { + + }; + + function retrieveClusterServerInfo() { + ClusterStateService.fetchClusterServerStateOfApp($scope.app).success(function (data) { + if (data.code === 0 && data.data) { + $scope.loadError = undefined; + $scope.serverVOList = data.data; + $scope.serverVOList.forEach(processServerData); + + if ($scope.serverVOList.length > 0) { + $scope.tmp.curChosenServer = $scope.serverVOList[0]; + $scope.onChosenServerChange(); + } + } else { + $scope.serverVOList = {}; + if (data.code === UNSUPPORTED_CODE) { + $scope.loadError = {message: '该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} + } else { + $scope.loadError = {message: data.msg}; + } + } + }).error(() => { + $scope.loadError = {message: '未知错误'}; + }); + } + + retrieveClusterServerInfo(); + + $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; + } + }; + }]); diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_token_client_list.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_token_client_list.js new file mode 100644 index 00000000..177161b8 --- /dev/null +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_token_client_list.js @@ -0,0 +1,121 @@ +var app = angular.module('sentinelDashboardApp'); + +app.controller('SentinelClusterAppTokenClientListController', ['$scope', '$stateParams', 'ngDialog', + 'MachineService', 'ClusterStateService', + function ($scope, $stateParams, ngDialog, MachineService, ClusterStateService) { + $scope.app = $stateParams.app; + + const UNSUPPORTED_CODE = 4041; + const CLUSTER_MODE_CLIENT = 0; + const CLUSTER_MODE_SERVER = 1; + + function processClientData(clientVO) { + + } + + $scope.modifyClientConfigDialog = (clientVO) => { + if (!clientVO) { + return; + } + $scope.ccDialogData = { + ip: clientVO.ip, + commandPort: clientVO.commandPort, + clientId: clientVO.id, + serverHost: clientVO.state.clientConfig.serverHost, + serverPort: clientVO.state.clientConfig.serverPort, + requestTimeout: clientVO.state.clientConfig.requestTimeout, + }; + $scope.ccDialog = ngDialog.open({ + template: '/app/views/dialog/cluster/cluster-client-config-dialog.html', + width: 700, + overlay: true, + scope: $scope + }); + }; + + function checkValidClientConfig(config) { + if (!config.serverHost || config.serverHost.trim() == '') { + alert('请输入有效的 Token Server IP'); + return false; + } + if (config.serverPort === undefined || config.serverPort <= 0 || config.serverPort > 65535) { + alert('请输入有效的 Token Server 端口'); + return false; + } + if (config.requestTimeout === undefined || config.requestTimeout <= 0) { + alert('请输入有效的请求超时时长'); + return false; + } + return true; + } + + $scope.doModifyClientConfig = () => { + if (!checkValidClientConfig($scope.ccDialogData)) { + return; + } + let id = $scope.ccDialogData.id; + let request = { + app: $scope.app, + ip: $scope.ccDialogData.ip, + port: $scope.ccDialogData.commandPort, + mode: CLUSTER_MODE_CLIENT, + clientConfig: { + serverHost: $scope.ccDialogData.serverHost, + serverPort: $scope.ccDialogData.serverPort, + requestTimeout: $scope.ccDialogData.requestTimeout, + } + }; + ClusterStateService.modifyClusterConfig(request).success((data) => { + if (data.code === 0 && data.data) { + alert('修改 Token Client 配置成功'); + window.location.reload(); + } else { + if (data.code === UNSUPPORTED_CODE) { + alert('机器 ' + id + ' 的 Sentinel 没有引入集群限流客户端,请升级至 1.4.0 以上版本并引入相关依赖。'); + } else { + alert('修改失败:' + data.msg); + } + } + }).error((data, header, config, status) => { + alert('未知错误'); + }); + }; + + function retrieveClusterTokenClientInfo() { + ClusterStateService.fetchClusterClientStateOfApp($scope.app) + .success((data) => { + if (data.code === 0 && data.data) { + $scope.loadError = undefined; + $scope.clientVOList = data.data; + $scope.clientVOList.forEach(processClientData); + } else { + $scope.clientVOList = []; + if (data.code === UNSUPPORTED_CODE) { + $scope.loadError = {message: '该应用的 Sentinel 客户端不支持集群限流,请升级至 1.4.0 以上版本并引入相关依赖。'} + } else { + $scope.loadError = {message: data.msg}; + } + } + }) + .error(() => { + $scope.loadError = {message: '未知错误'}; + }); + } + + retrieveClusterTokenClientInfo(); + + $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; + } + }; + }]); diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_single.js similarity index 85% rename from sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster.js rename to sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_single.js index 2e6505c6..ffdbe637 100644 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_single.js @@ -1,6 +1,6 @@ var app = angular.module('sentinelDashboardApp'); -app.controller('SentinelClusterController', ['$scope', '$stateParams', 'ngDialog', +app.controller('SentinelClusterSingleController', ['$scope', '$stateParams', 'ngDialog', 'MachineService', 'ClusterStateService', function ($scope, $stateParams, ngDialog, MachineService, ClusterStateService) { $scope.app = $stateParams.app; @@ -39,7 +39,7 @@ app.controller('SentinelClusterController', ['$scope', '$stateParams', 'ngDialog } function convertStrToNamespaceSet(str) { - if (str === undefined || str == '') { + if (str === undefined || str === '') { return []; } let arr = []; @@ -51,11 +51,11 @@ app.controller('SentinelClusterController', ['$scope', '$stateParams', 'ngDialog } function fetchMachineClusterState() { - if (!$scope.macInputModel) { + if (!$scope.macInputModel || $scope.macInputModel === '') { return; } let mac = $scope.macInputModel.split(':'); - ClusterStateService.fetchClusterUniversalState($scope.app, mac[0], mac[1]).success(function (data) { + ClusterStateService.fetchClusterUniversalStateSingle($scope.app, mac[0], mac[1]).success(function (data) { if (data.code == 0 && data.data) { $scope.loadError = undefined; $scope.stateVO = data.data; @@ -144,6 +144,11 @@ app.controller('SentinelClusterController', ['$scope', '$stateParams', 'ngDialog alert('请输入有效的 Token Server 端口'); return false; } + let flowConfig = stateVO.server.flow; + if (flowConfig.maxAllowedQps === undefined || flowConfig.maxAllowedQps < 0) { + alert('请输入有效的最大允许 QPS'); + return false; + } // if (transportConfig.idleSeconds === undefined || transportConfig.idleSeconds <= 0) { // alert('请输入有效的连接清理时长 (idleSeconds)'); // return false; @@ -205,19 +210,21 @@ app.controller('SentinelClusterController', ['$scope', '$stateParams', 'ngDialog function queryAppMachines() { MachineService.getAppMachines($scope.app).success( function (data) { - if (data.code == 0) { + if (data.code === 0) { // $scope.machines = data.data; if (data.data) { $scope.machines = []; + $scope.macsInputOptionsOrigin = []; $scope.macsInputOptions = []; data.data.forEach(function (item) { if (item.health) { - $scope.macsInputOptions.push({ + $scope.macsInputOptionsOrigin.push({ text: item.ip + ':' + item.port, value: item.ip + ':' + item.port }); } }); + $scope.macsInputOptions = $scope.macsInputOptionsOrigin; } if ($scope.macsInputOptions.length > 0) { $scope.macInputModel = $scope.macsInputOptions[0].value; @@ -227,11 +234,29 @@ app.controller('SentinelClusterController', ['$scope', '$stateParams', 'ngDialog } } ); - }; + } + queryAppMachines(); + + $scope.$watch('searchKey', function () { + if (!$scope.macsInputOptions) { + return; + } + if ($scope.searchKey) { + $scope.macsInputOptions = $scope.macsInputOptionsOrigin + .filter((e) => e.value.indexOf($scope.searchKey) !== -1); + } else { + $scope.macsInputOptions = $scope.macsInputOptionsOrigin; + } + if ($scope.macsInputOptions.length > 0) { + $scope.macInputModel = $scope.macsInputOptions[0].value; + } else { + $scope.macInputModel = ''; + } + }); + $scope.$watch('macInputModel', function () { if ($scope.macInputModel) { fetchMachineClusterState(); } }); - queryAppMachines(); }]); diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow_v1.js similarity index 92% rename from sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow.js rename to sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow_v1.js index fb49cfbe..d587a13d 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow_v1.js @@ -1,6 +1,6 @@ var app = angular.module('sentinelDashboardApp'); -app.controller('FlowController', ['$scope', '$stateParams', 'FlowServiceV2', 'ngDialog', +app.controller('FlowControllerV1', ['$scope', '$stateParams', 'FlowServiceV1', 'ngDialog', 'MachineService', function ($scope, $stateParams, FlowService, ngDialog, MachineService) { @@ -27,6 +27,19 @@ app.controller('FlowController', ['$scope', '$stateParams', 'FlowServiceV2', 'ng } }; + $scope.generateThresholdTypeShow = (rule) => { + if (!rule.clusterMode) { + return '单机'; + } + if (rule.clusterConfig.thresholdType === 0) { + return '集群均摊'; + } else if (rule.clusterConfig.thresholdType === 1) { + return '集群总体'; + } else { + return '集群'; + } + }; + getMachineRules(); function getMachineRules() { if (!$scope.macInputModel) { diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow_old.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow_v2.js similarity index 91% rename from sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow_old.js rename to sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow_v2.js index 658c9d0c..4e11c244 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow_old.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow_v2.js @@ -1,6 +1,6 @@ var app = angular.module('sentinelDashboardApp'); -app.controller('FlowControllerV1', ['$scope', '$stateParams', 'FlowServiceV1', 'ngDialog', +app.controller('FlowControllerV2', ['$scope', '$stateParams', 'FlowServiceV2', 'ngDialog', 'MachineService', function ($scope, $stateParams, FlowService, ngDialog, MachineService) { @@ -27,6 +27,19 @@ app.controller('FlowControllerV1', ['$scope', '$stateParams', 'FlowServiceV1', ' } }; + $scope.generateThresholdTypeShow = (rule) => { + if (!rule.clusterMode) { + return '单机'; + } + if (rule.clusterConfig.thresholdType === 0) { + return '集群均摊'; + } else if (rule.clusterConfig.thresholdType === 1) { + return '集群总体'; + } else { + return '集群'; + } + }; + getMachineRules(); function getMachineRules() { if (!$scope.macInputModel) { @@ -72,7 +85,11 @@ app.controller('FlowControllerV1', ['$scope', '$stateParams', 'FlowServiceV1', ' app: $scope.app, ip: mac[0], port: mac[1], - limitApp: 'default' + limitApp: 'default', + clusterMode: false, + clusterConfig: { + thresholdType: 0 + } }; $scope.flowRuleDialog = { title: '新增流控规则', diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/identity.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/identity.js index 0c740234..2b09ffd4 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/identity.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/identity.js @@ -1,7 +1,7 @@ var app = angular.module('sentinelDashboardApp'); app.controller('IdentityCtl', ['$scope', '$stateParams', 'IdentityService', - 'ngDialog', 'FlowServiceV2', 'DegradeService', 'AuthorityRuleService', 'ParamFlowService', 'MachineService', + 'ngDialog', 'FlowServiceV1', 'DegradeService', 'AuthorityRuleService', 'ParamFlowService', 'MachineService', '$interval', '$location', '$timeout', function ($scope, $stateParams, IdentityService, ngDialog, FlowService, DegradeService, AuthorityRuleService, ParamFlowService, MachineService, $interval, $location, $timeout) { @@ -52,6 +52,10 @@ app.controller('IdentityCtl', ['$scope', '$stateParams', 'IdentityService', controlBehavior: 0, resource: resource, limitApp: 'default', + clusterMode: false, + clusterConfig: { + thresholdType: 0 + }, app: $scope.app, ip: mac[0], port: mac[1] @@ -89,13 +93,15 @@ app.controller('IdentityCtl', ['$scope', '$stateParams', 'IdentityService', return; } FlowService.newRule(flowRuleDialogScope.currentRule).success(function (data) { - if (data.code == 0) { + if (data.code === 0) { flowRuleDialog.close(); let url = '/dashboard/flow/' + $scope.app; $location.path(url); } else { alert('失败!'); } + }).error((data, header, config, status) => { + alert('未知错误'); }); } diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/param_flow.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/param_flow.js index ebdbf6bd..4b6f799e 100644 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/param_flow.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/param_flow.js @@ -105,7 +105,7 @@ angular.module('sentinelDashboardApp').controller('ParamFlowController', ['$scop let mac = $scope.macInputModel.split(':'); ParamFlowService.queryMachineRules($scope.app, mac[0], mac[1]) .success(function (data) { - if (data.code == 0 && data.data) { + if (data.code === 0 && data.data) { $scope.loadError = undefined; $scope.rules = data.data; $scope.rulesPageConfig.totalCount = $scope.rules.length; @@ -157,6 +157,10 @@ angular.module('sentinelDashboardApp').controller('ParamFlowController', ['$scop paramFlowItemList: [], count: 0, limitApp: 'default', + clusterMode: false, + clusterConfig: { + thresholdType: 0 + } } }; $scope.paramFlowRuleDialog = { 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 fd435ed2..6a030b37 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 @@ -57,8 +57,8 @@   授权规则
  • - -   集群限流 + +   集群流控
  • diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/cluster_state_service.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/cluster_state_service.js index 4d5facd0..7bca8161 100644 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/cluster_state_service.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/cluster_state_service.js @@ -1,28 +1,73 @@ /** - * Parameter flow control service. + * Cluster state control service. * * @author Eric Zhao */ angular.module('sentinelDashboardApp').service('ClusterStateService', ['$http', function ($http) { - this.fetchClusterUniversalState = function(app, ip, port) { + this.fetchClusterUniversalStateSingle = function(app, ip, port) { var param = { app: app, ip: ip, port: port }; return $http({ - url: '/cluster/state', + url: '/cluster/state_single', params: param, method: 'GET' }); }; + this.fetchClusterUniversalStateOfApp = function(app) { + return $http({ + url: '/cluster/state/' + app, + method: 'GET' + }); + }; + + this.fetchClusterServerStateOfApp = function(app) { + return $http({ + url: '/cluster/server_state/' + app, + method: 'GET' + }); + }; + + this.fetchClusterClientStateOfApp = function(app) { + return $http({ + url: '/cluster/client_state/' + app, + method: 'GET' + }); + }; + this.modifyClusterConfig = function(config) { return $http({ - url: '/cluster/config/modify', + url: '/cluster/config/modify_single', data: config, method: 'POST' }); }; + + this.applyClusterFullAssignOfApp = function(app, clusterMap) { + return $http({ + url: '/cluster/assign/all_server/' + app, + data: clusterMap, + method: 'POST' + }); + }; + + this.applyClusterSingleServerAssignOfApp = function(app, request) { + return $http({ + url: '/cluster/assign/single_server/' + app, + data: request, + method: 'POST' + }); + }; + + this.applyClusterServerBatchUnbind = function(app, machineSet) { + return $http({ + url: '/cluster/assign/unbind_server/' + app, + data: machineSet, + method: 'POST' + }); + }; }]); diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/flow_service_v1.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/flow_service_v1.js index 02e5796d..051a3c71 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/flow_service_v1.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/flow_service_v1.js @@ -1,76 +1,76 @@ var app = angular.module('sentinelDashboardApp'); app.service('FlowServiceV1', ['$http', function ($http) { - this.queryMachineRules = function (app, ip, port) { - var param = { - app: app, - ip: ip, - port: port + this.queryMachineRules = function (app, ip, port) { + var param = { + app: app, + ip: ip, + port: port + }; + return $http({ + url: '/v1/flow/rules', + params: param, + method: 'GET' + }); }; - return $http({ - url: '/v1/flow/rules', - params: param, - method: 'GET' - }); - }; - this.newRule = function (rule) { - var param = { - resource: rule.resource, - limitApp: rule.limitApp, - grade: rule.grade, - count: rule.count, - strategy: rule.strategy, - refResource: rule.refResource, - controlBehavior: rule.controlBehavior, - warmUpPeriodSec: rule.warmUpPeriodSec, - maxQueueingTimeMs: rule.maxQueueingTimeMs, - app: rule.app, - ip: rule.ip, - port: rule.port + this.newRule = function (rule) { + var param = { + resource: rule.resource, + limitApp: rule.limitApp, + grade: rule.grade, + count: rule.count, + strategy: rule.strategy, + refResource: rule.refResource, + controlBehavior: rule.controlBehavior, + warmUpPeriodSec: rule.warmUpPeriodSec, + maxQueueingTimeMs: rule.maxQueueingTimeMs, + app: rule.app, + ip: rule.ip, + port: rule.port + }; + + return $http({ + url: '/v1/flow/rule', + data: rule, + method: 'POST' + }); }; - return $http({ - url: '/v1/flow/rule', - data: rule, - method: 'POST' - }); - }; + this.saveRule = function (rule) { + var param = { + id: rule.id, + resource: rule.resource, + limitApp: rule.limitApp, + grade: rule.grade, + count: rule.count, + strategy: rule.strategy, + refResource: rule.refResource, + controlBehavior: rule.controlBehavior, + warmUpPeriodSec: rule.warmUpPeriodSec, + maxQueueingTimeMs: rule.maxQueueingTimeMs, + }; - this.saveRule = function (rule) { - var param = { - id: rule.id, - resource: rule.resource, - limitApp: rule.limitApp, - grade: rule.grade, - count: rule.count, - strategy: rule.strategy, - refResource: rule.refResource, - controlBehavior: rule.controlBehavior, - warmUpPeriodSec: rule.warmUpPeriodSec, - maxQueueingTimeMs: rule.maxQueueingTimeMs, + return $http({ + url: '/v1/flow/save.json', + params: param, + method: 'PUT' + }); }; - return $http({ - url: '/v1/flow/save.json', - params: param, - method: 'PUT' - }); - }; + this.deleteRule = function (rule) { + var param = { + id: rule.id, + app: rule.app + }; - this.deleteRule = function (rule) { - var param = { - id: rule.id, - app: rule.app + return $http({ + url: '/v1/flow/delete.json', + params: param, + method: 'DELETE' + }); }; - return $http({ - url: '/v1/flow/delete.json', - params: param, - method: 'DELETE' - }); - }; - function notNumberAtLeastZero(num) { return num === undefined || num === '' || isNaN(num) || num < 0; } @@ -79,7 +79,7 @@ app.service('FlowServiceV1', ['$http', function ($http) { return num === undefined || num === '' || isNaN(num) || num <= 0; } - this.checkRuleValid = function (rule) { + this.checkRuleValid = function (rule) { if (rule.resource === undefined || rule.resource === '') { alert('资源名称不能为空'); return false; @@ -110,6 +110,10 @@ app.service('FlowServiceV1', ['$http', function ($http) { alert('排队超时时间必须大于 0'); return false; } + if (rule.clusterMode && (rule.clusterConfig === undefined || rule.clusterConfig.thresholdType === undefined)) { + alert('集群限流配置不正确'); + return false; + } return true; }; }]); diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/flow_service_v2.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/flow_service_v2.js index 37ae69a4..716d66d2 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/flow_service_v2.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/flow_service_v2.js @@ -1,41 +1,41 @@ var app = angular.module('sentinelDashboardApp'); app.service('FlowServiceV2', ['$http', function ($http) { - this.queryMachineRules = function (app, ip, port) { - var param = { - app: app, - ip: ip, - port: port + this.queryMachineRules = function (app, ip, port) { + var param = { + app: app, + ip: ip, + port: port + }; + return $http({ + url: '/v2/flow/rules', + params: param, + method: 'GET' + }); }; - return $http({ - url: '/v2/flow/rules', - params: param, - method: 'GET' - }); - }; - this.newRule = function (rule) { - return $http({ - url: '/v2/flow/rule', - data: rule, - method: 'POST' - }); - }; + this.newRule = function (rule) { + return $http({ + url: '/v2/flow/rule', + data: rule, + method: 'POST' + }); + }; - this.saveRule = function (rule) { - return $http({ - url: '/v2/flow/rule/' + rule.id, - data: rule, - method: 'PUT' - }); - }; + this.saveRule = function (rule) { + return $http({ + url: '/v2/flow/rule/' + rule.id, + data: rule, + method: 'PUT' + }); + }; - this.deleteRule = function (rule) { - return $http({ - url: '/v2/flow/rule/' + rule.id, - method: 'DELETE' - }); - }; + this.deleteRule = function (rule) { + return $http({ + url: '/v2/flow/rule/' + rule.id, + method: 'DELETE' + }); + }; function notNumberAtLeastZero(num) { return num === undefined || num === '' || isNaN(num) || num < 0; @@ -45,7 +45,7 @@ app.service('FlowServiceV2', ['$http', function ($http) { return num === undefined || num === '' || isNaN(num) || num <= 0; } - this.checkRuleValid = function (rule) { + this.checkRuleValid = function (rule) { if (rule.resource === undefined || rule.resource === '') { alert('资源名称不能为空'); return false; @@ -76,10 +76,10 @@ app.service('FlowServiceV2', ['$http', function ($http) { alert('排队超时时间必须大于 0'); return false; } - if (rule.clusterMode && (rule.clusterConfig === undefined || rule.clusterConfig.thresholdType === undefined)) { - alert('集群限流配置不正确'); - return false; - } + if (rule.clusterMode && (rule.clusterConfig === undefined || rule.clusterConfig.thresholdType === undefined)) { + alert('集群限流配置不正确'); + return false; + } return true; }; }]); diff --git a/sentinel-dashboard/src/main/webapp/resources/app/styles/main.css b/sentinel-dashboard/src/main/webapp/resources/app/styles/main.css index dc2f89d4..f6073368 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/styles/main.css +++ b/sentinel-dashboard/src/main/webapp/resources/app/styles/main.css @@ -525,6 +525,10 @@ body { max-width: 200px; } +.width-200 { + max-width: 200px; +} + .witdh-300 { max-width: 300px; } diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/cluster/client.html b/sentinel-dashboard/src/main/webapp/resources/app/views/cluster/client.html index db05aec0..7fc751da 100644 --- a/sentinel-dashboard/src/main/webapp/resources/app/views/cluster/client.html +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/cluster/client.html @@ -1,5 +1,13 @@
    +
    + +
    +

    未连接

    +

    连接中

    +

    已连接

    +
    +
    diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/cluster/server.html b/sentinel-dashboard/src/main/webapp/resources/app/views/cluster/server.html index cebf857c..8c045876 100644 --- a/sentinel-dashboard/src/main/webapp/resources/app/views/cluster/server.html +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/cluster/server.html @@ -1,9 +1,16 @@
    +
    + +
    +

    独立模式 (Alone)

    +

    嵌入模式 (Embedded)

    +
    +
    - +
    @@ -12,5 +19,11 @@
    +
    + +
    + +
    +
    \ No newline at end of file diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_assign_manage.html b/sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_assign_manage.html new file mode 100644 index 00000000..550ff230 --- /dev/null +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_assign_manage.html @@ -0,0 +1,118 @@ +
    +
    + {{app}} +
    +
    + +
    +
    +
    +
    +
    +
    + 集群限流 - 机器分配/管控 +
    + + +
    +
    +
    +
    +
    +

    {{loadError.message}}

    +
    +
    +
    +
    +
    + + +
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    + + +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    + +
    + +
    + +
    + +
    + diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_client_list.html b/sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_client_list.html new file mode 100644 index 00000000..b779e30e --- /dev/null +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_client_list.html @@ -0,0 +1,73 @@ +
    + +
    +
    +
    +
    +
    +
    + 集群限流 - Token Client 列表 +
    + + +
    +
    +
    +
    +
    +

    {{loadError.message}}

    +
    +
    +
    +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + +
    Client IDServer IPServer 端口连接状态操作
    {{clientVO.id}}{{clientVO.state.clientConfig.serverHost}}{{clientVO.state.clientConfig.serverPort}} + 未连接 + 连接中 + 已连接 + + +
    +
    + +
    + +
    + +
    + +
    + diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_server_list.html b/sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_server_list.html new file mode 100644 index 00000000..e69153b7 --- /dev/null +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_server_list.html @@ -0,0 +1,87 @@ +
    +
    + {{app}} +
    +
    + + + Token Client 列表 + +
    +
    + +
    +
    +
    +
    +
    +
    + 集群限流 - Token Server 列表 + +
    + + +
    +
    +
    +
    +
    +

    {{loadError.message}}

    +
    +
    +
    +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    Server IDPort命名空间集合总连接数QPS 总览操作
    {{serverVO.id}}{{serverVO.port}} + {{serverVO.state.namespaceSetStr}} + + {{serverVO.connectedCount}} + + {{serverVO.state.requestLimitDataStr}} + + + + + + + +
    +
    + +
    + +
    + +
    + +
    + diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_server_overview.html b/sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_server_overview.html new file mode 100644 index 00000000..4e411a2b --- /dev/null +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_server_overview.html @@ -0,0 +1,88 @@ +
    +
    + {{app}} +
    +
    + +
    +
    +
    +
    +
    +
    + 集群限流 - Token Server 总览 +
    + + +
    +
    +
    +
    +
    +

    {{loadError.message}}

    +
    +
    +
    +
    +
    + + +
    +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + +
    Server IDPort命名空间集合总连接数连接情况QPS 总览
    {{serverVO.id}}{{serverVO.port}} + {{serverVO.state.namespaceSetStr}} + + {{serverVO.connectedCount}} + +

    + namespace: {{cg.namespace}}, 连接数: {{cg.connectedCount}}, clients: + {{generateConnectionSet(cg.connectionSet)}} +

    +
    +

    + namespace: {{crl.namespace}}, 当前 QPS: {{crl.currentQps}}, 最大允许 QPS: + {{crl.maxAllowedQps}} +

    +
    +
    + +
    + +
    + +
    + +
    + diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/cluster.html b/sentinel-dashboard/src/main/webapp/resources/app/views/cluster_single_config.html similarity index 89% rename from sentinel-dashboard/src/main/webapp/resources/app/views/cluster.html rename to sentinel-dashboard/src/main/webapp/resources/app/views/cluster_single_config.html index a02c7a61..a82f1ab3 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/views/cluster.html +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/cluster_single_config.html @@ -12,8 +12,7 @@
    集群限流 - - +
    @@ -34,8 +33,7 @@
    -
    - +
    @@ -80,7 +78,8 @@
    - +
    diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-client-config-dialog.html b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-client-config-dialog.html new file mode 100755 index 00000000..128ab785 --- /dev/null +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-client-config-dialog.html @@ -0,0 +1,40 @@ +
    + 修改 Token Client 配置 +
    +
    +
    + +
    + +
    +

    {{ccDialogData.clientId}}

    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-server-assign-dialog.html b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-server-assign-dialog.html new file mode 100644 index 00000000..350c2e41 --- /dev/null +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-server-assign-dialog.html @@ -0,0 +1,139 @@ +
    + {{serverAssignDialogData.title}} +
    +
    +
    +
    +
    +
    + +
    +

    {{serverAssignDialogData.serverData.currentServer}}

    +
    + + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + +
    +
    +  应用内机器   +  外部指定机器 +
    +
    + +
    +
    +

    若指定外部 server,请先在相应页面对外部 server 进行配置,然后在此页面指定。

    +
    +
    +
    + +
    +
    + +
    + +
    + + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + +
    + +
    + + +
    + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + + +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-server-connection-detail-dialog.html b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-server-connection-detail-dialog.html new file mode 100644 index 00000000..afbf29ad --- /dev/null +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-server-connection-detail-dialog.html @@ -0,0 +1,37 @@ +
    + 连接详情 +
    +
    +
    +
    +
    + +
    +

    {{connectionDetailDialogData.serverData.id}}

    +
    +
    +
    + +
    + + + + + + + + + + + + + + + + +
    命名空间连接数连接详情
    {{cg.namespace}}{{cg.connectedCount}}{{generateConnectionSet(cg.connectionSet)}}
    +
    +
    +
    +
    +
    diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/flow-rule-dialog.html b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/flow-rule-dialog.html index 52ea78c8..bd07ac36 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/flow-rule-dialog.html +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/flow-rule-dialog.html @@ -15,7 +15,7 @@
    - +
    @@ -60,7 +60,7 @@
     单机均摊   -  Global +  总体阈值
    diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/param-flow-rule-dialog.html b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/param-flow-rule-dialog.html index 31f90669..eae5d08a 100644 --- a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/param-flow-rule-dialog.html +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/param-flow-rule-dialog.html @@ -25,12 +25,42 @@
    - -
    - +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    + +
    + +
    +
    + +
    +
    +  单机均摊   +  总体阈值 +
    +
    +
    +
    +
    diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/system-rule-dialog.html b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/system-rule-dialog.html index d4fc8394..728909d5 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/system-rule-dialog.html +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/system-rule-dialog.html @@ -17,7 +17,7 @@  线程数    QPS + /> 入口 QPS
    @@ -28,7 +28,7 @@  线程数    QPS + /> 入口 QPS
    diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/flow_old.html b/sentinel-dashboard/src/main/webapp/resources/app/views/flow_v1.html similarity index 87% rename from sentinel-dashboard/src/main/webapp/resources/app/views/flow_old.html rename to sentinel-dashboard/src/main/webapp/resources/app/views/flow_v1.html index 480e1c8b..22b79c6f 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/views/flow_old.html +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/flow_v1.html @@ -5,8 +5,8 @@
    - - 回到新版 + +
    @@ -35,24 +35,24 @@ 资源名 - - 流控应用 + + 来源应用 - + 流控模式 - + 阈值类型 - - 单机阈值 + + 阈值 + + + 阈值模式 流控效果 - - - 操作 @@ -74,10 +74,14 @@ {{rule.count}} + + {{generateThresholdTypeShow(rule)}} + 快速失败 Warm Up 排队等待 + 预热排队 diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/flow.html b/sentinel-dashboard/src/main/webapp/resources/app/views/flow_v2.html similarity index 94% rename from sentinel-dashboard/src/main/webapp/resources/app/views/flow.html rename to sentinel-dashboard/src/main/webapp/resources/app/views/flow_v2.html index bdec617b..7e0dcc81 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/views/flow.html +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/flow_v2.html @@ -7,7 +7,8 @@   新增流控规则 - 回到旧版(单机) + 回到单机页面 +
    @@ -31,7 +32,7 @@ 资源名 - 流控应用 + 来源应用 流控模式 @@ -40,10 +41,10 @@ 阈值类型 - 单机阈值 + 阈值 - 是否集群 + 阈值模式 流控效果 @@ -70,8 +71,7 @@ {{rule.count}} - - + {{generateThresholdTypeShow(rule)}} 快速失败 diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/param_flow.html b/sentinel-dashboard/src/main/webapp/resources/app/views/param_flow.html index f41a44b0..c94219b6 100644 --- a/sentinel-dashboard/src/main/webapp/resources/app/views/param_flow.html +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/param_flow.html @@ -53,7 +53,10 @@ 流控模式 - 单机阈值 + 阈值 + + + 是否集群 例外项数目 @@ -74,6 +77,10 @@ {{ruleEntity.rule.count}} + + + + {{ruleEntity.rule.paramFlowItemList == undefined ? 0 : ruleEntity.rule.paramFlowItemList.length}} diff --git a/sentinel-dashboard/src/main/webapp/resources/dist/css/app.css b/sentinel-dashboard/src/main/webapp/resources/dist/css/app.css index c160ddf0..ba69a703 100755 --- a/sentinel-dashboard/src/main/webapp/resources/dist/css/app.css +++ b/sentinel-dashboard/src/main/webapp/resources/dist/css/app.css @@ -2,4 +2,4 @@ * Start Bootstrap - SB Admin 2 Bootstrap Admin Theme (http://startbootstrap.com) * Code licensed under the Apache License v2.0. * For details, see http://www.apache.org/licenses/LICENSE-2.0. - */body{background-color:#f8f8f8}.example{padding:.625rem 1.825rem .625rem 2.5rem;border:1px dashed #ccc;position:relative;margin:0 0 .625rem;background-color:#fff}dl dd,dl dt{line-height:1.25rem}dl dt{font-style:normal;font-weight:700}dl dd{margin-left:.9375rem}dl.horizontal dt{float:left;width:10rem;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}dl.horizontal dd{margin-left:11.25rem}#wrapper{width:100%}#page-wrapper{min-height:568px;background-color:#fff}@media(min-width:768px){#page-wrapper{position:inherit;margin:0 0 0 250px;padding:0 30px;border-left:1px solid #e7e7e7}}.navbar-top-links{margin-right:0}.navbar-top-links li{display:inline-block}.flot-chart,.navbar-top-links .dropdown-menu li{display:block}.navbar-top-links li:last-child{margin-right:15px}.navbar-top-links li a{padding:15px;min-height:50px}.navbar-top-links .dropdown-menu li:last-child{margin-right:0}.navbar-top-links .dropdown-menu li a{padding:3px 20px;min-height:0}.navbar-top-links .dropdown-menu li a div{white-space:normal}.navbar-top-links .dropdown-alerts,.navbar-top-links .dropdown-messages,.navbar-top-links .dropdown-tasks{width:310px;min-width:0}.navbar-top-links .dropdown-messages{margin-left:5px}.navbar-top-links .dropdown-tasks{margin-left:-59px}.navbar-top-links .dropdown-alerts{margin-left:-123px}.navbar-top-links .dropdown-user{right:0;left:auto}.sidebar .sidebar-search{padding:15px}.sidebar ul li{border-bottom:1px solid #e7e7e7}.sidebar ul li a.active{background-color:#fff;color:#fff}.sidebar .arrow{float:right}.sidebar .fa.arrow:before{content:"\f104"}.sidebar .active>a>.fa.arrow:before{content:"\f107"}.sidebar .nav-second-level li,.sidebar .nav-third-level li{border-bottom:0!important}.sidebar .nav-second-level li a{padding-left:37px}.sidebar .nav-third-level li a{padding-left:52px}@media(min-width:768px){.sidebar{z-index:1;position:absolute;width:250px;margin-top:51px}.navbar-top-links .dropdown-alerts,.navbar-top-links .dropdown-messages,.navbar-top-links .dropdown-tasks{margin-left:auto}}.btn-outline{color:inherit;background-color:transparent;transition:all .5s}.btn-primary.btn-outline{color:#428bca}.btn-success.btn-outline{color:#5cb85c}.btn-info.btn-outline{color:#5bc0de}.btn-warning.btn-outline{color:#f0ad4e}.btn-danger.btn-outline{color:#d9534f}.btn-danger.btn-outline:hover,.btn-info.btn-outline:hover,.btn-primary.btn-outline:hover,.btn-success.btn-outline:hover,.btn-warning.btn-outline:hover{color:#fff}.chat{margin:0;padding:0}.chat li{margin-bottom:10px;padding-bottom:5px;border-bottom:1px dotted #999}.chat li.left .chat-body{margin-left:60px}.chat li.right .chat-body{margin-right:60px}.chat li .chat-body p{margin:0}.chat .glyphicon,.panel .slidedown .glyphicon{margin-right:5px}.chat-panel .panel-body{height:350px;overflow-y:scroll}.login-panel{margin-top:25%}.flot-chart{height:400px}.flot-chart-content{width:100%;height:100%}.dataTables_wrapper{position:relative;clear:both}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_desc_disabled{background:0 0}table.dataTable thead .sorting_asc:after{content:"\f0de";float:right;font-family:fontawesome}table.dataTable thead .sorting_desc:after{content:"\f0dd";float:right;font-family:fontawesome}table.dataTable thead .sorting:after{content:"\f0dc";float:right;font-family:fontawesome;color:rgba(50,50,50,.5)}.btn-circle{width:30px;height:30px;padding:6px 0;border-radius:15px;text-align:center;font-size:12px;line-height:1.428571429}.btn-circle.btn-lg{width:50px;height:50px;padding:10px 16px;border-radius:25px;font-size:18px;line-height:1.33}.btn-circle.btn-xl{width:70px;height:70px;padding:10px 16px;border-radius:35px;font-size:24px;line-height:1.33}.show-grid [class^=col-]{padding-top:10px;padding-bottom:10px;border:1px solid #ddd;background-color:#eee!important}.show-grid{margin:15px 0}.huge{font-size:40px}.panel-green{border-color:#5cb85c}.panel-green .panel-heading{border-color:#5cb85c;color:#fff;background-color:#5cb85c}.panel-green a{color:#5cb85c}.panel-green a:hover{color:#3d8b3d}.panel-red{border-color:#d9534f}.panel-red .panel-heading{border-color:#d9534f;color:#fff;background-color:#d9534f}.panel-red a{color:#d9534f}.panel-red a:hover{color:#b52b27}.panel-yellow{border-color:#f0ad4e}.panel-yellow .panel-heading{border-color:#f0ad4e;color:#fff;background-color:#f0ad4e}.panel-yellow a{color:#f0ad4e}.panel-yellow a:hover{color:#df8a13}.timeline{position:relative;padding:20px 0}.timeline:before{content:" ";position:absolute;top:0;bottom:0;left:50%;width:3px;margin-left:-1.5px;background-color:#eee}.timeline>li{position:relative;margin-bottom:20px}.timeline>li:after,.timeline>li:before{content:" ";display:table}.timeline>li:after{clear:both}.timeline>li>.timeline-panel{float:left;position:relative;width:46%;padding:20px;border:1px solid #d4d4d4;border-radius:2px;-webkit-box-shadow:0 1px 6px rgba(0,0,0,.175);box-shadow:0 1px 6px rgba(0,0,0,.175)}.timeline>li>.timeline-panel:before{content:" ";display:inline-block;position:absolute;top:26px;right:-15px;border-top:15px solid transparent;border-right:0 solid #ccc;border-bottom:15px solid transparent;border-left:15px solid #ccc}.timeline>li>.timeline-panel:after{content:" ";display:inline-block;position:absolute;top:27px;right:-14px;border-top:14px solid transparent;border-right:0 solid #fff;border-bottom:14px solid transparent;border-left:14px solid #fff}.timeline>li>.timeline-badge{z-index:100;position:absolute;top:16px;left:50%;width:50px;height:50px;margin-left:-25px;border-radius:50%;text-align:center;font-size:1.4em;line-height:50px;color:#fff;background-color:#999}.timeline>li.timeline-inverted>.timeline-panel{float:right}.timeline>li.timeline-inverted>.timeline-panel:before{right:auto;left:-15px;border-right-width:15px;border-left-width:0}.timeline>li.timeline-inverted>.timeline-panel:after{right:auto;left:-14px;border-right-width:14px;border-left-width:0}.timeline-badge.primary{background-color:#2e6da4!important}.timeline-badge.success{background-color:#3f903f!important}.timeline-badge.warning{background-color:#f0ad4e!important}.timeline-badge.danger{background-color:#d9534f!important}.timeline-badge.info{background-color:#5bc0de!important}.timeline-title{margin-top:0;color:inherit}.timeline-body>p,.timeline-body>ul{margin-bottom:0}.timeline-body>p+p{margin-top:5px}@media(max-width:767px){ul.timeline:before{left:40px}ul.timeline>li>.timeline-panel{width:calc(100% - 90px);width:-moz-calc(100% - 90px);width:-webkit-calc(100% - 90px);float:right}ul.timeline>li>.timeline-badge{top:16px;left:15px;margin-left:0}ul.timeline>li>.timeline-panel:before{right:auto;left:-15px;border-right-width:15px;border-left-width:0}ul.timeline>li>.timeline-panel:after{right:auto;left:-14px;border-right-width:14px;border-left-width:0}}.header,.jumbotron{border-bottom:1px solid #e5e5e5}.btn{height:32px}.witdh-300{max-width:300px}body{padding:0}.footer,.header,.marketing{padding-left:15px;padding-right:15px}.header{margin-bottom:10px}.header h3{margin-top:0;margin-bottom:0;line-height:40px;padding-bottom:19px}.card .detail,.card .detail-brand{line-height:98px;text-align:center}.footer{padding-top:19px;color:#777;border-top:1px solid #e5e5e5}.container-narrow>hr{margin:30px 0}.jumbotron{text-align:center}.jumbotron .btn{font-size:21px;padding:14px 24px}.marketing{margin:40px 0}.marketing p+h4{margin-top:28px}@media screen and (min-width:768px){.container{width:inherit;margin-left:60px;margin-right:5px}.footer,.header,.marketing{padding-left:0;padding-right:0}.header{margin-bottom:30px}.jumbotron{border-bottom:0}}.navbar-inverse .navbar-nav>li>a{color:#b0ddce;font-size:15px}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{background-color:#1b926c}@media (min-width:900px){.navbar-right,.navbar-right~.navbar-right{margin-right:0}.navbar-left{float:left!important}.navbar-right{float:right!important}}.dropdown-menu{min-width:100px!important}.nav-sidebar li.active a{background:#DDD}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background:#1d9d74;color:#fff}.broadcast-message,.broadcast-message-preview{padding:10px;text-align:center;background:#555;color:#BBB;margin-top:50px}.card{position:relative;border:1px solid #d9d9d9;color:#666;background-color:#fff;width:100%;border-radius:5px}.card .card-header,.tools-header{border-top-left-radius:4px;border-top-right-radius:4px}.card .card-header{padding:9px 0;height:40px;background:#555;color:#fff;text-align:center}.card .card-body{padding:12px 10px}.card .card-footer{height:20px;font-size:10px;color:#777;margin:-15px 20px 5px}.card .detail-brand{float:left;width:30%;font-size:30px;color:#fff}.card .default{background:#1d9d74}.card .info{background:#6EBEE7}.card .warn{background:#ED7F54}.card .danger{background:#6583BE}.card .detail .text-default{color:#1d9d74}.card .detail .text-info{color:#6EBEE7}.card .detail .text-warn{color:#ED7F54}.card .detail .text-danger{color:#6583BE}.card .detail{float:right;width:70%}.card .detail .text{font-size:12px}.card .detail .number{font-size:30px;font-weight:500}.h100{height:100px}.inline{display:inline}.separator{height:1px;background-color:#e5e5e5;margin-top:10px}.card>.card-body>table>tbody>tr>td,.card>.card-body>table>thead>tr>td{word-wrap:break-word;word-break:break-all}.card>.card-body>table>thead>tr>td{font-weight:500;font-size:13px;text-align:center}.card>.card-body>table>thead>tr>td>span{font-weight:500;font-size:10px}.card>.card-body>table>tbody>tr>td{font-size:12px;text-align:center}.card>.card-body>table>tbody>tr>td>a{color:#666}.thumbnails>.card>.card-body>table>tbody>tr>td,.thumbnails>.card>.card-body>table>thead>tr>td{font-size:12px;color:#777;word-wrap:break-word;word-break:break-all}.thumbnails>.card>.card-body>table>thead>tr>td:nth-child(n+2){text-align:center}.thumbnails>.card>.card-body>table>tbody>tr>td:nth-child(n+2){font-weight:700;text-align:center}.thumbnails>.card>.card-body>table>tbody>tr>td:nth-child(1),.thumbnails>.card>.card-body>table>thead>tr>td:nth-child(1){text-align:left}.tools-header{background:#f5f5f5;padding:9px 0;height:40px}.tools-header .brand{font-size:13px;margin:2px 10px;font-weight:700;float:left}.tools-header .brand>a{color:#666}.tools-header>a,.tools-header>button,.tools-header>select{float:right;max-width:80px;margin:1px 10px;height:25px;padding:0 10px;line-height:25px;color:#666}.tools-header .paged{margin-right:0}.btn.btn-danger-tag{color:#fff;background-color:#d9534f;border-color:#d43f3a;line-height:1px;font-size:11px;padding:4px}.btn.btn-danger{color:#333;background-color:#fff;border-color:#ccc}.btn.btn-danger:active,.btn.btn-danger:focus,.btn.btn-danger:hover{color:#d9534f;border-color:#d9534f;background:#fff}.form-control{height:32px}.input-label:before{display:inline-block;content:"*";color:#f44336;font-family:SimSun;font-size:12px;-webkit-transform:TranslateX(-10px);-ms-transform:TranslateX(-10px);transform:TranslateX(-10px)}.badge-main,.label.label-main{color:#fff;background-color:#1d9d74;border-color:#1d9d74}.bootstrap-tagsinput{background-color:#fff;border:1px solid #ccc;box-shadow:inset 0 1px 1px rgba(0,0,0,.075);display:inline-block;padding:4px 6px;color:#555;vertical-align:middle;border-radius:4px;width:85%;height:100px;line-height:20px;cursor:text}.bootstrap-tagsinput>.dropdown-menu{min-width:40px;font-size:12px}.bootstrap-tagsinput>.dropdown-menu>.active>a,.bootstrap-tagsinput>.dropdown-menu>.active>a:focus,.bootstrap-tagsinput>.dropdown-menu>.active>a:hover{background-image:-webkit-linear-gradient(top,#1d9d74 0,#1d9d74 100%);background-image:-o-linear-gradient(top,#1d9d74 0,#1d9d74 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#1d9d74),to(#1d9d74));background-image:linear-gradient(to bottom,#1d9d74 0,#1d9d74 100%);filter:progid: DXImageTransform.Microsoft.gradient(startColorstr='#1d9d74', endColorstr='#1d9d74', GradientType=0);background-repeat:repeat-x;color:#fff;text-decoration:none;outline:0;background-color:#1d9d74}.inputs-header{padding:9px 0;height:50px;border-top-left-radius:4px;border-top-right-radius:4px}.inputs-header .brand{font-size:13px;margin:2px 10px;font-weight:700;float:left}.inputs-header .brand>a{color:#666}.inputs-header>input{float:right;margin:1px 10px;height:30px;padding:0 10px;color:#666}.inputs-header>a{float:right;margin:1px 10px;height:30px;padding:5 5px}.inputs-header>select{float:right;max-width:80px;margin:1px 10px;padding:0 10px;color:#666;height:25px;font-size:12px}.witdh-150{max-width:150px}.witdh-200{max-width:200px}.card.highlight{border-color:#d9534f}.card .pagination-footer{height:40px;font-size:10px;color:#777;margin:-15px 20px 5px}.card .pagination-footer .tools{font-size:12px;margin:11px 20px 11px 0;float:right;display:inline}.card>.pagination-footer>.tools>span>input{height:25px;max-width:50px;display:inline}.pagination{display:inline-block;padding-left:0;margin:8px 0;float:right;border-radius:4px}.pagination>a{margin-right:5px;height:28px;width:28px;padding:5px 0}.datepicker>.table>tbody>tr>td,.datepicker>.table>thead>tr>td,.timepicker>.table>tbody>tr>td,.timepicker>.table>thead>tr>td{padding:5px 3px}.datepicker>.table>tbody>tr>td>.btn,.datepicker>.table>thead>tr>td>.btn,.timepicker>.table>tbody>tr>td>.btn,.timepicker>.table>thead>tr>td>.btn{border:1px solid #FFFDFD}.datepicker>.table>tbody>tr>td>.btn-default:active,.datepicker>.table>tbody>tr>td>.btn-default:focus,.datepicker>.table>tbody>tr>td>.btn-default:hover,.datepicker>.table>thead>tr>td>.btn-default:active,.datepicker>.table>thead>tr>td>.btn-default:focus,.datepicker>.table>thead>tr>td>.btn-default:hover,.timepicker>.table>tbody>tr>td>.btn-default:active,.timepicker>.table>tbody>tr>td>.btn-default:focus,.timepicker>.table>tbody>tr>td>.btn-default:hover,.timepicker>.table>thead>tr>td>.btn-default:active,.timepicker>.table>thead>tr>td>.btn-default:focus,.timepicker>.table>thead>tr>td>.btn-default:hover{color:#1d9d74;border-color:#1d9d74;background:#fff}.datepicker>.table>tbody>tr>td>a,.datepicker>.table>thead>tr>td>a,.timepicker>.table>tbody>tr>td>a,.timepicker>.table>thead>tr>td>a{height:25px;width:25px;padding:3px 0}.datepicker>.table>tbody>tr:first-child>td>a{padding:4px 0}.datepicker>.table>tbody>tr>td>a.btn.active,.datepicker>.table>thead>tr>td>a.btn.active,.timepicker>.table>tbody>tr>td>a.btn.active,.timepicker>.table>thead>tr>td>a.btn.active{color:#1d9d74;border-color:#1d9d74;background:#fff;box-shadow:inset 0 0 0 rgba(0,0,0,.125)}.datepicker>.table>thead>tr>td:not(:first-child):last-child>a,.timepicker>.table>thead>tr>td:not(:first-child):last-child>a{height:25px;width:50px;padding:5px 0}.datepicker>.table>tbody>tr>td>a,.timepicker>.table>tbody>tr>td>a{margin-left:8px}.sortorder:after{content:'\25b2'}.sortorder.reverse:after{content:'\25bc'}.input-control select{-moz-appearance:none;-webkit-appearance:none;appearance:none;position:relative;border:1px solid #d9d9d9;width:100%;height:100%;padding:.3125rem;z-index:0}.navbar-inverse{background-color:#337ab7;border-color:#337ab7}.sidebar{z-index:1;width:220px;top:0;left:0;height:100%}#page-wrapper{position:inherit;margin:70px 0 0 220px;padding:12px 30px;border-left:0 solid #e7e7e7}.sidebar .sidebar-nav.navbar-collapse{background-color:#F5F5F5;position:relative;color:#000;width:100%;padding:0;margin:0;list-style:none inside}.sidebar a{color:#555}.sidebar ul li:hover{color:red}.form-control{border-radius:8px}.form-control:focus,.highlight-border{border-color:#337ab7;box-shadow:0 0 0 rgba(0,0,0,.075) inset,0 0 0 rgba(29,157,116,1)}.btn-outline-primary.focus,.btn-outline-primary:focus,.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.browsehappy{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.btn.btn-main{color:#fff;background-color:#337ab7;border-color:#337ab7}.btn-default-inverse,.btn-default-inverse:focus,.btn-default-inverse:hover,.btn-default:active{color:#337ab7;border-color:#337ab7;background:#fff}.btn-danger-inverse,.btn-danger-inverse:focus,.btn-danger-inverse:hover,.btn-danger:active{color:#d9534f;border-color:#d9534f;background:#fff}.btn-tab-active,.btn-tab-active:focus,.btn-tab-active:hover,.btn-tab-default:active,.btn-tab-default:focus,.btn-tab-default:hover{color:#337ab7;border-color:#337ab7;background:#fff;font-weight:600}.btn-tab-default{color:#777;background:#fff;font-weight:600}.pagination>.btn.active{color:#fff;background-color:#337ab7;border-color:#337ab7}.btn-default:active,.btn-default:focus,.btn-default:hover{color:#337ab7;border-color:#337ab7;background:#fff}.bootstrap-switch.bootstrap-switch-on{border-color:#337ab7}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success{color:#fff;background:#337ab7}.selectize-input-200>.selectize-input{min-width:200px;border-color:#337ab7}.btn-outline-primary{color:#007bff;background-color:transparent;background-image:none;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-secondary.focus,.btn-outline-secondary:focus,.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary{color:#6c757d;background-color:transparent;background-image:none;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-success.focus,.btn-outline-success:focus,.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success{color:#28a745;background-color:transparent;background-image:none;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-info.focus,.btn-outline-info:focus,.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info{color:#17a2b8;background-color:transparent;background-image:none;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-warning.focus,.btn-outline-warning:focus,.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning{color:#ffc107;background-color:transparent;background-image:none;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-danger.focus,.btn-outline-danger:focus,.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger{color:#dc3545;background-color:transparent;background-image:none;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-light.focus,.btn-outline-light:focus,.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light{color:#f8f9fa;background-color:transparent;background-image:none;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-dark.focus,.btn-outline-dark:focus,.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark{color:#343a40;background-color:transparent;background-image:none;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40} \ No newline at end of file + */body{background-color:#f8f8f8}.example{padding:.625rem 1.825rem .625rem 2.5rem;border:1px dashed #ccc;position:relative;margin:0 0 .625rem;background-color:#fff}dl dd,dl dt{line-height:1.25rem}dl dt{font-style:normal;font-weight:700}dl dd{margin-left:.9375rem}dl.horizontal dt{float:left;width:10rem;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}dl.horizontal dd{margin-left:11.25rem}#wrapper{width:100%}#page-wrapper{min-height:568px;background-color:#fff}@media(min-width:768px){#page-wrapper{position:inherit;margin:0 0 0 250px;padding:0 30px;border-left:1px solid #e7e7e7}}.navbar-top-links{margin-right:0}.navbar-top-links li{display:inline-block}.flot-chart,.navbar-top-links .dropdown-menu li{display:block}.navbar-top-links li:last-child{margin-right:15px}.navbar-top-links li a{padding:15px;min-height:50px}.navbar-top-links .dropdown-menu li:last-child{margin-right:0}.navbar-top-links .dropdown-menu li a{padding:3px 20px;min-height:0}.navbar-top-links .dropdown-menu li a div{white-space:normal}.navbar-top-links .dropdown-alerts,.navbar-top-links .dropdown-messages,.navbar-top-links .dropdown-tasks{width:310px;min-width:0}.navbar-top-links .dropdown-messages{margin-left:5px}.navbar-top-links .dropdown-tasks{margin-left:-59px}.navbar-top-links .dropdown-alerts{margin-left:-123px}.navbar-top-links .dropdown-user{right:0;left:auto}.sidebar .sidebar-search{padding:15px}.sidebar ul li{border-bottom:1px solid #e7e7e7}.sidebar ul li a.active{background-color:#fff;color:#fff}.sidebar .arrow{float:right}.sidebar .fa.arrow:before{content:"\f104"}.sidebar .active>a>.fa.arrow:before{content:"\f107"}.sidebar .nav-second-level li,.sidebar .nav-third-level li{border-bottom:0!important}.sidebar .nav-second-level li a{padding-left:37px}.sidebar .nav-third-level li a{padding-left:52px}@media(min-width:768px){.sidebar{z-index:1;position:absolute;width:250px;margin-top:51px}.navbar-top-links .dropdown-alerts,.navbar-top-links .dropdown-messages,.navbar-top-links .dropdown-tasks{margin-left:auto}}.btn-outline{color:inherit;background-color:transparent;transition:all .5s}.btn-primary.btn-outline{color:#428bca}.btn-success.btn-outline{color:#5cb85c}.btn-info.btn-outline{color:#5bc0de}.btn-warning.btn-outline{color:#f0ad4e}.btn-danger.btn-outline{color:#d9534f}.btn-danger.btn-outline:hover,.btn-info.btn-outline:hover,.btn-primary.btn-outline:hover,.btn-success.btn-outline:hover,.btn-warning.btn-outline:hover{color:#fff}.chat{margin:0;padding:0}.chat li{margin-bottom:10px;padding-bottom:5px;border-bottom:1px dotted #999}.chat li.left .chat-body{margin-left:60px}.chat li.right .chat-body{margin-right:60px}.chat li .chat-body p{margin:0}.chat .glyphicon,.panel .slidedown .glyphicon{margin-right:5px}.chat-panel .panel-body{height:350px;overflow-y:scroll}.login-panel{margin-top:25%}.flot-chart{height:400px}.flot-chart-content{width:100%;height:100%}.dataTables_wrapper{position:relative;clear:both}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_desc_disabled{background:0 0}table.dataTable thead .sorting_asc:after{content:"\f0de";float:right;font-family:fontawesome}table.dataTable thead .sorting_desc:after{content:"\f0dd";float:right;font-family:fontawesome}table.dataTable thead .sorting:after{content:"\f0dc";float:right;font-family:fontawesome;color:rgba(50,50,50,.5)}.btn-circle{width:30px;height:30px;padding:6px 0;border-radius:15px;text-align:center;font-size:12px;line-height:1.428571429}.btn-circle.btn-lg{width:50px;height:50px;padding:10px 16px;border-radius:25px;font-size:18px;line-height:1.33}.btn-circle.btn-xl{width:70px;height:70px;padding:10px 16px;border-radius:35px;font-size:24px;line-height:1.33}.show-grid [class^=col-]{padding-top:10px;padding-bottom:10px;border:1px solid #ddd;background-color:#eee!important}.show-grid{margin:15px 0}.huge{font-size:40px}.panel-green{border-color:#5cb85c}.panel-green .panel-heading{border-color:#5cb85c;color:#fff;background-color:#5cb85c}.panel-green a{color:#5cb85c}.panel-green a:hover{color:#3d8b3d}.panel-red{border-color:#d9534f}.panel-red .panel-heading{border-color:#d9534f;color:#fff;background-color:#d9534f}.panel-red a{color:#d9534f}.panel-red a:hover{color:#b52b27}.panel-yellow{border-color:#f0ad4e}.panel-yellow .panel-heading{border-color:#f0ad4e;color:#fff;background-color:#f0ad4e}.panel-yellow a{color:#f0ad4e}.panel-yellow a:hover{color:#df8a13}.timeline{position:relative;padding:20px 0}.timeline:before{content:" ";position:absolute;top:0;bottom:0;left:50%;width:3px;margin-left:-1.5px;background-color:#eee}.timeline>li{position:relative;margin-bottom:20px}.timeline>li:after,.timeline>li:before{content:" ";display:table}.timeline>li:after{clear:both}.timeline>li>.timeline-panel{float:left;position:relative;width:46%;padding:20px;border:1px solid #d4d4d4;border-radius:2px;-webkit-box-shadow:0 1px 6px rgba(0,0,0,.175);box-shadow:0 1px 6px rgba(0,0,0,.175)}.timeline>li>.timeline-panel:before{content:" ";display:inline-block;position:absolute;top:26px;right:-15px;border-top:15px solid transparent;border-right:0 solid #ccc;border-bottom:15px solid transparent;border-left:15px solid #ccc}.timeline>li>.timeline-panel:after{content:" ";display:inline-block;position:absolute;top:27px;right:-14px;border-top:14px solid transparent;border-right:0 solid #fff;border-bottom:14px solid transparent;border-left:14px solid #fff}.timeline>li>.timeline-badge{z-index:100;position:absolute;top:16px;left:50%;width:50px;height:50px;margin-left:-25px;border-radius:50%;text-align:center;font-size:1.4em;line-height:50px;color:#fff;background-color:#999}.timeline>li.timeline-inverted>.timeline-panel{float:right}.timeline>li.timeline-inverted>.timeline-panel:before{right:auto;left:-15px;border-right-width:15px;border-left-width:0}.timeline>li.timeline-inverted>.timeline-panel:after{right:auto;left:-14px;border-right-width:14px;border-left-width:0}.timeline-badge.primary{background-color:#2e6da4!important}.timeline-badge.success{background-color:#3f903f!important}.timeline-badge.warning{background-color:#f0ad4e!important}.timeline-badge.danger{background-color:#d9534f!important}.timeline-badge.info{background-color:#5bc0de!important}.timeline-title{margin-top:0;color:inherit}.timeline-body>p,.timeline-body>ul{margin-bottom:0}.timeline-body>p+p{margin-top:5px}@media(max-width:767px){ul.timeline:before{left:40px}ul.timeline>li>.timeline-panel{width:calc(100% - 90px);width:-moz-calc(100% - 90px);width:-webkit-calc(100% - 90px);float:right}ul.timeline>li>.timeline-badge{top:16px;left:15px;margin-left:0}ul.timeline>li>.timeline-panel:before{right:auto;left:-15px;border-right-width:15px;border-left-width:0}ul.timeline>li>.timeline-panel:after{right:auto;left:-14px;border-right-width:14px;border-left-width:0}}.header,.jumbotron{border-bottom:1px solid #e5e5e5}.btn{height:32px}.width-200{max-width:200px}.witdh-300{max-width:300px}body{padding:0}.footer,.header,.marketing{padding-left:15px;padding-right:15px}.header{margin-bottom:10px}.header h3{margin-top:0;margin-bottom:0;line-height:40px;padding-bottom:19px}.card .detail,.card .detail-brand{line-height:98px;text-align:center}.footer{padding-top:19px;color:#777;border-top:1px solid #e5e5e5}.container-narrow>hr{margin:30px 0}.jumbotron{text-align:center}.jumbotron .btn{font-size:21px;padding:14px 24px}.marketing{margin:40px 0}.marketing p+h4{margin-top:28px}@media screen and (min-width:768px){.container{width:inherit;margin-left:60px;margin-right:5px}.footer,.header,.marketing{padding-left:0;padding-right:0}.header{margin-bottom:30px}.jumbotron{border-bottom:0}}.navbar-inverse .navbar-nav>li>a{color:#b0ddce;font-size:15px}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{background-color:#1b926c}@media (min-width:900px){.navbar-right,.navbar-right~.navbar-right{margin-right:0}.navbar-left{float:left!important}.navbar-right{float:right!important}}.dropdown-menu{min-width:100px!important}.nav-sidebar li.active a{background:#DDD}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background:#1d9d74;color:#fff}.broadcast-message,.broadcast-message-preview{padding:10px;text-align:center;background:#555;color:#BBB;margin-top:50px}.card{position:relative;border:1px solid #d9d9d9;color:#666;background-color:#fff;width:100%;border-radius:5px}.card .card-header,.tools-header{border-top-left-radius:4px;border-top-right-radius:4px}.card .card-header{padding:9px 0;height:40px;background:#555;color:#fff;text-align:center}.card .card-body{padding:12px 10px}.card .card-footer{height:20px;font-size:10px;color:#777;margin:-15px 20px 5px}.card .detail-brand{float:left;width:30%;font-size:30px;color:#fff}.card .default{background:#1d9d74}.card .info{background:#6EBEE7}.card .warn{background:#ED7F54}.card .danger{background:#6583BE}.card .detail .text-default{color:#1d9d74}.card .detail .text-info{color:#6EBEE7}.card .detail .text-warn{color:#ED7F54}.card .detail .text-danger{color:#6583BE}.card .detail{float:right;width:70%}.card .detail .text{font-size:12px}.card .detail .number{font-size:30px;font-weight:500}.h100{height:100px}.inline{display:inline}.separator{height:1px;background-color:#e5e5e5;margin-top:10px}.card>.card-body>table>tbody>tr>td,.card>.card-body>table>thead>tr>td{word-wrap:break-word;word-break:break-all}.card>.card-body>table>thead>tr>td{font-weight:500;font-size:13px;text-align:center}.card>.card-body>table>thead>tr>td>span{font-weight:500;font-size:10px}.card>.card-body>table>tbody>tr>td{font-size:12px;text-align:center}.card>.card-body>table>tbody>tr>td>a{color:#666}.thumbnails>.card>.card-body>table>tbody>tr>td,.thumbnails>.card>.card-body>table>thead>tr>td{font-size:12px;color:#777;word-wrap:break-word;word-break:break-all}.thumbnails>.card>.card-body>table>thead>tr>td:nth-child(n+2){text-align:center}.thumbnails>.card>.card-body>table>tbody>tr>td:nth-child(n+2){font-weight:700;text-align:center}.thumbnails>.card>.card-body>table>tbody>tr>td:nth-child(1),.thumbnails>.card>.card-body>table>thead>tr>td:nth-child(1){text-align:left}.tools-header{background:#f5f5f5;padding:9px 0;height:40px}.tools-header .brand{font-size:13px;margin:2px 10px;font-weight:700;float:left}.tools-header .brand>a{color:#666}.tools-header>a,.tools-header>button,.tools-header>select{float:right;max-width:80px;margin:1px 10px;height:25px;padding:0 10px;line-height:25px;color:#666}.tools-header .paged{margin-right:0}.btn.btn-danger-tag{color:#fff;background-color:#d9534f;border-color:#d43f3a;line-height:1px;font-size:11px;padding:4px}.btn.btn-danger{color:#333;background-color:#fff;border-color:#ccc}.btn.btn-danger:active,.btn.btn-danger:focus,.btn.btn-danger:hover{color:#d9534f;border-color:#d9534f;background:#fff}.form-control{height:32px}.input-label:before{display:inline-block;content:"*";color:#f44336;font-family:SimSun;font-size:12px;-webkit-transform:TranslateX(-10px);-ms-transform:TranslateX(-10px);transform:TranslateX(-10px)}.badge-main,.label.label-main{color:#fff;background-color:#1d9d74;border-color:#1d9d74}.bootstrap-tagsinput{background-color:#fff;border:1px solid #ccc;box-shadow:inset 0 1px 1px rgba(0,0,0,.075);display:inline-block;padding:4px 6px;color:#555;vertical-align:middle;border-radius:4px;width:85%;height:100px;line-height:20px;cursor:text}.bootstrap-tagsinput>.dropdown-menu{min-width:40px;font-size:12px}.bootstrap-tagsinput>.dropdown-menu>.active>a,.bootstrap-tagsinput>.dropdown-menu>.active>a:focus,.bootstrap-tagsinput>.dropdown-menu>.active>a:hover{background-image:-webkit-linear-gradient(top,#1d9d74 0,#1d9d74 100%);background-image:-o-linear-gradient(top,#1d9d74 0,#1d9d74 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#1d9d74),to(#1d9d74));background-image:linear-gradient(to bottom,#1d9d74 0,#1d9d74 100%);filter:progid: DXImageTransform.Microsoft.gradient(startColorstr='#1d9d74', endColorstr='#1d9d74', GradientType=0);background-repeat:repeat-x;color:#fff;text-decoration:none;outline:0;background-color:#1d9d74}.inputs-header{padding:9px 0;height:50px;border-top-left-radius:4px;border-top-right-radius:4px}.inputs-header .brand{font-size:13px;margin:2px 10px;font-weight:700;float:left}.inputs-header .brand>a{color:#666}.inputs-header>input{float:right;margin:1px 10px;height:30px;padding:0 10px;color:#666}.inputs-header>a{float:right;margin:1px 10px;height:30px;padding:5 5px}.inputs-header>select{float:right;max-width:80px;margin:1px 10px;padding:0 10px;color:#666;height:25px;font-size:12px}.witdh-150{max-width:150px}.witdh-200{max-width:200px}.card.highlight{border-color:#d9534f}.card .pagination-footer{height:40px;font-size:10px;color:#777;margin:-15px 20px 5px}.card .pagination-footer .tools{font-size:12px;margin:11px 20px 11px 0;float:right;display:inline}.card>.pagination-footer>.tools>span>input{height:25px;max-width:50px;display:inline}.pagination{display:inline-block;padding-left:0;margin:8px 0;float:right;border-radius:4px}.pagination>a{margin-right:5px;height:28px;width:28px;padding:5px 0}.datepicker>.table>tbody>tr>td,.datepicker>.table>thead>tr>td,.timepicker>.table>tbody>tr>td,.timepicker>.table>thead>tr>td{padding:5px 3px}.datepicker>.table>tbody>tr>td>.btn,.datepicker>.table>thead>tr>td>.btn,.timepicker>.table>tbody>tr>td>.btn,.timepicker>.table>thead>tr>td>.btn{border:1px solid #FFFDFD}.datepicker>.table>tbody>tr>td>.btn-default:active,.datepicker>.table>tbody>tr>td>.btn-default:focus,.datepicker>.table>tbody>tr>td>.btn-default:hover,.datepicker>.table>thead>tr>td>.btn-default:active,.datepicker>.table>thead>tr>td>.btn-default:focus,.datepicker>.table>thead>tr>td>.btn-default:hover,.timepicker>.table>tbody>tr>td>.btn-default:active,.timepicker>.table>tbody>tr>td>.btn-default:focus,.timepicker>.table>tbody>tr>td>.btn-default:hover,.timepicker>.table>thead>tr>td>.btn-default:active,.timepicker>.table>thead>tr>td>.btn-default:focus,.timepicker>.table>thead>tr>td>.btn-default:hover{color:#1d9d74;border-color:#1d9d74;background:#fff}.datepicker>.table>tbody>tr>td>a,.datepicker>.table>thead>tr>td>a,.timepicker>.table>tbody>tr>td>a,.timepicker>.table>thead>tr>td>a{height:25px;width:25px;padding:3px 0}.datepicker>.table>tbody>tr:first-child>td>a{padding:4px 0}.datepicker>.table>tbody>tr>td>a.btn.active,.datepicker>.table>thead>tr>td>a.btn.active,.timepicker>.table>tbody>tr>td>a.btn.active,.timepicker>.table>thead>tr>td>a.btn.active{color:#1d9d74;border-color:#1d9d74;background:#fff;box-shadow:inset 0 0 0 rgba(0,0,0,.125)}.datepicker>.table>thead>tr>td:not(:first-child):last-child>a,.timepicker>.table>thead>tr>td:not(:first-child):last-child>a{height:25px;width:50px;padding:5px 0}.datepicker>.table>tbody>tr>td>a,.timepicker>.table>tbody>tr>td>a{margin-left:8px}.sortorder:after{content:'\25b2'}.sortorder.reverse:after{content:'\25bc'}.input-control select{-moz-appearance:none;-webkit-appearance:none;appearance:none;position:relative;border:1px solid #d9d9d9;width:100%;height:100%;padding:.3125rem;z-index:0}.navbar-inverse{background-color:#337ab7;border-color:#337ab7}.sidebar{z-index:1;width:220px;top:0;left:0;height:100%}#page-wrapper{position:inherit;margin:70px 0 0 220px;padding:12px 30px;border-left:0 solid #e7e7e7}.sidebar .sidebar-nav.navbar-collapse{background-color:#F5F5F5;position:relative;color:#000;width:100%;padding:0;margin:0;list-style:none inside}.sidebar a{color:#555}.sidebar ul li:hover{color:red}.form-control{border-radius:8px}.form-control:focus,.highlight-border{border-color:#337ab7;box-shadow:0 0 0 rgba(0,0,0,.075) inset,0 0 0 rgba(29,157,116,1)}.btn-outline-primary.focus,.btn-outline-primary:focus,.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.browsehappy{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.btn.btn-main{color:#fff;background-color:#337ab7;border-color:#337ab7}.btn-default-inverse,.btn-default-inverse:focus,.btn-default-inverse:hover,.btn-default:active{color:#337ab7;border-color:#337ab7;background:#fff}.btn-danger-inverse,.btn-danger-inverse:focus,.btn-danger-inverse:hover,.btn-danger:active{color:#d9534f;border-color:#d9534f;background:#fff}.btn-tab-active,.btn-tab-active:focus,.btn-tab-active:hover,.btn-tab-default:active,.btn-tab-default:focus,.btn-tab-default:hover{color:#337ab7;border-color:#337ab7;background:#fff;font-weight:600}.btn-tab-default{color:#777;background:#fff;font-weight:600}.pagination>.btn.active{color:#fff;background-color:#337ab7;border-color:#337ab7}.btn-default:active,.btn-default:focus,.btn-default:hover{color:#337ab7;border-color:#337ab7;background:#fff}.bootstrap-switch.bootstrap-switch-on{border-color:#337ab7}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success{color:#fff;background:#337ab7}.selectize-input-200>.selectize-input{min-width:200px;border-color:#337ab7}.btn-outline-primary{color:#007bff;background-color:transparent;background-image:none;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-secondary.focus,.btn-outline-secondary:focus,.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary{color:#6c757d;background-color:transparent;background-image:none;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-success.focus,.btn-outline-success:focus,.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success{color:#28a745;background-color:transparent;background-image:none;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-info.focus,.btn-outline-info:focus,.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info{color:#17a2b8;background-color:transparent;background-image:none;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-warning.focus,.btn-outline-warning:focus,.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning{color:#ffc107;background-color:transparent;background-image:none;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-danger.focus,.btn-outline-danger:focus,.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger{color:#dc3545;background-color:transparent;background-image:none;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-light.focus,.btn-outline-light:focus,.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light{color:#f8f9fa;background-color:transparent;background-image:none;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-dark.focus,.btn-outline-dark:focus,.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark{color:#343a40;background-color:transparent;background-image:none;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40} \ 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 063515aa..d3b99352 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"]).config(["$stateProvider","$urlRouterProvider","$ocLazyLoadProvider",function(e,r,t){t.config({debug:!1,events:!0}),r.otherwise("/dashboard/home"),e.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_old.html",url:"/v1/flow/:app",controller:"FlowControllerV1",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/flow_old.js"]})}]}}).state("dashboard.flow",{templateUrl:"app/views/flow.html",url:"/flow/:app",controller:"FlowController",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/flow.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.clusterAll",{templateUrl:"app/views/cluster.html",url:"/cluster/:app",controller:"SentinelClusterController",resolve:{loadMyFiles:["$ocLazyLoad",function(e){return e.load({name:"sentinelDashboardApp",files:["app/scripts/controllers/cluster.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("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):(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