ソースを参照

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 <sczyh16@gmail.com>
master
Eric Zhao 5年前
コミット
bf34f8b3be
34個のファイルの変更2299行の追加163行の削除
  1. +61
    -13
      sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js
  2. +283
    -0
      sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_assign_manage.js
  3. +543
    -0
      sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_list.js
  4. +283
    -0
      sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_manage.js
  5. +97
    -0
      sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_server_monitor.js
  6. +121
    -0
      sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_app_token_client_list.js
  7. +33
    -8
      sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster_single.js
  8. +14
    -1
      sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow_v1.js
  9. +19
    -2
      sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow_v2.js
  10. +8
    -2
      sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/identity.js
  11. +5
    -1
      sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/param_flow.js
  12. +2
    -2
      sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html
  13. +49
    -4
      sentinel-dashboard/src/main/webapp/resources/app/scripts/services/cluster_state_service.js
  14. +65
    -61
      sentinel-dashboard/src/main/webapp/resources/app/scripts/services/flow_service_v1.js
  15. +36
    -36
      sentinel-dashboard/src/main/webapp/resources/app/scripts/services/flow_service_v2.js
  16. +4
    -0
      sentinel-dashboard/src/main/webapp/resources/app/styles/main.css
  17. +8
    -0
      sentinel-dashboard/src/main/webapp/resources/app/views/cluster/client.html
  18. +14
    -1
      sentinel-dashboard/src/main/webapp/resources/app/views/cluster/server.html
  19. +118
    -0
      sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_assign_manage.html
  20. +73
    -0
      sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_client_list.html
  21. +87
    -0
      sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_server_list.html
  22. +88
    -0
      sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_server_overview.html
  23. +4
    -5
      sentinel-dashboard/src/main/webapp/resources/app/views/cluster_single_config.html
  24. +40
    -0
      sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-client-config-dialog.html
  25. +139
    -0
      sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-server-assign-dialog.html
  26. +37
    -0
      sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-server-connection-detail-dialog.html
  27. +2
    -2
      sentinel-dashboard/src/main/webapp/resources/app/views/dialog/flow-rule-dialog.html
  28. +33
    -3
      sentinel-dashboard/src/main/webapp/resources/app/views/dialog/param-flow-rule-dialog.html
  29. +2
    -2
      sentinel-dashboard/src/main/webapp/resources/app/views/dialog/system-rule-dialog.html
  30. +15
    -11
      sentinel-dashboard/src/main/webapp/resources/app/views/flow_v1.html
  31. +6
    -6
      sentinel-dashboard/src/main/webapp/resources/app/views/flow_v2.html
  32. +8
    -1
      sentinel-dashboard/src/main/webapp/resources/app/views/param_flow.html
  33. +1
    -1
      sentinel-dashboard/src/main/webapp/resources/dist/css/app.css
  34. +1
    -1
      sentinel-dashboard/src/main/webapp/resources/dist/js/app.js

+ 61
- 13
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
}]
}
});
}]);
}]);

+ 283
- 0
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('未知错误');
});
};
}]);

+ 543
- 0
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');
}
};
}]);

+ 283
- 0
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('未知错误');
});
};
}]);

+ 97
- 0
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 '<div>' + escape(data.text) + '</div>';
}
},
onChange: function (value, oldValue) {
$scope.macInputModel = value;
}
};
}]);

+ 121
- 0
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 '<div>' + escape(data.text) + '</div>';
}
},
onChange: function (value, oldValue) {
$scope.macInputModel = value;
}
};
}]);

sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/cluster.js → 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();
}]);

sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow.js → 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) {

sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/flow_old.js → 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: '新增流控规则',

+ 8
- 2
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('未知错误');
});
}



+ 5
- 1
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 = {


+ 2
- 2
sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html ファイルの表示

@@ -57,8 +57,8 @@
<i class="glyphicon glyphicon-check"></i>&nbsp;&nbsp;授权规则</a>
</li>
<li ui-sref-active="active">
<a ui-sref="dashboard.clusterAll({app: entry.app})">
<i class="glyphicon glyphicon-cloud"></i>&nbsp;&nbsp;集群流</a>
<a ui-sref="dashboard.clusterAppServerList({app: entry.app})">
<i class="glyphicon glyphicon-cloud"></i>&nbsp;&nbsp;集群流</a>
</li>
<li ui-sref-active="active">
<a ui-sref="dashboard.machine({app: entry.app})">


+ 49
- 4
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'
});
};
}]);

+ 65
- 61
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;
};
}]);

+ 36
- 36
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;
};
}]);

+ 4
- 0
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;
}


+ 8
- 0
sentinel-dashboard/src/main/webapp/resources/app/views/cluster/client.html ファイルの表示

