Browse Source

Add support for authority rule configuration in Sentinel Dashboard (#189)

- Update dashboard API client to support authority rule commands
- Add REST API and frontend page for authority rules

Signed-off-by: Eric Zhao <sczyh16@gmail.com>
master
Eric Zhao GitHub 6 years ago
parent
commit
0855809149
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 648 additions and 1 deletions
  1. +21
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/SentinelApiClient.java
  2. +7
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/AuthorityRuleEntity.java
  3. +38
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemAuthorityRuleStore.java
  4. +146
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/AuthorityRuleController.java
  5. +16
    -0
      sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js
  6. +243
    -0
      sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/authority.js
  7. +4
    -0
      sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html
  8. +40
    -0
      sentinel-dashboard/src/main/webapp/resources/app/scripts/services/authority_service.js
  9. +85
    -0
      sentinel-dashboard/src/main/webapp/resources/app/views/authority.html
  10. +46
    -0
      sentinel-dashboard/src/main/webapp/resources/app/views/dialog/authority-rule-dialog.html
  11. +1
    -1
      sentinel-dashboard/src/main/webapp/resources/dist/js/app.js
  12. +1
    -0
      sentinel-dashboard/src/main/webapp/resources/gulpfile.js

+ 21
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/SentinelApiClient.java View File

@@ -331,6 +331,27 @@ public class SentinelApiClient {
return true;
}

public boolean setAuthorityRuleOfMachine(String app, String ip, int port, List<AuthorityRuleEntity> rules) {
if (rules == null) {
return true;
}
if (StringUtil.isBlank(ip) || port <= 0) {
throw new IllegalArgumentException("Invalid IP or port");
}
String data = JSON.toJSONString(
rules.stream().map(AuthorityRuleEntity::getRule).collect(Collectors.toList()));
try {
data = URLEncoder.encode(data, DEFAULT_CHARSET.name());
} catch (UnsupportedEncodingException e) {
logger.info("Encode rule error", e);
return false;
}
String url = "http://" + ip + ":" + port + "/" + SET_RULES_PATH + "?type=" + AUTHORITY_TYPE + "&data=" + data;
String result = httpGetContent(url);
logger.info("Push authority rules: " + result);
return true;
}

public CompletableFuture<Void> setParamFlowRuleOfMachine(String app, String ip, int port, List<ParamFlowRuleEntity> rules) {
if (rules == null) {
return CompletableFuture.completedFuture(null);


+ 7
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/AuthorityRuleEntity.java View File

@@ -18,12 +18,16 @@ package com.taobao.csp.sentinel.dashboard.datasource.entity.rule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.util.AssertUtil;

import com.fasterxml.jackson.annotation.JsonIgnore;

/**
* @author Eric Zhao
* @since 0.2.1
*/
public class AuthorityRuleEntity extends AbstractRuleEntity<AuthorityRule> {

public AuthorityRuleEntity() {}

public AuthorityRuleEntity(AuthorityRule authorityRule) {
AssertUtil.notNull(authorityRule, "Authority rule should not be null");
this.rule = authorityRule;
@@ -37,14 +41,17 @@ public class AuthorityRuleEntity extends AbstractRuleEntity<AuthorityRule> {
return entity;
}

@JsonIgnore
public String getLimitApp() {
return rule.getLimitApp();
}

@JsonIgnore
public String getResource() {
return rule.getResource();
}

@JsonIgnore
public int getStrategy() {
return rule.getStrategy();
}


+ 38
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemAuthorityRuleStore.java View File

@@ -0,0 +1,38 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.repository.rule;

import java.util.concurrent.atomic.AtomicLong;

import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity;
import org.springframework.stereotype.Component;

/**
* In-memory storage for authority rules.
*
* @author Eric Zhao
* @since 0.2.1
*/
@Component
public class InMemAuthorityRuleStore extends InMemoryRuleRepositoryAdapter<AuthorityRuleEntity> {

private static AtomicLong ids = new AtomicLong(0);

@Override
protected long nextId() {
return ids.incrementAndGet();
}
}

+ 146
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/AuthorityRuleController.java View File

@@ -15,11 +15,27 @@
*/
package com.taobao.csp.sentinel.dashboard.view;

import java.util.Date;
import java.util.List;

import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.util.StringUtil;

import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.AuthorityRuleEntity;
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo;
import com.taobao.csp.sentinel.dashboard.repository.rule.RuleRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
@@ -34,4 +50,134 @@ public class AuthorityRuleController {

@Autowired
private SentinelApiClient sentinelApiClient;
@Autowired
private RuleRepository<AuthorityRuleEntity, Long> repository;

@GetMapping("/rules")
public Result<List<AuthorityRuleEntity>> apiQueryAllRulesForMachine(@RequestParam String app,
@RequestParam String ip,
@RequestParam Integer port) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
if (StringUtil.isEmpty(ip)) {
return Result.ofFail(-1, "ip cannot be null or empty");
}
if (port == null || port <= 0) {
return Result.ofFail(-1, "Invalid parameter: port");
}
try {
List<AuthorityRuleEntity> rules = sentinelApiClient.fetchAuthorityRulesOfMachine(app, ip, port);
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (Throwable throwable) {
logger.error("Error when querying authority rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}

private <R> Result<R> checkEntityInternal(AuthorityRuleEntity entity) {
if (entity == null) {
return Result.ofFail(-1, "bad rule body");
}
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(entity.getIp())) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (entity.getPort() == null || entity.getPort() <= 0) {
return Result.ofFail(-1, "port can't be null");
}
if (entity.getRule() == null) {
return Result.ofFail(-1, "rule can't be null");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource name cannot be null or empty");
}
if (StringUtil.isBlank(entity.getLimitApp())) {
return Result.ofFail(-1, "limitApp should be valid");
}
if (entity.getStrategy() != RuleConstant.AUTHORITY_WHITE
&& entity.getStrategy() != RuleConstant.AUTHORITY_BLACK) {
return Result.ofFail(-1, "Unknown strategy (must be blacklist or whitelist)");
}
return null;
}

@PostMapping("/rule")
public Result<AuthorityRuleEntity> apiAddAuthorityRule(@RequestBody AuthorityRuleEntity entity) {
Result<AuthorityRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(null);
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
} catch (Throwable throwable) {
logger.error("Failed to add authority rule", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
logger.info("Publish authority rules failed after rule add");
}
return Result.ofSuccess(entity);
}

@PutMapping("/rule/{id}")
public Result<AuthorityRuleEntity> apiUpdateParamFlowRule(@PathVariable("id") Long id,
@RequestBody AuthorityRuleEntity entity) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
Result<AuthorityRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(id);
Date date = new Date();
entity.setGmtCreate(null);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
if (entity == null) {
return Result.ofFail(-1, "Failed to save authority rule");
}
} catch (Throwable throwable) {
logger.error("Failed to save authority rule", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
logger.info("Publish authority rules failed after rule update");
}
return Result.ofSuccess(entity);
}

@DeleteMapping("/rule/{id}")
public Result<Long> apiDeleteRule(@PathVariable("id") Long id) {
if (id == null) {
return Result.ofFail(-1, "id cannot be null");
}
AuthorityRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
} catch (Exception e) {
return Result.ofFail(-1, e.getMessage());
}
if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
logger.error("Publish authority rules failed after rule delete");
}
return Result.ofSuccess(id);
}

private boolean publishRules(String app, String ip, Integer port) {
List<AuthorityRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
return sentinelApiClient.setAuthorityRuleOfMachine(app, ip, port, rules);
}
}

+ 16
- 0
sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js View File

@@ -98,6 +98,22 @@ angular
}
})

.state('dashboard.authority', {
templateUrl: 'app/views/authority.html',
url: '/authority/:app',
controller: 'AuthorityRuleController',
resolve: {
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load({
name: 'sentinelDashboardApp',
files: [
'app/scripts/controllers/authority.js',
]
});
}]
}
})

