先添加一个空页面: resource/sync.vue
<template>
<div>
资产同步
</div>
</template>
<script>
export default {
name: "ResourceSync",
}
</script>
然后添加路由:
{
path: "/cmdb",
component: Layout,
redirect: "/cmdb/search",
children: [
...
{
path: "sync/list",
component: () => import("@/views/cmdb/sync/index"),
name: "ResourceSync",
},
],
},
补充menu:
<el-submenu index="/sync">
<!-- 添加个title -->
<template slot="title">
<i class="el-icon-s-tools"></i>
<span slot="title">资源同步</span>
</template>
<!-- 导航条目 -->
<el-menu-item index="/cmdb/sync/list">凭证管理</el-menu-item>
</el-submenu>
API接口,我们上次课已经完成:
添加前端API: cmdb/secret.js
import request from "@/api/client";
export function LIST_SECRET(params) {
return request({
url: "/secrets",
method: "get",
params: params,
});
}
通过接口获取数据:
<script>
import { LIST_SECRET } from "@/api/cmdb/secret";
export default {
name: "ResourceSync",
data() {
return {
secrets: [],
fetchSecretLoading: false,
total: 0,
query: {
page_size: 20,
page_number: 1,
},
};
},
mounted() {
this.get_secrets();
},
methods: {
async get_secrets() {
this.fetchSecretLoading = true;
try {
const resp = await LIST_SECRET(this.query);
this.secrets = resp.data.items;
this.total = resp.data.total;
} catch (error) {
this.$notify.error({
title: "获取凭证异常",
message: error,
});
} finally {
this.fetchSecretLoading = false;
}
},
},
};
</script>
使用Table展示数据
<template>
<div>
<tips :tips="tips" />
<!-- 凭证表格 -->
<div class="box-shadow secret-box">
<el-table
:data="secrets"
v-loading="fetchSecretLoading"
style="width: 100%"
>
<el-table-column prop="name" label="描述">
<template slot-scope="{ row }">
{{ row.description }}
</template>
</el-table-column>
<el-table-column prop="name" label="厂商">
<template slot-scope="{ row }">
{{ row.vendor }}
</template>
</el-table-column>
<el-table-column prop="name" label="类型">
<template slot-scope="{ row }">
{{ row.crendential_type }}
</template>
</el-table-column>
<el-table-column prop="name" label="凭证">
<template slot-scope="{ row }">
{{ row.api_key }}
</template>
</el-table-column>
<el-table-column prop="name" label="同步地域">
<template slot-scope="{ row }">
{{ row.allow_regions.join(",") }}
</template>
</el-table-column>
<el-table-column prop="name" label="创建时间">
<template slot-scope="{ row }">
{{ row.create_at | parseTime }}
</template>
</el-table-column>
<el-table-column prop="name" label="速率限制">
<template slot-scope="{ row }">
{{ row.request_rate }}
</template>
</el-table-column>
<el-table-column prop="操作" align="center" label="状态">
<template>
<el-button type="text" disabled>删除</el-button>
<el-button type="text" disabled>禁用</el-button>
<el-button type="text" disabled>测试</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="query.page_number"
:limit.sync="query.page_size"
@pagination="get_secrets"
/>
</div>
</div>
</template>
我们在table上方添加 搜索和创建
<!-- 表格功能区 -->
<div class="table-op">
<div class="search">
<el-input
v-model="query.keywords"
placeholder="请输入凭证描述|API KEY|用户名 敲回车进行搜索"
@keyup.enter.native="get_secrets"
></el-input>
</div>
<div class="op">
<el-button icon="el-icon-plus" type="primary">添加凭证</el-button>
</div>
</div>
样式调试
<style scoped>
.secret-box {
margin-top: 8px;
}
.table-op {
margin-top: 12px;
display: flex;
align-items: center;
}
.op {
margin-left: auto;
}
</style>
后端 API 添加关键字搜索
我们采用抽屉组件 作为添加凭证的弹窗: Drawer 抽屉
添加组件: AddSecret.vue
<template>
<div>
<!-- 添加凭证表单 -->
<el-drawer
title="添加凭证"
:visible.sync="isVisible"
:before-close="handleClose"
size="40%"
>
<span>我来啦!</span>
</el-drawer>
</div>
</template>
<script>
export default {
name: "AddSecret",
props: {
visible: {
default: false,
type: Boolean,
},
},
methods: {
handleClose() {
this.$emit("update:visible", false);
},
},
computed: {
isVisible: {
get() {
return this.visible;
},
set(val) {
this.$emit("update:visible", val);
},
},
},
};
</script>
然后在页面引入该组件
<!-- 添加secret -->
<add-secret :visible.sync="showAddSecretDrawer" />
import AddSecret from "./components/AddSecret";
export default {
name: "ResourceSync",
components: { Tips, Pagination, AddSecret },
点击添加组件是,显示我们的抽屉
<el-button icon="el-icon-plus" type="primary" @click="handleAddSecret"
>添加凭证</el-button
>
补充函数
handleAddSecret() {
this.showAddSecretDrawer = true;
},
测试该组件能否正常显示
使用Form组件来 显示表单: Form 表单
我们在抽屉组件里面添加一个添加凭证的表单:
<template>
<div>
<!-- 添加凭证表单 -->
<el-drawer
title="添加凭证"
:visible.sync="isVisible"
:before-close="handleClose"
size="40%"
>
<div class="add-secret-form">
<el-form ref="addSecretForm" :model="form">
<el-form-item label="厂商" :label-width="formLabelWidth">
</el-form-item>
<el-form-item label="类型" :label-width="formLabelWidth">
</el-form-item>
<div>
<el-form-item label="API Key" :label-width="formLabelWidth">
</el-form-item>
<el-form-item label="API Secret" :label-width="formLabelWidth">
</el-form-item>
<el-form-item label="Region" :label-width="formLabelWidth">
</el-form-item>
</div>
<div>
<el-form-item label="Address" :label-width="formLabelWidth">
</el-form-item>
<el-form-item label="Username" :label-width="formLabelWidth">
</el-form-item>
<el-form-item label="Password" :label-width="formLabelWidth">
</el-form-item>
</div>
<el-form-item label="速率限制" :label-width="formLabelWidth">
</el-form-item>
</el-form>
<div class="drawer-footer">
<el-button @click="cancelForm">取 消</el-button>
<el-button
type="primary"
:loading="addSecretLoading"
@click="submit"
>{{ addSecretLoading ? "提交中 ..." : "确 定" }}</el-button
>
</div>
</div>
</el-drawer>
</div>
</template>
<script>
export default {
name: "AddSecret",
props: {
visible: {
default: false,
type: Boolean,
},
},
data() {
return {
form: {},
formLabelWidth: "80px",
addSecretLoading: false,
};
},
methods: {
handleClose() {
this.$emit("update:visible", false);
},
cancelForm() {
this.addSecretLoading = false;
this.$emit("update:visible", false);
},
},
computed: {
isVisible: {
get() {
return this.visible;
},
set(val) {
this.$emit("update:visible", val);
},
},
},
};
</script>
最终骨架样子:
可以看到有2个地方需要有枚举的数据:
后端 sync模块 为该枚举提供API接口
utils下面补充专门用于返回枚举的数据结构
type EnumDescribe struct {
Value string `json:"value"`
Describe string `json:"describe"`
}
厂商: vednor, resource 模块
func RegistAPI(r *httprouter.Router) {
...
r.GET("/vendors", api.ListVendor)
}
handler
func (h *handler) ListVendor(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
resp := []utils.EnumDescribe{
{Value: resource.VendorAliYun.String(), Describe: "阿里云"},
{Value: resource.VendorTencent.String(), Describe: "腾讯云"},
{Value: resource.VendorHuaWei.String(), Describe: "华为云"},
{Value: resource.VendorVsphere.String(), Describe: "Vsphere"},
}
response.Success(w, resp)
}
类型: crendential_type
func RegistAPI(r *httprouter.Router) {
...
r.GET("/crendential_types", api.ListCrendentialType)
}
handler
func (h *handler) ListCrendentialType(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
resp := []utils.EnumDescribe{
{Value: syncer.CrendentialAPIKey.String(), Describe: "API凭证"},
{Value: syncer.CrendentialPassword.String(), Describe: "用户名密码"},
}
response.Success(w, resp)
}
测试API
前端添加枚举的API: cmdb/enum.js
import request from "@/api/client";
export function LIST_VENDOR(params) {
return request({
url: "/vendors",
method: "get",
params: params,
});
}
export function LIST_CRENDENTIAL_TYPE(params) {
return request({
url: "/crendential_types",
method: "get",
params: params,
});
}
由于我们列表页的枚举显示的API 值,不太友好, 可以将其转换为对应的描述显示:
secret/index.vue
import { LIST_VENDOR, LIST_CRENDENTIAL_TYPE } from "@/api/cmdb/enum.js";
// 补充枚举值
data() {
return {
...
vendors: [],
crendentialTypes: [],
};
}
// 编写请求方法
async getVendors() {
try {
const resp = await LIST_VENDOR();
this.vendors = resp.data.items;
} catch (error) {
this.$notify.error({
title: "获取厂商异常",
message: error,
});
}
},
async getCrendentialTypes() {
try {
const resp = await LIST_CRENDENTIAL_TYPE();
this.crendentialTypes = resp.data.items;
} catch (error) {
this.$notify.error({
title: "获取凭证类型异常",
message: error,
});
}
},
// 页面加载时 获取枚举
mounted() {
this.getVendors();
this.getCrendentialTypes();
this.get_secrets();
},
添加转换方法:
getEnumDescribe(t, v) {
let showVendor = v
switch (t) {
case "vendor":
this.vendors.forEach((item) => {
if (item.value === v) {
showVendor = item.describe;
}
});
break;
case "cendential":
this.crendentialTypes.forEach((item) => {
if (item.value === v) {
showVendor = item.describe;
}
});
break;
}
return showVendor;
},
页面展示是 使用函数转换
<el-table-column prop="name" label="厂商">
<template slot-scope="{ row }">
{{ getEnumDescribe("vendor", row.vendor) }}
</template>
</el-table-column>
<el-table-column prop="name" label="类型">
<template slot-scope="{ row }">
{{ getEnumDescribe("cendential", row.crendential_type) }}
</template>
</el-table-column>
我们将vendor 和 cendential types传入给 组件内部使用
添加2个属性:
vendors: {
default() {
return [];
},
type: Array,
},
cendentialTypes: {
default() {
return [];
},
type: Array,
},
然后 index页面传入参数:
<!-- 添加secret -->
<add-secret
:visible.sync="showAddSecretDrawer"
:vendors="vendors"
:cendentialTypes="crendentialTypes"
/>
这里使用 Radio 单选框
动态选择枚举列表:
<el-form-item label="厂商" :label-width="formLabelWidth">
<el-radio-group v-model="form.vendor">
<el-radio-button
v-for="item in vendors"
:key="item.value"
:label="item.value"
>
{{ item.describe }}
</el-radio-button>
</el-radio-group>
</el-form-item>
通过watch 设置初始值: 注意, 由于数据是挂载后加载的, 所以页面早于数据, 需要watch动态修改(或者父页面使用created钩子)
watch: {
visible: {
handler(newV) {
if (newV) {
this.form.vendor = this.vendors[0].value;
}
},
immediate: true,
},
},
定义我们要提交的表单:
data() {
return {
form: {
description: "",
vendor: "",
crendential_type: "",
api_key: "",
api_secret: "",
request_rate: 0.2,
address: "",
username: "",
password: "",
},
formLabelWidth: "80px",
addSecretLoading: false,
};
},
绑定数据
<template>
<div>
<!-- 添加凭证表单 -->
<el-drawer
title="添加凭证"
:visible.sync="isVisible"
:before-close="handleClose"
size="40%"
>
<div class="add-secret-form">
<el-form ref="addSecretForm" :model="form">
<el-form-item label="厂商" :label-width="formLabelWidth">
<el-radio-group v-model="form.vendor">
<el-radio-button
v-for="item in vendors"
:key="item.value"
:label="item.value"
>
{{ item.describe }}
</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="类型" :label-width="formLabelWidth">
<el-radio-group v-model="form.crendential_type">
<el-radio-button
v-for="item in cendentialTypes"
:key="item.value"
:label="item.value"
>
{{ item.describe }}
</el-radio-button>
</el-radio-group>
</el-form-item>
<div>
<el-form-item label="API Key" :label-width="formLabelWidth">
<el-input
v-model="form.api_key"
placeholder="请输入内容"
></el-input>
</el-form-item>
<el-form-item label="API Secret" :label-width="formLabelWidth">
<el-input
v-model="form.api_secret"
placeholder="请输入内容"
></el-input>
</el-form-item>
<el-form-item label="Region" :label-width="formLabelWidth">
<el-input
v-model="form.allow_regions"
placeholder="请输入内容"
></el-input>
</el-form-item>
</div>
<div>
<el-form-item label="Address" :label-width="formLabelWidth">
<el-input
v-model="form.address"
placeholder="请输入内容"
></el-input>
</el-form-item>
<el-form-item label="Username" :label-width="formLabelWidth">
<el-input
v-model="form.username"
placeholder="请输入内容"
></el-input>
</el-form-item>
<el-form-item label="Password" :label-width="formLabelWidth">
<el-input
v-model="form.password"
placeholder="请输入内容"
></el-input>
</el-form-item>
</div>
<el-form-item label="速率限制" :label-width="formLabelWidth">
<el-input
v-model="form.request_rate"
placeholder="请输入内容"
></el-input>
</el-form-item>
</el-form>
<div class="drawer-footer">
<el-button @click="cancelForm">取 消</el-button>
<el-button
type="primary"
:loading="addSecretLoading"
@click="submit"
>{{ addSecretLoading ? "提交中 ..." : "确 定" }}</el-button
>
</div>
</div>
</el-drawer>
</div>
</template>
添加样式
<style scoped>
/* form表单添加边距 */
.add-secret-form {
padding: 20px 20px;
}
/* 提交表单居中 */
.drawer-footer {
display: flex;
align-items: center;
justify-content: center;
}
</style>
使用v-if做判断:
<div v-if="form.crendential_type === 'CRENDENTIAL_API_KEY'">
<el-form-item label="API Key" :label-width="formLabelWidth">
<el-input
v-model="form.api_key"
placeholder="请输入内容"
></el-input>
</el-form-item>
<el-form-item label="API Secret" :label-width="formLabelWidth">
<el-input
v-model="form.api_secret"
placeholder="请输入内容"
></el-input>
</el-form-item>
<el-form-item label="Region" :label-width="formLabelWidth">
<el-input
v-model="form.allow_regions"
placeholder="请输入内容"
></el-input>
</el-form-item>
</div>
<div v-if="form.crendential_type === 'CRENDENTIAL_PASSWORD'">
<el-form-item label="Address" :label-width="formLabelWidth">
<el-input
v-model="form.address"
placeholder="请输入内容"
></el-input>
</el-form-item>
<el-form-item label="Username" :label-width="formLabelWidth">
<el-input
v-model="form.username"
placeholder="请输入内容"
></el-input>
</el-form-item>
<el-form-item label="Password" :label-width="formLabelWidth">
<el-input
v-model="form.password"
placeholder="请输入内容"
></el-input>
</el-form-item>
</div>
厂商绑定changeVendor方法
<el-form-item label="厂商" :label-width="formLabelWidth">
<el-radio-group @change="changeVendor" v-model="form.vendor">
<el-radio-button
v-for="item in vendors"
:key="item.value"
:label="item.value"
>
{{ item.describe }}
</el-radio-button>
</el-radio-group>
</el-form-item>
当有变化是, 修正crendential type
changeVendor(val) {
if (val === "VENDOR_VSPHERE") {
this.form.crendential_type = "CRENDENTIAL_PASSWORD";
} else {
this.form.crendential_type = "CRENDENTIAL_API_KEY";
}
},
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。