@@ -1,5 +1,13 @@
<div class="row clearfix">
<form role="form" class="form-horizontal">
<div class="form-group" ng-if="stateVO.currentMode == 0">
<label class="col-sm-2 control-label">连接状态</label>
<div class="col-sm-4">
<p class="form-control-static text-danger" ng-if="stateVO.client.clientConfig.clientState === 0">未连接</p>
<p class="form-control-static" ng-if="stateVO.client.clientConfig.clientState === 1">连接中</p>
<p class="form-control-static text-success" ng-if="stateVO.client.clientConfig.clientState === 2">已连接</p>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Token Server IP</label>
<div class="col-sm-4">


+ 14
- 1
sentinel-dashboard/src/main/webapp/resources/app/views/cluster/server.html ファイルの表示

@@ -1,9 +1,16 @@
<div class="row clearfix">
<form role="form" class="form-horizontal">
<div class="form-group" ng-if="stateVO.currentMode == 1">
<label class="col-sm-2 control-label">Token Server 模式</label>
<div class="col-sm-4">
<p class="form-control-static" ng-if="!stateVO.server.embedded">独立模式 (Alone)</p>
<p class="form-control-static" ng-if="stateVO.server.embedded">嵌入模式 (Embedded)</p>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Token Server 端口</label>
<div class="col-sm-4">
<input type="number" min="0" max="65535" required class="form-control highlight-border" ng-model='stateVO.server.transport.port' placeholder='请指定 Token Server 端口' />
<input type="number" min="1" max="65535" required class="form-control highlight-border" ng-model='stateVO.server.transport.port' placeholder='请指定 Token Server 端口' />
</div>
</div>
<div class="form-group">
@@ -12,5 +19,11 @@
<input type="text" required class="form-control highlight-border" ng-model='stateVO.server.namespaceSetStr' placeholder='请指定服务端服务的命名空间集合(以,分隔)' />
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">最大全局 QPS</label>
<div class="col-sm-4">
<input type="number" min="0" max="100000" required class="form-control highlight-border" ng-model='stateVO.server.flow.maxAllowedQps' placeholder='请指定服务端最大全局 QPS' />
</div>
</div>
</form>
</div>

+ 118
- 0
sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_assign_manage.html ファイルの表示

@@ -0,0 +1,118 @@
<div class="row" style="margin-left: 1px; margin-top:10px; height: 50px;">
<div class="col-md-6" style="margin-bottom: 10px;">
<span style="font-size: 30px;font-weight: bold;">{{app}}</span>
</div>
</div>

<div class="separator"></div>
<div class="container-fluid">
<div class="row" style="margin-top: 20px; margin-bottom: 20px;">
<div class="col-md-12">
<div class="card">
<div class="inputs-header">
<span class="brand" style="font-size: 13px;">集群限流 - 机器分配/管控</span>
</div>

<!-- error panel -->
<div class="row clearfix" ng-if="loadError">
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-default">
<div class="panel-body">
<center>
<p>{{loadError.message}}</p>
</center>
</div>
</div>
</div>
</div>

<!--.tools-header -->
<div class="card-body" style="padding: 0px 0px;" ng-if="!loadError">
<form role="form" class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">Server 列表</label>
<div class="col-sm-4">
<select ng-model="tmp.curChosenServer" ng-change="onCurrentServerChange()" size="8"
ng-options="serverGroup.machineId for serverGroup in clusterMap"
class="form-control"></select>
</div>
<button type="button" class="btn btn-outline-warning" ng-click="removeFromServerList()">移除
</button>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Token Server 端口</label>
<div class="col-sm-4">
<input type="number" class="form-control highlight-border"
ng-disabled="!tmp.curChosenServer.belongToApp"
ng-model='tmp.curChosenServer.port' placeholder='port' min="1" max="65535"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">命名空间集合</label>
<div class="col-sm-4">
<input type="text" required class="form-control highlight-border"
ng-disabled="!tmp.curChosenServer.belongToApp"
ng-model='tmp.curChosenServer.namespaceSetStr'
placeholder='请指定服务端服务的命名空间集合(以,分隔)'/>
</div>
</div>
</form>
<form role="form" class="form-inline" style="margin-top: 30px; margin-left: 20px;">
<div>
<div class="form-group">
<div class="col-sm-12">
<label class="control-label" style="width: 200px; text-align: center;">当前对应客户端列表</label>
<select size="8" multiple="multiple" ng-model="tmp.curClientChosen"
ng-options="ip for ip in tmp.curChosenServer.clientSet"
class="form-control" style="width: 100%;"></select>
</div>

</div>
<div class="form-group">
<div class="col-sm-12">
<button type="button" class="btn btn-outline-primary"
ng-disabled="!tmp.curChosenServer || !tmp.curChosenServer.machineId"
ng-click="moveToServerGroup()">←
</button>
<button type="button" class="btn btn-outline-primary"
ng-click="moveToRemainingSharePool()">→
</button>
</div>
</div>