.state('dashboard.degrade', {
templateUrl: 'app/views/degrade.html',
url: '/degrade/:app',


+ 243
- 0
sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/authority.js View File

@@ -0,0 +1,243 @@
/**
* Authority rule controller.
*/
angular.module('sentinelDashboardApp').controller('AuthorityRuleController', ['$scope', '$stateParams', 'AuthorityRuleService', 'ngDialog',
'MachineService',
function ($scope, $stateParams, AuthorityRuleService, ngDialog,
MachineService) {
$scope.app = $stateParams.app;

$scope.rulesPageConfig = {
pageSize: 10,
currentPageIndex: 1,
totalPage: 1,
totalCount: 0,
};
$scope.macsInputConfig = {
searchField: ['text', 'value'],
persist: true,
create: false,
maxItems: 1,
render: {
item: function (data, escape) {
return '<div>' + escape(data.text) + '</div>';
}
},
onChange: function (value, oldValue) {
$scope.macInputModel = value;
}
};

function getMachineRules() {
if (!$scope.macInputModel) {
return;
}
let mac = $scope.macInputModel.split(':');
AuthorityRuleService.queryMachineRules($scope.app, mac[0], mac[1])
.success(function (data) {
if (data.code === 0 && data.data) {
$scope.loadError = undefined;
$scope.rules = data.data;
$scope.rulesPageConfig.totalCount = $scope.rules.length;
} else {
$scope.rules = [];
$scope.rulesPageConfig.totalCount = 0;
$scope.loadError = {message: data.msg};
}
})
.error((data, header, config, status) => {
$scope.loadError = {message: "未知错误"};
});
};
$scope.getMachineRules = getMachineRules;
getMachineRules();

var authorityRuleDialog;

$scope.editRule = function (rule) {
$scope.currentRule = rule;
$scope.authorityRuleDialog = {
title: '编辑授权规则',
type: 'edit',
confirmBtnText: '保存',
};
authorityRuleDialog = ngDialog.open({
template: '/app/views/dialog/authority-rule-dialog.html',
width: 680,
overlay: true,
scope: $scope
});
};

$scope.addNewRule = function () {
var mac = $scope.macInputModel.split(':');
$scope.currentRule = {
app: $scope.app,
ip: mac[0],
port: mac[1],
rule: {
strategy: 0,
limitApp: '',
}
};
$scope.authorityRuleDialog = {
title: '新增授权规则',
type: 'add',
confirmBtnText: '新增',
showAdvanceButton: true,
};
authorityRuleDialog = ngDialog.open({
template: '/app/views/dialog/authority-rule-dialog.html',
width: 680,
overlay: true,
scope: $scope
});
};

function checkRuleValid(rule) {
if (rule.resource === undefined || rule.resource === '') {
alert('资源名称不能为空');
return false;
}
if (rule.limitApp === undefined || rule.limitApp === '') {
alert('流控针对应用不能为空');
return false;
}
if (rule.strategy === undefined) {
alert('必须选择黑白名单模式');
return false;
}
return true;
}

$scope.saveRule = function () {
if (!checkRuleValid($scope.currentRule.rule)) {
return;
}
if ($scope.authorityRuleDialog.type === 'add') {
addNewRuleAndPush($scope.currentRule);
} else if ($scope.authorityRuleDialog.type === 'edit') {
saveRuleAndPush($scope.currentRule, true);
}
};

function addNewRuleAndPush(rule) {
AuthorityRuleService.addNewRule(rule).success((data) => {
if (data.success) {
getMachineRules();
authorityRuleDialog.close();
} else {
alert('添加规则失败:' + data.msg);
}
}).error((data) => {
if (data) {
alert('添加规则失败:' + data.msg);
} else {
alert("添加规则失败:未知错误");
}
});
};

function saveRuleAndPush(rule, edit) {
AuthorityRuleService.saveRule(rule).success(function (data) {
if (data.success) {
alert("修改规则成功");
getMachineRules();
if (edit) {
authorityRuleDialog.close();
} else {
confirmDialog.close();
}
} else {
alert('修改规则失败:' + data.msg);
}
}).error((data) => {
if (data) {
alert('修改规则失败:' + data.msg);
} else {
alert("修改规则失败:未知错误");
}
});
}

function deleteRuleAndPush(entity) {
if (entity.id === undefined || isNaN(entity.id)) {
alert('规则 ID 不合法!');
return;
}
AuthorityRuleService.deleteRule(entity).success((data) => {
if (data.code == 0) {
getMachineRules();
confirmDialog.close();
} else {
alert('删除规则失败:' + data.msg);
}
}).error((data) => {
if (data) {
alert('删除规则失败:' + data.msg);
} else {
alert("删除规则失败:未知错误");
}
});
};

var confirmDialog;
$scope.deleteRule = function (ruleEntity) {
$scope.currentRule = ruleEntity;
$scope.confirmDialog = {
title: '删除授权规则',
type: 'delete_rule',
attentionTitle: '请确认是否删除如下授权限流规则',
attention: '资源名: ' + ruleEntity.rule.resource + ', 流控应用: ' + ruleEntity.rule.limitApp +
', 类型: ' + (ruleEntity.rule.strategy === 0 ? '白名单' : '黑名单'),
confirmBtnText: '删除',
};
confirmDialog = ngDialog.open({
template: '/app/views/dialog/confirm-dialog.html',
scope: $scope,
overlay: true
});
};

$scope.confirm = function () {
if ($scope.confirmDialog.type === 'delete_rule') {
deleteRuleAndPush($scope.currentRule);
} else {
console.error('error');
}
};

queryAppMachines();

function queryAppMachines() {
MachineService.getAppMachines($scope.app).success(
function (data) {
if (data.code == 0) {
// $scope.machines = data.data;
if (data.data) {
$scope.machines = [];
$scope.macsInputOptions = [];
data.data.forEach(function (item) {
if (item.health) {
$scope.macsInputOptions.push({
text: item.ip + ':' + item.port,
value: item.ip + ':' + item.port
});
}
});
}
if ($scope.macsInputOptions.length > 0) {
$scope.macInputModel = $scope.macsInputOptions[0].value;
}
} else {
$scope.macsInputOptions = [];
}
}
);
};
$scope.$watch('macInputModel', function () {
if ($scope.macInputModel) {
getMachineRules();
}
});
}]);

+ 4
- 0
sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html View File

@@ -48,6 +48,10 @@
<a ui-sref="dashboard.system({app: entry.app})">
<i class="glyphicon glyphicon-lock"></i>&nbsp;&nbsp;系统规则</a>
</li>
<li ui-sref-active="active">
<a ui-sref="dashboard.authority({app: entry.app})">
<i class="glyphicon glyphicon-check"></i>&nbsp;&nbsp;授权规则</a>
</li>
<li ui-sref-active="active">
<a ui-sref="dashboard.machine({app: entry.app})">
<i class="glyphicon glyphicon-th-list"></i>&nbsp;&nbsp;机器列表</a>


+ 40
- 0
sentinel-dashboard/src/main/webapp/resources/app/scripts/services/authority_service.js View File

@@ -0,0 +1,40 @@
/**
* Authority rule service.
*/
angular.module('sentinelDashboardApp').service('AuthorityRuleService', ['$http', function ($http) {
this.queryMachineRules = function(app, ip, port) {
var param = {
app: app,
ip: ip,
port: port
};
return $http({
url: '/authority/rules',
params: param,
method: 'GET'
});
};

this.addNewRule = function(rule) {
return $http({
url: '/authority/rule',
data: rule,
method: 'POST'
});
};

this.saveRule = function (entity) {
return $http({
url: '/authority/rule/' + entity.id,
data: entity,
method: 'PUT'
});
};

this.deleteRule = function (entity) {
return $http({
url: '/authority/rule/' + entity.id,
method: 'DELETE'
});
};
}]);

+ 85
- 0
sentinel-dashboard/src/main/webapp/resources/app/views/authority.html View File

@@ -0,0 +1,85 @@
<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-disabled="!macInputModel" ng-click="addNewRule()">
<i class="fa fa-plus"></i>&nbsp;&nbsp;新增授权规则</button>
</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>
<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">
<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>
</div>
</div>

<!--.tools-header -->
<div class="card-body" style="padding: 0px 0px;">
<table class="table" style="border-left: none; border-right:none;margin-top: 10px;">
<thead>
<tr style="background: #F3F5F7;">
<td style="width: 40%">
资源名
</td>
<td style="width: 10%;">
流控应用
</td>
<td style="width: 10%;">
授权类型
</td>
<td style="width: 12%;">
操作
</td>
</tr>
</thead>
<tbody>
<tr dir-paginate="ruleEntity in rules | filter: searchKey | itemsPerPage: rulesPageConfig.pageSize " current-page="rulesPageConfig.currentPageIndex"
pagination-id="entriesPagination">
<td style="word-wrap:break-word;word-break:break-all;">{{ruleEntity.rule.resource}}</td>
<td style="word-wrap:break-word;word-break:break-all;">{{ruleEntity.rule.limitApp }}</td>
<td>
<span ng-if="ruleEntity.rule.strategy == 0">白名单</span>
<span ng-if="ruleEntity.rule.strategy == 1">黑名单</span>
</td>
<td>
<button class="btn btn-xs btn-default" type="button" ng-click="editRule(ruleEntity)" style="font-size: 12px; height:25px;">编辑</button>
<button class="btn btn-xs btn-default" type="button" ng-click="deleteRule(ruleEntity)" style="font-size: 12px; height:25px;">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- .card-body -->
<div class="pagination-footer">
<dir-pagination-controls boundary-links="true" template-url="app/views/pagination.tpl.html" pagination-id="entriesPagination"
on-page-change="">
</dir-pagination-controls>
<div class="tools" style="">
<span>共 {{rulesPageConfig.totalCount}} 条记录, </span>
<span>
每页
<input class="form-control" ng-model="rulesPageConfig.pageSize"> 条记录
</span>
</div>
<!-- .tools -->
</div>
<!-- pagination-footer -->
</div>
<!-- .card -->
</div>
<!-- .col-md-12 -->
</div>
<!-- -->
</div>
<!-- .container-fluid -->

+ 46
- 0
sentinel-dashboard/src/main/webapp/resources/app/views/dialog/authority-rule-dialog.html View File

@@ -0,0 +1,46 @@
<div>
<span class="brand" style="font-weight:bold;">{{authorityRuleDialog.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 class="form-group">
<label class="col-sm-2 control-label">资源名</label>
<div class="col-sm-9">
<input type="text" ng-if="authorityRuleDialog.type == 'edit'" class="form-control" placeholder="资源名" ng-model='currentRule.rule.resource'
disabled="" />
<input type="text" ng-if="authorityRuleDialog.type == 'add'" class="form-control highlight-border" placeholder="资源名称" ng-model='currentRule.rule.resource'
/>
</div>
</div>

<div class="form-group">
<label class="col-sm-2 control-label">流控应用</label>
<div class="col-sm-9">
<input type="text" class="form-control highlight-border" ng-model='currentRule.rule.limitApp' placeholder='指调用方,多个调用方名称用半角英文逗号(,)分隔'
/>
</div>
</div>

<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='currentRule.rule.strategy' />&nbsp;白名单&nbsp;&nbsp;
<input type="radio" name="strategy" value="1" ng-model='currentRule.rule.strategy' />&nbsp;黑名单
</div>
</div>
</div>

</form>
</div>
<div class="separator"></div>
<div clss="row" style="margin-top: 20px;">
<button class="btn btn-default-inverse" style="float:right; height: 30px;font-size: 12px;margin-left: 10px;" ng-click="closeThisDialog()">取消</button>
<button class="btn btn-danger-inverse" style="float:right; height: 30px;font-size: 12px;margin-left: 10px;" ng-click="saveRule()">{{authorityRuleDialog.confirmBtnText}}</button>
<button ng-if="authorityRuleDialog.saveAndContinueBtnText" class="btn btn-default" style="float:right; height: 30px;font-size: 12px;"
ng-click="saveRuleAndContinue()">{{authorityRuleDialog.saveAndContinueBtnText}}</button>
</div>
</div>
</div>
</div>

+ 1
- 1
sentinel-dashboard/src/main/webapp/resources/dist/js/app.js
File diff suppressed because it is too large
View File


+ 1
- 0
sentinel-dashboard/src/main/webapp/resources/gulpfile.js View File

@@ -51,6 +51,7 @@ const JS_APP = [
'app/scripts/services/identityservice.js',
'app/scripts/services/metricservice.js',
'app/scripts/services/param_flow_service.js',
'app/scripts/services/authority_service.js',
];

gulp.task('lib', function () {


Loading…
Cancel
Save