同步操作将从 infraboard/go-course 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
之前我们使用 npm run serve, 是因为package.json配置的脚手架如下:
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
}
一般我们习惯调整成为: npm run dev
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
}
根据我们的数据结构:
{
"code":0,
"data":{
"items":[
{
"id":"c5pu17p3n7phb445t73g",
"sync_at":1634984095854,
"secret_id":"",
"vendor":"VENDOR_TENCENT",
"resource_type":"HOST_RESOURCE",
"region":"ap-shanghai",
"zone":"ap-shanghai-3",
"create_at":1593580796000,
"instance_id":"ins-7sgo2va9",
"resource_hash":"231eb53f386be0ad4a502ddfff663e107b0e0405",
"describe_hash":"e168c71a705013413e83705dfe3ff3c4504b6a98",
"expire_at":1657171197000,
"category":"",
"type":"S2.SMALL2",
"name":"nbtuan-web",
"description":"",
"status":"RUNNING",
"tags":null,
"update_at":0,
"sync_accout":"",
"public_ip":[
"49.234.114.127"
],
"private_ip":[
"172.17.0.7"
],
"pay_type":"PREPAID",
"resource_id":"c5pu17p3n7phb445t73g",
"cpu":1,
"memory":2,
"gpu_amount":0,
"gpu_spec":"",
"os_type":"",
"os_name":"CentOS 7.6 64bit",
"serial_number":"f191197c-c009-4a08-9a52-e6f08bacebbf",
"image_id":"img-9qabwvbn",
"internet_max_bandwidth_out":1,
"internet_max_bandwidth_in":0,
"security_groups":[
"sg-05url5pe"
]
}
],
"total":1
}
}
在列表页做对应的展示:
<el-table :data="hosts" style="width: 100%">
<el-table-column prop="name" label="名称">
<template slot-scope="{ row }">
{{ row.resource_id }} <br />
{{ row.name }}
</template>
</el-table-column>
<el-table-column prop="name" label="资产来源">
<template slot-scope="{ row }">
{{ row.vendor }} <br />
{{ row.region }}
</template>
</el-table-column>
<el-table-column prop="name" label="内网IP/外网IP">
<template slot-scope="{ row }">
{{ row.private_ip }} <br />
{{ row.public_ip }}
</template>
</el-table-column>
<el-table-column prop="name" label="系统类型">
<template slot-scope="{ row }">
{{ row.os_name }}
</template>
</el-table-column>
<el-table-column prop="sync_at" label="创建时间">
<template slot-scope="scope">
{{ scope.row.create_at | parseTime }}
</template>
</el-table-column>
<el-table-column prop="expire_at" label="过期时间">
<template slot-scope="scope">
{{ scope.row.expire_at | parseTime }}
</template>
</el-table-column>
<el-table-column prop="name" label="规格">
<template slot-scope="{ row }">
{{ row.cpu }} / {{ row.memory }}
</template>
</el-table-column>
<el-table-column prop="name" label="状态">
<template slot-scope="{ row }">
{{ row.status }}
</template>
</el-table-column>
<el-table-column prop="操作" align="center" label="状态">
<template slot-scope="{ row }">
<el-button type="text" disabled>归档</el-button>
<el-button type="text" disabled>监控</el-button>
</template>
</el-table-column>
</el-table>
我们使用一个关键字输入框进行搜索支持:
调整后端API, 关键字支持这些字段
if req.Keywords != "" {
query.Where("r.name LIKE ? OR r.id = ? OR r.instance_id = ? OR r.private_ip LIKE ? OR r.public_ip LIKE ?",
"%"+req.Keywords+"%",
req.Keywords,
req.Keywords,
req.Keywords+"%",
req.Keywords+"%",
)
}
使用Postman测试关键字搜索是否正常
添加前端搜索框,对接关键字搜索
<div class="search">
<el-input
v-model="query.keywords"
placeholder="请输入实例ID|名称|IP 敲回车进行搜索"
@keyup.enter.native="get_hosts"
></el-input>
</div>
测试关键字搜索是否都能正常
加载数据的时候, 可能由于网络原因, 加载缓慢, 为了对用户友好显示, 会提示用户加载中, 让用户等待
这里采用Element的Loading方案: Loading 加载
<el-table :data="hosts" v-loading="fetchHostLoading" style="width: 100%">
...
<script>
data() {
return {
fetchHostLoading: false,
...
};
},
</script>
改造get_hosts函数:
async get_hosts() {
this.fetchHostLoading = true;
try {
const resp = await LIST_HOST(this.query);
this.hosts = resp.data.items;
this.total = resp.data.total;
} catch (error) {
this.$notify.error({
title: "获取主机异常",
message: error,
});
} finally {
this.fetchHostLoading = false;
}
},
为了能调试出Loading效果,我们修改下后端API, 在返回数据前Sleep 4秒
func (h *handler) QueryHost(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
time.Sleep(4 * time.Second)
...
}
最后调整下API, 测试当API请求异常时,是否能正常提示
首先我们添加一个空的详情页面, 页面访问路径: hosts/detail?id=xxxx
添加 host/detail.vue
<template>
<div>详情页</div>
</template>
<script>
export default {
name: "HostDetail",
};
</script>
添加路由,
{
path: "/cmdb",
component: Layout,
redirect: "/cmdb/search",
children: [
{
path: "search",
component: () => import("@/views/cmdb/search/index"),
name: "ResourceSearch",
},
{
path: "host",
component: () => import("@/views/cmdb/host/index"),
name: "ResourceHost",
},
{
path: "host/detail",
component: () => import("@/views/cmdb/host/detail"),
name: "ResourceHost",
},
],
},
通过URL访问页面,查看页面是否正常: http://localhost:9527/cmdb/host/detail
注意: 为了让路由更有规则, 修改之前的host列表页路由为: host/list
{
path: "/cmdb",
component: Layout,
redirect: "/cmdb/search",
children: [
{
path: "search",
component: () => import("@/views/cmdb/search/index"),
name: "ResourceSearch",
},
{
path: "host/list",
component: () => import("@/views/cmdb/host/index"),
name: "ResourceHost",
},
{
path: "host/detail",
component: () => import("@/views/cmdb/host/detail"),
name: "ResourceHost",
},
],
},
调整sidebar的host列表页path
<!-- 导航条目 -->
<el-menu-item index="/cmdb/search">资源检索</el-menu-item>
<el-menu-item index="/cmdb/host/list">主机</el-menu-item>
每次刷新,为了让基础资源展开,我们添加 default-openeds 属性进行控制
<el-menu
default-active="/host"
:default-openeds="['/host']"
class="sidebar-el-menu"
:collapse="isCollapse"
router
>
这里使用vue router的 route-link 进行跳转: Vue Router
route-link的to参数 就是: router.push()的参数, 可以灵活使用
<!-- 字符串 -->
<router-link to="home">Home</router-link>
<!-- 渲染结果 -->
<a href="home">Home</a>
<!-- 使用 v-bind 的 JS 表达式 -->
<router-link v-bind:to="'home'">Home</router-link>
<!-- 不写 v-bind 也可以,就像绑定别的属性一样 -->
<router-link :to="'home'">Home</router-link>
<!-- 同上 -->
<router-link :to="{ path: 'home' }">Home</router-link>
<!-- 命名的路由 -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
<!-- 带查询参数,下面的结果为 /register?plan=private -->
<router-link :to="{ path: 'register', query: { plan: 'private' }}">Register</router-link>
这里采用最后一种带查询参数的to, 使用instance_id作为跳转
<el-table-column prop="name" label="名称">
<template slot-scope="{ row }">
<router-link
:to="{ path: '/cmdb/host/detail', query: { id: row.id } }"
>
{{ row.resource_id }}
</router-link>
<br />
{{ row.name }}
</template>
</el-table-column>
去掉A标签的下划线:
a {
text-decoration: none;
}
详情页布局大概如下:
<template>
<div>
<!-- 基础信息 -->
<div class="box-shadow basic-info">详情页</div>
<!-- 关联信息 -->
</div>
</template>
具体的信息的展示我们采用:Descriptions 描述列表
我们复制一个样例过来修改:
<!-- 基础信息 -->
<div class="box-shadow basic-info">
<el-descriptions title="用户信息">
<el-descriptions-item label="用户名">kooriookami</el-descriptions-item>
<el-descriptions-item label="手机号">18100000000</el-descriptions-item>
<el-descriptions-item label="居住地">苏州市</el-descriptions-item>
<el-descriptions-item label="备注">
<el-tag size="small">学校</el-tag>
</el-descriptions-item>
<el-descriptions-item label="联系地址"
>江苏省苏州市吴中区吴中大道 1188 号</el-descriptions-item
>
</el-descriptions>
</div>
我们调整下基础信息的样式:
<style scoped>
.basic-info {
padding: 8px;
background-color: white;
}
</style>
调整下title的字体:
.el-descriptions__title {
font-size: 14px;
}
我们通过API 获取详情页的数据
之前API publicIPList, privateIPList keyPairNameList, securityGroupsList 的这些字段调整为了[]string, 需要修复下:
修复详细API查询问题:
func (s *service) DescribeHost(ctx context.Context, req *host.DescribeHostRequest) (
*host.Host, error) {
query := sqlbuilder.NewQuery(queryHostSQL)
querySQL, args := query.Where("id = ?", req.Id).BuildQuery()
s.log.Debugf("sql: %s", querySQL)
queryStmt, err := s.db.Prepare(querySQL)
if err != nil {
return nil, exception.NewInternalServerError("prepare query host error, %s", err.Error())
}
defer queryStmt.Close()
ins := host.NewDefaultHost()
var (
publicIPList, privateIPList, keyPairNameList, securityGroupsList string
)
err = queryStmt.QueryRow(args...).Scan(
&ins.Id, &ins.Vendor, &ins.Region, &ins.Zone, &ins.CreateAt, &ins.ExpireAt,
&ins.Category, &ins.Type, &ins.InstanceId, &ins.Name, &ins.Description,
&ins.Status, &ins.UpdateAt, &ins.SyncAt, &ins.SyncAccount,
&publicIPList, &privateIPList, &ins.PayType, &ins.DescribeHash, &ins.ResourceHash, &ins.ResourceId,
&ins.CPU, &ins.Memory, &ins.GPUAmount, &ins.GPUSpec, &ins.OSType, &ins.OSName,
&ins.SerialNumber, &ins.ImageID, &ins.InternetMaxBandwidthOut, &ins.InternetMaxBandwidthIn,
&keyPairNameList, &securityGroupsList,
)
if err != nil {
if err == sql.ErrNoRows {
return nil, exception.NewNotFound("%#v not found", req)
}
return nil, exception.NewInternalServerError("describe host error, %s", err.Error())
}
ins.LoadPrivateIPString(privateIPList)
ins.LoadPublicIPString(publicIPList)
ins.LoadKeyPairNameString(keyPairNameList)
ins.LoadSecurityGroupsString(securityGroupsList)
return ins, nil
}
最终测试API接口:
前端封装API请求: host模块添加GET_HOST
export function GET_HOST(id, params) {
return request({
url: `/hosts/${id}`,
method: "get",
params: params,
});
}
引入API, 我们通过当前route参数, 获取到URL当前参数, 先看看route对象
<script>
import { GET_HOST } from '@/api/cmdb/host'
export default {
name: "HostDetail",
mounted() {
console.log(this.$route)
GET_HOST()
}
};
</script>
完善API请求, 把请求回来的数据绑定到host对象上, 并处理异常
async mounted() {
try {
let resp = await GET_HOST(this.$route.query.id);
this.host = resp.data;
} catch (error) {
this.$notify.error({
title: "获取主机异常",
message: error,
});
}
},
展示数据:
<!-- 基础信息 -->
<div class="box-shadow basic-info">
<el-descriptions title="主机信息">
<el-descriptions-item label="名称">
{{ host.name }}
</el-descriptions-item>
<el-descriptions-item label="实例ID">
{{ host.instance_id }}
</el-descriptions-item>
<el-descriptions-item label="状态">
{{ host.status }}
</el-descriptions-item>
<el-descriptions-item label="规格">
{{ host.cpu }} / {{ host.memory }}
</el-descriptions-item>
<el-descriptions-item label="厂商">
{{ host.vendor }}
</el-descriptions-item>
<el-descriptions-item label="系统">
{{ host.os_name }}
</el-descriptions-item>
<el-descriptions-item label="地域">
{{ host.region }}
</el-descriptions-item>
<el-descriptions-item label="创建时间">
{{ host.create_at | parseTime }}
</el-descriptions-item>
<el-descriptions-item label="序列号">
{{ host.serial_number }}
</el-descriptions-item>
<el-descriptions-item label="过期时间">
{{ host.expire_at | parseTime }}
</el-descriptions-item>
<el-descriptions-item label="同步时间">
{{ host.sync_at | parseTime }}
</el-descriptions-item>
<el-descriptions-item label="同步账号">
{{ host.sync_account }}
</el-descriptions-item>
<el-descriptions-item label="内网IP">
{{ host.private_ip.join(",") }}
</el-descriptions-item>
<el-descriptions-item label="公网IP">
{{ host.public_ip.join(",") }}
</el-descriptions-item>
</el-descriptions>
</div>
采用: Tabs 标签页 来展示关联信息
<!-- 关联信息 -->
<el-card class="box-shadow associate-info">
<el-tabs v-model="activeName">
<el-tab-pane label="主机事件" name="event"> 主机事件 </el-tab-pane>
</el-tabs>
</el-card>
补充默认显示的标签页
data() {
return {
host: {},
activeName: "event",
};
},
具体事件后面补充
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。