<div class="form-group">
<div class="col-sm-12">
<label class="control-label" style="width: 220px; text-align: center;">未分配机器列表</label>
<div>
<select size="8" multiple="multiple" ng-model="tmp.curRemainingClientChosen"
ng-options="ip for ip in remainingClientAddressList"
class="form-control" style="width: 100%;">
</select>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-6">
<button type="button" class="btn btn-outline-primary"
ng-click="addToServerList()">添加为 server
</button>
</div>
</div>
</div>
</form>
<div class="separator"></div>
<div style="margin-top: 20px;">
<button type="button" style="margin: 0 10px 10px 10px;" class="btn btn-outline-success"
ng-click="saveAndApplyAssign()">保存并执行分配
</button>
</div>
</div>
<!-- .card-body -->
</div>
<!-- .card -->
</div>
<!-- .col-md-12 -->
</div>
<!-- -->
</div>
<!-- .container-fluid -->

+ 73
- 0
sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_client_list.html ファイルの表示

@@ -0,0 +1,73 @@
<div class="row" style="margin-left: 1px; margin-top:10px; height: 50px;">
<div class="col-md-6" style="margin-bottom: 10px;">
<span style="font-size: 30px;font-weight: bold;">{{app}}</span>
</div>
<div class="col-md-6">
<a class="btn btn-default-inverse" style="float: right; margin-right: 10px;" ui-sref="dashboard.clusterAppServerList({app: app})">
Token Server 列表
</a>
</div>
</div>

<div class="separator"></div>
<div class="container-fluid">
<div class="row" style="margin-top: 20px; margin-bottom: 20px;">
<div class="col-md-12">
<div class="card">
<div class="inputs-header">
<span class="brand" style="font-size: 13px;">集群限流 - Token Client 列表</span>
</div>

<!-- error panel -->
<div class="row clearfix" ng-if="loadError">
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-default">
<div class="panel-body">
<center>
<p>{{loadError.message}}</p>
</center>
</div>
</div>
</div>
</div>

<!--.tools-header -->
<div class="card-body" style="padding: 0px 0px;" ng-if="!loadError">
<!-- table start -->
<table class="table" style="border-left: none; border-right:none;margin-top: 10px;">
<thead>
<tr style="background: #F3F5F7;">
<td style="min-width: 12%;">Client ID</td>
<td>Server IP</td>
<td>Server 端口</td>
<td>连接状态</td>
<td style="min-width: 15%;">操作</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="clientVO in clientVOList">
<td style="word-wrap:break-word;word-break:break-all;">{{clientVO.id}}</td>
<td style="word-wrap:break-word;word-break:break-all;">{{clientVO.state.clientConfig.serverHost}}</td>
<td>{{clientVO.state.clientConfig.serverPort}}</td>
<td>
<span class="form-control-static text-danger" ng-if="clientVO.state.clientConfig.clientState === 0">未连接</span>
<span class="form-control-static" ng-if="clientVO.state.clientConfig.clientState === 1">连接中</span>
<span class="form-control-static text-success" ng-if="clientVO.state.clientConfig.clientState === 2">已连接</span>
</td>
<td>
<button class="btn btn-xs btn-outline-primary" type="button"
ng-click="modifyClientConfigDialog(clientVO)" style="font-size: 12px; height:25px;">编辑配置</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- .card-body -->
</div>
<!-- .card -->
</div>
<!-- .col-md-12 -->
</div>
<!-- -->
</div>
<!-- .container-fluid -->

+ 87
- 0
sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_server_list.html ファイルの表示

@@ -0,0 +1,87 @@
<div class="row" style="margin-left: 1px; margin-top:10px; height: 50px;">
<div class="col-md-6" style="margin-bottom: 10px;">
<span style="font-size: 30px;font-weight: bold;">{{app}}</span>
</div>
<div class="col-md-6">
<button class="btn btn-default-inverse" style="float: right; margin-right: 10px;" ng-if="!loadError" ng-click="newServerDialog()">
<i class="fa fa-plus"></i>&nbsp;&nbsp;新增 Token Server</button>
<a class="btn btn-default-inverse" style="float: right; margin-right: 10px;" ui-sref="dashboard.clusterAppClientList({app: app})">
Token Client 列表
</a>
</div>
</div>

<div class="separator"></div>
<div class="container-fluid">
<div class="row" style="margin-top: 20px; margin-bottom: 20px;">
<div class="col-md-12">
<div class="card">
<div class="inputs-header">
<span class="brand" style="font-size: 13px;">集群限流 - Token Server 列表</span>
<input class="form-control width-200" placeholder="搜索 server..." ng-model="searchKey">
</div>

<!-- error panel -->
<div class="row clearfix" ng-if="loadError">
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-default">
<div class="panel-body">
<center>
<p>{{loadError.message}}</p>
</center>
</div>
</div>
</div>
</div>

