k8s管理系统项目前端-cluster
k8s管理系统项目前端-cluster
一、namespace
<template>
<div class="container">
<!-- 第一部分: 头部 -->
<div class="header shadow">
<div class="left-group">
<el-button type="primary" @click="goToCreateNamespace()">
<el-icon><EditPen /></el-icon>
<span style="vertical-align: middle"> 创建 </span>
</el-button>
<el-input placeholder="请输入内容" v-model="searchText" class="search-input" @input="handleInput">
<template #append>
<el-button type="primary" @click="search()">
<el-icon><Search /></el-icon>
</el-button>
</template>
</el-input>
</div>
<div class="right-group">
<el-button @click="refresh">
<el-icon><Refresh /></el-icon>
<span> 刷新 </span>
</el-button>
</div>
</div>
<!-- 第二部分: 表格 -->
<div class="table-wrapper shadow">
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="nsName" label="Namespace名" width="180" align="center">
<template #default="{ row }">
<div>
<p style="color: #4795EE; margin: 0;">{{ row.nsName }}</p>
</div>
</template>
</el-table-column>
<el-table-column prop="Labels" label="标签" width="230" align="center">
<template #default="{ row }">
<div>
<el-tooltip
v-for="(label, index) in row.Labels"
:key="index"
class="item"
effect="dark"
:content="label"
placement="top"
>
<el-tag class="ml-2" type="success">{{ label }}</el-tag>
</el-tooltip>
</div>
</template>
</el-table-column>
<el-table-column prop="Status" label="状态" align="center">
<template #default="{ row }">
<div :class="{'status-active': row.Status === 'Active', 'status-inactive': row.Status !== 'Active'}">
{{ row.Status }}
</div>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" align="center">
<template #default="{ row }">
<div>
<el-tag class="ml-2" type="warning">{{row.createTime}}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="Operation" label="操作" align="center">
<template #default="{ row }">
<el-button color="#5AABFF" :dark="isDark" plain @click="viewYAML(row)">
<el-icon><EditPen /></el-icon>
<span style="vertical-align: middle"> YAML </span>
</el-button>
<el-button :disabled="systemNamespaces.includes(row.nsName)" color="#F58D79" :dark="isDark" plain @click="confirmDelete(row)">
<el-icon><Tickets /></el-icon>
<span style="vertical-align: middle"> 删除 </span>
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[5, 10, 20, 30]"
:small="small"
:disabled="disabled"
:background="background"
layout="total, sizes, prev, pager, next, jumper"
:total=namespaceCount
class="paginations"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<el-dialog title="YAML信息" v-model="dialogVisible" width="45%" top="5%">
<codemirror
v-model="yamlContent"
placeholder="请输入代码..."
:style="{ height: '100%' }"
v-bind="cmOptions"
:extensions="extensions"
/>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="updateNS()">更 新</el-button>
</span>
</template>
</el-dialog>
<el-dialog title="创建命名空间" v-model="createDialogVisible" width="30%">
<el-form label-position="right" label-width="100px">
<el-form-item label="名称">
<el-input v-model="newNamespace.name" ref="nameInput" placeholder="请输入名称" style="width: 200px"></el-input>
</el-form-item>
<div style="margin-left: 100px; color: #606266; margin-top: -10px; margin-bottom: 20px;">
最长63个字符,只能包含小写字母、数字及分隔符("-"),且必须以小写字母开头,数字或小写字母结尾
</div>
<el-form-item label="描述">
<el-input
type="textarea"
v-model="newNamespace.description"
placeholder="请输入描述"
></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="createDialogVisible = false">取消</el-button>
<el-button type="primary" @click="createNamespace">创建</el-button>
</span>
</el-dialog>
</div>
</template>
<script setup>
import {nextTick, onBeforeMount, reactive, ref} from 'vue';
import {
CREATE_NAMESPACE,
DELETE_NAMESPACE,
NAMESPACE_DETAIL,
NAMESPACE_LIST,
UPDATE_NAMESPACE
} from "../../../api/k8s.js";
import { Codemirror } from 'vue-codemirror'
import { javascript} from '@codemirror/lang-javascript'
// import { javascript } from 'codemirror/lang-javascript'
import { oneDark } from '@codemirror/theme-one-dark'
import _ from 'lodash';
import json2yaml from 'json2yaml'
import yaml2obj from 'js-yaml';
import {ElMessage} from "element-plus";
import router from "@/router/index.js";
import { ElMessageBox } from 'element-plus';
import axios from "axios";
const extensions = [javascript(),oneDark]
const createDialogVisible = ref(false);
const newNamespace = reactive({
name: '',
description: ''
});
let currentPage = ref(1)
let pageSize = ref(5)
const small = ref(false)
const background = ref(false)
const disabled = ref(false)
const dialogVisible = ref(false);
const currentNsName = ref('');
let yamlContent = ref('');
const goToCreateNamespace = () => {
createDialogVisible.value = true
};
const createNamespace = async () => {
const nameInput = newNamespace.name.trim();
const nameRegex = /^[a-z]([-a-z0-9]*[a-z0-9])?$/;
// 如果名称不符合长度限制或正则表达式规则
if (nameInput.length > 63 || !nameRegex.test(nameInput)) {
ElMessage.error('名称格式不正确');
return;
}
try {
const resp = await CREATE_NAMESPACE(newNamespace)
ElMessage.success("创建成功")
createDialogVisible.value = false;
// 如果验证通过,关闭弹窗
// 清空表单
newNamespace.name = '';
newNamespace.description = '';
console.log('创建命名空间', newNamespace);
getNSData()
}catch (e){
console.log(e)
ElMessage.error(`${e}`)
}
// 发送创建命名空间请求的逻辑...
};
//编辑器配置
const cmOptions = {
// 语言及语法模式
mode: 'text/yaml',
// 主题
theme: 'monokai',
lint: true,
// 显示行数
lineNumbers: true,
smartIndent: true, //智能缩进
indentUnit: 4, // 智能缩进单元长度为 4 个空格
styleActiveLine: true, // 显示选中行的样式
matchBrackets: true, //每当光标位于匹配的方括号旁边时,都会使其高亮显示
readOnly: false,
lineWrapping: true //自动换行
}
const handleSizeChange = (val) => {
console.log(`${val} items per page`)
namespaceAllParameters.page_size = val
localStorage.setItem('nspageSize',val)
getNSData()
}
const handleInput = _.debounce(async () => {
await search();
}, 500); // 使用 lodash 的 debounce 函数来防抖,防止搜索操作太频繁
const updateNS = async () =>{
try {
const updatedJson = yaml2obj.load(yamlContent.value)
const jsonString = JSON.stringify(updatedJson);
console.log("?????????",updatedJson)
updateParameters.name = currentNsName.value
updateParameters.content = jsonString
await updateNSData()
}catch (e){
ElMessage.error(`${e}`)
}
}
const onChange = (val)=> {
yamlContent.value = val
}
const handleCurrentChange = (val) => {
console.log(`current page: ${val}`)
currentPage.value = val
namespaceAllParameters.page_number = val
localStorage.setItem('nscurrentPage', val); // 将当前页保存在 localStorage
getNSData()
}
const viewYAML = (row) => {
console.log('显示YAML信息', row);
// 这里可以编写显示YAML信息的逻辑
// 假设提供了 `getYAMLContent` 方法来获取 YAML 内容
// 直接使用传入的 row 作为 YAML 内容
getNsDetailData(row.nsName)
};
const confirmDelete = (row) => {
ElMessageBox.confirm(
`确定要删除命名空间 "${row.nsName}" 吗?`,
'警告',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
).then(async () => {
const deleteNSParameters = reactive({
name: row.nsName,
})
try {
const name = row.nsName
console.log("nameaaaaaa : ",name)
const resp = await DELETE_NAMESPACE(name,deleteNSParameters)
console.log("————————————————",resp)
ElMessage({
type: 'success',
message: `命名空间 "${row.nsName}" 已被删除`
});
getNSData()
}catch (e){
console.log(e)
ElMessage.error(`删除失败: ${e}`);
}
}).catch(() => {
ElMessage({
type: 'info',
message: '已取消删除'
});
});
};
const deleteNSData = async (row)=>{
}
const formatDateTime = (dateTimeString) => {
return dateTimeString.replace('T', ' ').replace('Z', '');
};
// 示例数据和方法
const searchText = ref('');
const tableData = ref([
{
nsName: "",
Labels: '',
Status: '',
createTime:"",
Operation:""
},
// ...其他数据项
]);
const search = async () => {
console.log('执行搜索:', searchText.value);
const searchParameters = reactive({
page_size: pageSize,
page_number: currentPage,
filterName: searchText.value
})
try {
const resp = await NAMESPACE_LIST(searchParameters)
if (resp && resp.data && Array.isArray(resp.data.Items)) {
tableData.value = resp.data.Items.map((item) => ({
nsName: item.metadata.name,
Labels: formatLabels(item.metadata.labels),
Status: item.status.phase,
createTime: formatDateTime(item.metadata.creationTimestamp),
Operation: "",
// 其他属性...
}));
}
console.log(resp)
}catch (e){
console.log(e)
}
};
const refresh = () => {
getNSData()
console.log('刷新表格数据');
};
const updateParameters = reactive({
name: "",
content: ""
})
const updateNSData = async ()=>{
try {
console.log("你吗的",updateParameters.content)
const resp = await UPDATE_NAMESPACE(updateParameters)
if (resp.code !== 200){
ElMessage.error(resp.message)
return
}
ElMessage.success("更新成功")
dialogVisible.value = false
}catch (e){
console.log(e)
ElMessage.error(`${e}`)
}
}
const namespaceAllParameters = reactive({
page_size: pageSize,
page_number: currentPage,
})
var namespaceCount = ref(0)
// 在这里创建一个方法来将 Labels 对象转换为一个字符串数组
const formatLabels = (labelsMap) => {
return Object.entries(labelsMap).map(([key, value]) => `${key}: ${value}`);
};
const systemNamespaces = ['default', 'kube-system', 'kube-public', 'kube-node-lease'];
const getNSData = async ()=>{
try {
const resp = await NAMESPACE_LIST(namespaceAllParameters)
if (resp && resp.data && Array.isArray(resp.data.Items)) {
tableData.value = resp.data.Items.map((item) => ({
nsName: item.metadata.name,
Labels: formatLabels(item.metadata.labels),
Status: item.status.phase,
createTime: formatDateTime(item.metadata.creationTimestamp),
isSystem: systemNamespaces.includes(item.metadata.name), // 添加这个属性
// 其他属性...
}));
}
namespaceCount.value = resp.data.total
console.log(resp)
}catch (e){
console.log(e)
}
}
const getNsDetailData = async (nsName)=>{
try {
console.log(nsName)
const params = reactive({
name: nsName,
})
const resp = await NAMESPACE_DETAIL(params)
console.log("yaml =======",json2yaml.stringify(resp.data))
yamlContent.value = json2yaml.stringify(resp.data); // 确保将数据赋值给 yamlContent
currentNsName.value = resp.data.metadata.name
updateParameters.content = yamlContent.value
await nextTick()
dialogVisible.value = true;
console.log(yamlContent.value)
console.log("握草")
}catch (e){
console.log(e)
console.log("你吗")
}
}
onBeforeMount( ()=> {
// 尝试从 localStorage 中读取状态
const savedPageSize = localStorage.getItem('nspageSize');
const savedCurrentPage = localStorage.getItem('nscurrentPage');
// 如果存在则更新到响应式变量中
if (savedPageSize) {
pageSize.value = parseInt(savedPageSize, 10);
}
if (savedCurrentPage) {
currentPage.value = parseInt(savedCurrentPage, 10);
}
getNSData()
// getNSAllData()
});
</script>
<style scoped>
.container {
margin: 10px;
}
.header {
display: flex;
align-items: center;
justify-content: space-between; /* 添加此属性对子元素进行分散对齐 */
margin-bottom: 0px;
gap: 10px;
padding: 10px;
background: #FFF;
border: 2px solid #ebeef5;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.search-input {
/*flex-grow: 1;*/
width: 200px;
}
.table-wrapper {
background: #FFF;
border: 2px solid #ebeef5; /* 浅色边框线 */
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.paginations {
margin-top: 10px;
margin-left: 20px;
margin-bottom: 10px;
}
/* 左侧组合样式,创建按钮和搜索框靠在一起 */
.left-group {
display: flex;
align-items: center;
gap: 10px; /* You can adjust the gap as needed */
}
/* 右侧刷新按钮 */
.right-group {
display: flex;
align-items: center;
}
.yaml-content {
background-color: #f5f5f5;
border-left: 3px solid #4795EE;
padding: 15px;
white-space: pre-wrap;
text-align: left;
margin: 20px 0;
overflow-x: auto;
}
.status-active {
color: #67C23A;
}
.status-inactive {
color: red;
}
.dialog-footer {
/*text-align: right;*/
display: flex;
justify-content: flex-end;
padding: 8px;
}
</style>
二、node开发
<template>
<div class="container">
<!-- 第一部分: 头部 -->
<div class="header shadow">
<div class="left-group">
<el-button disabled type="primary">
<el-icon><EditPen /></el-icon>
<span style="vertical-align: middle"> 创建 </span>
</el-button>
<el-input placeholder="请输入内容" v-model="searchText" class="search-input" @input="handleInput">
<template #append>
<el-button type="primary" @click="search()">
<el-icon><Search /></el-icon>
</el-button>
</template>
</el-input>
</div>
<div class="right-group">
<el-button @click="refresh">
<el-icon><Refresh /></el-icon>
<span> 刷新 </span>
</el-button>
</div>
</div>
<!-- 第二部分: 表格 -->
<div class="table-wrapper shadow">
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="nodeName" label="Node名" width="180" align="center">
<template #default="{ row }">
<div>
<p style="color: #4795EE; margin: 0;">{{ row.nodeName }}</p>
<p style="color: #aaa; margin: 0;">{{ row.hostName }}</p>
</div>
</template>
</el-table-column>
<el-table-column prop="Specifications" label="规格" width="180" align="center">
<template #default="{ row }">
<div>
<el-tag class="ml-2" type="success">{{row.Specifications}}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="PodCIDR" label="POD-CIDR" align="center"></el-table-column>
<el-table-column prop="version" label="版本" align="center"></el-table-column>
<el-table-column prop="createTime" label="创建时间" align="center">
<template #default="{ row }">
<div>
<el-tag class="ml-2" type="warning">{{row.createTime}}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="Operation" label="操作" align="center">
<template #default="{ row }">
<el-button color="#5AABFF" :dark="isDark" plain @click="viewYAML(row)">
<el-icon><EditPen /></el-icon>
<span style="vertical-align: middle"> YAML </span>
</el-button>
<el-button color="#5AABFF" :dark="isDark" plain>
<el-icon><Tickets /></el-icon>
<span style="vertical-align: middle"> 详情 </span>
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[5, 10, 20, 30]"
:small="small"
:disabled="disabled"
:background="background"
layout="total, sizes, prev, pager, next, jumper"
:total=nodeCount
class="paginations"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<el-dialog title="YAML信息" v-model="dialogVisible" width="45%" top="5%">
<codemirror
v-model="yamlContent"
placeholder="请输入代码..."
:style="{ height: '100%' }"
v-bind="cmOptions"
:extensions="extensions"
/>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button disabled type="primary" >更 新</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {nextTick, onBeforeMount, reactive, ref} from 'vue';
import {NAMESPACE_LIST, NODE_DETAIL_LIST, NODE_LIST} from "../../../api/k8s.js";
import { Codemirror } from "vue-codemirror";
import json2yaml from 'json2yaml'
import idea from "vue-codemirror"
import {javascript} from "@codemirror/lang-javascript";
import {oneDark} from "@codemirror/theme-one-dark";
import _ from "lodash";
const extensions = [javascript(),oneDark]
// import 'codemirror/mode/yaml/yaml.js'
// import 'codemirror/theme/idea.css'
let currentPage = ref(1)
let pageSize = ref(5)
const small = ref(false)
const background = ref(false)
const disabled = ref(false)
const isLoading = ref(true)
const dialogVisible = ref(false);
const currentYAMLContent = ref('');
let yamlContent = ref('');
//编辑器配置
const cmOptions = {
// 语言及语法模式
mode: 'text/yaml',
// 主题
theme: 'idea',
// 显示行数
lineNumbers: true,
smartIndent: true, //智能缩进
indentUnit: 4, // 智能缩进单元长度为 4 个空格
styleActiveLine: true, // 显示选中行的样式
matchBrackets: true, //每当光标位于匹配的方括号旁边时,都会使其高亮显示
readOnly: false,
lineWrapping: true //自动换行
}
const handleInput = _.debounce(async () => {
await search();
}, 500); // 使用 lodash 的 debounce 函数来防抖,防止搜索操作太频繁
const handleSizeChange = (val) => {
console.log(`${val} items per page`)
nodeParameters.page_size = val
localStorage.setItem('nodepageSize', val); // 将页面大小保存在 localStorage
getNodeData()
}
const onChange = (val)=> {
yamlContent = val
}
// 转换Ki到G的函数
const kiToG = (ki) => {
const kiValue = parseInt(ki.replace('Ki', '')); // 移除Ki并转换为整数
return (kiValue / (1024 * 1024)).toFixed(2) + 'G'; // 转换为G并保留两位小数
};
const handleCurrentChange = (val) => {
console.log(`current page: ${val}`)
currentPage.value = val
localStorage.setItem('nodecurrentPage', val); // 将当前页保存在 localStorage
nodeParameters.page_number = val
getNodeData()
}
const viewYAML = (row) => {
console.log('显示YAML信息', row);
// 这里可以编写显示YAML信息的逻辑
// 假设提供了 `getYAMLContent` 方法来获取 YAML 内容
// 直接使用传入的 row 作为 YAML 内容
getNodeDetailData(row.nodeName)
};
const viewDetails = (row) => {
console.log('显示详情', row);
// 这里可以编写显示详情信息的逻辑
};
const formatDateTime = (dateTimeString) => {
return dateTimeString.replace('T', ' ').replace('Z', '');
};
// 示例数据和方法
const searchText = ref('');
const tableData = ref([
{
},
// ...其他数据项
]);
const search = async () => {
console.log('执行搜索:', searchText.value);
const searchParameters = reactive({
page_size: pageSize,
page_number: currentPage,
filterName: searchText.value,
isMaster: 3
})
try {
const resp = await NODE_LIST(searchParameters)
if (resp && resp.data && Array.isArray(resp.data.Items)) {
tableData.value = resp.data.Items.map((item) => ({
nodeName: item.metadata.name,
hostName: item.metadata.labels['kubernetes.io/hostname'],
Specifications: item.status.capacity.cpu + "核" + kiToG(item.status.capacity.memory),
PodCIDR: item.spec.podCIDR,
version: item.status.nodeInfo.kubeletVersion,
createTime: formatDateTime(item.metadata.creationTimestamp),
Operation: "hha",
// 其他属性...
}));
}
console.log(resp)
}catch (e){
console.log(e)
}
};
const refresh = () => {
console.log('刷新表格数据');
};
const nodeParameters = reactive({
page_size: pageSize,
page_number: currentPage,
isMaster: 3,
})
var nodeCount = ref(0)
const getNodeData = async ()=>{
try {
const resp = await NODE_LIST(nodeParameters)
if (resp && resp.data && Array.isArray(resp.data.Items)) {
tableData.value = resp.data.Items.map((item) => ({
nodeName: item.metadata.name,
hostName: item.metadata.labels['kubernetes.io/hostname'],
Specifications: item.status.capacity.cpu + "核" + kiToG(item.status.capacity.memory),
PodCIDR: item.spec.podCIDR,
version: item.status.nodeInfo.kubeletVersion,
createTime: formatDateTime(item.metadata.creationTimestamp),
Operation: "hha",
// 其他属性...
}));
}
nodeCount.value = resp.data.total
console.log(resp)
// for (const respKey of resp.data.Items) {
// console.log(respKey)
//
// }
}catch (e){
console.log(e)
}
}
const getNodeDetailData = async (nodeName)=>{
try {
console.log(nodeName)
const params = reactive({
nodeName: nodeName,
})
const resp = await NODE_DETAIL_LIST(params)
console.log("0000000000:",resp)
console.log("yaml =======",json2yaml.stringify(resp.data))
yamlContent.value = json2yaml.stringify(resp.data); // 确保将数据赋值给 yamlContent
await nextTick()
dialogVisible.value = true;
}catch (e){
console.log(e)
}
}
const getNodeAllData = async ()=>{
const nodeAllParameters = reactive({
page_size: 100000,
page_number: 1,
isMaster: 3,
})
try {
const resp = await NODE_LIST(nodeAllParameters)
console.log(resp)
nodeCount.value = resp.data.total
// for (const respKey of resp.data.Items) {
// console.log(respKey)
//
// }
}catch (e){
console.log(e)
}
}
onBeforeMount( ()=> {
const savedPageSize = localStorage.getItem('nodepageSize');
const savedCurrentPage = localStorage.getItem('nodecurrentPage');
if (savedPageSize && !isNaN(savedPageSize)) {
pageSize.value = Number(savedPageSize);
}
if (savedCurrentPage && !isNaN(savedCurrentPage)) {
currentPage.value = Number(savedCurrentPage);
}
getNodeData()
});
</script>
<style scoped>
.container {
margin: 10px;
}
.header {
display: flex;
align-items: center;
justify-content: space-between; /* 添加此属性对子元素进行分散对齐 */
margin-bottom: 0px;
gap: 10px;
padding: 10px;
background: #FFF;
border: 2px solid #ebeef5;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.search-input {
/*flex-grow: 1;*/
width: 200px;
}
.table-wrapper {
background: #FFF;
border: 2px solid #ebeef5; /* 浅色边框线 */
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.paginations {
margin-top: 10px;
margin-left: 20px;
margin-bottom: 10px;
}
/* 左侧组合样式,创建按钮和搜索框靠在一起 */
.left-group {
display: flex;
align-items: center;
gap: 10px; /* You can adjust the gap as needed */
}
/* 右侧刷新按钮 */
.right-group {
display: flex;
align-items: center;
}
.yaml-content {
background-color: #f5f5f5;
border-left: 3px solid #4795EE;
padding: 15px;
white-space: pre-wrap;
text-align: left;
margin: 20px 0;
overflow-x: auto;
}
.dialog-footer {
text-align: right;
}
</style>
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 J.のblog!
评论