DKube v1.0
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
77
README.md
@ -1,2 +1,75 @@
|
||||
# DKube-Web
|
||||
K8s管理系统,前端平台项目
|
||||
<div style="text-align: center"></div>
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/42825450/201523059-ed28e427-e1b6-443a-8326-100460e6dec9.jpg" width="250px" height="220px">
|
||||
<br>
|
||||
<i>Make the project development and release simpler, easier and more efficient.</i>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
## What is DKube
|
||||
This is a K8s cluster management platform;DKube Provides a wizard-style operation interface for K8s cluster management to help your team manage your cluster environment quickly and easily
|
||||
|
||||
## 功能
|
||||
|
||||
<details>
|
||||
<summary><b> K8s集群管理</b></summary>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b> 平台化界面</b></summary>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b> 更加便捷的管理K8s</b></summary>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b> 支持YAML信息查看\变更</b></summary>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b> 平台化管理控制器</b></summary>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b> 实时查看容器日志</b></summary>
|
||||
</details>
|
||||
|
||||
## 截图
|
||||
<br/>
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%" align="center"><b>登入认证管理</b></td>
|
||||
<td width="50%" align="center"><b>集群信息状态</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" align="center"><img src="https://user-images.githubusercontent.com/42825450/193593148-4d258b30-b972-4583-b359-32978a8a8637.jpg?raw=true"></td>
|
||||
<td width="50%" align="center"><img src="https://user-images.githubusercontent.com/42825450/193593170-3373dabd-8d5d-4a01-a59f-49851f11f433.jpg?raw=true"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" align="center"><b>节点资源管理</b></td>
|
||||
<td width="50%" align="center"><b>名称空间管理</b></td>
|
||||
</tr>
|
||||
<td width="50%" align="center"><img src="https://user-images.githubusercontent.com/42825450/193593569-daebc649-f6c4-45a2-88f6-2aa4860c3dea.jpg?raw=true"></td>
|
||||
<td width="50%" align="center"><img src="https://user-images.githubusercontent.com/42825450/193593579-e0539ab0-6b22-4060-b254-c6495fb87cbd.jpg?raw=true"></td>
|
||||
<tr>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" align="center"><b>YAML信息管理</b></td>
|
||||
<td width="50%" align="center"><b>Pod副本管理</b></td>
|
||||
</tr>
|
||||
<td width="50%" align="center"><img src="https://user-images.githubusercontent.com/42825450/193593867-4a98bd0f-a910-4b90-92e3-6a3164d0c241.jpg?raw=true"></td>
|
||||
<td width="50%" align="center"><img src="https://user-images.githubusercontent.com/42825450/193593871-ee004cb8-42cb-427a-a0cc-fa1e15e7d466.jpg?raw=true"></td>
|
||||
<tr>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
## 部署
|
||||
|
||||
- 前端 `dkube-web`,后端接口:`src\views\common\Config.js`
|
||||
```shell
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
5
babel.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
19
jsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
29239
package-lock.json
generated
Normal file
55
package.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "dkube-web",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^1.1.4",
|
||||
"axios": "^0.26.1",
|
||||
"codemirror-editor-vue3": "^2.1.3",
|
||||
"core-js": "^3.6.5",
|
||||
"echarts": "^5.3.2",
|
||||
"element-plus": "^2.1.5",
|
||||
"json-editor-vue3": "^1.0.5",
|
||||
"json2yaml": "^1.1.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"moment": "^2.29.2",
|
||||
"nprogress": "^0.2.0",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.14",
|
||||
"xterm": "^4.18.0",
|
||||
"xterm-addon-fit": "^0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.15",
|
||||
"@vue/cli-plugin-eslint": "~4.5.15",
|
||||
"@vue/cli-service": "~4.5.15",
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^7.0.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 9.3 KiB |
17
public/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
15
src/App.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
html,body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
#nprogress .bar {
|
||||
background: #2186c0 !important;
|
||||
}
|
||||
</style>
|
BIN
src/assets/avator/avator.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/assets/img/1.webp
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
src/assets/img/403.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/assets/img/404.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/img/login3.webp
Normal file
After Width: | Height: | Size: 8.0 MiB |
BIN
src/assets/k8s/k8s-metrics.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
src/assets/logo.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
58
src/components/HelloWorld.vue
Normal file
@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br>
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelloWorld',
|
||||
props: {
|
||||
msg: String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
219
src/layout/Layout.vue
Normal file
@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<div class="common-layout">
|
||||
<el-container style="height: 100vh;">
|
||||
<el-aside class="aside" :width="asideWidth">
|
||||
<el-affix class="aside-affix" :z-index="1200">
|
||||
<div class="aside-logo" >
|
||||
<el-image class="logo-image" :src="logo" />
|
||||
<span :class="[isCollapse ? 'is-collapse' : '']">
|
||||
<span class="logo-name" >DKube</span>
|
||||
</span>
|
||||
</div>
|
||||
</el-affix>
|
||||
<el-menu class="aside-menu"
|
||||
router
|
||||
:default-active="$route.path"
|
||||
:collapse="isCollapse"
|
||||
background-color="#131b27"
|
||||
text-color="#bfcbd9"
|
||||
active-text-color="#20a0ff">
|
||||
<div v-for="menu in routers" :key="menu">
|
||||
<el-menu-item class="aside-menu-item" v-if="menu.children && menu.children.length == 1" :index="menu.children[0].path">
|
||||
<el-icon><component :is="menu.children[0].icon" /></el-icon>
|
||||
<template #title>
|
||||
{{menu.children[0].name}}
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-sub-menu class="aside-submenu" v-else-if="menu.children" :index="menu.path">
|
||||
<template #title>
|
||||
<el-icon><component :is="menu.icon" /></el-icon>
|
||||
<span :class="[isCollapse ? 'is-collapse' : '']">{{menu.name}}</span>
|
||||
</template>
|
||||
<el-menu-item class="aside-menu-childitem" v-for="child in menu.children" :key="child" :index="child.path">
|
||||
<el-icon><component :is="child.icon" /></el-icon>
|
||||
<template #title>
|
||||
{{child.name}}
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</el-sub-menu>
|
||||
</div>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
<el-container>
|
||||
<el-header class="header" >
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="1">
|
||||
<div class="header-collapse" @click="onCollapse">
|
||||
<el-icon><component :is="isCollapse ? 'expand':'fold'" /></el-icon>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="10" >
|
||||
<div class="header-breadcrumb">
|
||||
<el-breadcrumb separator="/" v-if="this.$route.matched[0].path != '/main'">
|
||||
<el-breadcrumb-item :to="{ path: '/' }">工作台</el-breadcrumb-item>
|
||||
<template v-for="(matched,m) in this.$route.matched" :key="m">
|
||||
<el-breadcrumb-item v-if="matched.name != undefined" >
|
||||
{{ matched.name }}
|
||||
</el-breadcrumb-item>
|
||||
</template>
|
||||
</el-breadcrumb>
|
||||
<el-breadcrumb separator="/" v-else>
|
||||
<el-breadcrumb-item>工作台</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col class="header-menu" :span="13">
|
||||
<el-dropdown>
|
||||
<div class="header-dropdown">
|
||||
<el-image class="avator-image" :src="avator" />
|
||||
<span>{{ username }}</span>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item icon="el-icon-switch-button" @click="logout()">退出</el-dropdown-item>
|
||||
<el-dropdown-item icon="el-icon-unlock">修改密码</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-header>
|
||||
<el-main class="main">
|
||||
<router-view></router-view>
|
||||
</el-main>
|
||||
<el-footer class="footer">
|
||||
<el-icon style="width:2em;top:3px;font-size:18px"><place/></el-icon>
|
||||
<a class="footer el-icon-place">2022 DevOps </a>
|
||||
</el-footer>
|
||||
<el-backtop target=".el-main"></el-backtop>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {useRouter} from 'vue-router'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
avator: require('@/assets/avator/avator.png'),
|
||||
logo: require('@/assets/k8s/k8s-metrics.png'),
|
||||
isCollapse: false,
|
||||
asideWidth: '220px',
|
||||
routers: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
username() {
|
||||
let username = localStorage.getItem('username');
|
||||
return username ? username : '未知';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onCollapse() {
|
||||
if (this.isCollapse) {
|
||||
this.asideWidth = '220px'
|
||||
this.isCollapse = false
|
||||
} else {
|
||||
this.isCollapse = true
|
||||
this.asideWidth = '64px'
|
||||
}
|
||||
},
|
||||
logout() {
|
||||
localStorage.removeItem('username');
|
||||
localStorage.removeItem('token');
|
||||
this.$router.push('/login');
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
this.routers = useRouter().options.routes
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aside {
|
||||
transition: all .5s;
|
||||
background-color: #131b27;
|
||||
}
|
||||
.aside-logo {
|
||||
background-color: #131b27;
|
||||
height: 60px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
.logo-image {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
top: 12px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
.logo-name {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
}
|
||||
.aside::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.aside-affix {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
.aside-menu {
|
||||
border-right-width: 0;
|
||||
}
|
||||
.aside-menu-item.is-active {
|
||||
background-color: #1f2a3a;
|
||||
}
|
||||
.aside-menu-item {
|
||||
padding-left: 20px !important;
|
||||
}
|
||||
.aside-menu-childitem {
|
||||
padding-left: 20px !important;
|
||||
}
|
||||
.aside-menu-childitem.is-active {
|
||||
background-color: #1f2a3a;
|
||||
}
|
||||
.aside-menu-childitem:hover {
|
||||
background-color: #142c4e;
|
||||
}
|
||||
.header {
|
||||
z-index:1200;
|
||||
line-height: 60px;
|
||||
font-size: 24px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, .12),0 0 6px rgba(0, 0, 0, .04)
|
||||
}
|
||||
.header-collapse {
|
||||
cursor: pointer;
|
||||
}
|
||||
.header-breadcrumb {
|
||||
padding-top: 0.9em;
|
||||
}
|
||||
.header-menu {
|
||||
text-align: right;
|
||||
}
|
||||
.is-collapse {
|
||||
display: none;
|
||||
}
|
||||
.header-dropdown {
|
||||
line-height: 60px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.avator-image {
|
||||
top: 12px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.main {
|
||||
padding: 10px;
|
||||
}
|
||||
.footer {
|
||||
z-index: 1200;
|
||||
color: rgb(187, 184, 184);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
}
|
||||
</style>
|
18
src/main.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { createApp } from 'vue'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import * as ELIcons from '@element-plus/icons-vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import { GlobalCmComponent } from "codemirror-editor-vue3";
|
||||
import 'codemirror/theme/idea.css'
|
||||
import 'codemirror/mode/yaml/yaml.js'
|
||||
|
||||
const app = createApp(App)
|
||||
for (let iconName in ELIcons) {
|
||||
app.component(iconName, ELIcons[iconName])
|
||||
}
|
||||
app.use(ElementPlus)
|
||||
app.use(GlobalCmComponent, { componentName: "codemirror" });
|
||||
app.use(router)
|
||||
app.mount('#app')
|
221
src/router/index.js
Normal file
@ -0,0 +1,221 @@
|
||||
import {createRouter,createWebHistory} from 'vue-router'
|
||||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
import Layout from "@/layout/Layout"
|
||||
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
const routes =[
|
||||
{
|
||||
path: '/login',
|
||||
component: () => import('@/views/login/Login.vue'),
|
||||
icon: "odometer",
|
||||
meta: {title: "登录", requireAuth: false},
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/home'
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
component: Layout,
|
||||
icon: "odometer",
|
||||
children: [
|
||||
{
|
||||
path: "/home",
|
||||
name: "K8s概览",
|
||||
icon: "odometer",
|
||||
meta: {title: "K8s概览", requireAuth: true},
|
||||
component: () => import('@/views/home/Home.vue'),
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/workflow',
|
||||
component: Layout,
|
||||
icon: "VideoPlay",
|
||||
children: [
|
||||
{
|
||||
path: "/workflow",
|
||||
name: "工作流",
|
||||
icon: "VideoPlay",
|
||||
meta: {title: "工作流", requireAuth: true},
|
||||
component: () => import('@/views/workflow/Workflow.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/cluster",
|
||||
name: "集群信息",
|
||||
component: Layout,
|
||||
icon: "home-filled",
|
||||
meta: {title: "集群信息", requireAuth: true},
|
||||
children: [
|
||||
{
|
||||
path: "/cluster/node",
|
||||
name: "Node",
|
||||
icon: "el-icon-s-data",
|
||||
meta: {title: "Node", requireAuth: true},
|
||||
component: () => import("@/views/node/Node.vue")
|
||||
},
|
||||
{
|
||||
path: "/cluster/namespace",
|
||||
name: "Namespace",
|
||||
icon: "el-icon-document-add",
|
||||
meta: {title: "Namespace", requireAuth: true},
|
||||
component: () => import("@/views/namespace/Namespace.vue")
|
||||
},
|
||||
{
|
||||
path: "/cluster/persistentvolume",
|
||||
name: "PersistentVolume",
|
||||
icon: "el-icon-document-add",
|
||||
meta: {title: "PersistemtVolume", requireAuth: true},
|
||||
component: () => import("@/views/persistentvolume/PersistentVolume.vue")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/workload',
|
||||
name: '工作负载',
|
||||
component: Layout,
|
||||
icon: "menu",
|
||||
meta: {title: "工作负载", requireAuth: true},
|
||||
children: [
|
||||
{
|
||||
path: '/workload/deployment',
|
||||
name: 'Deployment',
|
||||
icon: 'el-icon-s-data',
|
||||
meta: {title: "Deployment", requireAuth: true},
|
||||
component: () => import("@/views/deployment/Deployment.vue")
|
||||
},
|
||||
{
|
||||
path: '/workload/pod',
|
||||
name: 'Pod',
|
||||
icon: 'el-icon-document-add',
|
||||
meta: {title: "Pod", requireAuth: true},
|
||||
component: () => import("@/views/pod/Pod.vue")
|
||||
},
|
||||
{
|
||||
path: '/workload/daemonset',
|
||||
name: 'DaemonSet',
|
||||
icon: 'el-icon-document-add',
|
||||
meta: {title: "DaemonSet", requireAuth: true},
|
||||
component: () => import("@/views/daemonset/DaemonSet.vue")
|
||||
},
|
||||
{
|
||||
path: '/workload/statefulset',
|
||||
name: 'StatefulSet',
|
||||
icon: 'el-icon-document-add',
|
||||
meta: {title: "StatefulSet", requireAuth: true},
|
||||
component: () => import("@/views/statefulset/StatefulSet.vue")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/loadbalance",
|
||||
name: "负载均衡",
|
||||
component: Layout,
|
||||
icon: "files",
|
||||
meta: {title: "负载均衡", requireAuth: true},
|
||||
children: [
|
||||
{
|
||||
path: "/loadbalance/service",
|
||||
name: "Service",
|
||||
icon: "el-icon-s-data",
|
||||
meta: {title: "Service", requireAuth: true},
|
||||
component: () => import("@/views/service/Service.vue")
|
||||
},
|
||||
{
|
||||
path: "/loadbalance/ingress",
|
||||
name: "Ingress",
|
||||
icon: "el-icon-document-add",
|
||||
meta: {title: "Ingress", requireAuth: true},
|
||||
component: () => import("@/views/ingress/Ingress.vue")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/storage",
|
||||
name: "存储与配置",
|
||||
component: Layout,
|
||||
icon: "tickets",
|
||||
meta: {title: "存储与配置", requireAuth: true},
|
||||
children: [
|
||||
{
|
||||
path: "/storage/configmap",
|
||||
name: "Configmap",
|
||||
icon: "el-icon-document-add",
|
||||
meta: {title: "Configmap", requireAuth: true},
|
||||
component: () => import("@/views/configmap/ConfigMap.vue")
|
||||
},
|
||||
{
|
||||
path: "/storage/secret",
|
||||
name: "Secret",
|
||||
icon: "el-icon-document-add",
|
||||
meta: {title: "Secret", requireAuth: true},
|
||||
component: () => import("@/views/secret/Secret.vue")
|
||||
},
|
||||
{
|
||||
path: "/storage/persistentvolumeclaim",
|
||||
name: "PersistentVolumeClaim",
|
||||
icon: "el-icon-s-data",
|
||||
meta: {title: "PersistentVolumeClaim", requireAuth: true},
|
||||
component: () => import("@/views/persistentvolumeclaim/PersistentVolumeClaim.vue")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/404',
|
||||
component: () => import('@/views/common/404.vue'),
|
||||
meta: {
|
||||
title: '404'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/403',
|
||||
component: () => import('@/views/common/403.vue'),
|
||||
meta: {
|
||||
title: '403'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)',
|
||||
redirect: '/404'
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter ({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
NProgress.inc(100)
|
||||
NProgress.configure({ easing: 'ease', speed: 600,showSpinner: false})
|
||||
|
||||
router.beforeEach((to,from,next) => {
|
||||
NProgress.start()
|
||||
if (to.meta.title) {
|
||||
document.title = to.meta.title
|
||||
} else {
|
||||
document.title = "DKube"
|
||||
}
|
||||
next()
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
jwt.verify(localStorage.getItem('token'), 'devops', function (err) {
|
||||
if (to.path === '/login') {
|
||||
next()
|
||||
} else if (err) {
|
||||
next('/login');
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.afterEach (() => {
|
||||
NProgress.done()
|
||||
})
|
||||
|
||||
export default router
|
44
src/utils/request.js
Normal file
@ -0,0 +1,44 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const httpClient = axios.create({
|
||||
validateStatus(status) {
|
||||
return status >= 200 && status < 504
|
||||
},
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
httpClient.defaults.retry = 3
|
||||
httpClient.defaults.retryDelay = 1000
|
||||
httpClient.defaults.shouldRetry = true
|
||||
|
||||
httpClient.interceptors.request.use (
|
||||
config => {
|
||||
config.headers['Content-Type'] = 'application/json'
|
||||
config.headers['Accept-Language'] = 'zh-CN'
|
||||
config.headers['Authorization'] = localStorage.getItem('token')
|
||||
if (config.method === 'post') {
|
||||
if (!config.data) {
|
||||
config.data = {}
|
||||
}
|
||||
}
|
||||
return config
|
||||
},
|
||||
err => {
|
||||
Promise.reject(err)
|
||||
}
|
||||
);
|
||||
|
||||
httpClient.interceptors.response.use (
|
||||
response => {
|
||||
if (response.status !== 200) {
|
||||
return Promise.reject(response.data)
|
||||
} else {
|
||||
return response.data
|
||||
}
|
||||
},
|
||||
err => {
|
||||
return Promise.reject(err)
|
||||
}
|
||||
);
|
||||
|
||||
export default httpClient;
|
44
src/views/common/403.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="main-body-div">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<img class="main-body-img" src="../../assets/img/403.png" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<p class="status-code">403</p>
|
||||
<p class="status-describe">你暂时无权限访问该页面······</p>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.main-body-img {
|
||||
margin-top: 150px
|
||||
}
|
||||
.main-body-div {
|
||||
text-align: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
.status-code {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 95px;
|
||||
font-weight: bold;
|
||||
color: rgb(54, 95, 230);
|
||||
}
|
||||
.status-describe {
|
||||
color: rgb(145, 143, 143);
|
||||
}
|
||||
</style>
|
43
src/views/common/404.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="main-body-div">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<img class="main-body-img" src="../../assets/img/404.png" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<p class="status-code">404</p>
|
||||
<p class="status-describe">你所访问的页面不存在······</p>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.main-body-img {
|
||||
margin-top: 15%;
|
||||
}
|
||||
.main-body-div {
|
||||
text-align: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
.status-code {
|
||||
margin: 20px 0 20px 0;
|
||||
font-size: 95px;
|
||||
font-weight: bold;
|
||||
color: rgb(54, 95, 230);
|
||||
}
|
||||
.status-describe {
|
||||
color: rgb(145, 143, 143);
|
||||
}
|
||||
</style>
|
72
src/views/common/Config.js
Normal file
@ -0,0 +1,72 @@
|
||||
export default {
|
||||
loginAuth: 'http://localhost:9090/api/login',
|
||||
k8sWorkflowCreate: 'http://localhost:9090/api/k8s/workflow/create',
|
||||
k8sWorkflowDetail: 'http://localhost:9090/api/k8s/workflow/detail',
|
||||
k8sWorkflowList: 'http://localhost:9090/api/k8s/workflows',
|
||||
k8sWorkflowDel: 'http://localhost:9090/api/k8s/workflow/del',
|
||||
k8sDeploymentList: 'http://localhost:9090/api/k8s/deployments',
|
||||
k8sDeploymentDetail: 'http://localhost:9090/api/k8s/deployment/detail',
|
||||
k8sDeploymentUpdate: 'http://localhost:9090/api/k8s/deployment/update',
|
||||
k8sDeploymentScale: 'http://localhost:9090/api/k8s/deployment/scale',
|
||||
k8sDeploymentRestart: 'http://localhost:9090/api/k8s/deployment/restart',
|
||||
k8sDeploymentDel: 'http://localhost:9090/api/k8s/deployment/del',
|
||||
k8sDeploymentCreate: 'http://localhost:9090/api/k8s/deployment/create',
|
||||
k8sDeploymentNumNp: 'http://localhost:9090/api/k8s/deployment/numnp',
|
||||
k8sPodList: 'http://localhost:9090/api/k8s/pods',
|
||||
k8sPodDetail: 'http://localhost:9090/api/k8s/pod/detail',
|
||||
k8sPodUpdate: 'http://localhost:9090/api/k8s/pod/update',
|
||||
k8sPodDel: 'http://localhost:9090/api/k8s/pod/del',
|
||||
k8sPodContainer: 'http://localhost:9090/api/k8s/pod/container',
|
||||
k8sPodLog: 'http://localhost:9090/api/k8s/pod/log',
|
||||
k8sPodNumNp: 'http://localhost:9090/api/k8s/pod/numnp',
|
||||
k8sDaemonSetList: 'http://localhost:9090/api/k8s/daemonsets',
|
||||
k8sDaemonSetDetail: 'http://localhost:9090/api/k8s/daemonset/detail',
|
||||
k8sDaemonSetUpdate: 'http://localhost:9090/api/k8s/daemonset/update',
|
||||
k8sDaemonSetDel: 'http://localhost:9090/api/k8s/daemonset/del',
|
||||
k8sStatefulSetList: 'http://localhost:9090/api/k8s/statefulsets',
|
||||
k8sStatefulSetDetail: 'http://localhost:9090/api/k8s/daemonset/detail',
|
||||
k8sStatefulSetUpdate: 'http://localhost:9090/api/k8s/daemonset/update',
|
||||
k8sStatefulSetDel: 'http://localhost:9090/api/k8s/daemonset/del',
|
||||
k8sServiceList: 'http://localhost:9090/api/k8s/services',
|
||||
k8sServiceDetail: 'http://localhost:9090/api/k8s/service/detail',
|
||||
k8sServiceUpdate: 'http://localhost:9090/api/k8s/service/update',
|
||||
k8sServiceDel: 'http://localhost:9090/api/k8s/service/del',
|
||||
k8sServiceCreate: 'http://localhost:9090/api/k8s/service/create',
|
||||
k8sIngressList: 'http://localhost:9090/api/k8s/ingresses',
|
||||
k8sIngressDetail: 'http://localhost:9090/api/k8s/ingress/detail',
|
||||
k8sIngressUpdate: 'http://localhost:9090/api/k8s/ingress/update',
|
||||
k8sIngressDel: 'http://localhost:9090/api/k8s/ingress/del',
|
||||
k8sIngressCreate: 'http://localhost:9090/api/k8s/ingress/create',
|
||||
k8sConfigMapList: 'http://localhost:9090/api/k8s/configmaps',
|
||||
k8sConfigMapDetail: 'http://localhost:9090/api/k8s/configmap/detail',
|
||||
k8sConfigMapUpdate: 'http://localhost:9090/api/k8s/configmap/update',
|
||||
k8sConfigMapDel: 'http://localhost:9090/api/k8s/configmap/del',
|
||||
k8sSecretList: 'http://localhost:9090/api/k8s/secrets',
|
||||
k8sSecretDetail: 'http://localhost:9090/api/k8s/secret/detail',
|
||||
k8sSecretUpdate: 'http://localhost:9090/api/k8s/secret/update',
|
||||
k8sSecretDel: 'http://localhost:9090/api/k8s/secret/del',
|
||||
k8sPvcList: 'http://localhost:9090/api/k8s/pvcs',
|
||||
k8sPvcDetail: 'http://localhost:9090/api/k8s/pvc/detail',
|
||||
k8sPvcUpdate: 'http://localhost:9090/api/k8s/pvc/update',
|
||||
k8sPvcDel: 'http://localhost:9090/api/k8s/pvc/del',
|
||||
k8sNodeList: 'http://localhost:9090/api/k8s/nodes',
|
||||
k8sNodeDetail: 'http://localhost:9090/api/k8s/node/detail',
|
||||
k8sNamespaceList: 'http://localhost:9090/api/k8s/namespaces',
|
||||
k8sNamespaceDetail: 'http://localhost:9090/api/k8s/namespace/detail',
|
||||
k8sNamespaceDel: 'http://localhost:9090/api/k8s/namespace/del',
|
||||
k8sPvList: 'http://localhost:9090/api/k8s/pvs',
|
||||
k8sPvDetail: 'http://localhost:9090/api/k8s/pv/detail',
|
||||
k8sTerminalWs: 'ws://localhost:8081/ws',
|
||||
|
||||
cmOptions: {
|
||||
mode: 'text/yaml',
|
||||
theme: 'idea',
|
||||
lineNumbers: true,
|
||||
smartIndent: true,
|
||||
indentUnit: 4,
|
||||
styleActiveLine: true,
|
||||
matchBrackets: true,
|
||||
readOnly: false,
|
||||
lineWrapping: true
|
||||
}
|
||||
}
|
365
src/views/configmap/ConfigMap.vue
Normal file
@ -0,0 +1,365 @@
|
||||
<template>
|
||||
<div class="configmap">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="configmap-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<span>命名空间: </span>
|
||||
<el-select v-model="namespaceValue" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in namespaceList"
|
||||
:key="index"
|
||||
:label="item.metadata.name"
|
||||
:value="item.metadata.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" :offset="16">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Refresh" plain @click="getConfigMaps()">刷新</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="configmap-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
<div>
|
||||
<el-button disabled style="border-radius:2px;" icon="Edit" type="primary">创建</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-input class="configmap-head-search" clearable placeholder="请输入" v-model="searchInput"></el-input>
|
||||
<el-button style="border-radius:2px;" icon="Search" type="primary" plain @click="getConfigMaps()">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="configmap-body-card" shadow="never" :body-style="{padding:'5px'}">
|
||||
<el-table
|
||||
style="width:100%;font-size:12px;margin-bottom:10px;"
|
||||
:data="configMapList"
|
||||
v-loading="appLoading">
|
||||
<el-table-column width="20"></el-table-column>
|
||||
<el-table-column align=left label="ConfigMap名">
|
||||
<template v-slot="scope">
|
||||
<a class="configmap-body-configmapname">{{ scope.row.metadata.name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="标签">
|
||||
<template v-slot="scope">
|
||||
<div v-for="(val, key) in scope.row.metadata.labels" :key="key">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="200"
|
||||
trigger="hover"
|
||||
:content="key + ':' + val">
|
||||
<template #reference>
|
||||
<el-tag style="margin-bottom: 5px" type="warning">{{ ellipsis(key + ":" + val) }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="DATA">
|
||||
<template v-slot="scope">
|
||||
<el-popover
|
||||
style="overflow:auto"
|
||||
placement="right"
|
||||
:width="400"
|
||||
trigger="click">
|
||||
<div style="overflow-y:auto;max-height:500px;">
|
||||
<span>{{ scope.row.data }}</span>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon style="font-size:18px;cursor:pointer;"><reading/></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center min-width="100" label="创建时间">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="info">{{ timeTrans(scope.row.metadata.creationTimestamp) }} </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="操作" width="200">
|
||||
<template v-slot="scope">
|
||||
<el-button size="small" style="border-radius:2px;" icon="Edit" type="primary" plain @click="getConfigMapDetail(scope)">YAML</el-button>
|
||||
<el-button size="small" style="border-radius:2px;" icon="Delete" type="danger" @click="handleConfirm(scope, '删除', delConfigMap)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="configmap-body-pagination"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-sizes="pagesizeList"
|
||||
:page-size="pagesize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="configMapTotal">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="YAML信息" v-model="yamlDialog" width="45%" top="5%">
|
||||
<codemirror
|
||||
:value="contentYaml"
|
||||
border
|
||||
:options="cmOptions"
|
||||
height="500"
|
||||
style="font-size:14px;"
|
||||
@change="onChange"
|
||||
></codemirror>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="yamlDialog = false">取 消</el-button>
|
||||
<el-button type="primary" @click="updateConfigMap()">更 新</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
import yaml2obj from 'js-yaml';
|
||||
import json2yaml from 'json2yaml';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cmOptions: common.cmOptions,
|
||||
contentYaml: '',
|
||||
currentPage: 1,
|
||||
pagesize: 10,
|
||||
pagesizeList: [10, 20, 30],
|
||||
searchInput: '',
|
||||
namespaceValue: 'default',
|
||||
namespaceList: [],
|
||||
namespaceListUrl: common.k8sNamespaceList,
|
||||
appLoading: false,
|
||||
configMapList: [],
|
||||
configMapTotal: 0,
|
||||
getConfigMapsData: {
|
||||
url: common.k8sConfigMapList,
|
||||
params: {
|
||||
filter_name: '',
|
||||
namespace: '',
|
||||
page: '',
|
||||
limit: '',
|
||||
}
|
||||
},
|
||||
configMapDetail: {},
|
||||
getConfigMapDetailData: {
|
||||
url: common.k8sConfigMapDetail,
|
||||
params: {
|
||||
configmap_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
yamlDialog: false,
|
||||
updateConfigMapData: {
|
||||
url: common.k8sConfigMapUpdate,
|
||||
params: {
|
||||
namespace: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
delConfigMapData: {
|
||||
url: common.k8sconfigmapDel,
|
||||
params: {
|
||||
configmap_name: '',
|
||||
namespace: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
transYaml(content) {
|
||||
return json2yaml.stringify(content)
|
||||
},
|
||||
transObj(content) {
|
||||
return yaml2obj.load(content)
|
||||
},
|
||||
onChange(val) {
|
||||
this.contentYaml = val
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pagesize = size;
|
||||
this.getConfigMaps()
|
||||
},
|
||||
handleCurrentChange(currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
this.getConfigMaps()
|
||||
},
|
||||
handleClose(done) {
|
||||
this.$confirm('确认关闭?')
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
ellipsis(value) {
|
||||
return value.length>15?value.substring(0,15)+'...':value
|
||||
},
|
||||
timeTrans(timestamp) {
|
||||
let date = new Date(new Date(timestamp).getTime() + 8 * 3600 * 1000)
|
||||
date = date.toJSON();
|
||||
date = date.substring(0, 19).replace('T', ' ')
|
||||
return date
|
||||
},
|
||||
restartTotal(e) {
|
||||
let index, sum = 0
|
||||
let containerStatuses = e.row.status.containerStatuses
|
||||
for ( index in containerStatuses) {
|
||||
sum = sum + containerStatuses[index].restartCount
|
||||
}
|
||||
return sum
|
||||
},
|
||||
getNamespaces() {
|
||||
httpClient.get(this.namespaceListUrl)
|
||||
.then(res => {
|
||||
this.namespaceList = res.data.items
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getConfigMaps() {
|
||||
this.appLoading = true
|
||||
this.getConfigMapsData.params.filter_name = this.searchInput
|
||||
this.getConfigMapsData.params.namespace = this.namespaceValue
|
||||
this.getConfigMapsData.params.page = this.currentPage
|
||||
this.getConfigMapsData.params.limit = this.pagesize
|
||||
httpClient.get(this.getConfigMapsData.url, {params: this.getConfigMapsData.params})
|
||||
.then(res => {
|
||||
this.configMapList = res.data.items
|
||||
this.configMapTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.appLoading = false
|
||||
},
|
||||
getConfigMapDetail(e) {
|
||||
this.getConfigMapDetailData.params.configmap_name = e.row.metadata.name
|
||||
this.getConfigMapDetailData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getConfigMapDetailData.url, {params: this.getConfigMapDetailData.params})
|
||||
.then(res => {
|
||||
this.configMapDetail = res.data
|
||||
this.contentYaml = this.transYaml(this.configMapDetail)
|
||||
this.yamlDialog = true
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
updateConfigMap() {
|
||||
let content = JSON.stringify(this.transObj(this.contentYaml))
|
||||
this.updateConfigMapData.params.namespace = this.namespaceValue
|
||||
this.updateConfigMapData.params.content = content
|
||||
httpClient.put(this.updateConfigMapData.url, this.updateConfigMapData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.yamlDialog = false
|
||||
},
|
||||
delConfigMap(e) {
|
||||
this.delConfigMapData.params.configmap_name = e.row.metadata.name
|
||||
this.delConfigMapData.params.namespace = this.namespaceValue
|
||||
httpClient.delete(this.delConfigMapData.url, {data: this.delConfigMapData.params})
|
||||
.then(res => {
|
||||
this.getConfigMaps()
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
handleConfirm(obj, operateName, fn) {
|
||||
this.confirmContent = '确认继续 ' + operateName + ' 操作吗?'
|
||||
this.$confirm(this.confirmContent,'提示',{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
.then(() => {
|
||||
fn(obj)
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.info({
|
||||
message: '已取消操作'
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
namespaceValue: {
|
||||
handler() {
|
||||
localStorage.setItem('namespace', this.namespaceValue)
|
||||
this.currentPage = 1
|
||||
this.getConfigMaps()
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
if (localStorage.getItem('namespace') !== undefined && localStorage.getItem('namespace') !== null) {
|
||||
this.namespaceValue = localStorage.getItem('namespace')
|
||||
}
|
||||
this.getNamespaces()
|
||||
this.getConfigMaps()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.configmap-head-card,.configmap-body-card {
|
||||
border-radius: 1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.configmap-head-search {
|
||||
width:160px;
|
||||
margin-right:10px;
|
||||
}
|
||||
.configmap-body-configmapname {
|
||||
color: #4795EE;
|
||||
}
|
||||
.configmap-body-configmapname:hover {
|
||||
color: rgb(84, 138, 238);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
369
src/views/daemonset/DaemonSet.vue
Normal file
@ -0,0 +1,369 @@
|
||||
<template>
|
||||
<div class="daemonset">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="daemonset-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<span>命名空间: </span>
|
||||
<el-select v-model="namespaceValue" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in namespaceList"
|
||||
:key="index"
|
||||
:label="item.metadata.name"
|
||||
:value="item.metadata.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" :offset="16">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Refresh" plain @click="getDaemonSets()">刷新</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="daemonset-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
<div>
|
||||
<el-button disabled style="border-radius:2px;" icon="Edit" type="primary">创建</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-input class="daemonset-head-search" clearable placeholder="请输入" v-model="searchInput"></el-input>
|
||||
<el-button style="border-radius:2px;" icon="Search" type="primary" plain @click="getDaemonSets()">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="daemonset-body-card" shadow="never" :body-style="{padding:'5px'}">
|
||||
<el-table
|
||||
style="width:100%;font-size:12px;margin-bottom:10px;"
|
||||
:data="daemonSetList"
|
||||
v-loading="appLoading">
|
||||
<el-table-column width="20"></el-table-column>
|
||||
<el-table-column align=left label="DaemonSet名">
|
||||
<template v-slot="scope">
|
||||
<a class="daemonset-body-daemonsetname">{{ scope.row.metadata.name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="标签">
|
||||
<template v-slot="scope">
|
||||
<div v-for="(val, key) in scope.row.metadata.labels" :key="key">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="200"
|
||||
trigger="hover"
|
||||
:content="key + ':' + val">
|
||||
<template #reference>
|
||||
<el-tag style="margin-bottom: 5px" type="warning">{{ ellipsis(key + ":" + val) }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="容器组">
|
||||
<template v-slot="scope">
|
||||
<span>{{ scope.row.status.numberAvailable>0?scope.row.status.numberAvailable:0 }} / {{ scope.row.status.desiredNumberScheduled>0?scope.row.status.desiredNumberScheduled:0 }} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center min-width="100" label="创建时间">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="info">{{ timeTrans(scope.row.metadata.creationTimestamp) }} </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="镜像">
|
||||
<template v-slot="scope">
|
||||
<div v-for="(val, key) in scope.row.spec.template.spec.containers" :key="key">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="200"
|
||||
trigger="hover"
|
||||
:content="val.image">
|
||||
<template #reference>
|
||||
<el-tag style="margin-bottom: 5px">{{ ellipsis(val.image.split('/')[2]==undefined?val.image:val.image.split('/')[2]) }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="操作" width="200">
|
||||
<template v-slot="scope">
|
||||
<el-button size="small" style="border-radius:2px;" icon="Edit" type="primary" plain @click="getDaemonSetDetail(scope)">YAML</el-button>
|
||||
<el-button size="small" style="border-radius:2px;" icon="Delete" type="danger" @click="handleConfirm(scope, '删除', delDaemonSet)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="daemonset-body-pagination"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-sizes="pagesizeList"
|
||||
:page-size="pagesize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="daemonSetTotal">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="YAML信息" v-model="yamlDialog" width="45%" top="5%">
|
||||
<codemirror
|
||||
:value="contentYaml"
|
||||
border
|
||||
:options="cmOptions"
|
||||
height="500"
|
||||
style="font-size:14px;"
|
||||
@change="onChange"
|
||||
></codemirror>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="yamlDialog = false">取 消</el-button>
|
||||
<el-button type="primary" @click="updateDaemonSet()">更 新</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
import yaml2obj from 'js-yaml';
|
||||
import json2yaml from 'json2yaml';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cmOptions: common.cmOptions,
|
||||
contentYaml: '',
|
||||
currentPage: 1,
|
||||
pagesize: 10,
|
||||
pagesizeList: [10, 20, 30],
|
||||
searchInput: '',
|
||||
namespaceValue: 'default',
|
||||
namespaceList: [],
|
||||
namespaceListUrl: common.k8sNamespaceList,
|
||||
appLoading: false,
|
||||
daemonSetList: [],
|
||||
daemonSetTotal: 0,
|
||||
getDaemonSetsData: {
|
||||
url: common.k8sDaemonSetList,
|
||||
params: {
|
||||
filter_name: '',
|
||||
namespace: '',
|
||||
page: '',
|
||||
limit: '',
|
||||
}
|
||||
},
|
||||
daemonSetDetail: {},
|
||||
getDaemonSetDetailData: {
|
||||
url: common.k8sDaemonSetDetail,
|
||||
params: {
|
||||
daemonset_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
yamlDialog: false,
|
||||
updateDaemonSetData: {
|
||||
url: common.k8sDaemonSetUpdate,
|
||||
params: {
|
||||
namespace: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
delDaemonSetData: {
|
||||
url: common.k8sdaemonsetDel,
|
||||
params: {
|
||||
daemonset_name: '',
|
||||
namespace: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
transYaml(content) {
|
||||
return json2yaml.stringify(content)
|
||||
},
|
||||
transObj(content) {
|
||||
return yaml2obj.load(content)
|
||||
},
|
||||
onChange(val) {
|
||||
this.contentYaml = val
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pagesize = size;
|
||||
this.getDaemonSets()
|
||||
},
|
||||
handleCurrentChange(currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
this.getDaemonSets()
|
||||
},
|
||||
handleClose(done) {
|
||||
this.$confirm('确认关闭?')
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
ellipsis(value) {
|
||||
return value.length>15?value.substring(0,15)+'...':value
|
||||
},
|
||||
timeTrans(timestamp) {
|
||||
let date = new Date(new Date(timestamp).getTime() + 8 * 3600 * 1000)
|
||||
date = date.toJSON();
|
||||
date = date.substring(0, 19).replace('T', ' ')
|
||||
return date
|
||||
},
|
||||
restartTotal(e) {
|
||||
let index, sum = 0
|
||||
let containerStatuses = e.row.status.containerStatuses
|
||||
for ( index in containerStatuses) {
|
||||
sum = sum + containerStatuses[index].restartCount
|
||||
}
|
||||
return sum
|
||||
},
|
||||
getNamespaces() {
|
||||
httpClient.get(this.namespaceListUrl)
|
||||
.then(res => {
|
||||
this.namespaceList = res.data.items
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getDaemonSets() {
|
||||
this.appLoading = true
|
||||
this.getDaemonSetsData.params.filter_name = this.searchInput
|
||||
this.getDaemonSetsData.params.namespace = this.namespaceValue
|
||||
this.getDaemonSetsData.params.page = this.currentPage
|
||||
this.getDaemonSetsData.params.limit = this.pagesize
|
||||
httpClient.get(this.getDaemonSetsData.url, {params: this.getDaemonSetsData.params})
|
||||
.then(res => {
|
||||
this.daemonSetList = res.data.items
|
||||
this.daemonSetTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.appLoading = false
|
||||
},
|
||||
getDaemonSetDetail(e) {
|
||||
this.getDaemonSetDetailData.params.daemonset_name = e.row.metadata.name
|
||||
this.getDaemonSetDetailData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getDaemonSetDetailData.url, {params: this.getDaemonSetDetailData.params})
|
||||
.then(res => {
|
||||
this.daemonSetDetail = res.data
|
||||
this.contentYaml = this.transYaml(this.daemonSetDetail)
|
||||
this.yamlDialog = true
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
updateDaemonSet() {
|
||||
let content = JSON.stringify(this.transObj(this.contentYaml))
|
||||
this.updateDaemonSetData.params.namespace = this.namespaceValue
|
||||
this.updateDaemonSetData.params.content = content
|
||||
httpClient.put(this.updateDaemonSetData.url, this.updateDaemonSetData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.yamlDialog = false
|
||||
},
|
||||
delDaemonSet(e) {
|
||||
this.delDaemonSetData.params.daemonset_name = e.row.metadata.name
|
||||
this.delDaemonSetData.params.namespace = this.namespaceValue
|
||||
httpClient.delete(this.delDaemonSetData.url, {data: this.delDaemonSetData.params})
|
||||
.then(res => {
|
||||
this.getDaemonSets()
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
handleConfirm(obj, operateName, fn) {
|
||||
this.confirmContent = '确认继续 ' + operateName + ' 操作吗?'
|
||||
this.$confirm(this.confirmContent,'提示',{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
.then(() => {
|
||||
fn(obj)
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.info({
|
||||
message: '已取消操作'
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
namespaceValue: {
|
||||
handler() {
|
||||
localStorage.setItem('namespace', this.namespaceValue)
|
||||
this.currentPage = 1
|
||||
this.getDaemonSets()
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
if (localStorage.getItem('namespace') !== undefined && localStorage.getItem('namespace') !== null) {
|
||||
this.namespaceValue = localStorage.getItem('namespace')
|
||||
}
|
||||
this.getNamespaces()
|
||||
this.getDaemonSets()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.daemonset-head-card,.daemonset-body-card {
|
||||
border-radius: 1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.daemonset-head-search {
|
||||
width:160px;
|
||||
margin-right:10px;
|
||||
}
|
||||
.daemonset-body-daemonsetname {
|
||||
color: #4795EE;
|
||||
}
|
||||
.daemonset-body-daemonsetname:hover {
|
||||
color: rgb(84, 138, 238);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
611
src/views/deployment/Deployment.vue
Normal file
@ -0,0 +1,611 @@
|
||||
<template>
|
||||
<div class="deploy">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="deploy-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<span>命名空间: </span>
|
||||
<el-select v-model="namespaceValue" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in namespaceList"
|
||||
:key="index"
|
||||
:label="item.metadata.name"
|
||||
:value="item.metadata.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" :offset="16">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Refresh" plain @click="getDeployments()">刷新</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="deploy-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Edit" type="primary" @click="createDeploymentDrawer = true" v-loading.fullscreen.lock="fullscreenLoading">创建</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-input class="deploy-head-search" clearable placeholder="请输入" v-model="searchInput"></el-input>
|
||||
<el-button style="border-radius:2px;" icon="Search" type="primary" plain @click="getDeployments()">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="deploy-body-card" shadow="never" :body-style="{padding:'5px'}">
|
||||
<el-table
|
||||
style="width:100%;font-size:12px;margin-bottom:10px;"
|
||||
:data="deploymentList"
|
||||
v-loading="appLoading">
|
||||
<el-table-column width="20"></el-table-column>
|
||||
<el-table-column align=left label="Deployment名">
|
||||
<template v-slot="scope">
|
||||
<a class="deploy-body-deployname">{{ scope.row.metadata.name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="标签">
|
||||
<template v-slot="scope">
|
||||
<div v-for="(val, key) in scope.row.metadata.labels" :key="key">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="200"
|
||||
trigger="hover"
|
||||
:content="key + ':' + val">
|
||||
<template #reference>
|
||||
<el-tag style="margin-bottom: 5px" type="warning">{{ ellipsis(key + ":" + val) }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="容器组">
|
||||
<template v-slot="scope">
|
||||
<span>{{ scope.row.status.availableReplicas>0?scope.row.status.availableReplicas:0 }} / {{ scope.row.spec.replicas>0?scope.row.spec.replicas:0 }} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center min-width="100" label="创建时间">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="info">{{ timeTrans(scope.row.metadata.creationTimestamp) }} </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="镜像">
|
||||
<template v-slot="scope">
|
||||
<div v-for="(val, key) in scope.row.spec.template.spec.containers" :key="key">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="200"
|
||||
trigger="hover"
|
||||
:content="val.image">
|
||||
<template #reference>
|
||||
<el-tag style="margin-bottom: 5px">{{ ellipsis(val.image.split('/')[2]==undefined?val.image:val.image.split('/')[2]) }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="操作" width="400">
|
||||
<template v-slot="scope">
|
||||
<el-button size="small" style="border-radius:2px;" icon="Edit" type="primary" plain @click="getDeploymentDetail(scope)">YAML</el-button>
|
||||
<el-button size="small" style="border-radius:2px;" icon="Plus" type="primary" @click="handleScale(scope)">扩缩</el-button>
|
||||
<el-button size="small" style="border-radius:2px;" icon="RefreshLeft" type="primary" @click="handleConfirm(scope, '重启', restartDeployment)">重启</el-button>
|
||||
<el-button size="small" style="border-radius:2px;" icon="Delete" type="danger" @click="handleConfirm(scope, '删除', delDeployment)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="deploy-body-pagination"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-sizes="pagesizeList"
|
||||
:page-size="pagesize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="deploymentTotal">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-drawer
|
||||
v-model="createDeploymentDrawer"
|
||||
:direction="direction"
|
||||
:before-close="handleClose">
|
||||
<template #title>
|
||||
<h4>创建Deployment</h4>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="20">
|
||||
<el-form ref="createDeployment" :rules="createDeploymentRules" :model="createDeployment" label-width="80px">
|
||||
<el-form-item class="deploy-create-form" label="名称" prop="name">
|
||||
<el-input v-model="createDeployment.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="deploy-create-form" label="命名空间" prop="namespace">
|
||||
<el-select v-model="createDeployment.namespace" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in namespaceList"
|
||||
:key="index"
|
||||
:label="item.metadata.name"
|
||||
:value="item.metadata.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="deploy-create-form" label="副本数" prop="replicas">
|
||||
<el-input-number v-model="createDeployment.replicas" :min="1" :max="10"></el-input-number>
|
||||
<el-popover
|
||||
placement="top"
|
||||
:width="100"
|
||||
trigger="hover"
|
||||
content="申请副本数上限为10个">
|
||||
<template #reference>
|
||||
<el-icon style="width:2em;font-size:18px;color:#4795EE"><WarningFilled/></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
<el-form-item class="deploy-create-form" label="镜像" prop="image">
|
||||
<el-input v-model="createDeployment.image"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="deploy-create-form" label="标签" prop="label_str">
|
||||
<el-input v-model="createDeployment.label_str" placeholder="示例: project=ms,app=gateway"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="deploy-create-form" label="资源配额" prop="resource">
|
||||
<el-select v-model="createDeployment.resource" placeholder="请选择">
|
||||
<el-option value="0.5/1" label="0.5C1G"></el-option>
|
||||
<el-option value="1/2" label="1C2G"></el-option>
|
||||
<el-option value="2/4" label="2C4G"></el-option>
|
||||
<el-option value="4/8" label="4C8G"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="deploy-create-form" label="容器端口" prop="container_port">
|
||||
<el-input v-model="createDeployment.container_port" placeholder="示例: 80"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="deploy-create-form" label="健康检查" prop="health">
|
||||
<el-switch v-model="createDeployment.health_check" />
|
||||
</el-form-item>
|
||||
<el-form-item class="deploy-create-form" label="检查路径" prop="healthPath">
|
||||
<el-input v-model="createDeployment.health_path" placeholder="示例: /health"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="createDeploymentDrawer = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm('createDeployment')">立即创建</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
<el-dialog title="YAML信息" v-model="yamlDialog" width="45%" top="2%">
|
||||
<codemirror
|
||||
:value="contentYaml"
|
||||
border
|
||||
:options="cmOptions"
|
||||
height="500"
|
||||
style="font-size:14px;"
|
||||
@change="onChange"
|
||||
></codemirror>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="this.yamlDialog = false">取 消</el-button>
|
||||
<el-button type="primary" @click="updateDeployment()">更 新</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog title="副本数调整" v-model="scaleDialog" width="25%">
|
||||
<div style="text-align:center">
|
||||
<span>实例数: </span>
|
||||
<el-input-number :step="1" v-model="scaleNum" :min="0" :max="30" label="描述文字"></el-input-number>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="scaleDialog = false">取 消</el-button>
|
||||
<el-button type="primary" @click="scaleDeployment()">更 新</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
import yaml2obj from 'js-yaml';
|
||||
import json2yaml from 'json2yaml';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cmOptions: common.cmOptions,
|
||||
contentYaml: '',
|
||||
currentPage: 1,
|
||||
pagesize: 10,
|
||||
pagesizeList: [10, 20, 30],
|
||||
searchInput: '',
|
||||
namespaceValue: 'default',
|
||||
namespaceList: [],
|
||||
namespaceListUrl: common.k8sNamespaceList,
|
||||
appLoading: false,
|
||||
deploymentList: [],
|
||||
deploymentTotal: 0,
|
||||
getDeploymentsData: {
|
||||
url: common.k8sDeploymentList,
|
||||
params: {
|
||||
filter_name: '',
|
||||
namespace: '',
|
||||
page: '',
|
||||
limit: '',
|
||||
}
|
||||
},
|
||||
fullscreenLoading: false,
|
||||
direction: 'rtl',
|
||||
createDeploymentDrawer: false,
|
||||
createDeployment: {
|
||||
name: '',
|
||||
namespace: '',
|
||||
replicas: 1,
|
||||
image: '',
|
||||
resource: '',
|
||||
health_check: false,
|
||||
health_path: '',
|
||||
label_str: '',
|
||||
label: {},
|
||||
container_port: ''
|
||||
},
|
||||
createDeploymentData: {
|
||||
url: common.k8sDeploymentCreate,
|
||||
params: {}
|
||||
},
|
||||
createDeploymentRules: {
|
||||
name: [{
|
||||
required: true,
|
||||
message: '请填写名称',
|
||||
trigger: 'change'
|
||||
}],
|
||||
image: [{
|
||||
required: true,
|
||||
message: '请填写镜像',
|
||||
trigger: 'change'
|
||||
}],
|
||||
namespace: [{
|
||||
required: true,
|
||||
message: '请选择命名空间',
|
||||
trigger: 'change'
|
||||
}],
|
||||
resource: [{
|
||||
required: true,
|
||||
message: '请选择配额',
|
||||
trigger: 'change'
|
||||
}],
|
||||
label_str: [{
|
||||
required: true,
|
||||
message: '请填写标签',
|
||||
trigger: 'change'
|
||||
}],
|
||||
container_port: [{
|
||||
required: true,
|
||||
message: '请填写容器端口',
|
||||
trigger: 'change'
|
||||
}],
|
||||
},
|
||||
deploymentDetail: {},
|
||||
getDeploymentDetailData: {
|
||||
url: common.k8sDeploymentDetail,
|
||||
params: {
|
||||
deployment_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
yamlDialog: false,
|
||||
updateDeploymentData: {
|
||||
url: common.k8sDeploymentUpdate,
|
||||
params: {
|
||||
namespace: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
scaleNum: 0,
|
||||
scaleDialog: false,
|
||||
scaleDeploymentData: {
|
||||
url: common.k8sDeploymentScale,
|
||||
params: {
|
||||
deployment_name: '',
|
||||
namespace: '',
|
||||
scale_num: ''
|
||||
}
|
||||
},
|
||||
restartDeploymentData: {
|
||||
url: common.k8sDeploymentRestart,
|
||||
params: {
|
||||
deployment_name: '',
|
||||
namespace: '',
|
||||
}
|
||||
},
|
||||
delDeploymentData: {
|
||||
url: common.k8sDeploymentDel,
|
||||
params: {
|
||||
deployment_name: '',
|
||||
namespace: '',
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
transYaml(content) {
|
||||
return json2yaml.stringify(content)
|
||||
},
|
||||
transObj(content) {
|
||||
return yaml2obj.load(content)
|
||||
},
|
||||
onChange(val) {
|
||||
this.contentYaml = val
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pagesize = size;
|
||||
this.getDeployments()
|
||||
},
|
||||
handleCurrentChange(currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
this.getDeployments()
|
||||
},
|
||||
handleClose(done) {
|
||||
this.$confirm('确认关闭?')
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
ellipsis(value) {
|
||||
return value.length>15?value.substring(0,15)+'...':value
|
||||
},
|
||||
timeTrans(timestamp) {
|
||||
let date = new Date(new Date(timestamp).getTime() + 8 * 3600 * 1000)
|
||||
date = date.toJSON();
|
||||
date = date.substring(0, 19).replace('T', ' ')
|
||||
return date
|
||||
},
|
||||
getNamespaces() {
|
||||
httpClient.get(this.namespaceListUrl)
|
||||
.then(res => {
|
||||
this.namespaceList = res.data.items
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getDeployments() {
|
||||
this.appLoading = true
|
||||
this.getDeploymentsData.params.filter_name = this.searchInput
|
||||
this.getDeploymentsData.params.namespace = this.namespaceValue
|
||||
this.getDeploymentsData.params.page = this.currentPage
|
||||
this.getDeploymentsData.params.limit = this.pagesize
|
||||
httpClient.get(this.getDeploymentsData.url, {params: this.getDeploymentsData.params})
|
||||
.then(res => {
|
||||
this.deploymentList = res.data.items
|
||||
this.deploymentTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.appLoading = false
|
||||
},
|
||||
getDeploymentDetail(e) {
|
||||
this.getDeploymentDetailData.params.deployment_name = e.row.metadata.name
|
||||
this.getDeploymentDetailData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getDeploymentDetailData.url, {params: this.getDeploymentDetailData.params})
|
||||
.then(res => {
|
||||
this.deploymentDetail = res.data
|
||||
this.contentYaml = this.transYaml(this.deploymentDetail)
|
||||
this.yamlDialog = true
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
updateDeployment() {
|
||||
let content = JSON.stringify(this.transObj(this.contentYaml))
|
||||
this.updateDeploymentData.params.namespace = this.namespaceValue
|
||||
this.updateDeploymentData.params.content = content
|
||||
httpClient.put(this.updateDeploymentData.url, this.updateDeploymentData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
this.getDeployments()
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.yamlDialog = false
|
||||
},
|
||||
|
||||
handleScale(e) {
|
||||
this.scaleDialog = true
|
||||
this.deploymentDetail = e.row
|
||||
this.scaleNum = e.row.spec.replicas
|
||||
},
|
||||
|
||||
scaleDeployment() {
|
||||
this.scaleDeploymentData.params.deployment_name = this.deploymentDetail.metadata.name
|
||||
this.scaleDeploymentData.params.namespace = this.namespaceValue
|
||||
this.scaleDeploymentData.params.scale_num = this.scaleNum
|
||||
httpClient.put(this.scaleDeploymentData.url, this.scaleDeploymentData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
this.getDeployments()
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.scaleDialog = false
|
||||
},
|
||||
|
||||
restartDeployment(e) {
|
||||
this.restartDeploymentData.params.deployment_name = e.row.metadata.name
|
||||
this.restartDeploymentData.params.namespace = this.namespaceValue
|
||||
httpClient.put(this.restartDeploymentData.url, this.restartDeploymentData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
this.getDeployments()
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
delDeployment(e) {
|
||||
this.delDeploymentData.params.deployment_name = e.row.metadata.name
|
||||
this.delDeploymentData.params.namespace = this.namespaceValue
|
||||
httpClient.delete(this.delDeploymentData.url, {data: this.delDeploymentData.params})
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
this.getDeployments()
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleConfirm(obj, operateName, fn) {
|
||||
this.confirmContent = '确认继续 ' + operateName + ' 操作吗?'
|
||||
|
||||
this.$confirm(this.confirmContent,'提示',{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
.then(() => {
|
||||
fn(obj)
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.info({
|
||||
message: '已取消操作'
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
createDeployFunc() {
|
||||
let reg = new RegExp("(^[A-Za-z]+=[A-Za-z0-9]+).*")
|
||||
if (!reg.test(this.createDeployment.label_str)) {
|
||||
this.$message.warning({
|
||||
message: "标签填写异常,请确认后重新填写"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.fullscreenLoading = true
|
||||
let label = new Map()
|
||||
let cpu, memory
|
||||
let a = (this.createDeployment.label_str).split(",")
|
||||
a.forEach(item => {
|
||||
let b = item.split("=")
|
||||
label[b[0]] = b[1]
|
||||
})
|
||||
let resourceList = this.createDeployment.resource.split("/")
|
||||
cpu = resourceList[0]
|
||||
memory = resourceList[1] + "Gi"
|
||||
this.createDeploymentData.params = this.createDeployment
|
||||
this.createDeploymentData.params.container_port = parseInt(this.createDeployment.container_port)
|
||||
this.createDeploymentData.params.label = label
|
||||
this.createDeploymentData.params.cpu = cpu
|
||||
this.createDeploymentData.params.memory = memory
|
||||
httpClient.post(this.createDeploymentData.url, this.createDeploymentData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
this.getDeployments()
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.resetForm('createDeployment')
|
||||
this.fullscreenLoading = false
|
||||
this.createDeploymentDrawer = false
|
||||
},
|
||||
resetForm(formName) {
|
||||
this.$refs[formName].resetFields()
|
||||
},
|
||||
submitForm(formName) {
|
||||
this.$refs[formName].validate((valid) => {
|
||||
if (valid) {
|
||||
this.createDeployFunc()
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
namespaceValue: {
|
||||
handler() {
|
||||
localStorage.setItem('namespace', this.namespaceValue)
|
||||
this.currentPage = 1
|
||||
this.getDeployments()
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
if (localStorage.getItem('namespace') !== undefined && localStorage.getItem('namespace') !== null) {
|
||||
this.namespaceValue = localStorage.getItem('namespace')
|
||||
}
|
||||
this.getNamespaces()
|
||||
this.getDeployments()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.deploy-head-card,.deploy-body-card {
|
||||
border-radius: 1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.deploy-head-search {
|
||||
width:160px;
|
||||
margin-right:10px;
|
||||
}
|
||||
.deploy-body-deployname {
|
||||
color: #4795EE;
|
||||
}
|
||||
.deploy-body-deployname:hover {
|
||||
color: rgb(84, 138, 238);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
365
src/views/home/Home.vue
Normal file
@ -0,0 +1,365 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item title="集群资源" name="1">
|
||||
<el-row :gutter="10" style="margin-bottom: 10px;">
|
||||
<el-col :span="5">
|
||||
<el-card class="home-node-card" :body-style="{padding:'10px'}">
|
||||
<div style="float:left;padding-top:20%">
|
||||
<el-progress :stroke-width="20" :show-text="false" type="circle" :percentage="namespaceActive/namespaceTotal * 100"></el-progress>
|
||||
</div>
|
||||
<div>
|
||||
<p class="home-node-card-title">命名空间: Active/总量</p>
|
||||
<p class="home-node-card-num">{{ namespaceActive }}/{{ namespaceTotal }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-card class="home-node-card" :body-style="{padding:'10px'}">
|
||||
<div>
|
||||
<p class="home-node-card-title">服务数</p>
|
||||
<p class="home-node-card-num">{{ deploymentTotal }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-card class="home-node-card" :body-style="{padding:'10px'}">
|
||||
<div>
|
||||
<p class="home-node-card-title">实例数</p>
|
||||
<p class="home-node-card-num">{{ podTotal }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="节点资源" name="2">
|
||||
<el-row :gutter="10" style="margin-bottom: 10px;">
|
||||
<el-col :span="5">
|
||||
<el-card class="home-node-card" :body-style="{padding:'10px'}">
|
||||
<div style="float:left;padding-top:20%">
|
||||
<el-progress :stroke-width="20" :show-text="false" type="circle" :percentage="nodeTotal/nodeTotal * 100"></el-progress>
|
||||
</div>
|
||||
<div>
|
||||
<p class="home-node-card-title">节点: Ready/总数量</p>
|
||||
<p class="home-node-card-num">{{ nodeTotal }}/{{ nodeTotal }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-card class="home-node-card" :body-style="{padding:'10px'}">
|
||||
<div style="float:left;padding-top:20%">
|
||||
<el-progress :stroke-width="20" :show-text="false" type="circle" :percentage="nodeCpuAllocatable/nodeCpuCapacity * 100"></el-progress>
|
||||
</div>
|
||||
<div>
|
||||
<p class="home-node-card-title">CPU: 可分配/容量</p>
|
||||
<p class="home-node-card-num">{{ nodeCpuAllocatable }}/{{ nodeCpuCapacity }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-card class="home-node-card" :body-style="{padding:'10px'}">
|
||||
<div style="float:left;padding-top:20%">
|
||||
<el-progress :stroke-width="20" :show-text="false" type="circle" :percentage="nodeMemAllocatable/nodeMemCapacity * 100"></el-progress>
|
||||
</div>
|
||||
<div>
|
||||
<p class="home-node-card-title">内存: 可分配/容量</p>
|
||||
<p class="home-node-card-num">{{ specTrans(nodeMemAllocatable) }}Gi/{{ specTrans(nodeMemCapacity) }}Gi</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-card class="home-node-card" :body-style="{padding:'10px'}">
|
||||
<div style="float:left;padding-top:20%">
|
||||
<el-progress :stroke-width="20" :show-text="false" type="circle" :percentage="nodePodAllocatable/nodePodAllocatable * 100"></el-progress>
|
||||
</div>
|
||||
<div>
|
||||
<p class="home-node-card-title">POD: 可分配/容量</p>
|
||||
<p class="home-node-card-num">{{ nodePodAllocatable }}/{{ nodePodAllocatable }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="资源统计" name="3">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="24" style="margin-bottom: 10px;">
|
||||
<el-card class="home-dash-card" :body-style="{padding:'10px'}">
|
||||
<div id="podNumDash" style="height: 300px;">
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-card class="home-dash-card" :body-style="{padding:'10px'}">
|
||||
<div id="deployNumDash" style="height: 300px;">
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
activeNames: ["1", "2", "3"],
|
||||
namespaceActive: 0,
|
||||
namespaceValue: 'default',
|
||||
namespaceTotal: 0,
|
||||
namespaceListUrl: common.k8sNamespaceList,
|
||||
nodeTotal: 0,
|
||||
nodeCpuAllocatable: 0,
|
||||
nodeCpuCapacity: 0,
|
||||
nodeMemAllocatable: 0,
|
||||
nodeMemCapacity: 0,
|
||||
nodePodAllocatable: 0,
|
||||
nodePodCapacity: 0,
|
||||
getNodesData: {
|
||||
url: common.k8sNodeList,
|
||||
params: {}
|
||||
},
|
||||
deploymentTotal: 0,
|
||||
getDeploymentsData: {
|
||||
url: common.k8sDeploymentList,
|
||||
params: {
|
||||
namespace: '',
|
||||
}
|
||||
},
|
||||
podTotal: 0,
|
||||
getPodsData: {
|
||||
url: common.k8sPodList,
|
||||
params: {
|
||||
namespace: '',
|
||||
}
|
||||
},
|
||||
podNumNp: [],
|
||||
podNumNpUrl: common.k8sPodNumNp,
|
||||
deploymentNumNp: [],
|
||||
deploymentNumNpUrl: common.k8sDeploymentNumNp
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getNamespaces() {
|
||||
httpClient.get(this.namespaceListUrl)
|
||||
.then(res => {
|
||||
this.namespaceTotal = res.data.total
|
||||
let namespaceList = res.data.items
|
||||
let index
|
||||
for (index in namespaceList) {
|
||||
if (namespaceList[index].status.phase === "Active" ) {
|
||||
this.namespaceActive = this.namespaceActive + 1
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
specTrans(num) {
|
||||
let a = num / 1024 / 1024
|
||||
return a.toFixed(0)
|
||||
},
|
||||
getNodes() {
|
||||
httpClient.get(this.getNodesData.url, {params: this.getNodesData.params})
|
||||
.then(res => {
|
||||
this.nodeTotal = res.data.total
|
||||
let nodeList = res.data.items
|
||||
let index
|
||||
for (index in nodeList) {
|
||||
let isnum = /^\d+$/.test(nodeList[index].status.allocatable.cpu);
|
||||
if (!isnum) {
|
||||
continue
|
||||
}
|
||||
this.nodeCpuAllocatable = parseInt(nodeList[index].status.allocatable.cpu) + this.nodeCpuAllocatable
|
||||
this.nodeCpuCapacity = parseInt(nodeList[index].status.capacity.cpu) + this.nodeCpuCapacity
|
||||
this.nodeMemAllocatable = parseInt(nodeList[index].status.allocatable.memory) + this.nodeMemAllocatable
|
||||
this.nodeMemCapacity = parseInt(nodeList[index].status.capacity.memory) + this.nodeMemCapacity
|
||||
this.nodePodAllocatable = parseInt(nodeList[index].status.allocatable.pods) + this.nodePodAllocatable
|
||||
this.nodePodCapacity = parseInt(nodeList[index].status.capacity.pods) + this.nodePodCapacity
|
||||
}
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getDeployments() {
|
||||
this.getDeploymentsData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getDeploymentsData.url, {params: this.getDeploymentsData.params})
|
||||
.then(res => {
|
||||
this.deploymentTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getPods() {
|
||||
this.getPodsData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getPodsData.url, {params: this.getPodsData.params})
|
||||
.then(res => {
|
||||
this.podTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getDeploymentNumNp() {
|
||||
httpClient.get(this.deploymentNumNpUrl)
|
||||
.then(res => {
|
||||
this.deploymentNumNp = res.data
|
||||
this.getDeployNumDash()
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getPodNumNp() {
|
||||
httpClient.get(this.podNumNpUrl)
|
||||
.then(res => {
|
||||
this.podNumNp = res.data
|
||||
this.getPodNumDash()
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getPodNumDash(){
|
||||
if (this.podNumDash != null && this.podNumDash != "" && this.podNumDash != undefined) {
|
||||
this.podNumDash.dispose()
|
||||
}
|
||||
this.podNumDash = echarts.init(document.getElementById('podNumDash'));
|
||||
this.podNumDash.setOption({
|
||||
title: { text: 'Pods per Namespace', textStyle: {color:'rgb(134, 135, 136)'}},
|
||||
color: ['#67E0E3', '#9FE6B8', '#FFDB5C','#ff9f7f', '#fb7293', '#E062AE', '#E690D1', '#e7bcf3', '#9d96f5', '#8378EA', '#96BFFF'],
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
label: {
|
||||
backgroundColor: "#76baf1"
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['Pods']
|
||||
},
|
||||
dataset: {
|
||||
dimensions: ['namespace','pod_num'],
|
||||
source: this.podNumNp
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
axisLabel:{
|
||||
interval: 0,
|
||||
formatter: function (value) {
|
||||
return value.length>5?value.substring(0,5)+'...':value
|
||||
}
|
||||
},
|
||||
},
|
||||
yAxis: [
|
||||
{type: 'value'}
|
||||
],
|
||||
series: [{
|
||||
name: 'Pods',
|
||||
type: 'bar',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top'
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
getDeployNumDash(){
|
||||
if (this.deployNumDash != null && this.deployNumDash != "" && this.deployNumDash != undefined) {
|
||||
this.deployNumDash.dispose()
|
||||
}
|
||||
this.deployNumDash = echarts.init(document.getElementById('deployNumDash'));
|
||||
|
||||
this.deployNumDash.setOption({
|
||||
title: { text: 'Deployments per Namespace', textStyle: {color:'rgb(134, 135, 136)'}},
|
||||
color: ['#9FE6B8', '#FFDB5C','#ff9f7f', '#fb7293', '#E062AE', '#E690D1', '#e7bcf3', '#9d96f5', '#8378EA', '#96BFFF'],
|
||||
tooltip: { trigger: "axis", axisPointer: { type: "cross", label: { backgroundColor: "#76baf1" } } },
|
||||
legend: {
|
||||
data: ['Deployments']
|
||||
},
|
||||
dataset: {
|
||||
dimensions: ['namespace','deployment_num'],
|
||||
source: this.deploymentNumNp
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
axisLabel:{
|
||||
interval: 0,
|
||||
formatter: function (value) {
|
||||
return value.length>5?value.substring(0,5)+'...':value
|
||||
}
|
||||
},
|
||||
},
|
||||
yAxis: [
|
||||
{type: 'value'}
|
||||
],
|
||||
series: [{
|
||||
name: 'Deployments',
|
||||
type: 'bar',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top'
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
this.getNamespaces()
|
||||
this.getNodes()
|
||||
this.getDeployments()
|
||||
this.getPods()
|
||||
this.getDeploymentNumNp()
|
||||
this.getPodNumNp()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:v-deep .el-collapse-item__header {
|
||||
font-size: 16px;
|
||||
}
|
||||
.home-node-card {
|
||||
border-radius:1px;
|
||||
text-align: center;
|
||||
background-color: rgb(250, 253, 255);
|
||||
}
|
||||
.home-dash-card {
|
||||
border-radius:1px;
|
||||
}
|
||||
.home-node-card-title {
|
||||
font-size: 12px;
|
||||
}
|
||||
.home-node-card-num {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: rgb(63, 92, 135);
|
||||
}
|
||||
:v-deep .el-progress-circle {
|
||||
height: 50px !important;
|
||||
width: 50px !important;
|
||||
}
|
||||
</style>
|
556
src/views/ingress/Ingress.vue
Normal file
@ -0,0 +1,556 @@
|
||||
<template>
|
||||
<div class="ingress">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="ingress-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<span>命名空间: </span>
|
||||
<el-select v-model="namespaceValue" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in namespaceList"
|
||||
:key="index"
|
||||
:label="item.metadata.name"
|
||||
:value="item.metadata.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" :offset="16">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Refresh" plain @click="getIngresss()">刷新</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="ingress-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Edit" type="primary" @click="createIngressDrawer = true" v-loading.fullscreen.lock="fullscreenLoading">创建</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-input class="ingress-head-search" clearable placeholder="请输入" v-model="searchInput"></el-input>
|
||||
<el-button style="border-radius:2px;" icon="Search" type="primary" plain @click="getIngresss()">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="ingress-body-card" shadow="never" :body-style="{padding:'5px'}">
|
||||
<el-table
|
||||
style="width:100%;font-size:12px;margin-bottom:10px;"
|
||||
:data="ingressList"
|
||||
v-loading="appLoading">
|
||||
<el-table-column width="10"></el-table-column>
|
||||
<el-table-column align=left label="Ingress名">
|
||||
<template v-slot="scope">
|
||||
<a class="ingress-body-ingressname">{{ scope.row.metadata.name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="标签" min-width='120'>
|
||||
<template v-slot="scope">
|
||||
<div v-for="(val, key) in scope.row.metadata.labels" :key="key">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="200"
|
||||
trigger="hover"
|
||||
:content="key + ':' + val">
|
||||
<template #reference>
|
||||
<el-tag style="margin-bottom: 5px" type="warning">{{ ellipsis(key + ":" + val) }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="Host" min-width='120'>
|
||||
<template v-slot="scope">
|
||||
<div v-for="(item, index) in scope.row.spec.rules" :key="index">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="200"
|
||||
trigger="hover"
|
||||
:content="item.host">
|
||||
<template #reference>
|
||||
<el-tag style="margin-bottom: 5px" type="danger">{{ ellipsis(item.host) }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="Path">
|
||||
<template v-slot="scope">
|
||||
<div v-for="(item, index) in scope.row.spec.rules" :key="index">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="100"
|
||||
trigger="hover"
|
||||
:content="item.http.paths[0].path">
|
||||
<template #reference>
|
||||
<el-tag style="margin-bottom: 5px" type="danger">{{ item.http.paths[0].path }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="EXTERNAL-IP">
|
||||
<template v-slot="scope">
|
||||
<span>{{ scope.row.status.loadBalancer.ingress ? scope.row.status.loadBalancer.ingress[0].ip : '' }} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="TLS">
|
||||
<template v-slot="scope">
|
||||
<span>{{ scope.row.spec.tls ? 'YES' : '' }} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center min-width="100" label="创建时间">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="info">{{ timeTrans(scope.row.metadata.creationTimestamp) }} </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="操作" width="200">
|
||||
<template v-slot="scope">
|
||||
<el-button size="small" style="border-radius:2px;" icon="Edit" type="primary" plain @click="getIngressDetail(scope)">YAML</el-button>
|
||||
<el-button size="small" style="border-radius:2px;" icon="Delete" type="danger" @click="handleConfirm(scope, '删除', delIngress)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="ingress-body-pagination"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-sizes="pagesizeList"
|
||||
:page-size="pagesize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="ingressTotal">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="YAML信息" v-model="yamlDialog" width="45%" top="5%">
|
||||
<codemirror
|
||||
:value="contentYaml"
|
||||
border
|
||||
:options="cmOptions"
|
||||
height="500"
|
||||
style="font-size:14px;"
|
||||
@change="onChange"
|
||||
></codemirror>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="yamlDialog = false">取 消</el-button>
|
||||
<el-button type="primary" @click="updateIngress()">更 新</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-drawer
|
||||
v-model="createIngressDrawer"
|
||||
:direction="direction"
|
||||
:before-close="handleClose">
|
||||
<template #title>
|
||||
<h4>创建Ingress</h4>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="20">
|
||||
<el-form ref="createIngress" :rules="createIngressRules" :model="createIngress" label-width="80px">
|
||||
<el-form-item class="ingress-create-form" label="名称" prop="name">
|
||||
<el-input v-model="createIngress.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="ingress-create-form" label="命名空间" prop="namespace">
|
||||
<el-select v-model="createIngress.namespace" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in namespaceList"
|
||||
:key="index"
|
||||
:label="item.metadata.name"
|
||||
:value="item.metadata.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="SERVICE-create-form" label="标签" prop="label_str">
|
||||
<el-input v-model="createIngress.label_str" placeholder="示例: project=ms,app=gateway"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="deploy-create-form" label="域名" prop="host">
|
||||
<el-input v-model="createIngress.host" placeholder="示例: www.example.com"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="ingress-create-form" label="Path" prop="path">
|
||||
<el-input v-model="createIngress.path" placeholder="示例: /abc"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="deploy-create-form" label="匹配类型" prop="path_type">
|
||||
<el-select v-model="createIngress.path_type" placeholder="请选择">
|
||||
<el-option value="Prefix" label="Prefix"></el-option>
|
||||
<el-option value="Exact" label="Exact"></el-option>
|
||||
<el-option value="ImplementationSpecific" label="ImplementationSpecific"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="ingress-create-form" label="Service名" prop="service_name">
|
||||
<el-input disabled v-model="createIngress.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="ingress-create-form" label="Service端口" prop="service_port">
|
||||
<el-input v-model="createIngress.service_port" placeholder="示例: 80"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="createIngressDrawer = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm('createIngress')">立即创建</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
import yaml2obj from 'js-yaml';
|
||||
import json2yaml from 'json2yaml';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cmOptions: common.cmOptions,
|
||||
contentYaml: '',
|
||||
currentPage: 1,
|
||||
pagesize: 10,
|
||||
pagesizeList: [10, 20, 30],
|
||||
searchInput: '',
|
||||
namespaceValue: 'default',
|
||||
namespaceList: [],
|
||||
namespaceListUrl: common.k8sNamespaceList,
|
||||
appLoading: false,
|
||||
ingressList: [],
|
||||
ingressTotal: 0,
|
||||
getIngresssData: {
|
||||
url: common.k8sIngressList,
|
||||
params: {
|
||||
filter_name: '',
|
||||
namespace: '',
|
||||
page: '',
|
||||
limit: '',
|
||||
}
|
||||
},
|
||||
ingressDetail: {},
|
||||
getIngressDetailData: {
|
||||
url: common.k8sIngressDetail,
|
||||
params: {
|
||||
ingress_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
yamlDialog: false,
|
||||
updateIngressData: {
|
||||
url: common.k8sIngressUpdate,
|
||||
params: {
|
||||
namespace: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
delIngressData: {
|
||||
url: common.k8sIngressDel,
|
||||
params: {
|
||||
ingress_name: '',
|
||||
namespace: '',
|
||||
}
|
||||
},
|
||||
fullscreenLoading: false,
|
||||
direction: 'rtl',
|
||||
createIngressDrawer: false,
|
||||
createIngress: {
|
||||
name: '',
|
||||
namespace: '',
|
||||
label_str: '',
|
||||
host: '',
|
||||
path: '',
|
||||
path_type: '',
|
||||
service_name: '',
|
||||
service_port: '',
|
||||
hosts: {}
|
||||
},
|
||||
createIngressData: {
|
||||
url: common.k8sIngressCreate,
|
||||
params: {}
|
||||
},
|
||||
createIngressRules: {
|
||||
name: [{
|
||||
required: true,
|
||||
message: '请填写名称',
|
||||
trigger: 'change'
|
||||
}],
|
||||
namespace: [{
|
||||
required: true,
|
||||
message: '请选择命名空间',
|
||||
trigger: 'change'
|
||||
}],
|
||||
host: [{
|
||||
required: true,
|
||||
message: '请填写域名',
|
||||
trigger: 'change'
|
||||
}],
|
||||
path: [{
|
||||
required: true,
|
||||
message: '请填写路径',
|
||||
trigger: 'change'
|
||||
}],
|
||||
service_port: [{
|
||||
required: true,
|
||||
message: '请填写Service端口',
|
||||
trigger: 'change'
|
||||
}],
|
||||
label_str: [{
|
||||
required: true,
|
||||
message: '请填写标签',
|
||||
trigger: 'change'
|
||||
}],
|
||||
path_type: [{
|
||||
required: true,
|
||||
message: '请选择匹配类型',
|
||||
trigger: 'change'
|
||||
}],
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
transYaml(content) {
|
||||
return json2yaml.stringify(content)
|
||||
},
|
||||
transObj(content) {
|
||||
return yaml2obj.load(content)
|
||||
},
|
||||
onChange(val) {
|
||||
this.contentYaml = val
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pagesize = size;
|
||||
this.getIngresss()
|
||||
},
|
||||
handleCurrentChange(currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
this.getIngresss()
|
||||
},
|
||||
handleClose(done) {
|
||||
this.$confirm('确认关闭?')
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
ellipsis(value) {
|
||||
return value.length>15?value.substring(0,15)+'...':value
|
||||
},
|
||||
timeTrans(timestamp) {
|
||||
let date = new Date(new Date(timestamp).getTime() + 8 * 3600 * 1000)
|
||||
date = date.toJSON();
|
||||
date = date.substring(0, 19).replace('T', ' ')
|
||||
return date
|
||||
},
|
||||
restartTotal(e) {
|
||||
let index, sum = 0
|
||||
let containerStatuses = e.row.status.containerStatuses
|
||||
for ( index in containerStatuses) {
|
||||
sum = sum + containerStatuses[index].restartCount
|
||||
}
|
||||
return sum
|
||||
},
|
||||
getNamespaces() {
|
||||
httpClient.get(this.namespaceListUrl)
|
||||
.then(res => {
|
||||
this.namespaceList = res.data.items
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getIngresss() {
|
||||
this.appLoading = true
|
||||
this.getIngresssData.params.filter_name = this.searchInput
|
||||
this.getIngresssData.params.namespace = this.namespaceValue
|
||||
this.getIngresssData.params.page = this.currentPage
|
||||
this.getIngresssData.params.limit = this.pagesize
|
||||
httpClient.get(this.getIngresssData.url, {params: this.getIngresssData.params})
|
||||
.then(res => {
|
||||
this.ingressList = res.data.items
|
||||
this.ingressTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.appLoading = false
|
||||
},
|
||||
getIngressDetail(e) {
|
||||
this.getIngressDetailData.params.ingress_name = e.row.metadata.name
|
||||
this.getIngressDetailData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getIngressDetailData.url, {params: this.getIngressDetailData.params})
|
||||
.then(res => {
|
||||
this.ingressDetail = res.data
|
||||
this.contentYaml = this.transYaml(this.ingressDetail)
|
||||
this.yamlDialog = true
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
updateIngress() {
|
||||
let content = JSON.stringify(this.transObj(this.contentYaml))
|
||||
this.updateIngressData.params.namespace = this.namespaceValue
|
||||
this.updateIngressData.params.content = content
|
||||
httpClient.put(this.updateIngressData.url, this.updateIngressData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.yamlDialog = false
|
||||
},
|
||||
delIngress(e) {
|
||||
this.delIngressData.params.ingress_name = e.row.metadata.name
|
||||
this.delIngressData.params.namespace = this.namespaceValue
|
||||
httpClient.delete(this.delIngressData.url, {data: this.delIngressData.params})
|
||||
.then(res => {
|
||||
this.getIngresss()
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
handleConfirm(obj, operateName, fn) {
|
||||
this.confirmContent = '确认继续 ' + operateName + ' 操作吗?'
|
||||
this.$confirm(this.confirmContent,'提示',{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
.then(() => {
|
||||
fn(obj)
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.info({
|
||||
message: '已取消操作'
|
||||
})
|
||||
})
|
||||
},
|
||||
createIngressFunc() {
|
||||
let reg = new RegExp("(^[A-Za-z]+=[A-Za-z0-9]+).*")
|
||||
if (!reg.test(this.createIngress.label_str)) {
|
||||
this.$message.warning({
|
||||
message: "标签填写异常,请确认后重新填写"
|
||||
})
|
||||
return
|
||||
}
|
||||
this.fullscreenLoading = true
|
||||
let label = new Map()
|
||||
let a = (this.createIngress.label_str).split(",")
|
||||
a.forEach(item => {
|
||||
let b = item.split("=")
|
||||
label[b[0]] = b[1]
|
||||
})
|
||||
let hosts = new Map()
|
||||
let httpPaths = []
|
||||
let httpPath = {
|
||||
path: this.createIngress.path,
|
||||
path_type: this.createIngress.path_type,
|
||||
service_name: this.createIngress.name,
|
||||
service_port: parseInt(this.createIngress.service_port)
|
||||
}
|
||||
httpPaths.push(httpPath)
|
||||
hosts[this.createIngress.host] = httpPaths
|
||||
|
||||
this.createIngressData.params = this.createIngress
|
||||
this.createIngressData.params.label = label
|
||||
this.createIngressData.params.hosts = hosts
|
||||
httpClient.post(this.createIngressData.url, this.createIngressData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
this.getIngresss()
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.resetForm('createIngress')
|
||||
this.fullscreenLoading = false
|
||||
this.createIngressDrawer = false
|
||||
},
|
||||
resetForm(formName) {
|
||||
this.$refs[formName].resetFields()
|
||||
},
|
||||
submitForm(formName) {
|
||||
this.$refs[formName].validate((valid) => {
|
||||
if (valid) {
|
||||
this.createIngressFunc()
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
namespaceValue: {
|
||||
handler() {
|
||||
localStorage.setItem('namespace', this.namespaceValue)
|
||||
this.currentPage = 1
|
||||
this.getIngresss()
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
if (localStorage.getItem('namespace') !== undefined && localStorage.getItem('namespace') !== null) {
|
||||
this.namespaceValue = localStorage.getItem('namespace')
|
||||
}
|
||||
this.getNamespaces()
|
||||
this.getIngresss()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.ingress-head-card,.ingress-body-card {
|
||||
border-radius: 1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.ingress-head-search {
|
||||
width:160px;
|
||||
margin-right:10px;
|
||||
}
|
||||
.ingress-body-ingressname {
|
||||
color: #4795EE;
|
||||
}
|
||||
.ingress-body-ingressname:hover {
|
||||
color: rgb(84, 138, 238);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
96
src/views/login/Login.vue
Normal file
@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<el-card class="login-card">
|
||||
<template #header>
|
||||
<div class="login-card-header">
|
||||
<span>用户登录</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="loginData" :rules="loginDataRules" ref="loginData">
|
||||
<el-form-item prop="username">
|
||||
<el-input prefix-icon="UserFilled" v-model.trim="loginData.username" maxlength="32" placeholder="请输入账号" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input prefix-icon="Lock" v-model.trim="loginData.password" maxlength="16" show-password placeholder="请输入密码" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" style="width: 100%;border-radius: 2px" :loading="loginLoading" @click="handleLogin">登 录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
import moment from 'moment';
|
||||
import jwt from 'jsonwebtoken';
|
||||
export default{
|
||||
data() {
|
||||
return {
|
||||
loginLoading: false,
|
||||
loginUrl: common.loginAuth,
|
||||
loginData: {
|
||||
username: '',
|
||||
password: ''
|
||||
},
|
||||
loginDataRules: {
|
||||
username: [{
|
||||
required: true,
|
||||
message: '请填写用户名',
|
||||
trigger: 'change'
|
||||
}],
|
||||
password: [{
|
||||
required: true,
|
||||
message: '请填写密码',
|
||||
trigger: 'change'
|
||||
}],
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleLogin() {
|
||||
httpClient.post(this.loginUrl, this.loginData)
|
||||
.then(res => {
|
||||
localStorage.setItem('username', this.loginData.username);
|
||||
localStorage.setItem('loginDate', moment().format('YYYY-MM-DD HH:mm:ss'));
|
||||
let token = jwt.sign(this.loginData, 'devops', { expiresIn: '10h' });
|
||||
localStorage.setItem('token', token);
|
||||
this.$router.push('/');
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: aquamarine;
|
||||
background-image: url(../../assets/img/login3.webp);
|
||||
background-size: 100%;
|
||||
}
|
||||
.login-card {
|
||||
position: absolute;
|
||||
left: 40%;
|
||||
top: 30%;
|
||||
width: 350px;
|
||||
border-radius: 5px;
|
||||
background: rgb(255, 255, 255);
|
||||
overflow: hidden;
|
||||
}
|
||||
.login-card-header {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
313
src/views/namespace/Namespace.vue
Normal file
@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<div class="namespace">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="namespace-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
<div>
|
||||
<el-button disabled style="border-radius:2px;" icon="Edit" type="primary">创建</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-input class="namespace-head-search" clearable placeholder="请输入" v-model="searchInput"></el-input>
|
||||
<el-button style="border-radius:2px;" icon="Search" type="primary" plain @click="getNamespaces()">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" :offset="14">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Refresh" plain @click="getNamespaces()">刷新</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="namespace-body-card" shadow="never" :body-style="{padding:'5px'}">
|
||||
<el-table
|
||||
style="width:100%;font-size:12px;margin-bottom:10px;"
|
||||
:data="namespaceList"
|
||||
v-loading="appLoading">
|
||||
<el-table-column width="20"></el-table-column>
|
||||
<el-table-column align=left label="Namespace名">
|
||||
<template v-slot="scope">
|
||||
<a class="namespace-body-namespacename">{{ scope.row.metadata.name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="标签" min-width='120'>
|
||||
<template v-slot="scope">
|
||||
<div v-for="(val, key) in scope.row.metadata.labels" :key="key">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="200"
|
||||
trigger="hover"
|
||||
:content="key + ':' + val">
|
||||
<template #reference>
|
||||
<el-tag style="margin-bottom: 5px" type="warning">{{ ellipsis(key + ":" + val) }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center prop="status.phase" label="状态">
|
||||
<template v-slot="scope">
|
||||
<span :class="[scope.row.status.phase === 'Active' ? 'success-status' : 'error-status']">{{ scope.row.status.phase }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center min-width="100" label="创建时间">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="info">{{ timeTrans(scope.row.metadata.creationTimestamp) }} </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="操作" min-width="120">
|
||||
<template v-slot="scope">
|
||||
<el-button size="small" style="border-radius:2px;" icon="Edit" type="primary" plain @click="getNamespaceDetail(scope)">YAML</el-button>
|
||||
<el-button size="small" style="border-radius:2px;" icon="Delete" type="danger" @click="handleConfirm(scope, '删除', delNamespace)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="namespace-body-pagination"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-sizes="pagesizeList"
|
||||
:page-size="pagesize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="namespaceTotal">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="YAML信息" v-model="yamlDialog" width="45%" top="5%">
|
||||
<codemirror
|
||||
:value="contentYaml"
|
||||
border
|
||||
:options="cmOptions"
|
||||
height="500"
|
||||
style="font-size:14px;"
|
||||
@change="onChange"
|
||||
></codemirror>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="yamlDialog = false">取 消</el-button>
|
||||
<el-button disabled type="primary" @click="updateNamespace()">更 新</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
import yaml2obj from 'js-yaml';
|
||||
import json2yaml from 'json2yaml';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cmOptions: common.cmOptions,
|
||||
contentYaml: '',
|
||||
currentPage: 1,
|
||||
pagesize: 10,
|
||||
pagesizeList: [10, 20, 30],
|
||||
searchInput: '',
|
||||
namespaceValue: 'default',
|
||||
namespaceList: [],
|
||||
namespaceListUrl: common.k8sNamespaceList,
|
||||
appLoading: false,
|
||||
namespaceTotal: 0,
|
||||
getNamespacesData: {
|
||||
url: common.k8sNamespaceList,
|
||||
params: {
|
||||
filter_name: '',
|
||||
namespace: '',
|
||||
page: 1,
|
||||
limit: 10,
|
||||
}
|
||||
},
|
||||
namespaceDetail: {},
|
||||
getNamespaceDetailData: {
|
||||
url: common.k8sNamespaceDetail,
|
||||
params: {
|
||||
namespace_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
yamlDialog: false,
|
||||
updateNamespaceData: {
|
||||
url: common.k8sNamespaceUpdate,
|
||||
params: {
|
||||
namespace: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
delNamespaceData: {
|
||||
url: common.k8snamespaceDel,
|
||||
params: {
|
||||
namespace_name: '',
|
||||
namespace: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
transYaml(content) {
|
||||
return json2yaml.stringify(content)
|
||||
},
|
||||
transObj(content) {
|
||||
return yaml2obj.load(content)
|
||||
},
|
||||
onChange(val) {
|
||||
this.contentYaml = val
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pagesize = size;
|
||||
this.getNamespaces()
|
||||
},
|
||||
handleCurrentChange(currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
this.getNamespaces()
|
||||
},
|
||||
handleClose(done) {
|
||||
this.$confirm('确认关闭?')
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
ellipsis(value) {
|
||||
return value.length>15?value.substring(0,15)+'...':value
|
||||
},
|
||||
timeTrans(timestamp) {
|
||||
let date = new Date(new Date(timestamp).getTime() + 8 * 3600 * 1000)
|
||||
date = date.toJSON();
|
||||
date = date.substring(0, 19).replace('T', ' ')
|
||||
return date
|
||||
},
|
||||
specTrans(str) {
|
||||
if ( str.indexOf('Ki') == -1 ) {
|
||||
return str
|
||||
}
|
||||
let num = str.slice(0,-2) / 1024 / 1024
|
||||
return num.toFixed(0)
|
||||
},
|
||||
getNamespaces() {
|
||||
this.getNamespacesData.params.filter_name = this.searchInput
|
||||
this.getNamespacesData.params.limit = this.pagesize
|
||||
this.getNamespacesData.params.page = this.currentPage
|
||||
httpClient.get(this.getNamespacesData.url, {params: this.getNamespacesData.params})
|
||||
.then(res => {
|
||||
this.namespaceList = res.data.items
|
||||
this.namespaceTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getNamespaceDetail(e) {
|
||||
this.getNamespaceDetailData.params.namespace_name = e.row.metadata.name
|
||||
this.getNamespaceDetailData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getNamespaceDetailData.url, {params: this.getNamespaceDetailData.params})
|
||||
.then(res => {
|
||||
this.namespaceDetail = res.data
|
||||
this.contentYaml = this.transYaml(this.namespaceDetail)
|
||||
this.yamlDialog = true
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
updateNamespace() {
|
||||
let content = JSON.stringify(this.transObj(this.contentYaml))
|
||||
this.updateNamespaceData.params.namespace = this.namespaceValue
|
||||
this.updateNamespaceData.params.content = content
|
||||
httpClient.put(this.updateNamespaceData.url, this.updateNamespaceData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.yamlDialog = false
|
||||
},
|
||||
delNamespace(e) {
|
||||
this.delNamespaceData.params.namespace_name = e.row.metadata.name
|
||||
this.delNamespaceData.params.namespace = this.namespaceValue
|
||||
httpClient.delete(this.delNamespaceData.url, {data: this.delNamespaceData.params})
|
||||
.then(res => {
|
||||
this.getNamespaces()
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
handleConfirm(obj, operateName, fn) {
|
||||
this.confirmContent = '确认继续 ' + operateName + ' 操作吗?'
|
||||
this.$confirm(this.confirmContent,'提示',{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
.then(() => {
|
||||
fn(obj)
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.info({
|
||||
message: '已取消操作'
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
this.getNamespaces()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.namespace-head-card,.namespace-body-card {
|
||||
border-radius: 1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.namespace-head-search {
|
||||
width:160px;
|
||||
margin-right:10px;
|
||||
}
|
||||
.namespace-body-namespacename {
|
||||
color: #4795EE;
|
||||
}
|
||||
.namespace-body-namespacename:hover {
|
||||
color: rgb(84, 138, 238);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
.success-status {
|
||||
color: rgb(27, 202, 21);
|
||||
}
|
||||
.warning-status {
|
||||
color: rgb(233, 200, 16);
|
||||
}
|
||||
.error-status {
|
||||
color: rgb(226, 23, 23);
|
||||
}
|
||||
</style>
|
319
src/views/node/Node.vue
Normal file
@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<div class="node">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="node-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
<div>
|
||||
<el-button disabled style="border-radius:2px;" icon="Edit" type="primary">创建</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-input class="node-head-search" clearable placeholder="请输入" v-model="searchInput"></el-input>
|
||||
<el-button style="border-radius:2px;" icon="Search" type="primary" plain @click="getNodes()">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" :offset="14">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Refresh" plain @click="getNodes()">刷新</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="node-body-card" shadow="never" :body-style="{padding:'5px'}">
|
||||
<el-table
|
||||
style="width:100%;font-size:12px;margin-bottom:10px;"
|
||||
:data="nodeList"
|
||||
v-loading="appLoading">
|
||||
<el-table-column width="20"></el-table-column>
|
||||
<el-table-column align=left label="Node名">
|
||||
<template v-slot="scope">
|
||||
<p class="node-body-nodename">{{ scope.row.metadata.name }}</p>
|
||||
<p class="node-body-ip">{{ scope.row.status.addresses[0].address }}</p>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="规格">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="warning">{{ scope.row.status.capacity.cpu }}核{{ specTrans(scope.row.status.capacity.memory) }}G</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="POD-CIDR">
|
||||
<template v-slot="scope">
|
||||
<span>{{ scope.row.spec.podCIDR }} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="版本">
|
||||
<template v-slot="scope">
|
||||
<span>{{ scope.row.status.nodeInfo.kubeletVersion }} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center min-width="100" label="创建时间">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="info">{{ timeTrans(scope.row.metadata.creationTimestamp) }} </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="操作" min-width="120">
|
||||
<template v-slot="scope">
|
||||
<el-button size="small" style="border-radius:2px;" icon="Edit" type="primary" plain @click="getNodeDetail(scope)">YAML</el-button>
|
||||
<el-button disabled size="small" style="border-radius:2px;" icon="Document" type="warning" plain @click="handleConfirm(scope, '删除', delIngress)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="node-body-pagination"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-sizes="pagesizeList"
|
||||
:page-size="pagesize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="nodeTotal">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="YAML信息" v-model="yamlDialog" width="45%" top="5%">
|
||||
<codemirror
|
||||
:value="contentYaml"
|
||||
border
|
||||
:options="cmOptions"
|
||||
height="500"
|
||||
style="font-size:14px;"
|
||||
@change="onChange"
|
||||
></codemirror>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="yamlDialog = false">取 消</el-button>
|
||||
<el-button disabled type="primary" @click="updateNode()">更 新</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
import yaml2obj from 'js-yaml';
|
||||
import json2yaml from 'json2yaml';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cmOptions: common.cmOptions,
|
||||
contentYaml: '',
|
||||
currentPage: 1,
|
||||
pagesize: 10,
|
||||
pagesizeList: [10, 20, 30],
|
||||
searchInput: '',
|
||||
namespaceValue: 'default',
|
||||
namespaceList: [],
|
||||
namespaceListUrl: common.k8sNamespaceList,
|
||||
appLoading: false,
|
||||
nodeList: [],
|
||||
nodeTotal: 0,
|
||||
getNodesData: {
|
||||
url: common.k8sNodeList,
|
||||
params: {
|
||||
filter_name: '',
|
||||
namespace: '',
|
||||
page: '',
|
||||
limit: '',
|
||||
}
|
||||
},
|
||||
nodeDetail: {},
|
||||
getNodeDetailData: {
|
||||
url: common.k8sNodeDetail,
|
||||
params: {
|
||||
node_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
yamlDialog: false,
|
||||
updateNodeData: {
|
||||
url: common.k8sNodeUpdate,
|
||||
params: {
|
||||
namespace: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
delNodeData: {
|
||||
url: common.k8snodeDel,
|
||||
params: {
|
||||
node_name: '',
|
||||
namespace: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
transYaml(content) {
|
||||
return json2yaml.stringify(content)
|
||||
},
|
||||
transObj(content) {
|
||||
return yaml2obj.load(content)
|
||||
},
|
||||
onChange(val) {
|
||||
this.contentYaml = val
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pagesize = size;
|
||||
this.getNodes()
|
||||
},
|
||||
handleCurrentChange(currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
this.getNodes()
|
||||
},
|
||||
handleClose(done) {
|
||||
this.$confirm('确认关闭?')
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
ellipsis(value) {
|
||||
return value.length>15?value.substring(0,15)+'...':value
|
||||
},
|
||||
timeTrans(timestamp) {
|
||||
let date = new Date(new Date(timestamp).getTime() + 8 * 3600 * 1000)
|
||||
date = date.toJSON();
|
||||
date = date.substring(0, 19).replace('T', ' ')
|
||||
return date
|
||||
},
|
||||
specTrans(str) {
|
||||
if ( str.indexOf('Ki') == -1 ) {
|
||||
return str
|
||||
}
|
||||
let num = str.slice(0,-2) / 1024 / 1024
|
||||
return num.toFixed(0)
|
||||
},
|
||||
getNamespaces() {
|
||||
httpClient.get(this.namespaceListUrl)
|
||||
.then(res => {
|
||||
this.namespaceList = res.data.items
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getNodes() {
|
||||
this.appLoading = true
|
||||
this.getNodesData.params.filter_name = this.searchInput
|
||||
this.getNodesData.params.page = this.currentPage
|
||||
this.getNodesData.params.limit = this.pagesize
|
||||
httpClient.get(this.getNodesData.url, {params: this.getNodesData.params})
|
||||
.then(res => {
|
||||
this.nodeList = res.data.items
|
||||
this.nodeTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.appLoading = false
|
||||
},
|
||||
getNodeDetail(e) {
|
||||
this.getNodeDetailData.params.node_name = e.row.metadata.name
|
||||
this.getNodeDetailData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getNodeDetailData.url, {params: this.getNodeDetailData.params})
|
||||
.then(res => {
|
||||
this.nodeDetail = res.data
|
||||
this.contentYaml = this.transYaml(this.nodeDetail)
|
||||
this.yamlDialog = true
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
updateNode() {
|
||||
let content = JSON.stringify(this.transObj(this.contentYaml))
|
||||
this.updateNodeData.params.namespace = this.namespaceValue
|
||||
this.updateNodeData.params.content = content
|
||||
httpClient.put(this.updateNodeData.url, this.updateNodeData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.yamlDialog = false
|
||||
},
|
||||
delNode(e) {
|
||||
this.delNodeData.params.node_name = e.row.metadata.name
|
||||
this.delNodeData.params.namespace = this.namespaceValue
|
||||
httpClient.delete(this.delNodeData.url, {data: this.delNodeData.params})
|
||||
.then(res => {
|
||||
this.getNodes()
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
handleConfirm(obj, operateName, fn) {
|
||||
this.confirmContent = '确认继续 ' + operateName + ' 操作吗?'
|
||||
this.$confirm(this.confirmContent,'提示',{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
.then(() => {
|
||||
fn(obj)
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.info({
|
||||
message: '已取消操作'
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
this.getNodes()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.node-head-card,.node-body-card {
|
||||
border-radius: 1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.node-head-search {
|
||||
width:160px;
|
||||
margin-right:10px;
|
||||
}
|
||||
.node-body-nodename {
|
||||
margin: 0px;
|
||||
color: #4795EE;
|
||||
}
|
||||
.node-body-ip {
|
||||
margin: 0px;
|
||||
color: rgb(145, 145, 145);
|
||||
}
|
||||
.node-body-nodename:hover {
|
||||
color: rgb(84, 138, 238);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
316
src/views/persistentvolume/PersistentVolume.vue
Normal file
@ -0,0 +1,316 @@
|
||||
<template>
|
||||
<div class="pv">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="pv-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
<div>
|
||||
<el-button disabled style="border-radius:2px;" icon="Edit" type="primary">创建</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-input class="pv-head-search" clearable placeholder="请输入" v-model="searchInput"></el-input>
|
||||
<el-button style="border-radius:2px;" icon="Search" type="primary" plain @click="getPvs()">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" :offset="14">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Refresh" plain @click="getPvs()">刷新</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="pv-body-card" shadow="never" :body-style="{padding:'5px'}">
|
||||
<el-table
|
||||
style="width:100%;font-size:12px;margin-bottom:10px;"
|
||||
:data="pvList"
|
||||
v-loading="appLoading">
|
||||
<el-table-column width="20"></el-table-column>
|
||||
<el-table-column align=left label="PV名">
|
||||
<template v-slot="scope">
|
||||
<a class="pv-body-pvname">{{ scope.row.metadata.name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="状态">
|
||||
<template v-slot="scope">
|
||||
<span :class="[scope.row.status.phase === 'Bound' ? 'success-status' : 'error-status']">{{ scope.row.status.phase }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center prop="spec.accessModes[0]" label="访问模式"></el-table-column>
|
||||
<el-table-column align=center prop="spec.capacity.storage" label="容量"></el-table-column>
|
||||
<el-table-column align=center prop="spec.claimRef.name" label="PVC"></el-table-column>
|
||||
<el-table-column align=center min-width="100" label="创建时间">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="info">{{ timeTrans(scope.row.metadata.creationTimestamp) }} </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="操作" min-width="120">
|
||||
<template v-slot="scope">
|
||||
<el-button size="small" style="border-radius:2px;" icon="Edit" type="primary" plain @click="getPvDetail(scope)">YAML</el-button>
|
||||
<el-button size="small" style="border-radius:2px;" icon="Delete" type="danger" @click="handleConfirm(scope, '删除', delPv)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="pv-body-pagination"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-sizes="pagesizeList"
|
||||
:page-size="pagesize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pvTotal">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="YAML信息" v-model="yamlDialog" width="45%" top="5%">
|
||||
<codemirror
|
||||
:value="contentYaml"
|
||||
border
|
||||
:options="cmOptions"
|
||||
height="500"
|
||||
style="font-size:14px;"
|
||||
@change="onChange"
|
||||
></codemirror>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="yamlDialog = false">取 消</el-button>
|
||||
<el-button disabled type="primary" @click="updatePv()">更 新</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
import yaml2obj from 'js-yaml';
|
||||
import json2yaml from 'json2yaml';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cmOptions: common.cmOptions,
|
||||
contentYaml: '',
|
||||
currentPage: 1,
|
||||
pagesize: 10,
|
||||
pagesizeList: [10, 20, 30],
|
||||
searchInput: '',
|
||||
namespaceValue: 'default',
|
||||
namespaceList: [],
|
||||
namespaceListUrl: common.k8sNamespaceList,
|
||||
appLoading: false,
|
||||
pvList: [],
|
||||
pvTotal: 0,
|
||||
getPvsData: {
|
||||
url: common.k8sPvList,
|
||||
params: {
|
||||
filter_name: '',
|
||||
namespace: '',
|
||||
page: '',
|
||||
limit: '',
|
||||
}
|
||||
},
|
||||
pvDetail: {},
|
||||
getPvDetailData: {
|
||||
url: common.k8sPvDetail,
|
||||
params: {
|
||||
pv_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
yamlDialog: false,
|
||||
updatePvData: {
|
||||
url: common.k8sPvUpdate,
|
||||
params: {
|
||||
namespace: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
delPvData: {
|
||||
url: common.k8spvDel,
|
||||
params: {
|
||||
pv_name: '',
|
||||
namespace: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
transYaml(content) {
|
||||
return json2yaml.stringify(content)
|
||||
},
|
||||
transObj(content) {
|
||||
return yaml2obj.load(content)
|
||||
},
|
||||
onChange(val) {
|
||||
this.contentYaml = val
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pagesize = size;
|
||||
this.getPvs()
|
||||
},
|
||||
handleCurrentChange(currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
this.getPvs()
|
||||
},
|
||||
handleClose(done) {
|
||||
this.$confirm('确认关闭?')
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
ellipsis(value) {
|
||||
return value.length>15?value.substring(0,15)+'...':value
|
||||
},
|
||||
timeTrans(timestamp) {
|
||||
let date = new Date(new Date(timestamp).getTime() + 8 * 3600 * 1000)
|
||||
date = date.toJSON();
|
||||
date = date.substring(0, 19).replace('T', ' ')
|
||||
return date
|
||||
},
|
||||
specTrans(str) {
|
||||
if ( str.indexOf('Ki') == -1 ) {
|
||||
return str
|
||||
}
|
||||
let num = str.slice(0,-2) / 1024 / 1024
|
||||
return num.toFixed(0)
|
||||
},
|
||||
getNamespaces() {
|
||||
httpClient.get(this.namespaceListUrl)
|
||||
.then(res => {
|
||||
this.namespaceList = res.data.items
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getPvs() {
|
||||
this.appLoading = true
|
||||
this.getPvsData.params.filter_name = this.searchInput
|
||||
this.getPvsData.params.namespace = this.namespaceValue
|
||||
this.getPvsData.params.page = this.currentPage
|
||||
this.getPvsData.params.limit = this.pagesize
|
||||
httpClient.get(this.getPvsData.url, {params: this.getPvsData.params})
|
||||
.then(res => {
|
||||
this.pvList = res.data.items
|
||||
this.pvTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.appLoading = false
|
||||
},
|
||||
getPvDetail(e) {
|
||||
this.getPvDetailData.params.pv_name = e.row.metadata.name
|
||||
this.getPvDetailData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getPvDetailData.url, {params: this.getPvDetailData.params})
|
||||
.then(res => {
|
||||
this.pvDetail = res.data
|
||||
this.contentYaml = this.transYaml(this.pvDetail)
|
||||
this.yamlDialog = true
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
updatePv() {
|
||||
let content = JSON.stringify(this.transObj(this.contentYaml))
|
||||
this.updatePvData.params.namespace = this.namespaceValue
|
||||
this.updatePvData.params.content = content
|
||||
httpClient.put(this.updatePvData.url, this.updatePvData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.yamlDialog = false
|
||||
},
|
||||
delPv(e) {
|
||||
this.delPvData.params.pv_name = e.row.metadata.name
|
||||
this.delPvData.params.namespace = this.namespaceValue
|
||||
httpClient.delete(this.delPvData.url, {data: this.delPvData.params})
|
||||
.then(res => {
|
||||
this.getPvs()
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
handleConfirm(obj, operateName, fn) {
|
||||
this.confirmContent = '确认继续 ' + operateName + ' 操作吗?'
|
||||
this.$confirm(this.confirmContent,'提示',{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
.then(() => {
|
||||
fn(obj)
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.info({
|
||||
message: '已取消操作'
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
this.getPvs()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.pv-head-card,.pv-body-card {
|
||||
border-radius: 1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.pv-head-search {
|
||||
width:160px;
|
||||
margin-right:10px;
|
||||
}
|
||||
.pv-body-pvname {
|
||||
color: #4795EE;
|
||||
}
|
||||
.pv-body-pvname:hover {
|
||||
color: rgb(84, 138, 238);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
.success-status {
|
||||
color: rgb(27, 202, 21);
|
||||
}
|
||||
.warning-status {
|
||||
color: rgb(233, 200, 16);
|
||||
}
|
||||
.error-status {
|
||||
color: rgb(226, 23, 23);
|
||||
}
|
||||
</style>
|
369
src/views/persistentvolumeclaim/PersistentVolumeClaim.vue
Normal file
@ -0,0 +1,369 @@
|
||||
<template>
|
||||
<div class="pvc">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="pvc-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<span>命名空间: </span>
|
||||
<el-select v-model="namespaceValue" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in namespaceList"
|
||||
:key="index"
|
||||
:label="item.metadata.name"
|
||||
:value="item.metadata.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" :offset="16">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Refresh" plain @click="getPvcs()">刷新</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="pvc-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
<div>
|
||||
<el-button disabled style="border-radius:2px;" icon="Edit" type="primary">创建</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-input class="pvc-head-search" clearable placeholder="请输入" v-model="searchInput"></el-input>
|
||||
<el-button style="border-radius:2px;" icon="Search" type="primary" plain @click="getPvcs()">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="pvc-body-card" shadow="never" :body-style="{padding:'5px'}">
|
||||
<el-table
|
||||
style="width:100%;font-size:12px;margin-bottom:10px;"
|
||||
:data="pvcList"
|
||||
v-loading="appLoading">
|
||||
<el-table-column width="20"></el-table-column>
|
||||
<el-table-column align=left label="PVC名">
|
||||
<template v-slot="scope">
|
||||
<a class="pvc-body-pvcname">{{ scope.row.metadata.name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="标签">
|
||||
<template v-slot="scope">
|
||||
<div v-for="(val, key) in scope.row.metadata.labels" :key="key">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="200"
|
||||
trigger="hover"
|
||||
:content="key + ':' + val">
|
||||
<template #reference>
|
||||
<el-tag style="margin-bottom: 5px" type="warning">{{ ellipsis(key + ":" + val) }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="状态">
|
||||
<template v-slot="scope">
|
||||
<span :class="[scope.row.status.phase === 'Bound' ? 'success-status' : 'error-status']">{{ scope.row.status.phase }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center prop="status.capacity.storage" label="容量">
|
||||
</el-table-column>
|
||||
<el-table-column align=center prop="status.accessModes[0]" label="访问模式">
|
||||
</el-table-column>
|
||||
<el-table-column align=center prop="spec.storageClassName" label="StorageClass">
|
||||
</el-table-column>
|
||||
<el-table-column align=center min-width="100" label="创建时间">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="info">{{ timeTrans(scope.row.metadata.creationTimestamp) }} </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="操作" width="200">
|
||||
<template v-slot="scope">
|
||||
<el-button size="small" style="border-radius:2px;" icon="Edit" type="primary" plain @click="getPvcDetail(scope)">YAML</el-button>
|
||||
<el-button size="small" style="border-radius:2px;" icon="Delete" type="danger" @click="handleConfirm(scope, '删除', delPvc)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="pvc-body-pagination"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-sizes="pagesizeList"
|
||||
:page-size="pagesize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pvcTotal">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="YAML信息" v-model="yamlDialog" width="45%" top="5%">
|
||||
<codemirror
|
||||
:value="contentYaml"
|
||||
border
|
||||
:options="cmOptions"
|
||||
height="500"
|
||||
style="font-size:14px;"
|
||||
@change="onChange"
|
||||
></codemirror>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="yamlDialog = false">取 消</el-button>
|
||||
<el-button type="primary" @click="updatePvc()">更 新</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
import yaml2obj from 'js-yaml';
|
||||
import json2yaml from 'json2yaml';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cmOptions: common.cmOptions,
|
||||
contentYaml: '',
|
||||
currentPage: 1,
|
||||
pagesize: 10,
|
||||
pagesizeList: [10, 20, 30],
|
||||
searchInput: '',
|
||||
namespaceValue: 'default',
|
||||
namespaceList: [],
|
||||
namespaceListUrl: common.k8sNamespaceList,
|
||||
appLoading: false,
|
||||
pvcList: [],
|
||||
pvcTotal: 0,
|
||||
getPvcsData: {
|
||||
url: common.k8sPvcList,
|
||||
params: {
|
||||
filter_name: '',
|
||||
namespace: '',
|
||||
page: '',
|
||||
limit: '',
|
||||
}
|
||||
},
|
||||
pvcDetail: {},
|
||||
getPvcDetailData: {
|
||||
url: common.k8sPvcDetail,
|
||||
params: {
|
||||
pvc_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
yamlDialog: false,
|
||||
updatePvcData: {
|
||||
url: common.k8sPvcUpdate,
|
||||
params: {
|
||||
namespace: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
delPvcData: {
|
||||
url: common.k8spvcDel,
|
||||
params: {
|
||||
pvc_name: '',
|
||||
namespace: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
transYaml(content) {
|
||||
return json2yaml.stringify(content)
|
||||
},
|
||||
transObj(content) {
|
||||
return yaml2obj.load(content)
|
||||
},
|
||||
onChange(val) {
|
||||
this.contentYaml = val
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pagesize = size;
|
||||
this.getPvcs()
|
||||
},
|
||||
handleCurrentChange(currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
this.getPvcs()
|
||||
},
|
||||
handleClose(done) {
|
||||
this.$confirm('确认关闭?')
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
ellipsis(value) {
|
||||
return value.length>15?value.substring(0,15)+'...':value
|
||||
},
|
||||
timeTrans(timestamp) {
|
||||
let date = new Date(new Date(timestamp).getTime() + 8 * 3600 * 1000)
|
||||
date = date.toJSON();
|
||||
date = date.substring(0, 19).replace('T', ' ')
|
||||
return date
|
||||
},
|
||||
restartTotal(e) {
|
||||
let index, sum = 0
|
||||
let containerStatuses = e.row.status.containerStatuses
|
||||
for ( index in containerStatuses) {
|
||||
sum = sum + containerStatuses[index].restartCount
|
||||
}
|
||||
return sum
|
||||
},
|
||||
getNamespaces() {
|
||||
httpClient.get(this.namespaceListUrl)
|
||||
.then(res => {
|
||||
this.namespaceList = res.data.items
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getPvcs() {
|
||||
this.appLoading = true
|
||||
this.getPvcsData.params.filter_name = this.searchInput
|
||||
this.getPvcsData.params.namespace = this.namespaceValue
|
||||
this.getPvcsData.params.page = this.currentPage
|
||||
this.getPvcsData.params.limit = this.pagesize
|
||||
httpClient.get(this.getPvcsData.url, {params: this.getPvcsData.params})
|
||||
.then(res => {
|
||||
this.pvcList = res.data.items
|
||||
this.pvcTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.appLoading = false
|
||||
},
|
||||
getPvcDetail(e) {
|
||||
this.getPvcDetailData.params.pvc_name = e.row.metadata.name
|
||||
this.getPvcDetailData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getPvcDetailData.url, {params: this.getPvcDetailData.params})
|
||||
.then(res => {
|
||||
this.pvcDetail = res.data
|
||||
this.contentYaml = this.transYaml(this.pvcDetail)
|
||||
this.yamlDialog = true
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
updatePvc() {
|
||||
let content = JSON.stringify(this.transObj(this.contentYaml))
|
||||
this.updatePvcData.params.namespace = this.namespaceValue
|
||||
this.updatePvcData.params.content = content
|
||||
httpClient.put(this.updatePvcData.url, this.updatePvcData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.yamlDialog = false
|
||||
},
|
||||
delPvc(e) {
|
||||
this.delPvcData.params.pvc_name = e.row.metadata.name
|
||||
this.delPvcData.params.namespace = this.namespaceValue
|
||||
httpClient.delete(this.delPvcData.url, {data: this.delPvcData.params})
|
||||
.then(res => {
|
||||
this.getPvcs()
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
handleConfirm(obj, operateName, fn) {
|
||||
this.confirmContent = '确认继续 ' + operateName + ' 操作吗?'
|
||||
this.$confirm(this.confirmContent,'提示',{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
.then(() => {
|
||||
fn(obj)
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.info({
|
||||
message: '已取消操作'
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
namespaceValue: {
|
||||
handler() {
|
||||
localStorage.setItem('namespace', this.namespaceValue)
|
||||
this.currentPage = 1
|
||||
this.getPvcs()
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
if (localStorage.getItem('namespace') !== undefined && localStorage.getItem('namespace') !== null) {
|
||||
this.namespaceValue = localStorage.getItem('namespace')
|
||||
}
|
||||
this.getNamespaces()
|
||||
this.getPvcs()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.pvc-head-card,.pvc-body-card {
|
||||
border-radius: 1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.pvc-head-search {
|
||||
width:160px;
|
||||
margin-right:10px;
|
||||
}
|
||||
.pvc-body-pvcname {
|
||||
color: #4795EE;
|
||||
}
|
||||
.pvc-body-pvcname:hover {
|
||||
color: rgb(84, 138, 238);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
.success-status {
|
||||
color: rgb(27, 202, 21);
|
||||
}
|
||||
.warning-status {
|
||||
color: rgb(233, 200, 16);
|
||||
}
|
||||
.error-status {
|
||||
color: rgb(226, 23, 23);
|
||||
}
|
||||
</style>
|
644
src/views/pod/Pod.vue
Normal file
@ -0,0 +1,644 @@
|
||||
<template>
|
||||
<div class="pod">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="pod-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<span>命名空间: </span>
|
||||
<el-select v-model="namespaceValue" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in namespaceList"
|
||||
:key="index"
|
||||
:label="item.metadata.name"
|
||||
:value="item.metadata.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" :offset="16">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Refresh" plain @click="getPods()">刷新</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="pod-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
<div>
|
||||
<el-button disabled style="border-radius:2px;" icon="Edit" type="primary">创建</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-input class="pod-head-search" clearable placeholder="请输入" v-model="searchInput"></el-input>
|
||||
<el-button style="border-radius:2px;" icon="Search" type="primary" plain @click="getPods()">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="pod-body-card" shadow="never" :body-style="{padding:'5px'}">
|
||||
<el-table
|
||||
style="width:100%;font-size:12px;margin-bottom:10px;"
|
||||
:data="podList"
|
||||
v-loading="appLoading"
|
||||
:row-key="getRowKeys"
|
||||
:expand-row-keys="expandKeys"
|
||||
@expand-change="expandChange">
|
||||
<el-table-column width="10"></el-table-column>
|
||||
<el-table-column type="expand">
|
||||
<template #default="props">
|
||||
<el-tabs v-model="activeName" type="card">
|
||||
<el-tab-pane label="容器" name="container">
|
||||
<el-card shadow="never" style="border-radius:1px;" :body-style="{padding:'5px'}">
|
||||
<el-table
|
||||
style="width:100%;font-size:12px;"
|
||||
:data="props.row.spec.containers">
|
||||
<el-table-column align=left prop="name" label="容器名"></el-table-column>
|
||||
<el-table-column align=left prop="image" label="镜像"></el-table-column>
|
||||
<el-table-column align=center label="Pod IP">
|
||||
<span>{{ props.row.status.podIP }}</span>
|
||||
</el-table-column>
|
||||
<el-table-column align=center prop="args" label="启动命令"></el-table-column>
|
||||
<el-table-column align=center label="环境变量">
|
||||
<template v-slot="scope">
|
||||
<el-popover :width="500" placement="left" trigger="hover">
|
||||
<el-table style="width:100%;font-size:12px;" size="mini" :show-header="false" :data="scope.row.env">
|
||||
<el-table-column property="name" label="名称"></el-table-column>
|
||||
<el-table-column property="value" label="值"></el-table-column>
|
||||
</el-table>
|
||||
<template #reference>
|
||||
<el-button size="small">此处查看</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="日志" name="log">
|
||||
<el-card shadow="never" style="border-radius:1px;" :body-style="{padding:'5px'}">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="3">
|
||||
<el-select size="small" v-model="containerValue" placeholder="请选择">
|
||||
<el-option v-for="item in containerList" :key="item" :value="item">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<el-button style="border-radius:2px;" size="small" type="primary" @click="getPodLog(props.row.metadata.name)">查看</el-button>
|
||||
</el-col>
|
||||
<el-col :span="24" style="margin-top: 5px">
|
||||
<el-card shadow="never" class="pod-body-log-card" :body-style="{padding:'5px'}">
|
||||
<span class="pod-body-log-span">{{ logContent }}</span>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="终端" name="shell">
|
||||
<el-card shadow="never" style="border-radius:1px;" :body-style="{padding:'5px'}">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="3">
|
||||
<el-select size="small" v-model="containerValue" placeholder="请选择">
|
||||
<el-option v-for="item in containerList" :key="item" :value="item">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="1">
|
||||
<el-button style="border-radius:2px;" size="small" type="primary" @click="initSocket(props.row)">连接</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1">
|
||||
<el-button style="border-radius:2px;" size="small" type="danger" @click="closeSocket()">关闭</el-button>
|
||||
</el-col>
|
||||
<el-col :span="24" style="margin-top: 5px">
|
||||
<el-card shadow="never" class="pod-body-shell-card" :body-style="{padding:'5px'}">
|
||||
<div id="xterm"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=left label="Pod名">
|
||||
<template v-slot="scope">
|
||||
<a class="pod-body-podname" @click="expandMap[scope.row.metadata.name] ? expandChange(scope.row, []) : expandChange(scope.row, [scope.row])">{{ scope.row.metadata.name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center min-width="150" label="节点">
|
||||
<template v-slot="scope">
|
||||
<el-tag v-if="scope.row.spec.nodeName !== undefined" type="warning">{{ scope.row.spec.nodeName }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="状态">
|
||||
<template v-slot="scope">
|
||||
<div :class="{'success-dot':scope.row.status.phase == 'Running', 'warning-dot':scope.row.status.phase == 'Pending', 'error-dot':scope.row.status.phase != 'Running' && scope.row.status.phase != 'Pending'}"></div>
|
||||
<span :class="{'success-status':scope.row.status.phase == 'Running', 'warning-status':scope.row.status.phase == 'Pending', 'error-status':scope.row.status.phase != 'Running' && scope.row.status.phase != 'Pending'}">{{ scope.row.status.phase }} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="重启数">
|
||||
<template v-slot="scope">
|
||||
<span>{{ restartTotal(scope) }} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center min-width="100" label="创建时间">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="info">{{ timeTrans(scope.row.metadata.creationTimestamp) }} </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="操作" width="200">
|
||||
<template v-slot="scope">
|
||||
<el-button size="small" style="border-radius:2px;" icon="Edit" type="primary" plain @click="getPodDetail(scope)">YAML</el-button>
|
||||
<el-button size="small" style="border-radius:2px;" icon="Delete" type="danger" @click="handleConfirm(scope, '删除', delPod)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="pod-body-pagination"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-sizes="pagesizeList"
|
||||
:page-size="pagesize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="podTotal">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="YAML信息" v-model="yamlDialog" width="45%" top="5%">
|
||||
<codemirror
|
||||
:value="contentYaml"
|
||||
border
|
||||
:options="cmOptions"
|
||||
height="500"
|
||||
style="font-size:14px;"
|
||||
@change="onChange"
|
||||
></codemirror>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="yamlDialog = false">取 消</el-button>
|
||||
<el-button type="primary" @click="updatePod()">更 新</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
import { Terminal } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import 'xterm/css/xterm.css';
|
||||
import 'xterm/lib/xterm.js';
|
||||
import yaml2obj from 'js-yaml';
|
||||
import json2yaml from 'json2yaml';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cmOptions: common.cmOptions,
|
||||
contentYaml: '',
|
||||
currentPage: 1,
|
||||
pagesize: 10,
|
||||
pagesizeList: [10, 20, 30],
|
||||
searchInput: '',
|
||||
namespaceValue: 'default',
|
||||
namespaceList: [],
|
||||
namespaceListUrl: common.k8sNamespaceList,
|
||||
appLoading: false,
|
||||
podList: [],
|
||||
podTotal: 0,
|
||||
getPodsData: {
|
||||
url: common.k8sPodList,
|
||||
params: {
|
||||
filter_name: '',
|
||||
namespace: '',
|
||||
page: '',
|
||||
limit: '',
|
||||
}
|
||||
},
|
||||
podDetail: {},
|
||||
getPodDetailData: {
|
||||
url: common.k8sPodDetail,
|
||||
params: {
|
||||
pod_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
yamlDialog: false,
|
||||
updatePodData: {
|
||||
url: common.k8sPodUpdate,
|
||||
params: {
|
||||
namespace: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
delPodData: {
|
||||
url: common.k8sPodDel,
|
||||
params: {
|
||||
pod_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
activeName: 'container',
|
||||
expandKeys: [],
|
||||
expandMap: {},
|
||||
containerList: {},
|
||||
containerValue: '',
|
||||
getPodContainerData: {
|
||||
url: common.k8sPodContainer,
|
||||
params: {
|
||||
pod_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
logContent: '',
|
||||
getPodLogData: {
|
||||
url: common.k8sPodLog,
|
||||
params: {
|
||||
container_name: '',
|
||||
pod_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
term: null,
|
||||
socket: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
transYaml(content) {
|
||||
return json2yaml.stringify(content)
|
||||
},
|
||||
transObj(content) {
|
||||
return yaml2obj.load(content)
|
||||
},
|
||||
onChange(val) {
|
||||
this.contentYaml = val
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pagesize = size;
|
||||
this.getPods()
|
||||
},
|
||||
handleCurrentChange(currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
this.getPods()
|
||||
},
|
||||
handleClose(done) {
|
||||
this.$confirm('确认关闭?')
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
ellipsis(value) {
|
||||
return value.length>15?value.substring(0,15)+'...':value
|
||||
},
|
||||
timeTrans(timestamp) {
|
||||
let date = new Date(new Date(timestamp).getTime() + 8 * 3600 * 1000)
|
||||
date = date.toJSON();
|
||||
date = date.substring(0, 19).replace('T', ' ')
|
||||
return date
|
||||
},
|
||||
restartTotal(e) {
|
||||
let index, sum = 0
|
||||
let containerStatuses = e.row.status.containerStatuses
|
||||
for ( index in containerStatuses) {
|
||||
sum = sum + containerStatuses[index].restartCount
|
||||
}
|
||||
return sum
|
||||
},
|
||||
getNamespaces() {
|
||||
httpClient.get(this.namespaceListUrl)
|
||||
.then(res => {
|
||||
this.namespaceList = res.data.items
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getPods() {
|
||||
this.appLoading = true
|
||||
this.getPodsData.params.filter_name = this.searchInput
|
||||
this.getPodsData.params.namespace = this.namespaceValue
|
||||
this.getPodsData.params.page = this.currentPage
|
||||
this.getPodsData.params.limit = this.pagesize
|
||||
httpClient.get(this.getPodsData.url, {params: this.getPodsData.params})
|
||||
.then(res => {
|
||||
this.podList = res.data.items
|
||||
this.podTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.appLoading = false
|
||||
},
|
||||
getPodDetail(e) {
|
||||
this.getPodDetailData.params.pod_name = e.row.metadata.name
|
||||
this.getPodDetailData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getPodDetailData.url, {params: this.getPodDetailData.params})
|
||||
.then(res => {
|
||||
this.podDetail = res.data
|
||||
this.contentYaml = this.transYaml(this.podDetail)
|
||||
this.yamlDialog = true
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
updatePod() {
|
||||
let content = JSON.stringify(this.transObj(this.contentYaml))
|
||||
this.updatePodData.params.namespace = this.namespaceValue
|
||||
this.updatePodData.params.content = content
|
||||
httpClient.put(this.updatePodData.url, this.updatePodData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.yamlDialog = false
|
||||
},
|
||||
delPod(e) {
|
||||
this.delPodData.params.pod_name = e.row.metadata.name
|
||||
this.delPodData.params.namespace = this.namespaceValue
|
||||
httpClient.delete(this.delPodData.url, {data: this.delPodData.params})
|
||||
.then(res => {
|
||||
this.getPods()
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
handleConfirm(obj, operateName, fn) {
|
||||
this.confirmContent = '确认继续 ' + operateName + ' 操作吗?'
|
||||
this.$confirm(this.confirmContent,'提示',{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
.then(() => {
|
||||
fn(obj)
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.info({
|
||||
message: '已取消操作'
|
||||
})
|
||||
})
|
||||
},
|
||||
getRowKeys(row) {
|
||||
return row.metadata.name
|
||||
},
|
||||
expandChange(row, expandedRows) {
|
||||
this.expandKeys = []
|
||||
this.logContent= ''
|
||||
this.containerValue = ''
|
||||
this.activeName = 'container'
|
||||
if (expandedRows.length > 0) {
|
||||
this.expandMap[row.metadata.name] = 1
|
||||
this.setExpandMap(row.metadata.name)
|
||||
row ? (this.expandKeys.push(row.metadata.name), this. getPodContainer(row)) : ''
|
||||
} else {
|
||||
this.expandMap[row.metadata.name] = 0
|
||||
}
|
||||
},
|
||||
setExpandMap(podName) {
|
||||
let key
|
||||
for ( key in this.expandMap ) {
|
||||
key !== podName ? this.expandMap[key] = 0 : ''
|
||||
}
|
||||
},
|
||||
getPodContainer(row) {
|
||||
this.getPodContainerData.params.pod_name = row.metadata.name
|
||||
this.getPodContainerData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getPodContainerData.url, {params: this.getPodContainerData.params})
|
||||
.then(res => {
|
||||
this.containerList = res.data
|
||||
this.containerValue = this.containerList[0]
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getPodLog(podName) {
|
||||
this.getPodLogData.params.pod_name = podName
|
||||
this.getPodLogData.params.container_name = this.containerValue
|
||||
this.getPodLogData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getPodLogData.url, {params: this.getPodLogData.params})
|
||||
.then(res => {
|
||||
this.logContent = res.data
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
initTerm() {
|
||||
this.term = new Terminal({
|
||||
rendererType: 'canvas',
|
||||
rows: 30,
|
||||
cols: 110,
|
||||
convertEol: false,
|
||||
scrollback: 10,
|
||||
disableStdin: false,
|
||||
cursorStyle: 'underline',
|
||||
cursorBlink: true,
|
||||
theme: {
|
||||
foreground: 'white',
|
||||
background: '#060101',
|
||||
cursor: 'help'
|
||||
}
|
||||
});
|
||||
this.term.open(document.getElementById('xterm'))
|
||||
const fitAddon = new FitAddon()
|
||||
this.term.loadAddon(fitAddon)
|
||||
fitAddon.fit();
|
||||
this.term.focus();
|
||||
let _this = this;
|
||||
this.term.onData(function (key) {
|
||||
let msgOrder = {
|
||||
operation: 'stdin',
|
||||
data: key,
|
||||
};
|
||||
_this.socket.send(JSON.stringify(msgOrder));
|
||||
});
|
||||
let msgOrder2 = {
|
||||
operation: 'resize',
|
||||
cols: this.term.cols,
|
||||
rows: this.term.rows,
|
||||
};
|
||||
this.socket.send(JSON.stringify(msgOrder2))
|
||||
},
|
||||
initSocket(row) {
|
||||
let terminalWsUrl = common.k8sTerminalWs + "?pod_name=" + row.metadata.name + "&container_name=" + this.containerValue + "&namespace=" + this.namespaceValue
|
||||
this.socket = new WebSocket(terminalWsUrl);
|
||||
this.socketOnClose();
|
||||
this.socketOnOpen();
|
||||
this.socketOnMessage();
|
||||
this.socketOnError();
|
||||
},
|
||||
socketOnOpen() {
|
||||
this.socket.onopen = () => {
|
||||
this.initTerm()
|
||||
}
|
||||
},
|
||||
socketOnMessage() {
|
||||
this.socket.onmessage = (msg) => {
|
||||
let content = JSON.parse(msg.data)
|
||||
this.term.write(content.data)
|
||||
}
|
||||
},
|
||||
socketOnClose() {
|
||||
this.socket.onclose = () => {
|
||||
this.term.write("链接已关闭")
|
||||
}
|
||||
},
|
||||
socketOnError() {
|
||||
this.socket.onerror = () => {
|
||||
console.log('socket 链接失败')
|
||||
}
|
||||
},
|
||||
closeSocket() {
|
||||
if (this.socket === null) {
|
||||
return
|
||||
}
|
||||
this.term.write("链接关闭中。。。")
|
||||
this.socket.close()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
namespaceValue: {
|
||||
handler() {
|
||||
localStorage.setItem('namespace', this.namespaceValue)
|
||||
this.currentPage = 1
|
||||
this.getPods()
|
||||
}
|
||||
},
|
||||
activeName: {
|
||||
handler() {
|
||||
if ( this.activeName == 'log' ) {
|
||||
this.expandKeys.length == 1 ? this.getPodLog(this.expandKeys[0]) : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
if (localStorage.getItem('namespace') !== undefined && localStorage.getItem('namespace') !== null) {
|
||||
this.namespaceValue = localStorage.getItem('namespace')
|
||||
}
|
||||
this.getNamespaces()
|
||||
this.getPods()
|
||||
},
|
||||
beforeUnmount() {
|
||||
if ( this.socket !== null ) {
|
||||
this.socket.close()
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.pod-head-card,.pod-body-card {
|
||||
border-radius: 1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.pod-head-search {
|
||||
width:160px;
|
||||
margin-right:10px;
|
||||
}
|
||||
.pod-body-podname {
|
||||
color: #4795EE;
|
||||
}
|
||||
.pod-body-podname:hover {
|
||||
color: rgb(84, 138, 238);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
.success-dot{
|
||||
display:inline-block;
|
||||
width: 7px;
|
||||
height:7px;
|
||||
background: rgb(27, 202, 21);
|
||||
border-radius:50%;
|
||||
border:1px solid rgb(27, 202, 21);
|
||||
margin-right: 10px;
|
||||
}
|
||||
.warning-dot{
|
||||
display:inline-block;
|
||||
width: 7px;
|
||||
height:7px;
|
||||
background: rgb(233, 200, 16);
|
||||
border-radius:50%;
|
||||
border:1px solid rgb(233, 200, 16);
|
||||
margin-right: 10px;
|
||||
}
|
||||
.error-dot{
|
||||
display:inline-block;
|
||||
width: 7px;
|
||||
height:7px;
|
||||
background: rgb(226, 23, 23);
|
||||
border-radius:50%;
|
||||
border:1px solid rgb(226, 23, 23);
|
||||
margin-right: 10px;
|
||||
}
|
||||
.success-status {
|
||||
color: rgb(27, 202, 21);
|
||||
}
|
||||
.warning-status {
|
||||
color: rgb(233, 200, 16);
|
||||
}
|
||||
.error-status {
|
||||
color: rgb(226, 23, 23);
|
||||
}
|
||||
:v-deep .el-tabs__item {
|
||||
font-size: 12px;
|
||||
}
|
||||
:v-deep .el-tabs__header {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.pod-body-log-card, .pod-body-shell-card {
|
||||
border-radius:1px;
|
||||
height:600px;
|
||||
overflow:auto;
|
||||
background-color: #060101;
|
||||
}
|
||||
.pod-body-log-card {
|
||||
color: aliceblue;
|
||||
}
|
||||
.pod-body-log-span {
|
||||
white-space:pre;
|
||||
}
|
||||
</style>
|
367
src/views/secret/Secret.vue
Normal file
@ -0,0 +1,367 @@
|
||||
<template>
|
||||
<div class="secret">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="secret-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<span>命名空间: </span>
|
||||
<el-select v-model="namespaceValue" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in namespaceList"
|
||||
:key="index"
|
||||
:label="item.metadata.name"
|
||||
:value="item.metadata.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" :offset="16">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Refresh" plain @click="getSecrets()">刷新</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="secret-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
<div>
|
||||
<el-button disabled style="border-radius:2px;" icon="Edit" type="primary">创建</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-input class="secret-head-search" clearable placeholder="请输入" v-model="searchInput"></el-input>
|
||||
<el-button style="border-radius:2px;" icon="Search" type="primary" plain @click="getSecrets()">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="secret-body-card" shadow="never" :body-style="{padding:'5px'}">
|
||||
<el-table
|
||||
style="width:100%;font-size:12px;margin-bottom:10px;"
|
||||
:data="secretList"
|
||||
v-loading="appLoading">
|
||||
<el-table-column width="20"></el-table-column>
|
||||
<el-table-column align=left label="Secret名">
|
||||
<template v-slot="scope">
|
||||
<a class="secret-body-secretname">{{ scope.row.metadata.name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="标签">
|
||||
<template v-slot="scope">
|
||||
<div v-for="(val, key) in scope.row.metadata.labels" :key="key">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="200"
|
||||
trigger="hover"
|
||||
:content="key + ':' + val">
|
||||
<template #reference>
|
||||
<el-tag style="margin-bottom: 5px" type="warning">{{ ellipsis(key + ":" + val) }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="DATA">
|
||||
<template v-slot="scope">
|
||||
<el-popover
|
||||
style="overflow:auto"
|
||||
placement="right"
|
||||
:width="400"
|
||||
trigger="click">
|
||||
<div style="overflow-y:auto;max-height:500px;">
|
||||
<span>{{ scope.row.data }}</span>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon style="font-size:18px;cursor:pointer;"><reading/></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center prop="type" min-width="100" label="类型">
|
||||
</el-table-column>
|
||||
<el-table-column align=center min-width="100" label="创建时间">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="info">{{ timeTrans(scope.row.metadata.creationTimestamp) }} </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="操作" width="200">
|
||||
<template v-slot="scope">
|
||||
<el-button size="small" style="border-radius:2px;" icon="Edit" type="primary" plain @click="getSecretDetail(scope)">YAML</el-button>
|
||||
<el-button size="small" style="border-radius:2px;" icon="Delete" type="danger" @click="handleConfirm(scope, '删除', delSecret)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="secret-body-pagination"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-sizes="pagesizeList"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:prev-click="getSecrets"
|
||||
:total="secretTotal">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="YAML信息" v-model="yamlDialog" width="45%" top="5%">
|
||||
<codemirror
|
||||
:value="contentYaml"
|
||||
border
|
||||
:options="cmOptions"
|
||||
height="500"
|
||||
style="font-size:14px;"
|
||||
@change="onChange"
|
||||
></codemirror>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="yamlDialog = false">取 消</el-button>
|
||||
<el-button type="primary" @click="updateSecret()">更 新</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
import yaml2obj from 'js-yaml';
|
||||
import json2yaml from 'json2yaml';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cmOptions: common.cmOptions,
|
||||
contentYaml: '',
|
||||
currentPage: 1,
|
||||
pagesize: 10,
|
||||
pagesizeList: [10, 20, 30],
|
||||
searchInput: '',
|
||||
namespaceValue: 'default',
|
||||
namespaceList: [],
|
||||
namespaceListUrl: common.k8sNamespaceList,
|
||||
appLoading: false,
|
||||
secretList: [],
|
||||
secretTotal: 0,
|
||||
getSecretsData: {
|
||||
url: common.k8sSecretList,
|
||||
params: {
|
||||
filter_name: '',
|
||||
namespace: '',
|
||||
page: '',
|
||||
limit: '',
|
||||
}
|
||||
},
|
||||
secretDetail: {},
|
||||
getSecretDetailData: {
|
||||
url: common.k8sSecretDetail,
|
||||
params: {
|
||||
secret_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
yamlDialog: false,
|
||||
updateSecretData: {
|
||||
url: common.k8sSecretUpdate,
|
||||
params: {
|
||||
namespace: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
delSecretData: {
|
||||
url: common.k8ssecretDel,
|
||||
params: {
|
||||
secret_name: '',
|
||||
namespace: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
transYaml(content) {
|
||||
return json2yaml.stringify(content)
|
||||
},
|
||||
transObj(content) {
|
||||
return yaml2obj.load(content)
|
||||
},
|
||||
onChange(val) {
|
||||
this.contentYaml = val
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pagesize = size;
|
||||
this.getSecrets()
|
||||
},
|
||||
handleCurrentChange(currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
this.getSecrets()
|
||||
},
|
||||
handleClose(done) {
|
||||
this.$confirm('确认关闭?')
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
ellipsis(value) {
|
||||
return value.length>15?value.substring(0,15)+'...':value
|
||||
},
|
||||
timeTrans(timestamp) {
|
||||
let date = new Date(new Date(timestamp).getTime() + 8 * 3600 * 1000)
|
||||
date = date.toJSON();
|
||||
date = date.substring(0, 19).replace('T', ' ')
|
||||
return date
|
||||
},
|
||||
restartTotal(e) {
|
||||
let index, sum = 0
|
||||
let containerStatuses = e.row.status.containerStatuses
|
||||
for ( index in containerStatuses) {
|
||||
sum = sum + containerStatuses[index].restartCount
|
||||
}
|
||||
return sum
|
||||
},
|
||||
getNamespaces() {
|
||||
httpClient.get(this.namespaceListUrl)
|
||||
.then(res => {
|
||||
this.namespaceList = res.data.items
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getSecrets() {
|
||||
this.appLoading = true
|
||||
this.getSecretsData.params.filter_name = this.searchInput
|
||||
this.getSecretsData.params.namespace = this.namespaceValue
|
||||
this.getSecretsData.params.page = this.currentPage
|
||||
this.getSecretsData.params.limit = this.pagesize
|
||||
httpClient.get(this.getSecretsData.url, {params: this.getSecretsData.params})
|
||||
.then(res => {
|
||||
this.secretList = res.data.items
|
||||
this.secretTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.appLoading = false
|
||||
},
|
||||
getSecretDetail(e) {
|
||||
this.getSecretDetailData.params.secret_name = e.row.metadata.name
|
||||
this.getSecretDetailData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getSecretDetailData.url, {params: this.getSecretDetailData.params})
|
||||
.then(res => {
|
||||
this.secretDetail = res.data
|
||||
this.contentYaml = this.transYaml(this.secretDetail)
|
||||
this.yamlDialog = true
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
updateSecret() {
|
||||
let content = JSON.stringify(this.transObj(this.contentYaml))
|
||||
this.updateSecretData.params.namespace = this.namespaceValue
|
||||
this.updateSecretData.params.content = content
|
||||
httpClient.put(this.updateSecretData.url, this.updateSecretData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.yamlDialog = false
|
||||
},
|
||||
delSecret(e) {
|
||||
this.delSecretData.params.secret_name = e.row.metadata.name
|
||||
this.delSecretData.params.namespace = this.namespaceValue
|
||||
httpClient.delete(this.delSecretData.url, {data: this.delSecretData.params})
|
||||
.then(res => {
|
||||
this.getSecrets()
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
handleConfirm(obj, operateName, fn) {
|
||||
this.confirmContent = '确认继续 ' + operateName + ' 操作吗?'
|
||||
this.$confirm(this.confirmContent,'提示',{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
.then(() => {
|
||||
fn(obj)
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.info({
|
||||
message: '已取消操作'
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
namespaceValue: {
|
||||
handler() {
|
||||
localStorage.setItem('namespace', this.namespaceValue)
|
||||
this.currentPage = 1
|
||||
this.getSecrets()
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
if (localStorage.getItem('namespace') !== undefined && localStorage.getItem('namespace') !== null) {
|
||||
this.namespaceValue = localStorage.getItem('namespace')
|
||||
}
|
||||
this.getNamespaces()
|
||||
this.getSecrets()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.secret-head-card,.secret-body-card {
|
||||
border-radius: 1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.secret-head-search {
|
||||
width:160px;
|
||||
margin-right:10px;
|
||||
}
|
||||
.secret-body-secretname {
|
||||
color: #4795EE;
|
||||
}
|
||||
.secret-body-secretname:hover {
|
||||
color: rgb(84, 138, 238);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
518
src/views/service/Service.vue
Normal file
@ -0,0 +1,518 @@
|
||||
<template>
|
||||
<div class="service">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="service-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<span>命名空间: </span>
|
||||
<el-select v-model="namespaceValue" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in namespaceList"
|
||||
:key="index"
|
||||
:label="item.metadata.name"
|
||||
:value="item.metadata.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" :offset="16">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Refresh" plain @click="getServices()">刷新</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="service-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Edit" type="primary" @click="createServiceDrawer = true" v-loading.fullscreen.lock="fullscreenLoading">创建</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-input class="service-head-search" clearable placeholder="请输入" v-model="searchInput"></el-input>
|
||||
<el-button style="border-radius:2px;" icon="Search" type="primary" plain @click="getServices()">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="service-body-card" shadow="never" :body-style="{padding:'5px'}">
|
||||
<el-table
|
||||
style="width:100%;font-size:12px;margin-bottom:10px;"
|
||||
:data="serviceList"
|
||||
v-loading="appLoading">
|
||||
<el-table-column width="20"></el-table-column>
|
||||
<el-table-column align=left label="Service名">
|
||||
<template v-slot="scope">
|
||||
<a class="service-body-servicename">{{ scope.row.metadata.name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="标签" min-width='120'>
|
||||
<template v-slot="scope">
|
||||
<div v-for="(val, key) in scope.row.metadata.labels" :key="key">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="200"
|
||||
trigger="hover"
|
||||
:content="key + ':' + val">
|
||||
<template #reference>
|
||||
<el-tag style="margin-bottom: 5px" type="warning">{{ ellipsis(key + ":" + val) }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="类型">
|
||||
<template v-slot="scope">
|
||||
<span style="font-weight:bold;">{{ scope.row.spec.type }} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="CLUSTER-IP">
|
||||
<template v-slot="scope">
|
||||
<span>{{ scope.row.spec.clusterIP }} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="EXTERNAL-IP">
|
||||
<template v-slot="scope">
|
||||
<span>{{ scope.row.status.loadBalancer.ingress ? scope.row.status.loadBalancer.ingress[0].ip : '' }} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="端口">
|
||||
<template v-slot="scope">
|
||||
<span v-if="!scope.row.spec.ports[0].nodePort">{{ scope.row.spec.ports[0].port }}/{{ scope.row.spec.ports[0].protocol }}</span>
|
||||
<span v-if="scope.row.spec.ports[0].nodePort">{{ scope.row.spec.ports[0].port }}:{{ scope.row.spec.ports[0].nodePort }}/{{ scope.row.spec.ports[0].protocol }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center min-width="100" label="创建时间">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="info">{{ timeTrans(scope.row.metadata.creationTimestamp) }} </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="操作" width="200">
|
||||
<template v-slot="scope">
|
||||
<el-button size="small" style="border-radius:2px;" icon="Edit" type="primary" plain @click="getServiceDetail(scope)">YAML</el-button>
|
||||
<el-button size="small" style="border-radius:2px;" icon="Delete" type="danger" @click="handleConfirm(scope, '删除', delService)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="service-body-pagination"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-sizes="pagesizeList"
|
||||
:page-size="pagesize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="serviceTotal">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="YAML信息" v-model="yamlDialog" width="45%" top="5%">
|
||||
<codemirror
|
||||
:value="contentYaml"
|
||||
border
|
||||
:options="cmOptions"
|
||||
height="500"
|
||||
style="font-size:14px;"
|
||||
@change="onChange"
|
||||
></codemirror>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="yamlDialog = false">取 消</el-button>
|
||||
<el-button type="primary" @click="updateService()">更 新</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-drawer
|
||||
v-model="createServiceDrawer"
|
||||
:direction="direction"
|
||||
:before-close="handleClose">
|
||||
<template #title>
|
||||
<h4>创建Service</h4>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="20">
|
||||
<el-form ref="createService" :rules="createServiceRules" :model="createService" label-width="80px">
|
||||
<el-form-item class="service-create-form" label="名称" prop="name">
|
||||
<el-input v-model="createService.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="service-create-form" label="命名空间" prop="namespace">
|
||||
<el-select v-model="createService.namespace" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in namespaceList"
|
||||
:key="index"
|
||||
:label="item.metadata.name"
|
||||
:value="item.metadata.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="service-create-form" label="类型" prop="type">
|
||||
<el-select v-model="createService.type" placeholder="请选择">
|
||||
<el-option value="ClusterIP" label="ClusterIP"></el-option>
|
||||
<el-option value="NodePort" label="NodePort"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="deploy-create-form" label="容器端口" prop="container_port">
|
||||
<el-input v-model="createService.container_port" placeholder="示例: 80"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="service-create-form" label="Service端口" prop="port">
|
||||
<el-input v-model="createService.port" placeholder="示例: 80"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="createService.type == 'NodePort'" class="service-create-form" label="NodePort" prop="node_port">
|
||||
<el-input v-model="createService.node_port" placeholder="示例: 30001"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="SERVICE-create-form" label="标签" prop="label_str">
|
||||
<el-input v-model="createService.label_str" placeholder="示例: project=ms,app=gateway"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="createServiceDrawer = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm('createService')">立即创建</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
import yaml2obj from 'js-yaml';
|
||||
import json2yaml from 'json2yaml';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cmOptions: common.cmOptions,
|
||||
contentYaml: '',
|
||||
currentPage: 1,
|
||||
pagesize: 10,
|
||||
pagesizeList: [10, 20, 30],
|
||||
searchInput: '',
|
||||
namespaceValue: 'default',
|
||||
namespaceList: [],
|
||||
namespaceListUrl: common.k8sNamespaceList,
|
||||
appLoading: false,
|
||||
serviceList: [],
|
||||
serviceTotal: 0,
|
||||
getServicesData: {
|
||||
url: common.k8sServiceList,
|
||||
params: {
|
||||
filter_name: '',
|
||||
namespace: '',
|
||||
page: '',
|
||||
limit: '',
|
||||
}
|
||||
},
|
||||
serviceDetail: {},
|
||||
getServiceDetailData: {
|
||||
url: common.k8sServiceDetail,
|
||||
params: {
|
||||
service_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
yamlDialog: false,
|
||||
updateServiceData: {
|
||||
url: common.k8sServiceUpdate,
|
||||
params: {
|
||||
namespace: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
delServiceData: {
|
||||
url: common.k8sServiceDel,
|
||||
params: {
|
||||
service_name: '',
|
||||
namespace: '',
|
||||
}
|
||||
},
|
||||
fullscreenLoading: false,
|
||||
direction: 'rtl',
|
||||
createServiceDrawer: false,
|
||||
createService: {
|
||||
name: '',
|
||||
namespace: '',
|
||||
type: 'ClusterIP',
|
||||
container_port: '',
|
||||
port: '',
|
||||
node_port: '',
|
||||
label: {},
|
||||
label_str: ''
|
||||
},
|
||||
createServiceData: {
|
||||
url: common.k8sServiceCreate,
|
||||
params: {}
|
||||
},
|
||||
createServiceRules: {
|
||||
name: [{
|
||||
required: true,
|
||||
message: '请填写名称',
|
||||
trigger: 'change'
|
||||
}],
|
||||
namespace: [{
|
||||
required: true,
|
||||
message: '请选择命名空间',
|
||||
trigger: 'change'
|
||||
}],
|
||||
port: [{
|
||||
required: true,
|
||||
message: '请填写Service端口',
|
||||
trigger: 'change'
|
||||
}],
|
||||
node_port: [{
|
||||
required: true,
|
||||
message: '请填写NodePort',
|
||||
trigger: 'change'
|
||||
}],
|
||||
label_str: [{
|
||||
required: true,
|
||||
message: '请填写标签',
|
||||
trigger: 'change'
|
||||
}],
|
||||
container_port: [{
|
||||
required: true,
|
||||
message: '请填写容器端口',
|
||||
trigger: 'change'
|
||||
}],
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
transYaml(content) {
|
||||
return json2yaml.stringify(content)
|
||||
},
|
||||
transObj(content) {
|
||||
return yaml2obj.load(content)
|
||||
},
|
||||
onChange(val) {
|
||||
this.contentYaml = val
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pagesize = size;
|
||||
this.getServices()
|
||||
},
|
||||
handleCurrentChange(currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
this.getServices()
|
||||
},
|
||||
handleClose(done) {
|
||||
this.$confirm('确认关闭?')
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
ellipsis(value) {
|
||||
return value.length>15?value.substring(0,15)+'...':value
|
||||
},
|
||||
timeTrans(timestamp) {
|
||||
let date = new Date(new Date(timestamp).getTime() + 8 * 3600 * 1000)
|
||||
date = date.toJSON();
|
||||
date = date.substring(0, 19).replace('T', ' ')
|
||||
return date
|
||||
},
|
||||
restartTotal(e) {
|
||||
let index, sum = 0
|
||||
let containerStatuses = e.row.status.containerStatuses
|
||||
for ( index in containerStatuses) {
|
||||
sum = sum + containerStatuses[index].restartCount
|
||||
}
|
||||
return sum
|
||||
},
|
||||
getNamespaces() {
|
||||
httpClient.get(this.namespaceListUrl)
|
||||
.then(res => {
|
||||
this.namespaceList = res.data.items
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getServices() {
|
||||
this.appLoading = true
|
||||
this.getServicesData.params.filter_name = this.searchInput
|
||||
this.getServicesData.params.namespace = this.namespaceValue
|
||||
this.getServicesData.params.page = this.currentPage
|
||||
this.getServicesData.params.limit = this.pagesize
|
||||
httpClient.get(this.getServicesData.url, {params: this.getServicesData.params})
|
||||
.then(res => {
|
||||
this.serviceList = res.data.items
|
||||
this.serviceTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.appLoading = false
|
||||
},
|
||||
getServiceDetail(e) {
|
||||
this.getServiceDetailData.params.service_name = e.row.metadata.name
|
||||
this.getServiceDetailData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getServiceDetailData.url, {params: this.getServiceDetailData.params})
|
||||
.then(res => {
|
||||
this.serviceDetail = res.data
|
||||
this.contentYaml = this.transYaml(this.serviceDetail)
|
||||
this.yamlDialog = true
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
updateService() {
|
||||
let content = JSON.stringify(this.transObj(this.contentYaml))
|
||||
this.updateServiceData.params.namespace = this.namespaceValue
|
||||
this.updateServiceData.params.content = content
|
||||
httpClient.put(this.updateServiceData.url, this.updateServiceData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.yamlDialog = false
|
||||
},
|
||||
delService(e) {
|
||||
this.delServiceData.params.service_name = e.row.metadata.name
|
||||
this.delServiceData.params.namespace = this.namespaceValue
|
||||
httpClient.delete(this.delServiceData.url, {data: this.delServiceData.params})
|
||||
.then(res => {
|
||||
this.getServices()
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
handleConfirm(obj, operateName, fn) {
|
||||
this.confirmContent = '确认继续 ' + operateName + ' 操作吗?'
|
||||
this.$confirm(this.confirmContent,'提示',{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
.then(() => {
|
||||
fn(obj)
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.info({
|
||||
message: '已取消操作'
|
||||
})
|
||||
})
|
||||
},
|
||||
createServiceFunc() {
|
||||
let reg = new RegExp("(^[A-Za-z]+=[A-Za-z0-9]+).*")
|
||||
if (!reg.test(this.createService.label_str)) {
|
||||
this.$message.warning({
|
||||
message: "标签填写异常,请确认后重新填写"
|
||||
})
|
||||
return
|
||||
}
|
||||
this.fullscreenLoading = true
|
||||
let label = new Map()
|
||||
let a = (this.createService.label_str).split(",")
|
||||
a.forEach(item => {
|
||||
let b = item.split("=")
|
||||
label[b[0]] = b[1]
|
||||
})
|
||||
this.createServiceData.params = this.createService
|
||||
this.createServiceData.params.label = label
|
||||
this.createServiceData.params.container_port = parseInt(this.createService.container_port)
|
||||
this.createServiceData.params.port = parseInt(this.createService.port)
|
||||
this.createServiceData.params.node_port = parseInt(this.createService.node_port)
|
||||
httpClient.post(this.createServiceData.url, this.createServiceData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
this.getServices()
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.resetForm('createService')
|
||||
this.fullscreenLoading = false
|
||||
this.createServiceDrawer = false
|
||||
},
|
||||
resetForm(formName) {
|
||||
this.$refs[formName].resetFields()
|
||||
},
|
||||
submitForm(formName) {
|
||||
this.$refs[formName].validate((valid) => {
|
||||
if (valid) {
|
||||
this.createServiceFunc()
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
namespaceValue: {
|
||||
handler() {
|
||||
localStorage.setItem('namespace', this.namespaceValue)
|
||||
this.currentPage = 1
|
||||
this.getServices()
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
if (localStorage.getItem('namespace') !== undefined && localStorage.getItem('namespace') !== null) {
|
||||
this.namespaceValue = localStorage.getItem('namespace')
|
||||
}
|
||||
this.getNamespaces()
|
||||
this.getServices()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.service-head-card,.service-body-card {
|
||||
border-radius: 1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.service-head-search {
|
||||
width:160px;
|
||||
margin-right:10px;
|
||||
}
|
||||
.service-body-servicename {
|
||||
color: #4795EE;
|
||||
}
|
||||
.service-body-servicename:hover {
|
||||
color: rgb(84, 138, 238);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
370
src/views/statefulset/StatefulSet.vue
Normal file
@ -0,0 +1,370 @@
|
||||
<template>
|
||||
<div class="statefulset">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="statefulset-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<span>命名空间: </span>
|
||||
<el-select v-model="namespaceValue" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in namespaceList"
|
||||
:key="index"
|
||||
:label="item.metadata.name"
|
||||
:value="item.metadata.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" :offset="16">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Refresh" plain @click="getStatefulSets()">刷新</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="statefulset-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="2">
|
||||
<div>
|
||||
<el-button disabled style="border-radius:2px;" icon="Edit" type="primary">创建</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-input class="statefulset-head-search" clearable placeholder="请输入" v-model="searchInput"></el-input>
|
||||
<el-button style="border-radius:2px;" icon="Search" type="primary" plain @click="getStatefulSets()">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="statefulset-body-card" shadow="never" :body-style="{padding:'5px'}">
|
||||
<el-table
|
||||
style="width:100%;font-size:12px;margin-bottom:10px;"
|
||||
:data="statefulSetList"
|
||||
v-loading="appLoading">
|
||||
<el-table-column width="20"></el-table-column>
|
||||
<el-table-column align=left label="StatefulSet名">
|
||||
<template v-slot="scope">
|
||||
<a class="statefulset-body-statefulsetname">{{ scope.row.metadata.name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="标签">
|
||||
<template v-slot="scope">
|
||||
<div v-for="(val, key) in scope.row.metadata.labels" :key="key">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="200"
|
||||
trigger="hover"
|
||||
:content="key + ':' + val">
|
||||
<template #reference>
|
||||
<el-tag style="margin-bottom: 5px" type="warning">{{ ellipsis(key + ":" + val) }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="容器组">
|
||||
<template v-slot="scope">
|
||||
<span>{{ scope.row.status.currentReplicas>0?scope.row.status.currentReplicas:0 }} / {{ scope.row.spec.replicas>0?scope.row.spec.replicas:0 }} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center min-width="100" label="创建时间">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="info">{{ timeTrans(scope.row.metadata.creationTimestamp) }} </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="镜像">
|
||||
<template v-slot="scope">
|
||||
<div v-for="(val, key) in scope.row.spec.template.spec.containers" :key="key">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="200"
|
||||
trigger="hover"
|
||||
:content="val.image">
|
||||
<template #reference>
|
||||
<el-tag style="margin-bottom: 5px">{{ ellipsis(val.image.split('/')[2]==undefined?val.image:val.image.split('/')[2]) }}</el-tag>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="操作" width="200">
|
||||
<template v-slot="scope">
|
||||
<el-button size="small" style="border-radius:2px;" icon="Edit" type="primary" plain @click="getStatefulSetDetail(scope)">YAML</el-button>
|
||||
<el-button size="small" style="border-radius:2px;" icon="Delete" type="danger" @click="handleConfirm(scope, '删除', delStatefulSet)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="statefulset-body-pagination"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-sizes="pagesizeList"
|
||||
:page-size="pagesize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="statefulSetTotal">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="YAML信息" v-model="yamlDialog" width="45%" top="5%">
|
||||
<codemirror
|
||||
:value="contentYaml"
|
||||
border
|
||||
:options="cmOptions"
|
||||
height="500"
|
||||
style="font-size:14px;"
|
||||
@change="onChange"
|
||||
></codemirror>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="yamlDialog = false">取 消</el-button>
|
||||
<el-button type="primary" @click="updateStatefulSet()">更 新</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
import yaml2obj from 'js-yaml';
|
||||
import json2yaml from 'json2yaml';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cmOptions: common.cmOptions,
|
||||
contentYaml: '',
|
||||
currentPage: 1,
|
||||
pagesize: 10,
|
||||
pagesizeList: [10, 20, 30],
|
||||
searchInput: '',
|
||||
namespaceValue: 'default',
|
||||
namespaceList: [],
|
||||
namespaceListUrl: common.k8sNamespaceList,
|
||||
appLoading: false,
|
||||
statefulSetList: [],
|
||||
statefulSetTotal: 0,
|
||||
getStatefulSetsData: {
|
||||
url: common.k8sStatefulSetList,
|
||||
params: {
|
||||
filter_name: '',
|
||||
namespace: '',
|
||||
page: '',
|
||||
limit: '',
|
||||
}
|
||||
},
|
||||
statefulSetDetail: {},
|
||||
getStatefulSetDetailData: {
|
||||
url: common.k8sStatefulSetDetail,
|
||||
params: {
|
||||
statefulset_name: '',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
yamlDialog: false,
|
||||
updateStatefulSetData: {
|
||||
url: common.k8sStatefulSetUpdate,
|
||||
params: {
|
||||
namespace: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
delStatefulSetData: {
|
||||
url: common.k8sstatefulsetDel,
|
||||
params: {
|
||||
statefulset_name: '',
|
||||
namespace: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
transYaml(content) {
|
||||
return json2yaml.stringify(content)
|
||||
},
|
||||
transObj(content) {
|
||||
return yaml2obj.load(content)
|
||||
},
|
||||
onChange(val) {
|
||||
this.contentYaml = val
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.pagesize = size;
|
||||
this.getStatefulSets()
|
||||
},
|
||||
handleCurrentChange(currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
this.getStatefulSets()
|
||||
},
|
||||
handleClose(done) {
|
||||
this.$confirm('确认关闭?')
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
ellipsis(value) {
|
||||
return value.length>15?value.substring(0,15)+'...':value
|
||||
},
|
||||
timeTrans(timestamp) {
|
||||
let date = new Date(new Date(timestamp).getTime() + 8 * 3600 * 1000)
|
||||
date = date.toJSON();
|
||||
date = date.substring(0, 19).replace('T', ' ')
|
||||
return date
|
||||
},
|
||||
restartTotal(e) {
|
||||
let index, sum = 0
|
||||
let containerStatuses = e.row.status.containerStatuses
|
||||
for ( index in containerStatuses) {
|
||||
sum = sum + containerStatuses[index].restartCount
|
||||
}
|
||||
return sum
|
||||
},
|
||||
getNamespaces() {
|
||||
httpClient.get(this.namespaceListUrl)
|
||||
.then(res => {
|
||||
this.namespaceList = res.data.items
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getStatefulSets() {
|
||||
this.appLoading = true
|
||||
this.getStatefulSetsData.params.filter_name = this.searchInput
|
||||
this.getStatefulSetsData.params.namespace = this.namespaceValue
|
||||
this.getStatefulSetsData.params.page = this.currentPage
|
||||
this.getStatefulSetsData.params.limit = this.pagesize
|
||||
httpClient.get(this.getStatefulSetsData.url, {params: this.getStatefulSetsData.params})
|
||||
.then(res => {
|
||||
this.statefulSetList = res.data.items
|
||||
this.statefulSetTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.appLoading = false
|
||||
},
|
||||
getStatefulSetDetail(e) {
|
||||
this.getStatefulSetDetailData.params.statefulset_name = e.row.metadata.name
|
||||
this.getStatefulSetDetailData.params.namespace = this.namespaceValue
|
||||
httpClient.get(this.getStatefulSetDetailData.url, {params: this.getStatefulSetDetailData.params})
|
||||
.then(res => {
|
||||
this.statefulSetDetail = res.data
|
||||
this.contentYaml = this.transYaml(this.statefulSetDetail)
|
||||
this.yamlDialog = true
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
updateStatefulSet() {
|
||||
let content = JSON.stringify(this.transObj(this.contentYaml))
|
||||
this.updateStatefulSetData.params.namespace = this.namespaceValue
|
||||
this.updateStatefulSetData.params.content = content
|
||||
httpClient.put(this.updateStatefulSetData.url, this.updateStatefulSetData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.yamlDialog = false
|
||||
},
|
||||
delStatefulSet(e) {
|
||||
this.delStatefulSetData.params.statefulset_name = e.row.metadata.name
|
||||
this.delStatefulSetData.params.namespace = this.namespaceValue
|
||||
httpClient.delete(this.delStatefulSetData.url, {data: this.delStatefulSetData.params})
|
||||
.then(res => {
|
||||
this.getStatefulSets()
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
handleConfirm(obj, operateName, fn) {
|
||||
this.confirmContent = '确认继续 ' + operateName + ' 操作吗?'
|
||||
this.$confirm(this.confirmContent,'提示',{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
.then(() => {
|
||||
fn(obj)
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.info({
|
||||
message: '已取消操作'
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
namespaceValue: {
|
||||
handler() {
|
||||
localStorage.setItem('namespace', this.namespaceValue)
|
||||
this.currentPage = 1
|
||||
this.getStatefulSets()
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
if (localStorage.getItem('namespace') !== undefined && localStorage.getItem('namespace') !== null) {
|
||||
this.namespaceValue = localStorage.getItem('namespace')
|
||||
}
|
||||
this.getNamespaces()
|
||||
this.getStatefulSets()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.statefulset-head-card,.statefulset-body-card {
|
||||
border-radius: 1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.statefulset-head-search {
|
||||
width:160px;
|
||||
margin-right:10px;
|
||||
}
|
||||
.statefulset-body-statefulsetname {
|
||||
color: #4795EE;
|
||||
}
|
||||
.statefulset-body-statefulsetname:hover {
|
||||
color: rgb(84, 138, 238);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
608
src/views/workflow/Workflow.vue
Normal file
@ -0,0 +1,608 @@
|
||||
<template>
|
||||
<div class="workflow">
|
||||
<el-row>
|
||||
<!-- header1 -->
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="workflow-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<span>命名空间: </span>
|
||||
<el-select v-model="namespaceValue" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in namespaceList"
|
||||
:key="index"
|
||||
:label="item.metadata.name"
|
||||
:value="item.metadata.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2" :offset="16">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Refresh" plain @click="getWorkflows()">刷新</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<!-- header2 -->
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="workflow-head-card" shadow="never" :body-style="{padding:'30px 10px 20px 10px'}">
|
||||
<el-steps :active="active" align-center finish-status="success">
|
||||
<el-step title="步骤1" description="选择工作流类型, ClusterIP NodePort Workflow"></el-step>
|
||||
<el-step title="步骤2" description="填写Deployment Workflow Workflow表单"></el-step>
|
||||
<el-step title="步骤3" description="创建Deployment Workflow Workflow"></el-step>
|
||||
</el-steps>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<!-- header3 -->
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="workflow-head-card" shadow="never" :body-style="{padding:'10px'}">
|
||||
<el-row>
|
||||
<el-col :span="3">
|
||||
<div>
|
||||
<el-button style="border-radius:2px;" icon="Edit" type="primary" @click="createWorkflowDrawerIndex1 = true" v-loading.fullscreen.lock="fullscreenLoading">创建工作流</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-input class="workflow-head-search" clearable placeholder="请输入" v-model="searchInput"></el-input>
|
||||
<el-button style="border-radius:2px;" icon="Search" type="primary" plain @click="getWorkflows()">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
<el-card class="workflow-body-card" shadow="never" :body-style="{padding:'5px'}">
|
||||
<el-table
|
||||
style="width:100%;font-size:12px;margin-bottom:10px;"
|
||||
:data="workflowList"
|
||||
v-loading="appLoading">
|
||||
<el-table-column width="20"></el-table-column>
|
||||
<el-table-column min-width="50" align=left label="ID" prop="id"></el-table-column>
|
||||
<el-table-column min-width="100" label="Workflow名">
|
||||
<template v-slot="scope">
|
||||
<a class="workflow-body-workflowname">{{ scope.row.name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" prop="type">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="warning">{{ scope.row.type }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实例数" prop="replicas"></el-table-column>
|
||||
<el-table-column min-width="100" label="deployment" prop="deployment"></el-table-column>
|
||||
<el-table-column min-width="150" label="service" prop="service"></el-table-column>
|
||||
<el-table-column min-width="150" label="ingress" prop="ingress"></el-table-column>
|
||||
<el-table-column align=center min-width="150" label="创建时间">
|
||||
<template v-slot="scope">
|
||||
<el-tag type="info">{{ timeTransNot8(scope.row.created_at) }} </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align=center label="操作" width="200">
|
||||
<template v-slot="scope">
|
||||
<el-button size="small" disabled style="border-radius:2px;" icon="Edit" type="primary" plain @click="getWorkflowDetail(scope)">详情</el-button>
|
||||
<el-button size="small" style="border-radius:2px;" icon="Delete" type="danger" @click="handleConfirm(scope, '删除', delWorkflow)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="workflow-body-pagination"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="currentPage"
|
||||
:page-sizes="pagesizeList"
|
||||
:page-size="pagesize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="workflowTotal">
|
||||
</el-pagination>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-drawer
|
||||
v-model="createWorkflowDrawerIndex1"
|
||||
:direction="direction"
|
||||
:before-close="handleClose">
|
||||
<template #title>
|
||||
<h4>创建Workflow-步骤1</h4>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="20">
|
||||
<el-form label-width="80px">
|
||||
<el-form-item class="workflow-create-form" label="类型" prop="name">
|
||||
<el-radio v-model="createWorkflow.type" label="ClusterIP">ClusterIP</el-radio>
|
||||
<el-radio v-model="createWorkflow.type" label="NodePort">NodePort</el-radio>
|
||||
<el-radio v-model="createWorkflow.type" label="Ingress">Ingress</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="drawerCancel('createWorkflowDrawerIndex1')">取消</el-button>
|
||||
<el-button type="primary" @click="workflowIndex1Next()">下一步</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
<el-drawer
|
||||
v-model="createWorkflowDrawerIndex2_1"
|
||||
:direction="direction"
|
||||
:before-close="handleClose">
|
||||
<template #title>
|
||||
<h4>创建Workflow-步骤2</h4>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="20">
|
||||
<el-form ref="createWorkflow" :rules="createWorkflowRules" :model="createWorkflow" label-width="80px">
|
||||
<h4 style="margin-bottom:10px">Deployment</h4>
|
||||
<el-form-item class="workflow-create-form" label="名称" prop="name">
|
||||
<el-input v-model="createWorkflow.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="workflow-create-form" label="命名空间" prop="namespace">
|
||||
<el-select v-model="createWorkflow.namespace" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(item, index) in namespaceList"
|
||||
:key="index"
|
||||
:label="item.metadata.name"
|
||||
:value="item.metadata.name">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="workflow-create-form" label="副本数" prop="replicas">
|
||||
<el-input-number v-model="createWorkflow.replicas" :min="1" :max="10"></el-input-number>
|
||||
<el-popover
|
||||
placement="top"
|
||||
:width="100"
|
||||
trigger="hover"
|
||||
content="申请副本数上限为10个">
|
||||
<template #reference>
|
||||
<el-icon style="width:2em;font-size:18px;color:#4795EE"><WarningFilled/></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
<el-form-item class="workflow-create-form" label="镜像" prop="image">
|
||||
<el-input v-model="createWorkflow.image"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="workflow-create-form" label="标签" prop="label_str">
|
||||
<el-input v-model="createWorkflow.label_str" placeholder="示例: project=ms,app=gateway"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="workflow-create-form" label="资源配额" prop="resource">
|
||||
<el-select v-model="createWorkflow.resource" placeholder="请选择">
|
||||
<el-option value="0.5/1" label="0.5C1G"></el-option>
|
||||
<el-option value="1/2" label="1C2G"></el-option>
|
||||
<el-option value="2/4" label="2C4G"></el-option>
|
||||
<el-option value="4/8" label="4C8G"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="workflow-create-form" label="容器端口" prop="container_port">
|
||||
<el-input v-model="createWorkflow.container_port" placeholder="示例: 80"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="workflow-create-form" label="健康检查" prop="health">
|
||||
<el-switch v-model="createWorkflow.health_check" />
|
||||
</el-form-item>
|
||||
<el-form-item class="workflow-create-form" label="检查路径" prop="healthPath">
|
||||
<el-input v-model="createWorkflow.health_path" placeholder="示例: /health"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="drawerCancel('createWorkflowDrawerIndex2_1')">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm('createWorkflow', workflowIndex2_1Next)">下一步</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
<el-drawer
|
||||
v-model="createWorkflowDrawerIndex2_2"
|
||||
:direction="direction"
|
||||
:before-close="handleClose">
|
||||
<template #title>
|
||||
<h4>创建Workflow-步骤2</h4>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-row type="flex" justify="center">
|
||||
<el-col :span="20">
|
||||
<el-form ref="createWorkflow" :rules="createWorkflowRules" :model="createWorkflow" label-width="80px">
|
||||
<h4 style="margin-bottom:10px">Service</h4>
|
||||
<el-form-item class="service-create-form" label="Service端口" prop="port">
|
||||
<el-input v-model="createWorkflow.port" placeholder="示例: 80"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="createWorkflow.type == 'NodePort'" class="service-create-form" label="NodePort" prop="node_port">
|
||||
<el-input v-model="createWorkflow.node_port" placeholder="示例: 30001"></el-input>
|
||||
</el-form-item>
|
||||
<el-divider v-if="createWorkflow.type == 'Ingress'"></el-divider>
|
||||
<h4 v-if="createWorkflow.type == 'Ingress'" style="margin-bottom:10px">Ingress</h4>
|
||||
<el-form-item v-if="createWorkflow.type == 'Ingress'" class="deploy-create-form" label="域名" prop="host">
|
||||
<el-input v-model="createWorkflow.host" placeholder="示例: www.example.com"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="createWorkflow.type == 'Ingress'" class="ingress-create-form" label="Path" prop="path">
|
||||
<el-input v-model="createWorkflow.path" placeholder="示例: /abc"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="createWorkflow.type == 'Ingress'" class="deploy-create-form" label="匹配类型" prop="path_type">
|
||||
<el-select v-model="createWorkflow.path_type" placeholder="请选择">
|
||||
<el-option value="Prefix" label="Prefix"></el-option>
|
||||
<el-option value="Exact" label="Exact"></el-option>
|
||||
<el-option value="ImplementationSpecific" label="ImplementationSpecific"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template #footer>
|
||||
<el-button @click="drawerCancel('createWorkflowDrawerIndex2_2')">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm('createWorkflow', createWorkflowFunc)">立即创建</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import common from "../common/Config";
|
||||
import httpClient from '../../utils/request';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
active: 0,
|
||||
createWorkflowDrawerIndex1: false,
|
||||
createWorkflowDrawerIndex2_1: false,
|
||||
createWorkflowDrawerIndex2_2: false,
|
||||
currentPage: 1,
|
||||
pagesize: 10,
|
||||
pagesizeList: [10, 20, 30],
|
||||
searchInput: '',
|
||||
namespaceValue: 'default',
|
||||
namespaceList: [],
|
||||
namespaceListUrl: common.k8sNamespaceList,
|
||||
appLoading: false,
|
||||
workflowList: [],
|
||||
workflowTotal: 0,
|
||||
getWorkflowsData: {
|
||||
url: common.k8sWorkflowList,
|
||||
params: {
|
||||
name: '',
|
||||
namespace: '',
|
||||
page: '',
|
||||
limit: '',
|
||||
}
|
||||
},
|
||||
fullscreenLoading: false,
|
||||
direction: 'rtl',
|
||||
createWorkflowDrawer: false,
|
||||
createWorkflow: {
|
||||
name: '',
|
||||
namespace: '',
|
||||
replicas: 1,
|
||||
image: '',
|
||||
resource: '',
|
||||
health_check: false,
|
||||
health_path: '',
|
||||
label_str: '',
|
||||
label: {},
|
||||
container_port: '',
|
||||
type: '',
|
||||
port: '',
|
||||
node_port: '',
|
||||
host: '',
|
||||
path: '',
|
||||
path_type: ''
|
||||
},
|
||||
createWorkflowData: {
|
||||
url: common.k8sWorkflowCreate,
|
||||
params: {}
|
||||
},
|
||||
createWorkflowRules: {
|
||||
name: [{
|
||||
required: true,
|
||||
message: '请填写名称',
|
||||
trigger: 'change'
|
||||
}],
|
||||
image: [{
|
||||
required: true,
|
||||
message: '请填写镜像',
|
||||
trigger: 'change'
|
||||
}],
|
||||
namespace: [{
|
||||
required: true,
|
||||
message: '请选择命名空间',
|
||||
trigger: 'change'
|
||||
}],
|
||||
resource: [{
|
||||
required: true,
|
||||
message: '请选择配额',
|
||||
trigger: 'change'
|
||||
}],
|
||||
label_str: [{
|
||||
required: true,
|
||||
message: '请填写标签',
|
||||
trigger: 'change'
|
||||
}],
|
||||
container_port: [{
|
||||
required: true,
|
||||
message: '请填写容器端口',
|
||||
trigger: 'change'
|
||||
}],
|
||||
type: [{
|
||||
required: true,
|
||||
message: '请填写工作流类型',
|
||||
trigger: 'change'
|
||||
}],
|
||||
port: [{
|
||||
required: true,
|
||||
message: '请填写Workflow端口',
|
||||
trigger: 'change'
|
||||
}],
|
||||
node_port: [{
|
||||
required: true,
|
||||
message: '请填写NodePort',
|
||||
trigger: 'change'
|
||||
}],
|
||||
host: [{
|
||||
required: true,
|
||||
message: '请填写域名',
|
||||
trigger: 'change'
|
||||
}],
|
||||
path: [{
|
||||
required: true,
|
||||
message: '请填写路径',
|
||||
trigger: 'change'
|
||||
}],
|
||||
path_type: [{
|
||||
required: true,
|
||||
message: '你选择匹配类型',
|
||||
trigger: 'change'
|
||||
}],
|
||||
},
|
||||
delWorkflowData: {
|
||||
url: common.k8sWorkflowDel,
|
||||
params: {
|
||||
id: ''
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSizeChange(size) {
|
||||
this.pagesize = size;
|
||||
this.getWorkflows()
|
||||
},
|
||||
handleCurrentChange(currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
this.getWorkflows()
|
||||
},
|
||||
handleClose(done) {
|
||||
this.$confirm('确认关闭?')
|
||||
.then(() => {
|
||||
done();
|
||||
})
|
||||
.catch(() => {});
|
||||
this.active = 0
|
||||
},
|
||||
drawerCancel(drawerName) {
|
||||
switch (drawerName) {
|
||||
case 'createWorkflowDrawerIndex1':
|
||||
this.createWorkflowDrawerIndex1 = false
|
||||
break
|
||||
case 'createWorkflowDrawerIndex2_1':
|
||||
this.createWorkflowDrawerIndex2_1 = false
|
||||
break
|
||||
case 'createWorkflowDrawerIndex2_2':
|
||||
this.createWorkflowDrawerIndex2_2 = false
|
||||
}
|
||||
this.active = 0
|
||||
},
|
||||
ellipsis(value) {
|
||||
return value.length>15?value.substring(0,15)+'...':value
|
||||
},
|
||||
timeTrans(timestamp) {
|
||||
let date = new Date(new Date(timestamp).getTime() + 8 * 3600 * 1000)
|
||||
date = date.toJSON();
|
||||
date = date.substring(0, 19).replace('T', ' ')
|
||||
return date
|
||||
},
|
||||
timeTransNot8(timestamp) {
|
||||
let date = new Date(new Date(timestamp).getTime() + 8 * 3600 * 1000)
|
||||
date = date.toJSON();
|
||||
date = date.substring(0, 19).replace('T', ' ')
|
||||
return date
|
||||
},
|
||||
getNamespaces() {
|
||||
httpClient.get(this.namespaceListUrl)
|
||||
.then(res => {
|
||||
this.namespaceList = res.data.items
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
},
|
||||
getWorkflows() {
|
||||
this.appLoading = true
|
||||
this.getWorkflowsData.params.name = this.searchInput
|
||||
this.getWorkflowsData.params.namespace = this.namespaceValue
|
||||
this.getWorkflowsData.params.page = this.currentPage
|
||||
this.getWorkflowsData.params.limit = this.pagesize
|
||||
httpClient.get(this.getWorkflowsData.url, {params: this.getWorkflowsData.params})
|
||||
.then(res => {
|
||||
this.workflowList = res.data.items
|
||||
this.workflowTotal = res.data.total
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.appLoading = false
|
||||
},
|
||||
delWorkflow(e) {
|
||||
this.delWorkflowData.params.id = e.row.id
|
||||
httpClient.delete(this.delWorkflowData.url, {data: this.delWorkflowData.params})
|
||||
.then(res => {
|
||||
this.getWorkflows()
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
console.log(123)
|
||||
},
|
||||
handleConfirm(obj, operateName, fn) {
|
||||
this.confirmContent = '确认继续 ' + operateName + ' 操作吗?'
|
||||
this.$confirm(this.confirmContent,'提示',{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
.then(() => {
|
||||
fn(obj)
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.info({
|
||||
message: '已取消操作'
|
||||
})
|
||||
})
|
||||
},
|
||||
createWorkflowFunc() {
|
||||
let reg = new RegExp("(^[A-Za-z]+=[A-Za-z0-9]+).*")
|
||||
if (!reg.test(this.createWorkflow.label_str)) {
|
||||
this.$message.warning({
|
||||
message: "标签填写异常,请确认后重新填写"
|
||||
})
|
||||
return
|
||||
}
|
||||
this.fullscreenLoading = true
|
||||
let label = new Map()
|
||||
let cpu, memory
|
||||
let a = (this.createWorkflow.label_str).split(",")
|
||||
a.forEach(item => {
|
||||
let b = item.split("=")
|
||||
label[b[0]] = b[1]
|
||||
})
|
||||
let resourceList = this.createWorkflow.resource.split("/")
|
||||
cpu = resourceList[0]
|
||||
memory = resourceList[1] + "Gi"
|
||||
this.createWorkflowData.params = this.createWorkflow
|
||||
this.createWorkflowData.params.label = label
|
||||
this.createWorkflowData.params.cpu = cpu
|
||||
this.createWorkflowData.params.memory = memory
|
||||
this.createWorkflowData.params.container_port = parseInt(this.createWorkflow.container_port)
|
||||
this.createWorkflowData.params.port = parseInt(this.createWorkflow.port)
|
||||
this.createWorkflowData.params.node_port = parseInt(this.createWorkflow.node_port)
|
||||
if (this.createWorkflow.type == 'Ingress') {
|
||||
let hosts = new Map()
|
||||
let httpPaths = []
|
||||
let httpPath = {
|
||||
path: this.createWorkflow.path,
|
||||
path_type: this.createWorkflow.path_type,
|
||||
service_name: this.createWorkflow.name,
|
||||
service_port: parseInt(this.createWorkflow.port)
|
||||
}
|
||||
httpPaths.push(httpPath)
|
||||
hosts[this.createWorkflow.host] = httpPaths
|
||||
this.createWorkflowData.params.hosts = hosts
|
||||
}
|
||||
httpClient.post(this.createWorkflowData.url, this.createWorkflowData.params)
|
||||
.then(res => {
|
||||
this.$message.success({
|
||||
message: res.msg
|
||||
})
|
||||
this.getWorkflows()
|
||||
})
|
||||
.catch(res => {
|
||||
this.$message.error({
|
||||
message: res.msg
|
||||
})
|
||||
})
|
||||
this.resetForm('createWorkflow')
|
||||
this.createWorkflowDrawerIndex2_2 = false
|
||||
this.active = 3
|
||||
this.fullscreenLoading = false
|
||||
},
|
||||
resetForm(formName) {
|
||||
this.$refs[formName].resetFields()
|
||||
},
|
||||
submitForm(formName, fn) {
|
||||
this.$refs[formName].validate((valid) => {
|
||||
if (valid) {
|
||||
fn()
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
},
|
||||
workflowIndex1Next() {
|
||||
if (!this.createWorkflow.type) {
|
||||
this.$message.warning({
|
||||
message: "请选择工作流类型"
|
||||
})
|
||||
return
|
||||
}
|
||||
this.createWorkflowDrawerIndex1 = false
|
||||
this.createWorkflowDrawerIndex2_1 = true
|
||||
this.active = 1
|
||||
},
|
||||
workflowIndex2_1Next() {
|
||||
this.createWorkflowDrawerIndex2_1 = false
|
||||
this.createWorkflowDrawerIndex2_2 = true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
namespaceValue: {
|
||||
handler() {
|
||||
localStorage.setItem('namespace', this.namespaceValue)
|
||||
this.currentPage = 1
|
||||
this.getWorkflows()
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
if (localStorage.getItem('namespace') !== undefined && localStorage.getItem('namespace') !== null) {
|
||||
this.namespaceValue = localStorage.getItem('namespace')
|
||||
}
|
||||
this.getNamespaces()
|
||||
this.getWorkflows()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.workflow-head-card,.workflow-body-card {
|
||||
border-radius: 1px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.workflow-head-search {
|
||||
width:160px;
|
||||
margin-right:10px;
|
||||
}
|
||||
.workflow-body-workflowname {
|
||||
color: #4795EE;
|
||||
}
|
||||
.workflow-body-workflowname:hover {
|
||||
color: rgb(84, 138, 238);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
:v-deep .el-drawer__header {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
v-deep .el-drawer__body {
|
||||
padding: 0px 0px 0px 0px;
|
||||
}
|
||||
</style>
|
8
vue.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
devServer:{
|
||||
host: '0.0.0.0',
|
||||
port: 8080,
|
||||
open: true
|
||||
},
|
||||
lintOnSave: false
|
||||
}
|