<!--.tools-header -->
<div class="card-body" style="padding: 0px 0px;" ng-if="!loadError">
<!-- table start -->
<table class="table" style="border-left: none; border-right:none;margin-top: 10px;">
<thead>
<tr style="background: #F3F5F7;">
<td style="width: 15%;">Server ID</td>
<td style="width: 10%;">Port</td>
<td style="width: 15%;">命名空间集合</td>
<td>总连接数</td>
<td>QPS 总览</td>
<td style="width: 20%;">操作</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="serverVO in serverVOList | filter: {id: searchKey}">
<td style="word-wrap:break-word;word-break:break-all;">{{serverVO.id}}</td>
<td>{{serverVO.port}}</td>
<td style="word-wrap:break-word;word-break:break-all;">
{{serverVO.state.namespaceSetStr}}
</td>
<td style="word-wrap:break-word;word-break:break-all;">
{{serverVO.connectedCount}}
</td>
<td>
{{serverVO.state.requestLimitDataStr}}
<!--<p ng-repeat="crl in serverVO.state.requestLimitData">-->
<!--<span ng-if="crl.namespace === app">{{crl.namespace}}:{{crl.currentQps}} / {{crl.maxAllowedQps}}</span>-->
<!--</p>-->
</td>
<td>
<button class="btn btn-xs btn-outline-primary" type="button"
ng-click="viewConnectionDetail(serverVO)" style="font-size: 12px; height:25px;">连接详情</button>
<button class="btn btn-xs btn-outline-primary" type="button"
ng-click="modifyServerAssignConfig(serverVO.id)" style="font-size: 12px; height:25px;">管理</button>
<button class="btn btn-xs btn-outline-danger" type="button"
ng-click="unbindServer(serverVO.id)" style="font-size: 12px; height:25px;">移除</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- .card-body -->
</div>
<!-- .card -->
</div>
<!-- .col-md-12 -->
</div>
<!-- -->
</div>
<!-- .container-fluid -->

+ 88
- 0
sentinel-dashboard/src/main/webapp/resources/app/views/cluster_app_server_overview.html ファイルの表示

@@ -0,0 +1,88 @@
<div class="row" style="margin-left: 1px; margin-top:10px; height: 50px;">
<div class="col-md-6" style="margin-bottom: 10px;">
<span style="font-size: 30px;font-weight: bold;">{{app}}</span>
</div>
</div>

<div class="separator"></div>
<div class="container-fluid">
<div class="row" style="margin-top: 20px; margin-bottom: 20px;">
<div class="col-md-12">
<div class="card">
<div class="inputs-header">
<span class="brand" style="font-size: 13px;">集群限流 - Token Server 总览</span>
</div>

<!-- error panel -->
<div class="row clearfix" ng-if="loadError">
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-default">
<div class="panel-body">
<center>
<p>{{loadError.message}}</p>
</center>
</div>
</div>
</div>
</div>

<!--.tools-header -->
<div class="card-body" style="padding: 0px 0px;" ng-if="!loadError">
<form role="form" class="form-horizontal">
<div class="form-group" hidden>
<label class="col-sm-2 control-label">Token Server 列表</label>
<div class="col-sm-4">
<select ng-model="tmp.curChosenServer" ng-change="onChosenServerChange()"
ng-options="serverEntity.id for serverEntity in serverVOList"
class="form-control"></select>
</div>
</div>
</form>

<!-- table start -->
<table class="table" style="border-left: none; border-right:none;margin-top: 10px;">
<thead>
<tr style="background: #F3F5F7;">
<td style="width: 12%;">Server ID</td>
<td style="width: 5%;">Port</td>
<td style="width: 10%;">命名空间集合</td>
<td>总连接数</td>
<td>连接情况</td>
<td>QPS 总览</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="serverVO in clientVOList">
<td style="word-wrap:break-word;word-break:break-all;">{{serverVO.id}}</td>
<td>{{serverVO.port}}</td>
<td style="word-wrap:break-word;word-break:break-all;">
{{serverVO.state.namespaceSetStr}}
</td>
<td style="word-wrap:break-word;word-break:break-all;">
{{serverVO.connectedCount}}
</td>
<td>
<p ng-repeat="cg in serverVO.state.connection">
namespace: {{cg.namespace}}, 连接数: {{cg.connectedCount}}, clients:
{{generateConnectionSet(cg.connectionSet)}}
</p>
</td>
<td>
<p ng-repeat="crl in serverVO.state.requestLimitData">
namespace: {{crl.namespace}}, 当前 QPS: {{crl.currentQps}}, 最大允许 QPS:
{{crl.maxAllowedQps}}
</p>
</td>
</tr>
</tbody>
</table>
</div>
<!-- .card-body -->
</div>
<!-- .card -->
</div>
<!-- .col-md-12 -->
</div>
<!-- -->
</div>
<!-- .container-fluid -->

sentinel-dashboard/src/main/webapp/resources/app/views/cluster.html → sentinel-dashboard/src/main/webapp/resources/app/views/cluster_single_config.html ファイルの表示

