k8s管理系统项目前端-loadbalance
k8s管理系统项目前端-loadbalance
一、ingress开发
<template>
<!-- 新的 div 容器,使用flex布局对齐内容 -->
<div class="deployment-header" style="flex: 1">
<div class="left-section">
<span>命名空间:</span>
<el-select v-model="selectedNamespace" placeholder="请选择">
<el-option
v-for="namespace in namespaces"
:key="namespace"
:label="namespace"
:value="namespace">
</el-option>
</el-select>
</div>
<!-- 移动的刷新按钮,保留在右侧 -->
<div class="right-group">
<el-button @click="refresh">
<el-icon><Refresh /></el-icon>
<span> 刷新 </span>
</el-button>
</div>
</div>
<div class="container">
<!-- 第一部分: 头部 -->
<div class="header shadow">
<div class="left-group">
<el-button type="primary" @click="openCreateDrawer">
<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>
<!-- 第二部分: 表格 -->
<div class="table-wrapper shadow">
<el-table :data="tableData" style="width: 100%" >
<el-table-column prop="name" label="Ingress名" align="center">
<template #default="{ row }">
<div>
<span style="vertical-align: middle; color: #1395FF; cursor: pointer;" @click="handleRowClick(row)">
{{ row.name }}
</span>
</div>
</template>
</el-table-column>
<el-table-column prop="Labels" label="标签" 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="Host" label="Host" align="center">
<template #default="{ row }">
<div>
<el-tag class="ml-2" type="success">{{ row.Host }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="Path" label="Path" align="center">
<template #default="{ row }">
<div>
<el-tag class="ml-2" type="success">{{ row.Path }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="External_ip" label="EXTERNAL_IP" align="center" >
<template #default="{ row }">
<div style="display: flex; align-items: center;justify-content: center;height: 100%">
{{ row.External_ip }}
</div>
</template>
</el-table-column>
<el-table-column prop="TLS" label="TLS" align="center">
<template #default="{ row }">
<div style="display: flex; align-items: center;justify-content: center;height: 100%">
{{ row.TLS }}
</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 }">
<div class="button-group">
<el-button size="small" color="#5AABFF" :dark="isDark" plain @click="viewYAML(row)">
<el-icon><EditPen /></el-icon>
<span style="vertical-align: middle"> YAML </span>
</el-button>
<el-button size="small" color="#F58D79" :dark="isDark" plain @click="confirmDelete(row)">
<el-icon><Tickets /></el-icon>
<span style="vertical-align: middle"> 删除 </span>
</el-button>
</div>
</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=deploymentCount
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="updateDeploy()">更 新</el-button>
</span>
</template>
</el-dialog>
<!-- 对话框 -->
<el-dialog
title="副本数调整"
v-model="scaleDialogVisible"
width="30%"
>
<div style="text-align: center; padding: 20px;">
<label>实例数:</label>
<el-input-number v-model="replicaCount" :min="1" label="副本数:" />
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="scaleDialogVisible = false">取消</el-button>
<el-button type="primary" @click="updateReplicas">更新</el-button>
</span>
</el-dialog>
</div>
<el-drawer
title="创建Service"
v-model="createDrawer"
direction="rtl"
>
<h3 style="margin-right: 10px;margin-bottom: 10px;">Service</h3>
<div>
<el-form ref="formRef" :model="formData" :rules="formRules">
<div style="display: flex; align-items: center; margin-bottom: 20px;">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">名称:</span>
<el-form-item label="" prop="name">
<el-input v-model="formData.name" placeholder="请输入内容" style="width: 250px"></el-input>
</el-form-item>
</div>
<el-form-item label="" prop="namespace">
<div style="display: flex; align-items: center;margin-bottom: 20px">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">命名空间:</span>
<el-select v-model="formData.namespace" placeholder="请选择" style="width: 150px;">
<el-option
v-for="namespace in serviceNamespaces"
:key="namespace"
:label="namespace"
:value="namespace">
</el-option>
</el-select>
</div>
</el-form-item>
<el-form-item label="" prop="type">
<div style="display: flex; align-items: center;margin-bottom: 20px">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">类型:</span>
<el-select v-model="formData.type" placeholder="请选择" style="width: 150px;">
<el-option
v-for="type in serviceType"
:key="type"
:label="type"
:value="type">
</el-option>
</el-select>
</div>
</el-form-item>
<div style="display: flex; align-items: center; margin-bottom: 20px;">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">域名:</span>
<el-form-item label="" prop="domain">
<el-input v-model="formData.domain" placeholder="请输入内容" style="width: 250px"></el-input>
</el-form-item>
</div>
<div style="display: flex; align-items: center; margin-bottom: 20px;">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">Path:</span>
<el-form-item label="" prop="path">
<el-input v-model="formData.path" placeholder="请输入内容" style="width: 250px"></el-input>
</el-form-item>
</div>
<div style="display: flex; align-items: center; margin-bottom: 20px;">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">Service端口:</span>
<el-form-item label="" prop="port">
<el-input v-model="formData.port" placeholder="请输入内容" style="width: 250px"></el-input>
</el-form-item>
</div>
<div style="display: flex; align-items: center; margin-bottom: 20px;">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">Service名:</span>
<el-form-item label="" prop="serviceName">
<el-input v-model="formData.serviceName" placeholder="请输入内容" style="width: 250px"></el-input>
</el-form-item>
</div>
<div style="display: flex; align-items: center; margin-bottom: 20px;">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">标签:</span>
<el-form-item label="" prop="label">
<el-input v-model="formData.label" placeholder="请输入内容" style="width: 250px"></el-input>
</el-form-item>
</div>
</el-form>
<!-- 更多输入项 -->
</div>
<template #footer>
<el-button @click="closeDrawer">取消</el-button>
<el-button type="primary" @click="validateForm">创建</el-button>
</template>
</el-drawer>
</template>
<script setup>
import {nextTick, onBeforeMount, onBeforeUnmount, reactive, ref, watch} from 'vue';
import { Loading } from '@element-plus/icons-vue';
import 'element-plus/dist/index.css';
import { Refresh, EditPen, Search } from '@element-plus/icons-vue';
import { onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ArrowLeft } from '@element-plus/icons-vue';
// 在模板中可以直接使用 Refresh, EditPen, 和 Search
import {
CREATE_DEPLOYMENT, CREATE_INGRESS,
CREATE_NAMESPACE,
CREATE_SERVICE,
DELETE_DEPLOYMENT, DELETE_INGRESS,
DELETE_NAMESPACE,
DELETE_SERVICE,
DEPLOYMENT_DETAIL,
DEPLOYMENT_LIST,
INGRESS_DETAIL,
INGRESS_LIST,
NAMESPACE_DETAIL,
NAMESPACE_LIST,
RESTART_DEPLOYMENT,
SERVICE_DETAIL,
SERVICE_LIST,
UPDATE_DEPLOYMENT,
UPDATE_DEPLOYMENTSCALE,
UPDATE_INGRESS,
UPDATE_NAMESPACE,
UPDATE_SERVICE
} 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 _, {parseInt} 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";
import {value} from "lodash/seq.js";
const extensions = [javascript(),oneDark]
const formRef = ref(null);
const formData = ref({
name: '',
namespace:'',
type:'',
port:'',
label:'',
path:'',
domain:'',
serviceName:'',
// 初始化更多的表单数据
});
const validateForm = async () => {
try {
await formRef.value.validate();
console.log(serviceType.value)
console.log(formData.value)
createService()
} catch (error) {
ElMessage.error('表单验证失败,请检查输入');
}
};
const closeDrawer = () => {
createDrawer.value = false
};
const formRules = {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
serviceName: [
{ required: true, message: '请输入Service名称', trigger: 'blur' }
],
port: [
{ required: true, message: '请输入service端口', trigger: 'change' }
],
domain: [
{ required: true, message: '请输入域名', trigger: 'change' }
],
path: [
{ required: true, message: '请输入PATH', trigger: 'change' }
],
label: [
{ required: true, message: '请输入标签', trigger: 'blur' }
],
// 其他校验规则
};
const dialogSelectedNamespace = ref(''); // 对话框的v-model
const scaleDialogVisible= ref(false)
const replicaCount = ref(1)
const currentRow = ref(null)
const serviceNamespaces = ref([]); // 用于存储命名空间列表
const serviceType = ref(['Prefix','Exact','ImplementationSpecific']); // 用于存储命名空间列表
const openScaleDiglog = (row)=>{
replicaCount.value = row.Replicas
currentRow.value = row
scaleDialogVisible.value = true
}
const createDrawer = ref(false)
const openCreateDrawer = ()=>{
createDrawer.value = true
}
// 从localStorage加载保存的命名空间
const loadSelection = () => {
selectedNamespace.value = localStorage.getItem('selectedNamespace') || '';
};
// 保存当前选中的命名空间到localStorage
const saveSelection = (value) => {
localStorage.setItem('selectedNamespace', value);
};
const updateReplicas = async ()=> {
const updateScale = reactive(
{
namespace: selectedNamespace,
deploymentName: currentRow.value.deploymentName,
scaleNum: replicaCount.value
}
)
console.log("更新副本数至:", replicaCount.value);
try {
const resp = await UPDATE_DEPLOYMENTSCALE(updateScale);
console.log(resp)
getDeployData()
} catch (e) {
console.error(e);
return []; // 出错时返回空数组
}
// 在这里编写更新逻辑
scaleDialogVisible.value = false; // 关闭对话框
}
const handleRowClick = (row) => {
router.push({
name: 'Pod',
params: { type:'deployment',deploymentName: row.deploymentName ,namespace: selectedNamespace.value}
});
};
const createDialogVisible = ref(false);
const createForm = ref({
name: '',
description: '',
labels: [{ key: 'apps', value: '' }],
namespace: '',
replicas: '',
});
const rules = {
name: [
{ required: true, message: '请输入deployment名', trigger: 'blur' },
// 此处添加其他验证规则
],
description: [
{ required: true, message: '请输入描述', trigger: 'blur' },
// 此处添加其他验证规则
],
namespace: [
{ required: true, message: '请选择命名空间', trigger: 'change' },
],
};
const formItems = ref([
{ id: Date.now(), key: 'apps', value: '' } // 初始标签项,第一项是不可删除的
]);
const addFormItem = () => {
const newItem = { id: Date.now(), key: '', value: '', editable: true };
formItems.value.unshift(newItem);
console.log(formItems.value)
}
const changeReplicas = (value) => {
console.log(value)
}
const removeFormItem = (index) => {
const reversedIndex = formItems.value.length - 1 - index; // 反转后的索引
console.log("reverse = ",reversedIndex)
console.log(formItems.value[index])
console.log("no ",index)
if (reversedIndex !== 0) { // 确保不删除第一行标签项
formItems.value.splice(index,1)
}
console.log(formItems.value)
};
const instanceItems = ref([]);
const addInstanceItem = () => {
// 折叠所有现有的实例内容器
instanceItems.value.forEach(item => {
item.collapsed = true;
});
const id = generateUniqueId();
const newItem = {
id: id,
name: '',
image: '',
image_tag: '',
livenessCommand:'',
dialogSelectedNamespace:'',
livenessStartDelay:'',
// 存活检查响应延时时间
livenessResDelay:'',
// 存活检查间隔时间
livenessIntervalTime:'',
// 存活检查健康阈值
livenessHealththreshold:'',
// 存活检查不健康阈值
livenessUnHealththreshold:'',
livenessselectedCheckMethod:'http',
lives_selectedProtocol:'HTTP',
// 就绪检查的检查方法
ReadinessSelectedCheckMethod:'http',
// 是否开启就绪检查
isReadinessCheckActive: '',
// 是否开启存活检查
isLivenessCheckActive:'',
// 就绪检查 检查的协议
readnessSelectedProtocol:'',
// 就绪检查 检查的端口号
readnessHealthCheckPort:'80',
// 就绪检查的路径
readnessHealthCheckPath:'',
// 就绪检查TCP方式的端口号
readnessTcpCheckPort:'',
// 就绪检查执行的命令
readnessCommand:'',
// 就绪检查启动延时时间
readnessStartDelay:'',
// 就绪检查响应时间
readnessResDelay:'',
// 就绪检查间隔时间
readnessIntervalTime:'',
// 就绪检查健康阈值
readnessHealththreshold:'',
// 就绪检查不健康阈值
readnessUnHealththreshold:'',
replicas:'',
lives_tcpCheckPort: '',
lives_healthCheckPath:'/',
lives_healthCheckPort:'',
workspace: '',
args: '',
cmd: '',
cpu_request:'250',
cpu_limit:'500',
memory_request:'256',
memory_limit:'1024',
value: '',
collapsed: false,
editMode: true
};
instanceItems.value.push(newItem);
};
// 函数用来把健康检查类型转换为相应的数字
const mapHealthCheckType = (type) => {
console.log(type,"aaaaaaaaaaaaaaaaaaaaaaaaaaaa")
switch (type) {
case 'http':
return 0;
case 'tcp':
return 1;
case 'exec':
return 2;
default:
return -1; // 或者任何合适的默认值
}
};
const createService = async ()=> {
// 初始化labelMap
const labelMap = {};
if (formData.value.label) {
formData.value.label.split(',').forEach(pair => {
const [key, value] = pair.split('=').map(part => part.trim());
labelMap[key] = value;
});
}
const createParameters = ref({
labels: labelMap,
port: parseInt(formData.value.port,10),
rules: [{
host: formData.value.domain,
paths: [{
path: formData.value.path,
pathType: formData.value.type,
serviceName: formData.value.serviceName,
servicePort: parseInt(formData.value.port,10)
}]
}],
name: formData.value.name,
namespace: formData.value.namespace
});
console.log(createParameters.value,'nmmmmm')
// 发送 createParameters.value 到后端...
try {
const resps = await CREATE_INGRESS(createParameters.value);
console.log(resps,'fuckyou')
ElMessage.success("创建成功")
createDrawer.value = false
getDeployData()
} catch (e) {
console.error(e);
return []; // 出错时返回空数组
}
}
// 调用 goToCreateDeployment 函数以响应对话框的打开
watch(createDialogVisible, (newVal) => {
if (newVal) {
goToCreateDeployment();
}
});
const generateUniqueId = () => {
// 生成唯一标识符的逻辑
const timestamp = new Date().getTime();
const randomSuffix = Math.floor(Math.random() * 1000);
return `${timestamp}-${randomSuffix}`;
};
const expandInstanceItem = (index) => {
instanceItems.value[index].editMode = true;
instanceItems.value[index].collapsed = false;
};
const saveInstanceItem = (index) => {
instanceItems.value[index].editMode = false;
instanceItems.value[index].collapsed = true;
};
const removeInstanceItem = (index) => {
if (instanceItems.value.length > 1) {
instanceItems.value.splice(index, 1);
}};
const onSubmit = () => {
const formEl = ref(null);
formEl.value.validate((valid) => {
if (valid) {
// 提交表单
} else {
console.log('表单验证失败');
return 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 currentDeployName = ref('');
let yamlContent = ref('');
// 新增命名空间列表和选中的命名空间
const namespaces = ref([]); // 用于存储命名空间列表
const selectedNamespace = ref(localStorage.getItem('selectedNamespace') || ''); // 用于存储用户选择的命名空间
// 方法来获取命名空间列表
// fetchNamespaces 函数使用 getNSData 的返回值来更新命名空间的下拉菜单
const fetchNamespaces = async () => {
namespaces.value = await getNSData();
serviceNamespaces.value = await getNSData()
};
// fetchNamespaces 函数使用 getNSData 的返回值来更新命名空间的下拉菜单
// getNSData 函数获取命名空间并返回名称数组
const getNSData = async () => {
try {
const resp = await NAMESPACE_LIST(namespaceAllParameters);
if (resp && resp.code === 200 && resp.data && Array.isArray(resp.data.Items)) {
// 只返回命名空间的名称数组
return resp.data.Items.map(ns => ns.metadata.name);
} else {
throw new Error('无法获取数据或数据格式不正确');
}
} catch (e) {
console.error(e);
return []; // 出错时返回空数组
}
};
const namespaceAllParameters = reactive({
page_size: 100000,
page_number: currentPage,
})
const formatImages = (images) => {
return images.split('\n').map(image => `<div>${image}</div>`).join('');
};
const goToCreateDeployment = () => {
// dialogSelectedNamespace.value = selectedNamespace.value;
// createDialogVisible.value = true;
// // 清空现有的实例内容器并添加一个新的实例内容器
// instanceItems.value = [];
// addInstanceItem();
};
//编辑器配置
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 scaleDeployment = (row) => {
console.log(row.deploymentName)
console.log(selectedNamespace)
}
const restartDeployment = async (row) =>{
ElMessageBox.confirm(
`确定要重启Deployment "${row.deploymentName}" 吗?`,
'警告',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
).then(async () => {
const restarParameters = reactive({
deploymentName: row.deploymentName,
namespace: selectedNamespace
})
try {
const resp = await RESTART_DEPLOYMENT(restarParameters)
console.log("————————————————",resp)
ElMessage({
type: 'success',
message: `Deployment "${row.deploymentName}" 已重启`
});
getDeployData()
}catch (e){
console.log(e)
ElMessage.error(`重启失败: ${e}`);
}
}).catch(() => {
});
}
const handleSizeChange = (val) => {
console.log(`${val} items per page`)
namespaceAllParameters.page_size = val
localStorage.setItem('servicepageSize',val)
getDeployData()
}
const handleInput = _.debounce(async () => {
await search();
}, 500); // 使用 lodash 的 debounce 函数来防抖,防止搜索操作太频繁
const updateDeploy = async () =>{
try {
const updatedJson = yaml2obj.load(yamlContent.value)
const jsonString = JSON.stringify(updatedJson);
updateParameters.deploymentName = currentDeployName.value
updateParameters.content = jsonString
await updateDeployData()
await getDeployData()
}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('servicecurrentPage', val); // 将当前页保存在 localStorage
getDeployData()
}
const viewYAML = (row) => {
console.log('显示YAML信息', row.name);
// 这里可以编写显示YAML信息的逻辑
// 假设提供了 `getYAMLContent` 方法来获取 YAML 内容
// 直接使用传入的 row 作为 YAML 内容
getDeployDetailData(row.name)
};
const confirmDelete = (row) => {
ElMessageBox.confirm(
`确定要删除Ingress "${row.name}" 吗?`,
'警告',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
).then(async () => {
const deleteNSParameters = ref({
name: row.name,
namespace: selectedNamespace.value,
})
console.log(deleteNSParameters.value)
try {
const resp = await DELETE_INGRESS(deleteNSParameters.value)
console.log("————————————————",resp)
ElMessage({
type: 'success',
message: `Ingress "${row.name}" 已被删除`
});
getDeployData()
}catch (e){
console.log(e)
ElMessage.error(`删除失败: ${e}`);
}
}).catch(() => {
ElMessage({
type: 'info',
message: '已取消删除'
});
});
};
const formatDateTime = (dateTimeString) => {
return dateTimeString.replace('T', ' ').replace('Z', '');
};
// 示例数据和方法
const searchText = ref('');
const tableData = ref([
{
name: "",
Labels: '',
Host:'',
Path: '',
External_ip: '',
TLS:'',
createTime:"",
Operation:"",
},
// ...其他数据项
]);
const search = async () => {
console.log('执行搜索:', searchText.value);
const searchParameters = reactive({
page_size: pageSize,
page_number: currentPage,
filterName: searchText.value,
namespace: selectedNamespace
})
try {
const resp = await INGRESS_LIST(searchParameters);
if (resp && resp.data && Array.isArray(resp.data.items)) {
tableData.value = resp.data.items.map((item) => {
// 提取规则中的第一个规则和路径,这可能需要根据您的实际需要调整
const firstRule = item.spec.rules && item.spec.rules[0];
const firstPath = firstRule &&
firstRule.http &&
firstRule.http.paths &&
firstRule.http.paths[0];
const ip = item.status.loadBalancer.ingress && item.status.loadBalancer.ingress[0] && item.status.loadBalancer.ingress[0].ip;
return {
name: item.metadata.name,
Labels: formatLabels(item.metadata.labels), // 假设您已定义formatLabels函数
Host: firstRule ? firstRule.host : '', // 使用第一个规则的主机名
External_ip: ip || '', // 使用负载均衡器的IP地址
Path: firstPath ? firstPath.path : '', // 使用第一个路径的路径
TLS: '', // TLS信息需要从其他部分提取,这里留空
createTime: formatDateTime(item.metadata.creationTimestamp), // 假设您已定义formatDateTime函数
// 其他属性...
};
});
}
// 更新部署计数
deploymentCount.value = resp.data.count;
} catch (e) {
console.error(e);
// 处理错误情况,可能要向用户显示错误信息或日志输出
}
};
const refresh = () => {
getDeployData()
console.log('刷新表格数据');
};
const updateParameters = reactive({
name: "",
namespace: selectedNamespace,
content: ""
})
const updateDeployData = async ()=>{
try {
console.log("你吗的",updateParameters.content)
const resp = await UPDATE_INGRESS(updateParameters)
if (resp.code !== 200){
ElMessage.error(resp.message)
return
}
ElMessage.success("更新成功")
dialogVisible.value = false
}catch (e){
console.log(e)
ElMessage.error(`${e}`)
}
}
const deploymentParameters = reactive({
page_size: pageSize,
page_number: currentPage,
namespace: selectedNamespace,
})
// 设置watcher来监听selectedNamespace的改变
watch(selectedNamespace, (newNamespace) => {
// 更新deploymentParameters中的namespace
deploymentParameters.namespace = newNamespace;
// 重新调用getDeployData以获取新命名空间的部署数据
getDeployData();
});
var deploymentCount = ref(0)
// 在这里创建一个方法来将 Labels 对象转换为一个字符串数组
// 格式化标签为字符串数组
const formatLabels = (labelsMap) => {
return labelsMap ? Object.entries(labelsMap).map(([key, value]) => `${key}: ${value}`) : [];
};
const systemNamespaces = ['default', 'kube-system', 'kube-public', 'kube-node-lease'];
const getDeployData = async () => {
try {
const resp = await INGRESS_LIST(deploymentParameters);
if (resp && resp.data && Array.isArray(resp.data.items)) {
tableData.value = resp.data.items.map((item) => {
// 提取规则中的第一个规则和路径,这可能需要根据您的实际需要调整
const firstRule = item.spec.rules && item.spec.rules[0];
const firstPath = firstRule &&
firstRule.http &&
firstRule.http.paths &&
firstRule.http.paths[0];
const ip = item.status.loadBalancer.ingress && item.status.loadBalancer.ingress[0] && item.status.loadBalancer.ingress[0].ip;
return {
name: item.metadata.name,
Labels: formatLabels(item.metadata.labels), // 假设您已定义formatLabels函数
Host: firstRule ? firstRule.host : '', // 使用第一个规则的主机名
External_ip: ip || '', // 使用负载均衡器的IP地址
Path: firstPath ? firstPath.path : '', // 使用第一个路径的路径
TLS: '', // TLS信息需要从其他部分提取,这里留空
createTime: formatDateTime(item.metadata.creationTimestamp), // 假设您已定义formatDateTime函数
// 其他属性...
};
});
}
// 更新部署计数
deploymentCount.value = resp.data.count;
} catch (e) {
console.error(e);
// 处理错误情况,可能要向用户显示错误信息或日志输出
}
};
const getDeployDetailData = async (serviceName)=>{
try {
const DeploymentDetailparams = reactive({
name: serviceName,
namespace: selectedNamespace.value
})
console.log("ddddddddd",DeploymentDetailparams)
const resp = await INGRESS_DETAIL(DeploymentDetailparams)
console.log("cccccccc",resp)
console.log("yaml =======",json2yaml.stringify(resp.data))
yamlContent.value = json2yaml.stringify(resp.data); // 确保将数据赋值给 yamlContent
currentDeployName.value = resp.data.metadata.name
updateParameters.content = yamlContent.value
await nextTick()
dialogVisible.value = true;
}catch (e){
console.log(e)
console.log("你吗")
}
}
// 在setup函数中定义定时器
let refreshInterval = null;
onMounted(() => {
loadSelection();
refreshInterval = setInterval(() => {
getDeployData();
}, 30000); // 每30秒刷新一次
});
// 组件卸载前保存命名空间
onBeforeUnmount(() => {
saveSelection(selectedNamespace.value);
});
onBeforeMount( async ()=> {
clearInterval(refreshInterval);
// 尝试从 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);
}
getDeployData()
await fetchNamespaces(); // 获取并更新命名空间列表
// getNSAllData()
});
</script>
<style scoped>
.loading-icon {
width: 20px; /* 或者您希望的任何尺寸 */
height: 20px; /* 保持与宽度相同以保持图标的纵横比 */
margin-left: 5px; /* 添加一点空间在状态文本和图标之间 */
animation: loading-spin 2s linear infinite;
}
/* 如果需要,可以移除这个类,因为它可能会导致对齐问题 */
/* .loader {
animation: loader-spin 1s linear infinite;
} */
@keyframes loading-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.container {
margin: 10px;
background-color: #F2F2F2;
}
.deployment-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
margin: 10px;
margin-bottom: -10px;
background: #FFF;
border: 2px solid #ebeef5;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
/* 新增左侧部分样式,放置“命名空间:”文本和下拉框 */
.left-section {
display: flex;
align-items: center;
}
/* 确保下拉框和文本在同一行 */
.el-select {
margin-left: 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;
}
.deployment-header {
display: flex;
align-items: center;
height: 30px;
padding: 10px;
margin: 10px;
margin-bottom: -10px;
background: #FFF;
border: 2px solid #ebeef5;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.form-item-with-help {
margin-top: 10px;
margin-left: 20px;
}
.form-item-with-resource {
margin-top: 10px;
margin-left: 20px;
}
.form-item {
flex-wrap: wrap; /* 确保换行 */
/*align-items: center; !* 如果需要垂直居中 *!*/
margin-bottom: 10px; /* 保持垂直间距 */
}
.input-item {
width: 200px;
margin-right: 10px;
}
.help-text {
display: block; /* 保证帮助文本在新的行显示 */
font-size: 12px;
width: 1000px;
color: #999999;
margin-top: 5px;
}
.input-with-help {
margin-top: 20px;
display: flex;
flex-direction: column;
}
.input-deployname {
width: 300px; /* 限制这个div的宽度 */
display: flex;
flex-direction: column; /* 让子元素垂直排列 */
}
.input-containername {
width: 250px; /* 限制这个div的宽度 */
display: flex;
margin-left: -50px;
flex-direction: column; /* 让子元素垂直排列 */
}
.add-button {
margin-top: -20px; /* 添加按钮与输入行之间的空间 */
margin-left: 200px;
}
.input-container-cpu {
display: flex;
flex-direction: column;
/*padding-left: -20px; !* 往右移动整个容器 *!*/
}
.cpu-label {
margin-bottom: 5px; /* 调整为所需的间距 */
margin-left: -50px;
}
.container {
margin: 10px;
}
.item-container {
margin-bottom: 10px;
}
.content-container {
background-color: #F2F2F2;
padding: 10px;
display: flex;
width: 700px;
justify-content: space-between;
align-items: center;
transition: height 0.3s;
overflow: hidden;
position: relative;
}
.content-container.collapsed {
height: 40px;
}
.info {
display: flex;
align-items: center;
}
.actions {
position: absolute;
right: 10px; /* 调整为需要的值 */
top: 10px; /* 调整为需要的值 */
}
.input-row {
display: flex;
align-items: center;
gap: 10px;
/*margin-left: -50px;*/
}
.input-row .el-input {
flex: 1;
}
.input-row label {
margin-left: 10px; /* 或者需要的任何值 */
}
.input-row .el-input .el-input__inner {
width: 100%; /* 让输入框充满其容器 */
}
.input-containername p {
margin-bottom: 10px; /* 为“CPU:”下方添加一些间隔 */
}
.form-item-resource {
margin-left: 20px; /* 整个表单项向右移动 20px */
margin-top: 10px;
}
.custom-form-item {
height: auto;
overflow: auto;
}
.check-method-container,
.execution-command-container {
display: flex;
align-items: center;
margin-bottom: 10px; /* 添加一些垂直间距 */
}
.method-label {
width: 100px;
margin: 10px;
margin-right: 20px; /* 文本与选择框/输入框之间的间隔 */
}
.command-label {
/*margin-right: 20px; !* 文本与输入框之间的间隔 *!*/
width: 100px;
margin: 10px;
}
.livenessdiv {
background-color: white;
width: 500px;
}
.port-range {
margin-left: 10px;
}
.input-healthContainer {
margin-left: -50px;
}
.el-select,
.el-input {
/*flex-grow: 1; !* 输入框和选择框将占据剩余空间 *!*/
max-width: calc(100% - 100px); /* 限制最大宽度,这里的100px是示例,可以根据实际情况调整 */
}
.button-group {
display: flex;
justify-content: center;
align-items: center;
gap: 10px; /* 调整按钮之间的间隔 */
}
</style>
二、service开发
<template>
<!-- 新的 div 容器,使用flex布局对齐内容 -->
<div class="deployment-header" style="flex: 1">
<div class="left-section">
<span>命名空间:</span>
<el-select v-model="selectedNamespace" placeholder="请选择">
<el-option
v-for="namespace in namespaces"
:key="namespace"
:label="namespace"
:value="namespace">
</el-option>
</el-select>
</div>
<!-- 移动的刷新按钮,保留在右侧 -->
<div class="right-group">
<el-button @click="refresh">
<el-icon><Refresh /></el-icon>
<span> 刷新 </span>
</el-button>
</div>
</div>
<div class="container">
<!-- 第一部分: 头部 -->
<div class="header shadow">
<div class="left-group">
<el-button type="primary" @click="openCreateDrawer">
<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>
<!-- 第二部分: 表格 -->
<div class="table-wrapper shadow">
<el-table :data="tableData" style="width: 100%" >
<el-table-column prop="name" label="service名" align="center">
<template #default="{ row }">
<div>
<span style="vertical-align: middle; color: #1395FF; cursor: pointer;" @click="handleRowClick(row)">
{{ row.name }}
</span>
</div>
</template>
</el-table-column>
<el-table-column prop="Labels" label="标签" 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="Type" label="类型" align="center">
<template #default="{ row }">
<div>
<el-tag class="ml-2" type="success">{{ row.Type }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="Cluster_ip" label="CLUSTER_IP" align="center" >
<template #default="{ row }">
<div style="display: flex; align-items: center;justify-content: center;height: 100%">
{{ row.Cluster_ip }}
</div>
</template>
</el-table-column>
<el-table-column prop="External_ip" label="EXTERNAL_IP" align="center" >
<template #default="{ row }">
<div style="display: flex; align-items: center;justify-content: center;height: 100%">
{{ row.External_ip }}
</div>
</template>
</el-table-column>
<el-table-column prop="port" label="端口" align="center">
<template #default="{ row }">
<div style="display: flex; align-items: center;justify-content: center;height: 100%">
{{ row.port }}
</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 }">
<div class="button-group">
<el-button size="small" color="#5AABFF" :dark="isDark" plain @click="viewYAML(row)">
<el-icon><EditPen /></el-icon>
<span style="vertical-align: middle"> YAML </span>
</el-button>
<el-button size="small" color="#F58D79" :dark="isDark" plain @click="confirmDelete(row)">
<el-icon><Tickets /></el-icon>
<span style="vertical-align: middle"> 删除 </span>
</el-button>
</div>
</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=deploymentCount
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="updateDeploy()">更 新</el-button>
</span>
</template>
</el-dialog>
<!-- 对话框 -->
<el-dialog
title="副本数调整"
v-model="scaleDialogVisible"
width="30%"
>
<div style="text-align: center; padding: 20px;">
<label>实例数:</label>
<el-input-number v-model="replicaCount" :min="1" label="副本数:" />
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="scaleDialogVisible = false">取消</el-button>
<el-button type="primary" @click="updateReplicas">更新</el-button>
</span>
</el-dialog>
</div>
<el-drawer
title="创建Service"
v-model="createDrawer"
direction="rtl"
>
<h3 style="margin-right: 10px;margin-bottom: 10px;">Service</h3>
<div>
<el-form ref="formRef" :model="formData" :rules="formRules">
<div style="display: flex; align-items: center; margin-bottom: 20px;">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">名称:</span>
<el-form-item label="" prop="serviceName">
<el-input v-model="formData.name" placeholder="请输入内容" style="width: 250px"></el-input>
</el-form-item>
</div>
<el-form-item label="" prop="namespace">
<div style="display: flex; align-items: center;margin-bottom: 20px">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">命名空间:</span>
<el-select v-model="formData.namespace" placeholder="请选择" style="width: 150px;">
<el-option
v-for="namespace in serviceNamespaces"
:key="namespace"
:label="namespace"
:value="namespace">
</el-option>
</el-select>
</div>
</el-form-item>
<el-form-item label="" prop="type">
<div style="display: flex; align-items: center;margin-bottom: 20px">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">类型:</span>
<el-select v-model="formData.type" placeholder="请选择" style="width: 150px;">
<el-option
v-for="type in serviceType"
:key="type"
:label="type"
:value="type">
</el-option>
</el-select>
</div>
</el-form-item>
<div style="display: flex; align-items: center; margin-bottom: 20px;">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">容器端口:</span>
<el-form-item label="" prop="targetPort">
<el-input v-model="formData.targetPort" placeholder="请输入内容" style="width: 250px"></el-input>
</el-form-item>
</div>
<div style="display: flex; align-items: center; margin-bottom: 20px;">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">Service端口:</span>
<el-form-item label="" prop="port">
<el-input v-model="formData.port" placeholder="请输入内容" style="width: 250px"></el-input>
</el-form-item>
</div>
<div style="display: flex; align-items: center; margin-bottom: 20px;" v-if="formData.type === 'NodePort'">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">NodePort:</span>
<el-form-item label="" prop="nodePort">
<el-input v-model="formData.nodePort" placeholder="请输入内容" style="width: 250px"></el-input>
</el-form-item>
</div>
<div style="display: flex; align-items: center; margin-bottom: 20px;">
<span style="width: 100px; display: inline-block; text-align: right; margin-right: 10px;">标签:</span>
<el-form-item label="" prop="label">
<el-input v-model="formData.label" placeholder="请输入内容" style="width: 250px"></el-input>
</el-form-item>
</div>
</el-form>
<!-- 更多输入项 -->
</div>
<template #footer>
<el-button @click="closeDrawer">取消</el-button>
<el-button type="primary" @click="validateForm">创建</el-button>
</template>
</el-drawer>
</template>
<script setup>
import {nextTick, onBeforeMount, onBeforeUnmount, reactive, ref, watch} from 'vue';
import { Loading } from '@element-plus/icons-vue';
import 'element-plus/dist/index.css';
import { Refresh, EditPen, Search } from '@element-plus/icons-vue';
import { onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ArrowLeft } from '@element-plus/icons-vue';
// 在模板中可以直接使用 Refresh, EditPen, 和 Search
import {
CREATE_DEPLOYMENT,
CREATE_NAMESPACE, CREATE_SERVICE, DELETE_DEPLOYMENT,
DELETE_NAMESPACE, DELETE_SERVICE, DEPLOYMENT_DETAIL, DEPLOYMENT_LIST,
NAMESPACE_DETAIL,
NAMESPACE_LIST, RESTART_DEPLOYMENT, SERVICE_DETAIL, SERVICE_LIST, UPDATE_DEPLOYMENT, UPDATE_DEPLOYMENTSCALE,
UPDATE_NAMESPACE, UPDATE_SERVICE
} 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 _, {parseInt} 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";
import {value} from "lodash/seq.js";
const extensions = [javascript(),oneDark]
const formRef = ref(null);
const formData = ref({
name: '',
namespace:'',
type:'',
port:'',
label:'',
targetPort:'',
nodePort:'',
// 初始化更多的表单数据
});
const validateForm = async () => {
try {
await formRef.value.validate();
console.log(serviceType.value)
console.log(formData.value)
createService()
} catch (error) {
ElMessage.error('表单验证失败,请检查输入');
}
};
const closeDrawer = () => {
createDrawer.value = false
};
const formRules = {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
port: [
{ required: true, message: '请输入service端口', trigger: 'change' }
],
label: [
{ required: true, message: '请输入标签', trigger: 'blur' }
],
targetPort: [
{ required: true, message: '请输入容器端口', trigger: 'blur' }
],
nodePort: [
{ required: true, message: '请输入nodeport', trigger: 'blur' }
],
// 其他校验规则
};
const dialogSelectedNamespace = ref(''); // 对话框的v-model
const scaleDialogVisible= ref(false)
const replicaCount = ref(1)
const currentRow = ref(null)
const serviceNamespaces = ref([]); // 用于存储命名空间列表
const serviceType = ref(['NodePort','ClusterIP']); // 用于存储命名空间列表
const openScaleDiglog = (row)=>{
replicaCount.value = row.Replicas
currentRow.value = row
scaleDialogVisible.value = true
}
const createDrawer = ref(false)
const openCreateDrawer = ()=>{
createDrawer.value = true
}
// 从localStorage加载保存的命名空间
const loadSelection = () => {
selectedNamespace.value = localStorage.getItem('selectedNamespace') || '';
};
// 保存当前选中的命名空间到localStorage
const saveSelection = (value) => {
localStorage.setItem('selectedNamespace', value);
};
const updateReplicas = async ()=> {
const updateScale = reactive(
{
namespace: selectedNamespace,
deploymentName: currentRow.value.deploymentName,
scaleNum: replicaCount.value
}
)
console.log("更新副本数至:", replicaCount.value);
try {
const resp = await UPDATE_DEPLOYMENTSCALE(updateScale);
console.log(resp)
getDeployData()
} catch (e) {
console.error(e);
return []; // 出错时返回空数组
}
// 在这里编写更新逻辑
scaleDialogVisible.value = false; // 关闭对话框
}
const handleRowClick = (row) => {
router.push({
name: 'Pod',
params: { type:'deployment',deploymentName: row.deploymentName ,namespace: selectedNamespace.value}
});
};
const createDialogVisible = ref(false);
const createForm = ref({
name: '',
description: '',
labels: [{ key: 'apps', value: '' }],
namespace: '',
replicas: '',
});
const rules = {
name: [
{ required: true, message: '请输入deployment名', trigger: 'blur' },
// 此处添加其他验证规则
],
description: [
{ required: true, message: '请输入描述', trigger: 'blur' },
// 此处添加其他验证规则
],
namespace: [
{ required: true, message: '请选择命名空间', trigger: 'change' },
],
};
const formItems = ref([
{ id: Date.now(), key: 'apps', value: '' } // 初始标签项,第一项是不可删除的
]);
const addFormItem = () => {
const newItem = { id: Date.now(), key: '', value: '', editable: true };
formItems.value.unshift(newItem);
console.log(formItems.value)
}
const changeReplicas = (value) => {
console.log(value)
}
const removeFormItem = (index) => {
const reversedIndex = formItems.value.length - 1 - index; // 反转后的索引
console.log("reverse = ",reversedIndex)
console.log(formItems.value[index])
console.log("no ",index)
if (reversedIndex !== 0) { // 确保不删除第一行标签项
formItems.value.splice(index,1)
}
console.log(formItems.value)
};
const instanceItems = ref([]);
const addInstanceItem = () => {
// 折叠所有现有的实例内容器
instanceItems.value.forEach(item => {
item.collapsed = true;
});
const id = generateUniqueId();
const newItem = {
id: id,
name: '',
image: '',
image_tag: '',
livenessCommand:'',
dialogSelectedNamespace:'',
livenessStartDelay:'',
// 存活检查响应延时时间
livenessResDelay:'',
// 存活检查间隔时间
livenessIntervalTime:'',
// 存活检查健康阈值
livenessHealththreshold:'',
// 存活检查不健康阈值
livenessUnHealththreshold:'',
livenessselectedCheckMethod:'http',
lives_selectedProtocol:'HTTP',
// 就绪检查的检查方法
ReadinessSelectedCheckMethod:'http',
// 是否开启就绪检查
isReadinessCheckActive: '',
// 是否开启存活检查
isLivenessCheckActive:'',
// 就绪检查 检查的协议
readnessSelectedProtocol:'',
// 就绪检查 检查的端口号
readnessHealthCheckPort:'80',
// 就绪检查的路径
readnessHealthCheckPath:'',
// 就绪检查TCP方式的端口号
readnessTcpCheckPort:'',
// 就绪检查执行的命令
readnessCommand:'',
// 就绪检查启动延时时间
readnessStartDelay:'',
// 就绪检查响应时间
readnessResDelay:'',
// 就绪检查间隔时间
readnessIntervalTime:'',
// 就绪检查健康阈值
readnessHealththreshold:'',
// 就绪检查不健康阈值
readnessUnHealththreshold:'',
replicas:'',
lives_tcpCheckPort: '',
lives_healthCheckPath:'/',
lives_healthCheckPort:'',
workspace: '',
args: '',
cmd: '',
cpu_request:'250',
cpu_limit:'500',
memory_request:'256',
memory_limit:'1024',
value: '',
collapsed: false,
editMode: true
};
instanceItems.value.push(newItem);
};
// 函数用来把健康检查类型转换为相应的数字
const mapHealthCheckType = (type) => {
console.log(type,"aaaaaaaaaaaaaaaaaaaaaaaaaaaa")
switch (type) {
case 'http':
return 0;
case 'tcp':
return 1;
case 'exec':
return 2;
default:
return -1; // 或者任何合适的默认值
}
};
const createService = async ()=> {
let type
if (formData.value.type == 'NodePort'){
type = 1
}else if (formData.value.type == 'ClusterIP'){
type = 0
}
// 初始化labelMap
const labelMap = {};
if (formData.value.label) {
formData.value.label.split(',').forEach(pair => {
const [key, value] = pair.split('=').map(part => part.trim());
labelMap[key] = value;
});
}
const createParameters = ref({
labels: labelMap,
port: parseInt(formData.value.port,10),
targetPort: parseInt(formData.value.targetPort,10),
type: type,
name: formData.value.name,
namespace: formData.value.namespace
});
if (formData.value.type === 'NodePort') {
createParameters.value.nodePort = parseInt(formData.value.nodePort,10);
}
// 发送 createParameters.value 到后端...
try {
const resps = await CREATE_SERVICE(createParameters.value);
console.log(resps,'fuckyou')
ElMessage.success("创建成功")
createDrawer.value = false
getDeployData()
} catch (e) {
console.error(e);
return []; // 出错时返回空数组
}
}
// 调用 goToCreateDeployment 函数以响应对话框的打开
watch(createDialogVisible, (newVal) => {
if (newVal) {
goToCreateDeployment();
}
});
const generateUniqueId = () => {
// 生成唯一标识符的逻辑
const timestamp = new Date().getTime();
const randomSuffix = Math.floor(Math.random() * 1000);
return `${timestamp}-${randomSuffix}`;
};
const expandInstanceItem = (index) => {
instanceItems.value[index].editMode = true;
instanceItems.value[index].collapsed = false;
};
const saveInstanceItem = (index) => {
instanceItems.value[index].editMode = false;
instanceItems.value[index].collapsed = true;
};
const removeInstanceItem = (index) => {
if (instanceItems.value.length > 1) {
instanceItems.value.splice(index, 1);
}};
const onSubmit = () => {
const formEl = ref(null);
formEl.value.validate((valid) => {
if (valid) {
// 提交表单
} else {
console.log('表单验证失败');
return 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 currentDeployName = ref('');
let yamlContent = ref('');
// 新增命名空间列表和选中的命名空间
const namespaces = ref([]); // 用于存储命名空间列表
const selectedNamespace = ref(localStorage.getItem('selectedNamespace') || ''); // 用于存储用户选择的命名空间
// 方法来获取命名空间列表
// fetchNamespaces 函数使用 getNSData 的返回值来更新命名空间的下拉菜单
const fetchNamespaces = async () => {
namespaces.value = await getNSData();
serviceNamespaces.value = await getNSData()
};
// fetchNamespaces 函数使用 getNSData 的返回值来更新命名空间的下拉菜单
// getNSData 函数获取命名空间并返回名称数组
const getNSData = async () => {
try {
const resp = await NAMESPACE_LIST(namespaceAllParameters);
if (resp && resp.code === 200 && resp.data && Array.isArray(resp.data.Items)) {
// 只返回命名空间的名称数组
return resp.data.Items.map(ns => ns.metadata.name);
} else {
throw new Error('无法获取数据或数据格式不正确');
}
} catch (e) {
console.error(e);
return []; // 出错时返回空数组
}
};
const namespaceAllParameters = reactive({
page_size: 100000,
page_number: currentPage,
})
const formatImages = (images) => {
return images.split('\n').map(image => `<div>${image}</div>`).join('');
};
const goToCreateDeployment = () => {
// dialogSelectedNamespace.value = selectedNamespace.value;
// createDialogVisible.value = true;
// // 清空现有的实例内容器并添加一个新的实例内容器
// instanceItems.value = [];
// addInstanceItem();
};
//编辑器配置
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 scaleDeployment = (row) => {
console.log(row.deploymentName)
console.log(selectedNamespace)
}
const restartDeployment = async (row) =>{
ElMessageBox.confirm(
`确定要重启Deployment "${row.deploymentName}" 吗?`,
'警告',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
).then(async () => {
const restarParameters = reactive({
deploymentName: row.deploymentName,
namespace: selectedNamespace
})
try {
const resp = await RESTART_DEPLOYMENT(restarParameters)
console.log("————————————————",resp)
ElMessage({
type: 'success',
message: `Deployment "${row.deploymentName}" 已重启`
});
getDeployData()
}catch (e){
console.log(e)
ElMessage.error(`重启失败: ${e}`);
}
}).catch(() => {
});
}
const handleSizeChange = (val) => {
console.log(`${val} items per page`)
namespaceAllParameters.page_size = val
localStorage.setItem('servicepageSize',val)
getDeployData()
}
const handleInput = _.debounce(async () => {
await search();
}, 500); // 使用 lodash 的 debounce 函数来防抖,防止搜索操作太频繁
const updateDeploy = async () =>{
try {
const updatedJson = yaml2obj.load(yamlContent.value)
const jsonString = JSON.stringify(updatedJson);
updateParameters.deploymentName = currentDeployName.value
updateParameters.content = jsonString
await updateDeployData()
await getDeployData()
}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('servicecurrentPage', val); // 将当前页保存在 localStorage
getDeployData()
}
const viewYAML = (row) => {
console.log('显示YAML信息', row.name);
// 这里可以编写显示YAML信息的逻辑
// 假设提供了 `getYAMLContent` 方法来获取 YAML 内容
// 直接使用传入的 row 作为 YAML 内容
getDeployDetailData(row.name)
};
const confirmDelete = (row) => {confirmDelete
ElMessageBox.confirm(
`确定要删除Service "${row.serviceName}" 吗?`,
'警告',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
).then(async () => {
const deleteNSParameters = ref({
name: row.name,
namespace: selectedNamespace.value,
})
console.log(deleteNSParameters.value)
try {
const resp = await DELETE_SERVICE(deleteNSParameters.value)
console.log("————————————————",resp)
ElMessage({
type: 'success',
message: `Service "${row.name}" 已被删除`
});
getDeployData()
}catch (e){
console.log(e)
ElMessage.error(`删除失败: ${e}`);
}
}).catch(() => {
ElMessage({
type: 'info',
message: '已取消删除'
});
});
};
const formatDateTime = (dateTimeString) => {
return dateTimeString.replace('T', ' ').replace('Z', '');
};
// 示例数据和方法
const searchText = ref('');
const tableData = ref([
{
name: "",
Labels: '',
Type: '',
Cluster_ip: '',
External_ip: '',
port:'',
createTime:"",
Operation:"",
},
// ...其他数据项
]);
const search = async () => {
console.log('执行搜索:', searchText.value);
const searchParameters = reactive({
page_size: pageSize,
page_number: currentPage,
filterName: searchText.value,
namespace: selectedNamespace
})
try {
const resp = await SERVICE_LIST(searchParameters);
if (resp && resp.data && Array.isArray(resp.data.items)) {
tableData.value = resp.data.items.map((item) => {
const ports = item.spec.ports
.map(p => p.nodePort ? `${p.port}/${p.nodePort}` : p.port.toString()) // 如果nodePort存在,显示nodePort/port,否则只显示port
.join(', '); // 用逗号和空格连接
return {
name: item.metadata.name,
Labels: formatLabels(item.metadata.labels),
// 更新状态显示为“运行副本数/期望副本数”格式
Type: item.spec.type,
Cluster_ip: item.spec.clusterIP,
External_ip: "",
port: ports,
createTime: formatDateTime(item.metadata.creationTimestamp),
// 其他属性...
};
});
}
// 更新部署计数
deploymentCount.value = resp.data.count;
} catch (e) {
console.log(e);
}
};
const refresh = () => {
getDeployData()
console.log('刷新表格数据');
};
const updateParameters = reactive({
name: "",
namespace: selectedNamespace,
content: ""
})
const updateDeployData = async ()=>{
try {
console.log("你吗的",updateParameters.content)
const resp = await UPDATE_SERVICE(updateParameters)
if (resp.code !== 200){
ElMessage.error(resp.message)
return
}
ElMessage.success("更新成功")
dialogVisible.value = false
}catch (e){
console.log(e)
ElMessage.error(`${e}`)
}
}
const deploymentParameters = reactive({
page_size: pageSize,
page_number: currentPage,
namespace: selectedNamespace,
})
// 设置watcher来监听selectedNamespace的改变
watch(selectedNamespace, (newNamespace) => {
// 更新deploymentParameters中的namespace
deploymentParameters.namespace = newNamespace;
// 重新调用getDeployData以获取新命名空间的部署数据
getDeployData();
});
var deploymentCount = ref(0)
// 在这里创建一个方法来将 Labels 对象转换为一个字符串数组
// 格式化标签为字符串数组
const formatLabels = (labelsMap) => {
return labelsMap ? Object.entries(labelsMap).map(([key, value]) => `${key}: ${value}`) : [];
};
const systemNamespaces = ['default', 'kube-system', 'kube-public', 'kube-node-lease'];
const getDeployData = async () => {
try {
const resp = await SERVICE_LIST(deploymentParameters);
if (resp && resp.data && Array.isArray(resp.data.items)) {
tableData.value = resp.data.items.map((item) => {
const ports = item.spec.ports
.map(p => p.nodePort ? `${p.port}/${p.nodePort}` : p.port.toString()) // 如果nodePort存在,显示nodePort/port,否则只显示port
.join(', '); // 用逗号和空格连接
return {
name: item.metadata.name,
Labels: formatLabels(item.metadata.labels),
// 更新状态显示为“运行副本数/期望副本数”格式
Type: item.spec.type,
Cluster_ip: item.spec.clusterIP,
External_ip: "",
port: ports,
createTime: formatDateTime(item.metadata.creationTimestamp),
// 其他属性...
};
});
}
// 更新部署计数
deploymentCount.value = resp.data.count;
} catch (e) {
console.log(e);
}
};
const getDeployDetailData = async (serviceName)=>{
try {
const DeploymentDetailparams = reactive({
name: serviceName,
namespace: selectedNamespace.value
})
console.log("ddddddddd",DeploymentDetailparams)
const resp = await SERVICE_DETAIL(DeploymentDetailparams)
console.log("cccccccc",resp)
console.log("yaml =======",json2yaml.stringify(resp.data))
yamlContent.value = json2yaml.stringify(resp.data); // 确保将数据赋值给 yamlContent
currentDeployName.value = resp.data.metadata.name
updateParameters.content = yamlContent.value
await nextTick()
dialogVisible.value = true;
}catch (e){
console.log(e)
console.log("你吗")
}
}
// 在setup函数中定义定时器
let refreshInterval = null;
onMounted(() => {
loadSelection();
refreshInterval = setInterval(() => {
getDeployData();
}, 30000); // 每30秒刷新一次
});
// 组件卸载前保存命名空间
onBeforeUnmount(() => {
saveSelection(selectedNamespace.value);
});
onBeforeMount( async ()=> {
clearInterval(refreshInterval);
// 尝试从 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);
}
getDeployData()
await fetchNamespaces(); // 获取并更新命名空间列表
// getNSAllData()
});
</script>
<style scoped>
.loading-icon {
width: 20px; /* 或者您希望的任何尺寸 */
height: 20px; /* 保持与宽度相同以保持图标的纵横比 */
margin-left: 5px; /* 添加一点空间在状态文本和图标之间 */
animation: loading-spin 2s linear infinite;
}
/* 如果需要,可以移除这个类,因为它可能会导致对齐问题 */
/* .loader {
animation: loader-spin 1s linear infinite;
} */
@keyframes loading-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.container {
margin: 10px;
background-color: #F2F2F2;
}
.deployment-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
margin: 10px;
margin-bottom: -10px;
background: #FFF;
border: 2px solid #ebeef5;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
/* 新增左侧部分样式,放置“命名空间:”文本和下拉框 */
.left-section {
display: flex;
align-items: center;
}
/* 确保下拉框和文本在同一行 */
.el-select {
margin-left: 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;
}
.deployment-header {
display: flex;
align-items: center;
height: 30px;
padding: 10px;
margin: 10px;
margin-bottom: -10px;
background: #FFF;
border: 2px solid #ebeef5;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.form-item-with-help {
margin-top: 10px;
margin-left: 20px;
}
.form-item-with-resource {
margin-top: 10px;
margin-left: 20px;
}
.form-item {
flex-wrap: wrap; /* 确保换行 */
/*align-items: center; !* 如果需要垂直居中 *!*/
margin-bottom: 10px; /* 保持垂直间距 */
}
.input-item {
width: 200px;
margin-right: 10px;
}
.help-text {
display: block; /* 保证帮助文本在新的行显示 */
font-size: 12px;
width: 1000px;
color: #999999;
margin-top: 5px;
}
.input-with-help {
margin-top: 20px;
display: flex;
flex-direction: column;
}
.input-deployname {
width: 300px; /* 限制这个div的宽度 */
display: flex;
flex-direction: column; /* 让子元素垂直排列 */
}
.input-containername {
width: 250px; /* 限制这个div的宽度 */
display: flex;
margin-left: -50px;
flex-direction: column; /* 让子元素垂直排列 */
}
.add-button {
margin-top: -20px; /* 添加按钮与输入行之间的空间 */
margin-left: 200px;
}
.input-container-cpu {
display: flex;
flex-direction: column;
/*padding-left: -20px; !* 往右移动整个容器 *!*/
}
.cpu-label {
margin-bottom: 5px; /* 调整为所需的间距 */
margin-left: -50px;
}
.container {
margin: 10px;
}
.item-container {
margin-bottom: 10px;
}
.content-container {
background-color: #F2F2F2;
padding: 10px;
display: flex;
width: 700px;
justify-content: space-between;
align-items: center;
transition: height 0.3s;
overflow: hidden;
position: relative;
}
.content-container.collapsed {
height: 40px;
}
.info {
display: flex;
align-items: center;
}
.actions {
position: absolute;
right: 10px; /* 调整为需要的值 */
top: 10px; /* 调整为需要的值 */
}
.input-row {
display: flex;
align-items: center;
gap: 10px;
/*margin-left: -50px;*/
}
.input-row .el-input {
flex: 1;
}
.input-row label {
margin-left: 10px; /* 或者需要的任何值 */
}
.input-row .el-input .el-input__inner {
width: 100%; /* 让输入框充满其容器 */
}
.input-containername p {
margin-bottom: 10px; /* 为“CPU:”下方添加一些间隔 */
}
.form-item-resource {
margin-left: 20px; /* 整个表单项向右移动 20px */
margin-top: 10px;
}
.custom-form-item {
height: auto;
overflow: auto;
}
.check-method-container,
.execution-command-container {
display: flex;
align-items: center;
margin-bottom: 10px; /* 添加一些垂直间距 */
}
.method-label {
width: 100px;
margin: 10px;
margin-right: 20px; /* 文本与选择框/输入框之间的间隔 */
}
.command-label {
/*margin-right: 20px; !* 文本与输入框之间的间隔 *!*/
width: 100px;
margin: 10px;
}
.livenessdiv {
background-color: white;
width: 500px;
}
.port-range {
margin-left: 10px;
}
.input-healthContainer {
margin-left: -50px;
}
.el-select,
.el-input {
/*flex-grow: 1; !* 输入框和选择框将占据剩余空间 *!*/
max-width: calc(100% - 100px); /* 限制最大宽度,这里的100px是示例,可以根据实际情况调整 */
}
.button-group {
display: flex;
justify-content: center;
align-items: center;
gap: 10px; /* 调整按钮之间的间隔 */
}
</style>
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 J.のblog!
评论