LDAP基本功能实现,感谢@dbdocker 贡献代码!
parent
7bd42897d5
commit
206a44cfb6
|
@ -7,7 +7,7 @@ skey_path = 'ConsulManager/assets/secret/skey'
|
||||||
if consul_kv.get_kv_dict(skey_path) == {}:
|
if consul_kv.get_kv_dict(skey_path) == {}:
|
||||||
consul_kv.put_kv(skey_path,{'sk':''.join(str(uuid.uuid4()).split('-'))})
|
consul_kv.put_kv(skey_path,{'sk':''.join(str(uuid.uuid4()).split('-'))})
|
||||||
|
|
||||||
from views import login, blackbox, consul, jobs, nodes, selfnode, selfrds, avd, exp, jms, edit_cloud
|
from views import login, blackbox, consul, jobs, nodes, selfnode, selfrds, avd, exp, jms, edit_cloud, ldap
|
||||||
from views.prom import cloud_mysql_metrics
|
from views.prom import cloud_mysql_metrics
|
||||||
from units.cloud import huaweicloud,alicloud,tencent_cloud
|
from units.cloud import huaweicloud,alicloud,tencent_cloud
|
||||||
from units.avd import avd_list
|
from units.avd import avd_list
|
||||||
|
@ -29,6 +29,7 @@ app.register_blueprint(exp.blueprint)
|
||||||
app.register_blueprint(jms.blueprint)
|
app.register_blueprint(jms.blueprint)
|
||||||
app.register_blueprint(edit_cloud.blueprint)
|
app.register_blueprint(edit_cloud.blueprint)
|
||||||
app.register_blueprint(cloud_mysql_metrics.blueprint)
|
app.register_blueprint(cloud_mysql_metrics.blueprint)
|
||||||
|
app.register_blueprint(ldap.blueprint)
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
JOBS = []
|
JOBS = []
|
||||||
|
|
|
@ -10,6 +10,7 @@ xlrd==1.2.0
|
||||||
#pyDes==2.0.1
|
#pyDes==2.0.1
|
||||||
pycryptodome==3.14.1
|
pycryptodome==3.14.1
|
||||||
beautifulsoup4==4.11.1
|
beautifulsoup4==4.11.1
|
||||||
|
ldap3==2.9.1
|
||||||
huaweicloudsdkcore==3.1.8
|
huaweicloudsdkcore==3.1.8
|
||||||
huaweicloudsdkecs==3.1.8
|
huaweicloudsdkecs==3.1.8
|
||||||
huaweicloudsdkeps==3.1.8
|
huaweicloudsdkeps==3.1.8
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
"""
|
||||||
|
自定义返回数据格式
|
||||||
|
"""
|
||||||
|
|
||||||
|
def JsonResponse(data:str=None,msg:str=None,success:bool=None,code:int=200):
|
||||||
|
"""
|
||||||
|
An HttpResponse that allows its data to be rendered into
|
||||||
|
arbitrary media types.
|
||||||
|
"""
|
||||||
|
data = {"data": data, "msg": msg, "success": success, "code": code}
|
||||||
|
return data
|
|
@ -0,0 +1,91 @@
|
||||||
|
"""
|
||||||
|
ldap 用户认证
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ldap3 import Server, Connection, ALL
|
||||||
|
|
||||||
|
from units.ldap.ldap_consul import Ldap_Consul
|
||||||
|
|
||||||
|
|
||||||
|
class Ldap(object):
|
||||||
|
def __init__(self,**args):
|
||||||
|
self.ldap_url,self.port,self.rule,self.password = Ldap_Consul.get_consul_args(**args)
|
||||||
|
server = Server(self.ldap_url,port=self.port, get_info=ALL,connect_timeout=5)
|
||||||
|
self.conn = Connection(server, user=self.rule, password=self.password, auto_bind=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#校验登录
|
||||||
|
def authpass(self, username, password):
|
||||||
|
server = Server(self.ldap_url,port=self.port, get_info=ALL,connect_timeout=5)
|
||||||
|
conn = Connection(server, user="uid={0},xxxxxxxxxxxxx".format(username),
|
||||||
|
password="{0}".format(password),
|
||||||
|
check_names=True, lazy=False, raise_exceptions=False)
|
||||||
|
try:
|
||||||
|
conn.bind()
|
||||||
|
except Exception:
|
||||||
|
conn.bind()
|
||||||
|
|
||||||
|
if conn.result["description"] == "success":
|
||||||
|
data = True
|
||||||
|
else:
|
||||||
|
data = False
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
#连接
|
||||||
|
def conn_ldap(self):
|
||||||
|
self.conn.search('dc=lishicloud,dc=com', '(objectclass=person)',
|
||||||
|
attributes=['cn', 'displayName', 'departmentNumber'])
|
||||||
|
entry = self.conn.response
|
||||||
|
return entry
|
||||||
|
|
||||||
|
|
||||||
|
#获取用户
|
||||||
|
def get_user(self,username=None,all=False):
|
||||||
|
ldap_user = []
|
||||||
|
if all == False:
|
||||||
|
try:
|
||||||
|
result = self.conn_ldap()
|
||||||
|
except Exception:
|
||||||
|
result = self.conn_ldap()
|
||||||
|
|
||||||
|
for user in result:
|
||||||
|
users = user.get("raw_attributes").get("cn")[0].decode("utf8")
|
||||||
|
if users == username:
|
||||||
|
try:
|
||||||
|
users = user.get("raw_attributes").get("displayName")[0].decode("utf-8")
|
||||||
|
return users
|
||||||
|
except Exception as e:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
result = self.conn_ldap()
|
||||||
|
for user in result:
|
||||||
|
users = user.get("raw_attributes").get("cn")[0].decode("utf8")
|
||||||
|
ldap_user.append(users)
|
||||||
|
return ldap_user
|
||||||
|
|
||||||
|
#创建用户
|
||||||
|
def create_user(self):
|
||||||
|
objectclass = ['top', 'inetOrgPerson', 'posixAccount']
|
||||||
|
c = self.conn.add('uid=user1,ou=People,dc=xxx,dc=com',objectclass,
|
||||||
|
{'cn': "user1", 'sn': 'user1',"employeeType":"developer",
|
||||||
|
'gidNumber': 501, 'homeDirectory': '/home/users/{0}', 'uidNumber': 5000,"givenName":"user1",
|
||||||
|
"loginShell":"/bin/bash",'displayName': "测试用户",'userPassword': "111111", 'mail': 'user1@qq.com'}),
|
||||||
|
print(c)
|
||||||
|
|
||||||
|
|
||||||
|
#删除用户
|
||||||
|
def delete_user(self):
|
||||||
|
c = self.conn.delete('cn=xxx,ou=People,dc=xxx,dc=com')
|
||||||
|
print(c)
|
||||||
|
|
||||||
|
# def __del__(self):
|
||||||
|
# self.conn.delete()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ldap = Ldap()
|
||||||
|
result = ldap.delete_user()
|
||||||
|
print(result)
|
|
@ -0,0 +1,34 @@
|
||||||
|
"""
|
||||||
|
截取前端ldap信息存入consul
|
||||||
|
"""
|
||||||
|
from units import consul_kv
|
||||||
|
|
||||||
|
|
||||||
|
class Ldap_Consul():
|
||||||
|
|
||||||
|
"""
|
||||||
|
存储ldap信息
|
||||||
|
"""
|
||||||
|
@staticmethod
|
||||||
|
def set_consul_args(**kwargs):
|
||||||
|
kwargs['port'] = int(kwargs.get("port"))
|
||||||
|
result = consul_kv.put_kv(f'ConsulManager/ldap/report', {**kwargs})
|
||||||
|
if result:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
获取ldap信息进行链接服务端
|
||||||
|
"""
|
||||||
|
@staticmethod
|
||||||
|
def get_consul_args(**kwargs):
|
||||||
|
result = consul_kv.get_kv_dict("ConsulManager/ldap/report")
|
||||||
|
try:
|
||||||
|
result.get("ConsulManager/ldap/report").get("ldap_url")
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return result.get("ConsulManager/ldap/report").get("ldap_url"),\
|
||||||
|
result.get("ConsulManager/ldap/report").get("port"),\
|
||||||
|
result.get("ConsulManager/ldap/report").get("rule"),\
|
||||||
|
result.get("ConsulManager/ldap/report").get("password")
|
|
@ -0,0 +1,40 @@
|
||||||
|
"""
|
||||||
|
ldap信息填写
|
||||||
|
"""
|
||||||
|
from flask import Blueprint
|
||||||
|
from flask_restful import reqparse, Resource, Api
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from units.json_response import JsonResponse
|
||||||
|
from units.ldap.ldap_consul import Ldap_Consul
|
||||||
|
|
||||||
|
sys.path.append("..")
|
||||||
|
from units import token_auth, consul_kv
|
||||||
|
from itsdangerous import TimedJSONWebSignatureSerializer
|
||||||
|
|
||||||
|
secret_key = consul_kv.get_value('ConsulManager/assets/secret/skey')['sk']
|
||||||
|
s = TimedJSONWebSignatureSerializer(secret_key,expires_in=28800)
|
||||||
|
|
||||||
|
blueprint = Blueprint('ldap',__name__)
|
||||||
|
api = Api(blueprint)
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
|
||||||
|
parser.add_argument('ldap_url',type=str)
|
||||||
|
parser.add_argument('password',type=str)
|
||||||
|
parser.add_argument('port',type=str)
|
||||||
|
parser.add_argument('rule',type=str)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class LdapView(Resource):
|
||||||
|
"""
|
||||||
|
封装了公共返回格式
|
||||||
|
{"code": code,"success": success, "message": msg, "data": data}
|
||||||
|
"""
|
||||||
|
def post(self,):
|
||||||
|
args = parser.parse_args()
|
||||||
|
Ldap_Consul.set_consul_args(**args)
|
||||||
|
return JsonResponse(data="", code=20000, success=True, msg="添加统一认证成功")
|
||||||
|
|
||||||
|
api.add_resource(LdapView, '/api/ldap/config')
|
|
@ -5,6 +5,7 @@ import sys
|
||||||
sys.path.append("..")
|
sys.path.append("..")
|
||||||
from config import admin_passwd
|
from config import admin_passwd
|
||||||
from units import token_auth, consul_kv
|
from units import token_auth, consul_kv
|
||||||
|
from units.ldap.LdapUser import Ldap
|
||||||
secret_key = consul_kv.get_value('ConsulManager/assets/secret/skey')['sk']
|
secret_key = consul_kv.get_value('ConsulManager/assets/secret/skey')['sk']
|
||||||
s = TimedJSONWebSignatureSerializer(secret_key,expires_in=28800)
|
s = TimedJSONWebSignatureSerializer(secret_key,expires_in=28800)
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ api = Api(blueprint)
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument('username',type=str)
|
parser.add_argument('username',type=str)
|
||||||
parser.add_argument('password',type=str)
|
parser.add_argument('password',type=str)
|
||||||
|
parser.add_argument('ldap',type=str)
|
||||||
|
|
||||||
class User(Resource):
|
class User(Resource):
|
||||||
@token_auth.auth.login_required
|
@token_auth.auth.login_required
|
||||||
|
@ -23,15 +25,28 @@ class User(Resource):
|
||||||
"code": 20000,
|
"code": 20000,
|
||||||
"data": {"roles": ["admin"],"name": "admin","avatar": "/sl.png"}}
|
"data": {"roles": ["admin"],"name": "admin","avatar": "/sl.png"}}
|
||||||
def post(self, user_opt):
|
def post(self, user_opt):
|
||||||
if user_opt == 'login':
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
username = args.get('username')
|
username = args.get('username')
|
||||||
password = args.get('password')
|
password = args.get('password')
|
||||||
|
ldap = args.get('ldap')
|
||||||
|
#ldap认证
|
||||||
|
if user_opt == 'login' and ldap == "True":
|
||||||
|
print("ldap")
|
||||||
|
ldap_obj = Ldap()
|
||||||
|
ldap_result = ldap_obj.authpass(username,password)
|
||||||
|
if ldap_result:
|
||||||
|
token = str(s.dumps(admin_passwd), encoding="utf-8")
|
||||||
|
return {"code": 20000, "data": {"token": "Bearer " + token,"username":username}}
|
||||||
|
return {"code": 40000, "data": "ldap校验失败!"}
|
||||||
|
else:
|
||||||
|
if user_opt == 'login':
|
||||||
|
print("非ldap")
|
||||||
if password == admin_passwd:
|
if password == admin_passwd:
|
||||||
token = str(s.dumps(admin_passwd),encoding="utf-8")
|
token = str(s.dumps(admin_passwd),encoding="utf-8")
|
||||||
return {"code": 20000,"data": {"token": "Bearer " + token}}
|
return {"code": 20000,"data": {"token": "Bearer " + token,"username":username}}
|
||||||
else:
|
else:
|
||||||
return {"code": 40000, "data": "密码错误!"}
|
return {"code": 40000, "data": "密码错误!"}
|
||||||
|
|
||||||
elif user_opt == 'logout':
|
elif user_opt == 'logout':
|
||||||
return {"code": 20000,"data": "success"}
|
return {"code": 20000,"data": "success"}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import request from '@/utils/request-ops'
|
||||||
|
|
||||||
|
export function setldap(data) {
|
||||||
|
return request({
|
||||||
|
url: '/api/ldap/config',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
|
@ -46,7 +46,7 @@ Object.keys(filters).forEach(key => {
|
||||||
})
|
})
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
Vue.prototype.VER = 'v0.9.7'
|
Vue.prototype.VER = 'v0.10.0-alpha'
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
|
|
|
@ -234,6 +234,16 @@ export const constantRoutes = [
|
||||||
meta: { title: '漏洞通知', icon: 'el-icon-chat-line-square' }
|
meta: { title: '漏洞通知', icon: 'el-icon-chat-line-square' }
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/settings',
|
||||||
|
component: Layout,
|
||||||
|
children: [{
|
||||||
|
path: 'index',
|
||||||
|
name: '全局配置',
|
||||||
|
component: () => import('@/views/ldap/index'),
|
||||||
|
meta: { title: '全局配置', icon: 'el-icon-chat-line-square' }
|
||||||
|
}]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/link',
|
path: '/link',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
|
|
|
@ -30,9 +30,9 @@ const mutations = {
|
||||||
const actions = {
|
const actions = {
|
||||||
// user login
|
// user login
|
||||||
login({ commit }, userInfo) {
|
login({ commit }, userInfo) {
|
||||||
const { username, password } = userInfo
|
const { username, password, Ldapchecked } = userInfo
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
login({ username: username.trim(), password: password }).then(response => {
|
login({ username: username.trim(), password: password, ldap: Ldapchecked }).then(response => {
|
||||||
const { data } = response
|
const { data } = response
|
||||||
commit('SET_TOKEN', data.token)
|
commit('SET_TOKEN', data.token)
|
||||||
setToken(data.token)
|
setToken(data.token)
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
<template>
|
||||||
|
<el-main>
|
||||||
|
<el-tabs :tab-position="tabPosition" style="height: auto;width: 600px;">
|
||||||
|
<el-tab-pane label="统一认证">
|
||||||
|
<!-- 统一认证 -->
|
||||||
|
<el-form ref="ruleForm" :model="ruleForm" status-icon :rules="rules" label-width="100px" class="demo-ruleForm">
|
||||||
|
<el-form-item label="认证地址:" prop="ldap_url">
|
||||||
|
<el-input v-model="ruleForm.ldap_url" type="text" autocomplete="off" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="端口号:" prop="port">
|
||||||
|
<el-input v-model="ruleForm.port" type="text" autocomplete="off" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-alert class="alert" title="示例:uid=xxx,cn=xxx,dc=xxx,dc=xxx" type="info" />
|
||||||
|
<el-form-item label="bind_dn:" prop="rule">
|
||||||
|
<el-input v-model="ruleForm.rule" type="text" autocomplete="off" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="认证密码:" prop="password">
|
||||||
|
<el-input v-model="ruleForm.password" type="password" autocomplete="off" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>
|
||||||
|
<el-button @click="resetForm('ruleForm')">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</el-main>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { setldap } from '@/api/ldap'
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tabPosition: 'left',
|
||||||
|
ruleForm: {}, // 存储ldap
|
||||||
|
rules: {
|
||||||
|
ldap_url: [{ validator: 'xxx', trigger: 'blur' }],
|
||||||
|
port: [{ validator: 'xxxx', trigger: 'blur' }],
|
||||||
|
rule: [{ validator: 'xxx', trigger: 'blur' }],
|
||||||
|
password: [{ validator: 'xxx', trigger: 'blur' }]
|
||||||
|
} // 校验规则
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submitForm(formName) {
|
||||||
|
this.$refs[formName].validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
// 使用箭头函数进行发送请求
|
||||||
|
setldap(this.ruleForm).then(response => {
|
||||||
|
if (response.code === 200) {
|
||||||
|
this.$message({
|
||||||
|
type: 'success',
|
||||||
|
message: response.message
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$message({
|
||||||
|
type: 'error',
|
||||||
|
message: response.message
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log('error submit!!')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
resetForm(formName) {
|
||||||
|
this.$refs[formName].resetFields()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.alert{
|
||||||
|
width: 500px;
|
||||||
|
margin-left:100px;
|
||||||
|
margin-bottom:2px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -41,7 +41,7 @@
|
||||||
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
|
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
|
||||||
</span>
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-checkbox v-model="loginForm.Ldapchecked" label="启动ldap验证" border class="ldap" />
|
||||||
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登 录</el-button>
|
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登 录</el-button>
|
||||||
|
|
||||||
</el-form>
|
</el-form>
|
||||||
|
@ -52,13 +52,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { validUsername } from '@/utils/validate'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
data() {
|
data() {
|
||||||
const validateUsername = (rule, value, callback) => {
|
const validateUsername = (rule, value, callback) => {
|
||||||
if (!validUsername(value)) {
|
if (!value) {
|
||||||
callback(new Error('Please enter the correct user name'))
|
callback(new Error('Please enter the correct user name'))
|
||||||
} else {
|
} else {
|
||||||
callback()
|
callback()
|
||||||
|
@ -74,7 +73,8 @@ export default {
|
||||||
return {
|
return {
|
||||||
loginForm: {
|
loginForm: {
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
password: ''
|
password: '',
|
||||||
|
Ldapchecked: false // ldap验证开关
|
||||||
},
|
},
|
||||||
loginRules: {
|
loginRules: {
|
||||||
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
|
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
|
||||||
|
@ -176,6 +176,10 @@ $bg:#2d3a4b;
|
||||||
$dark_gray:#889aa4;
|
$dark_gray:#889aa4;
|
||||||
$light_gray:#eee;
|
$light_gray:#eee;
|
||||||
|
|
||||||
|
.ldap{
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.login-container {
|
.login-container {
|
||||||
// min-height: 100%;
|
// min-height: 100%;
|
||||||
// width: 100%;
|
// width: 100%;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<el-select v-model="services" multiple placeholder="请选择需要生成配置的服务" filterable collapse-tags clearable style="width: 350px" class="filter-item">
|
<el-select v-model="services" multiple placeholder="请选择需要生成配置的服务" filterable collapse-tags clearable style="width: 300px" class="filter-item">
|
||||||
<el-option v-for="item in services_list" :key="item" :label="item" :value="item" />
|
<el-option v-for="item in services_list" :key="item" :label="item" :value="item" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-select v-model="ostype" multiple placeholder="请选择系统" filterable clearable class="filter-item">
|
<el-select v-model="ostype" multiple placeholder="请选择系统" filterable clearable style="width: 200px" class="filter-item">
|
||||||
<el-option v-for="item in ostype_list" :key="item" :label="item" :value="item" />
|
<el-option v-for="item in ostype_list" :key="item" :label="item" :value="item" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-button class="filter-item" type="primary" icon="el-icon-magic-stick" @click="fetchEcsConfig">
|
<el-button class="filter-item" type="primary" icon="el-icon-magic-stick" @click="fetchEcsConfig">
|
||||||
生成配置
|
生成配置
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
Loading…
Reference in New Issue