@@ -12,8 +12,7 @@
<div class="card">
<div class="inputs-header">
<span class="brand" style="font-size: 13px;">集群限流</span>
<button class="btn btn-primary" style="float: right; margin-right: 10px; height: 30px;font-size: 12px;" ng-click="getMachineRules()">刷新</button>
<input class="form-control witdh-200" placeholder="关键字" ng-model="searchKey">
<input class="form-control witdh-200" placeholder="机器搜索" ng-model="searchKey">
<div class="control-group" style="float:right;margin-right: 10px;margin-bottom: -10px;">
<selectize id="gsInput" class="selectize-input-200" config="macsInputConfig" options="macsInputOptions" ng-model="macInputModel"
placeholder="机器"></selectize>
@@ -34,8 +33,7 @@
</div>

<!--.tools-header -->
<div class="card-body" style="padding: 0px 0px;">
<!--<span class="brand" style="font-weight:bold;">集群限流状态</span>-->
<div class="card-body" style="padding: 0px 0px;" ng-if="!loadError">
<form role="form" class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">当前模式</label>
@@ -80,7 +78,8 @@

<div class="separator"></div>
<div clss="row" style="margin-top: 20px;">
<button style="margin: 0 10px 10px 10px;" class="btn btn-outline-success" ng-click="saveConfig()">保存配置</button>
<button style="margin: 0 10px 10px 10px;" class="btn btn-outline-success"
ng-click="saveConfig()">保存配置</button>
</div>
</div>


+ 40
- 0
sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-client-config-dialog.html ファイルの表示

@@ -0,0 +1,40 @@
<div>
<span class="brand" style="font-weight:bold;">修改 Token Client 配置</span>
<div class="card" style="margin-top: 20px;margin-bottom: 10px;">
<div class="panel-body">
<div class="row">
<form role="form" class="form-horizontal">
<div class="form-group">
<label class="col-sm-3 control-label">Client ID</label>
<div class="col-sm-4">
<p class="form-control-static">{{ccDialogData.clientId}}</p>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Token Server IP</label>
<div class="col-sm-4">
<input type="text" class="form-control highlight-border" ng-model='ccDialogData.serverHost' placeholder='请指定 Token Server IP' />
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Token Server 端口</label>
<div class="col-sm-4">
<input type="number" min="0" max="65535" required class="form-control highlight-border" ng-model='ccDialogData.serverPort' placeholder='请指定 Token Server 端口' />
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">请求超时时间(ms)</label>
<div class="col-sm-4">
<input type="number" min="0" required class="form-control highlight-border" ng-model='ccDialogData.requestTimeout' placeholder='请指定请求超时时间(ms)' />
</div>
</div>
</form>
</div>
<div class="separator"></div>
<div clss="row" style="margin-top: 20px;">
<button class="btn btn-outline-danger" style="float:right; height: 30px;font-size: 12px;margin-left: 10px;" ng-click="ccDialog.close()">取消</button>
<button class="btn btn-outline-success" style="float:right; height: 30px;font-size: 12px;margin-left: 10px;" ng-click="doModifyClientConfig()">保存</button>
</div>
</div>
</div>
</div>

+ 139
- 0
sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-server-assign-dialog.html ファイルの表示

@@ -0,0 +1,139 @@
<div>
<span class="brand" style="font-weight:bold;">{{serverAssignDialogData.title}}</span>
<div class="card" style="margin-top: 20px;margin-bottom: 10px;">
<div class="panel-body">
<div class="row">
<form role="form" class="form-horizontal">
<div ng-if="serverAssignDialogData.type == 'edit'">
<div class="form-group">
<label class="col-sm-2 control-label">Token Server</label>
<div class="col-sm-4">
<p class="form-control-static">{{serverAssignDialogData.serverData.currentServer}}</p>
</div>

<label class="col-sm-2 control-label">Server 端口</label>
<div class="col-sm-3">
<input type="number" min="1" max="65535" class="form-control highlight-border"
ng-disabled="!serverAssignDialogData.serverData.belongToApp"
ng-model='serverAssignDialogData.serverData.serverPort' placeholder='请输入 Token Server 端口'/>
</div>
</div>
<div class="form-group" ng-if="serverAssignDialogData.serverData.belongToApp">
<label class="col-sm-2 control-label"
title="server 最大允许的总 QPS,注意 embedded 模式下不要设的太大">最大允许 QPS</label>
<div class="col-sm-3">
<input type="number" min="0" max="200000" class="form-control highlight-border"
ng-model='serverAssignDialogData.serverData.maxAllowedQps' placeholder='请输入 server 最大允许 QPS'/>
</div>
</div>
</div>

<div ng-if="serverAssignDialogData.type == 'add'">
<div class="form-group" >
<label class="col-sm-2 control-label">机器类型</label>
<div class="col-sm-4">
<div class="form-control highlight-border" align="center">
<input type="radio" name="strategy" value="0" checked ng-model='serverAssignDialogData.serverData.serverType' />&nbsp;应用内机器&nbsp;&nbsp;
<input type="radio" name="strategy" value="1" ng-model='serverAssignDialogData.serverData.serverType' />&nbsp;外部指定机器
</div>
</div>

<div ng-if="serverAssignDialogData.serverData.serverType == 1">
<div class="col-sm-6">
<p class="form-control-static text-primary" style="font-size: x-small;">若指定外部 server,请先在相应页面对外部 server 进行配置,然后在此页面指定。</p>
</div>
</div>
</div>

<div ng-if="serverAssignDialogData.serverData.serverType == 0">
<div class="form-group">
<label class="col-sm-2 control-label">选择机器</label>
<div class="col-sm-4">
<select ng-model="serverAssignDialogData.serverData.currentServer" ng-change="onCurrentServerChange()"
ng-options="machineId for machineId in remainingMachineList"
class="form-control"></select>
</div>

<label class="col-sm-2 control-label">Server 端口</label>
<div class="col-sm-3">
<input type="number" min="1" max="65535" class="form-control highlight-border"
ng-model='serverAssignDialogData.serverData.serverPort' placeholder='请输入 Token Server 端口号'/>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label"
title="server 最大允许的总 QPS,注意 embedded 模式下不要设的太大">最大允许 QPS</label>
<div class="col-sm-3">
<input type="number" min="0" max="200000" class="form-control highlight-border"
ng-model='serverAssignDialogData.serverData.maxAllowedQps' placeholder='请输入 server 最大允许 QPS'/>
</div>
</div>
</div>

<div ng-if="serverAssignDialogData.serverData.serverType == 1">
<div class="form-group">
<label class="col-sm-2 control-label">Server IP</label>
<div class="col-sm-4">
<input type="text" class="form-control highlight-border"
ng-model='serverAssignDialogData.serverData.currentServer' placeholder='请输入独立的 Token Server IP'/>
</div>

<label class="col-sm-2 control-label">Server 端口</label>
<div class="col-sm-3">
<input type="number" min="1" max="65535" class="form-control highlight-border"
ng-model='serverAssignDialogData.serverData.serverPort' placeholder='请输入 Token Server 端口号'/>
</div>
</div>
</div>
</div>
</form>

<!-- assign form start -->
<form role="form" class="form-inline" ng-if="serverAssignDialogData.serverData.currentServer"
style="margin-top: 30px; margin-left: 20px; text-align: center;">
<div>
<div class="form-group">
<div class="col-sm-12">
<label class="control-label" style="width: 220px; text-align: center;">请从中选取 client:</label>
<div>
<select size="8" multiple="multiple" ng-model="tmp.curRemainingClientChosen"
ng-options="ip for ip in remainingMachineList | filter: notChosenServer"
class="form-control" style="width: 100%;">
</select>
</div>
</div>
</div>

<div class="form-group">
<div class="col-sm-12">

<button type="button" class="btn btn-outline-primary"
ng-click="moveToRemainingSharePool()">←
</button>
<button type="button" class="btn btn-outline-primary"
ng-click="moveToServerGroup()">→
</button>
</div>
</div>

<div class="form-group">
<div class="col-sm-12">
<label class="control-label" style="width: 200px; text-align: center;">已选取的 client 列表</label>
<div>
<select size="8" multiple="multiple" ng-model="tmp.curClientChosen"
ng-options="ip for ip in serverAssignDialogData.serverData.clientSet"
class="form-control" style="width: 100%;"></select>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="separator"></div>
<div clss="row" style="margin-top: 20px;">
<button class="btn btn-outline-danger" style="float:right; height: 30px;font-size: 12px;margin-left: 10px;" ng-click="serverAssignDialog.close()">取消</button>
<button class="btn btn-outline-success" style="float:right; height: 30px;font-size: 12px;margin-left: 10px;" ng-click="saveAssignForDialog()">{{serverAssignDialogData.confirmBtnText}}</button>
</div>
</div>
</div>
</div>

+ 37
- 0
sentinel-dashboard/src/main/webapp/resources/app/views/dialog/cluster/cluster-server-connection-detail-dialog.html ファイルの表示

@@ -0,0 +1,37 @@
<div>
<span class="brand" style="font-weight:bold;">连接详情</span>
<div class="card" style="margin-top: 20px;margin-bottom: 10px;">
<div class="panel-body">
<div class="row">
<form role="form" class="form-horizontal">
<div class="form-group">
<label class="col-sm-3 control-label">Token Server</label>
<div class="col-sm-4">
<p class="form-control-static">{{connectionDetailDialogData.serverData.id}}</p>
</div>
</div>
</form>

<div class="col-md-12">
<!-- table start -->
<table class="table" style="border-left: none; border-right:none;margin-top: 10px;">
<thead>
<tr style="background: #F3F5F7;">
<td style="min-width: 15%;" class="text-center">命名空间</td>
<td class="text-center">连接数</td>
<td class="text-center">连接详情</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="cg in connectionDetailDialogData.serverData.state.connection">
<td style="word-wrap:break-word;word-break:break-all;" class="text-center">{{cg.namespace}}</td>
<td style="word-wrap:break-word;word-break:break-all;" class="text-center">{{cg.connectedCount}}</td>
<td class="text-center">{{generateConnectionSet(cg.connectionSet)}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

+ 2
- 2
sentinel-dashboard/src/main/webapp/resources/app/views/dialog/flow-rule-dialog.html ファイルの表示

@@ -15,7 +15,7 @@
</div>

<div class="form-group">
<label class="col-sm-2 control-label" title="流控针对应用,即流量入口的调用来源(origin)">流控应用</label>
<label class="col-sm-2 control-label" data-toggle="tooltip" title="流控针对应用,即流量入口的调用来源(origin)">来源应用</label>
<div class="col-sm-9">
<input type="text" class="form-control highlight-border" ng-model='currentRule.limitApp' placeholder='指调用方,"default"表示所有应用。'
/>
@@ -60,7 +60,7 @@
<div class="col-sm-4">
<div class="form-control highlight-border" align="center">
<input type="radio" name="clusterThresholdType" value="0" ng-model='currentRule.clusterConfig.thresholdType' />&nbsp;单机均摊&nbsp;&nbsp;
<input type="radio" name="clusterThresholdType" value="1" ng-model='currentRule.clusterConfig.thresholdType' />&nbsp;Global
<input type="radio" name="clusterThresholdType" value="1" ng-model='currentRule.clusterConfig.thresholdType' />&nbsp;总体阈值
</div>
</div>
</div>


+ 33
- 3
sentinel-dashboard/src/main/webapp/resources/app/views/dialog/param-flow-rule-dialog.html ファイルの表示

@@ -25,12 +25,42 @@
</div>
<div class="form-group">
<label class="col-sm-2 control-label">限流阈值</label>
<div class="col-sm-9">
<input type="number" class="form-control highlight-border" ng-model='currentRule.rule.count' placeholder='请填入限流阈值' />
<div ng-if="!currentRule.rule.clusterMode">
<label class="col-sm-2 control-label">单机阈值</label>
<div class="col-sm-9">
<input type="number" class="form-control highlight-border" ng-model='currentRule.rule.count' placeholder='单机阈值' />
</div>
</div>
<div ng-if="currentRule.rule.clusterMode && currentRule.rule.clusterConfig.thresholdType == 0">
<label class="col-sm-2 control-label">均摊阈值</label>
<div class="col-sm-9">
<input type="number" class="form-control highlight-border" ng-model='currentRule.rule.count' placeholder='集群均摊阈值' />
</div>
</div>
<div ng-if="currentRule.rule.clusterMode && currentRule.rule.clusterConfig.thresholdType == 1">
<label class="col-sm-2 control-label">集群阈值</label>
<div class="col-sm-9">
<input type="number" class="form-control highlight-border" ng-model='currentRule.rule.count' placeholder='集群总体阈值' />
</div>
</div>
</div>

<div class="form-group">
<label class="col-sm-2 control-label">是否集群</label>
<div class="col-sm-2">
<input type="checkbox" name="clusterMode" ng-model="currentRule.rule.clusterMode">
</div>
<div ng-if="currentRule.rule.clusterMode">
<label class="col-sm-3 control-label">集群阈值模式</label>
<div class="col-sm-4">
<div class="form-control highlight-border" align="center">
<input type="radio" name="clusterThresholdType" value="0" ng-model='currentRule.rule.clusterConfig.thresholdType' />&nbsp;单机均摊&nbsp;&nbsp;
<input type="radio" name="clusterThresholdType" value="1" ng-model='currentRule.rule.clusterConfig.thresholdType' />&nbsp;总体阈值
</div>
</div>
</div>
</div>


<!-- exclusion item part start -->
<div ng-if="!paramFlowRuleDialog.showAdvanceButton">


+ 2
- 2
sentinel-dashboard/src/main/webapp/resources/app/views/dialog/system-rule-dialog.html ファイルの表示

@@ -17,7 +17,7 @@
<input type="radio" name="grade" value="2" ng-model='currentRule.grade' ng-disabled="systemRuleDialog.type == 'edit'" />&nbsp;线程数&nbsp;&nbsp;
<!--qps -->
<input type="radio" name="grade" value="3" checked ng-model='currentRule.grade' ng-disabled="systemRuleDialog.type == 'edit'"
/>&nbsp;QPS
/>&nbsp;入口 QPS
</div>
<div class="form-control highlight-border" ng-if="systemRuleDialog.type == 'add'" align="center">
<!--avgLoad -->
@@ -28,7 +28,7 @@
<input type="radio" name="grade" value="2" ng-model='currentRule.grade' ng-disabled="systemRuleDialog.type == 'edit'" />&nbsp;线程数&nbsp;&nbsp;
<!--qps -->
<input type="radio" name="grade" value="3" checked ng-model='currentRule.grade' ng-disabled="systemRuleDialog.type == 'edit'"
/>&nbsp;QPS
/>&nbsp;入口 QPS
</div>
</div>
</div>


sentinel-dashboard/src/main/webapp/resources/app/views/flow_old.html → sentinel-dashboard/src/main/webapp/resources/app/views/flow_v1.html ファイルの表示

@@ -5,8 +5,8 @@
<div class="col-md-6">
<button class="btn btn-default-inverse" style="float: right; margin-right: 10px;" ng-disabled="!macInputModel" ng-click="addNewRule()">
<i class="fa fa-plus"></i>&nbsp;&nbsp;新增流控规则</button>
<a class="btn btn-outline-success" style="float: right; margin-right: 10px;" ui-sref="dashboard.flow({app: app})">
回到新版</a>
<!--<a class="btn btn-outline-success" style="float: right; margin-right: 10px;" ui-sref="dashboard.flow({app: app})">-->
<!--回到集群页面</a>-->
</div>
</div>

@@ -35,24 +35,24 @@
<td style="width: 40%">
资源名
</td>
<td style="width: 10%;">
流控应用
<td style="width: 8%;">
来源应用
</td>
<td style="width: 10%;">
<td style="width: 8%;">
流控模式
</td>
<td style="width: 10%;">
<td style="width: 8%;">
阈值类型
</td>
<td style="width: 10%;">
单机阈值
<td style="width: 6%;">
阈值
</td>
<td style="width: 8%;">
阈值模式
</td>
<td style="width: 10%;">
流控效果
</td>
<!--<td style="width: 8%;">-->
<!--状态-->
<!--</td>-->
<td style="width: 12%;">
操作
</td>
@@ -74,10 +74,14 @@
<td style="word-wrap:break-word;word-break:break-all;">
{{rule.count}}
</td>
<td>
<span>{{generateThresholdTypeShow(rule)}}</span>
</td>
<td>
<span ng-if="rule.controlBehavior == 0">快速失败</span>
<span ng-if="rule.controlBehavior == 1">Warm Up</span>
<span ng-if="rule.controlBehavior == 2">排队等待</span>
<span ng-if="rule.controlBehavior == 3">预热排队</span>
</td>
<td>
<button class="btn btn-xs btn-default" type="button" ng-click="editRule(rule)" style="font-size: 12px; height:25px;">编辑</button>

sentinel-dashboard/src/main/webapp/resources/app/views/flow.html → sentinel-dashboard/src/main/webapp/resources/app/views/flow_v2.html ファイルの表示

@@ -7,7 +7,8 @@
<i class="fa fa-plus"></i>&nbsp;&nbsp;新增流控规则
</button>
<a class="btn btn-default-inverse" style="float: right; margin-right: 10px;" ui-sref="dashboard.flowV1({app: app})">
回到旧版(单机)</a>
回到单机页面
</a>
</div>
</div>

@@ -31,7 +32,7 @@
资源名
</td>
<td style="width: 10%;">
流控应用
来源应用
</td>
<td style="width: 8%;">
流控模式
@@ -40,10 +41,10 @@
阈值类型
</td>
<td style="width: 8%;">
单机阈值
阈值
</td>
<td style="width: 8%;">
是否集群
阈值模式
</td>
<td style="width: 8%;">
流控效果
@@ -70,8 +71,7 @@
{{rule.count}}
</td>
<td>
<span ng-if="rule.clusterMode">是</span>
<span ng-if="!rule.clusterMode">否</span>
<span>{{generateThresholdTypeShow(rule)}}</span>
</td>
<td>
<span ng-if="rule.controlBehavior == 0">快速失败</span>

+ 8
- 1
sentinel-dashboard/src/main/webapp/resources/app/views/param_flow.html ファイルの表示

@@ -53,7 +53,10 @@
流控模式
</td>
<td style="width: 10%;">
单机阈值
阈值
</td>
<td style="width: 8%;">
是否集群
</td>
<td style="width: 10%;">
例外项数目
@@ -74,6 +77,10 @@
<td style="word-wrap:break-word;word-break:break-all;">
{{ruleEntity.rule.count}}
</td>
<td>
<span ng-if="ruleEntity.rule.clusterMode">是</span>
<span ng-if="!ruleEntity.rule.clusterMode">否</span>
</td>
<td>
{{ruleEntity.rule.paramFlowItemList == undefined ? 0 : ruleEntity.rule.paramFlowItemList.length}}
</td>


+ 1
- 1
sentinel-dashboard/src/main/webapp/resources/dist/css/app.css
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 1
- 1
sentinel-dashboard/src/main/webapp/resources/dist/js/app.js
ファイル差分が大きすぎるため省略します
ファイルの表示


読み込み中…
キャンセル
保存