k8s管理系统项目前端-workload
k8s管理系统项目前端-workload
一、功能开发
1、Deployment
(1)功能
列表、详情、新增、更新、删除、重启、修改副本数等
(2)Main布局
views/workload/deploymentView.vue
<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="goToCreateDeployment()">
<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="deploymentName" label="Deployment名" width="180px" align="center">
<template #default="{ row }">
<div>
<span style="vertical-align: middle; color: #1395FF; cursor: pointer;" @click="handleRowClick(row)">
{{ row.deploymentName }}
</span>
</div>
</template>
</el-table-column>
<el-table-column prop="Labels" label="标签" width="200px" 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="Pod数量 (运行/期望)" align="center" width="200px">
<template #default="{ row }">
<div style="display: flex; align-items: center;justify-content: center;height: 100%">
{{ row.Status }}
<img v-if="row.showLoading" src="@/assets/load.svg" class="loading-icon" alt="loading"/>
</div>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" align="center" width="155px">
<template #default="{ row }">
<div>
<el-tag class="ml-2" type="warning">{{row.createTime}}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="Images" label="镜像" align="center" width="180px">
<template #default="{ row }">
<div>
<el-tooltip
v-for="(images, index) in row.Images"
:key="index"
class="item"
effect="dark"
:content="images"
placement="top"
>
<el-tag class="ml-2" type="success">{{ images }}</el-tag>
</el-tooltip>
</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="openScaleDiglog(row)">
<el-icon><Tickets /></el-icon>
<span style="vertical-align: middle"> 扩缩 </span>
</el-button>
<el-button size="small" color="#F58D79" :dark="isDark" plain @click="restartDeployment(row)">
<el-icon><Tickets /></el-icon>
<span style="vertical-align: middle"> 重启 </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="创建Deployment" v-model="createDialogVisible" width="60%">
<el-form ref="form" :model="createForm" label-position="left" label-width="180px">
<div class="input-with-help">
<el-form-item label="deployment名:" prop="name" class="form-item-with-help">
<div class="input-deployname">
<el-input v-model="createForm.deploymentname" placeholder="请输入deployment名"></el-input>
<p class="help-text">
最长63个字符,只能包含小写字母、数字及分隔符("-"),且必须以小写字母开头,数字或小写字母结尾
</p>
</div>
</el-form-item>
</div>
<el-form-item label="描述:" prop="description" style="margin: 10px;margin-left: 20px">
<el-input
type="textarea"
v-model="createForm.description"
placeholder="请输入描述信息"
class="custom-textarea"
></el-input>
</el-form-item>
<el-form-item label="标签:" prop="labels" class="form-item-with-help">
<div v-for="(item, index) in formItems" :key="item.id" class="form-item">
<el-input
placeholder="Key"
v-model="item.key"
class="input-item"
:disabled="!item.editable"
></el-input>
<span> = </span>
<el-input
placeholder="Value"
v-model="item.value"
class="input-item"
></el-input>
<el-button @click="removeFormItem(index)" :disabled="!item.editable">
<el-icon><CircleClose /></el-icon>
</el-button>
</div>
<!-- 将添加新行的按钮移动到循环外部 -->
</el-form-item>
<div>
<el-button
type="primary"
@click="() => addFormItem(item)"
class="add-button"
>
添加新标签
</el-button>
</div>
<el-form-item label="命名空间:" prop="namespace" style="margin: 10px">
<el-select v-model="createForm.namespace" placeholder="请选择">
<el-option
v-for="namespace in namespaces"
:key="namespace"
:label="namespace"
:value="namespace">
</el-option>
</el-select>
</el-form-item>
<el-form-item class="custom-form-item" label="实例内容器:" style="margin: 10px">
<div class="container">
<div v-for="(item, index) in instanceItems" :key="item.id" class="item-container">
<div class="content-container" :class="{ 'collapsed': item.collapsed }">
<div class="info" v-if="item.collapsed">
<span>{{ item.name }}/</span> <!-- 显示名称 -->
<span>{{ item.image }}:{{ item.image_tag }}</span> <!-- 显示镜像 -->
</div>
<div class="actions" v-if="item.collapsed">
<el-button @click="expandInstanceItem(index)">编辑</el-button>
<el-button @click="removeInstanceItem(index)" :disabled="instanceItems.length === 1">删除</el-button>
</div>
<div class="info" v-else>
<!-- <span >{{ item.name }}</span>-->
<div class="input-with-help" v-if="!item.collapsed">
<el-form-item label="名称:" prop="name" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.name" placeholder="请输入容器名"></el-input>
<p class="help-text">
最长63个字符,只能包含小写字母、数字及分隔符("-"),且不能以分隔符开头或结尾
</p>
</div>
</el-form-item>
<el-form-item label="镜像:" prop="image" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.image" placeholder=""></el-input>
</div>
</el-form-item>
<el-form-item label="镜像版本:" prop="image_tag" class="form-item-with-resource">
<div class="input-containername">
<el-input v-model="item.image_tag" placeholder=""></el-input>
</div>
</el-form-item>
<el-form-item label="CPU/内存限制:" prop="form-item-with-help" class="form-item-resource">
<div class="input-container-cpu">
<label class="cpu-label">CPU:</label>
<div class="input-row">
<el-input v-model="item.cpu_request" >
<template #prepend>request</template>
</el-input>
<el-input v-model="item.cpu_limit" >
<template #prepend>limit</template>
</el-input>
<label >m</label>
</div>
</div>
<div class="input-container-memory">
<label class="cpu-label">内存:</label>
<div class="input-row">
<el-input v-model="item.memory_request">
<template #prepend>request</template>
</el-input>
<el-input v-model="item.memory_limit">
<template #prepend>limit</template>
</el-input>
<label >MiB</label>
</div>
</div>
</el-form-item>
<el-form-item label="工作目录:" prop="workspace" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.workspace" placeholder=""></el-input>
<p class="help-text">
指定容器运行后的工作目录
</p>
</div>
</el-form-item>
<el-form-item label="运行命令:" prop="cmd" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.cmd" placeholder=""></el-input>
<p class="help-text">
控制容器运行的输入命令
</p>
</div>
</el-form-item>
<el-form-item label="运行参数:" prop="args" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.args" placeholder=""></el-input>
<p class="help-text">
传递给容器运行命令的输入参数
</p>
</div>
</el-form-item>
<!-- HTML -->
<el-form-item label="容器健康检查" prop="health" class="form-item-with-help">
<div class="input-healthContainer">
<el-checkbox v-model="item.isLivenessCheckActive">存活检查</el-checkbox>
<div v-if="item.isLivenessCheckActive">
<div class="livenessdiv">
<div class="check-method-container">
<label class="method-label">检查方法:</label>
<el-select v-model="item.livenessselectedCheckMethod" placeholder="请选择">
<el-option label="HTTP" value="http"></el-option>
<el-option label="TCP" value="tcp"></el-option>
<el-option label="Exec" value="exec"></el-option>
</el-select>
</div>
<!-- HTTP选项时显示 -->
<div v-if="item.livenessselectedCheckMethod === 'http'">
<div class="check-method-container">
<label class="method-label">检查协议:</label>
<el-select v-model="item.lives_selectedProtocol" placeholder="请选择">
<el-option label="HTTP" value="http"></el-option>
<el-option label="HTTPS" value="https"></el-option>
</el-select>
</div>
<div class="check-method-container">
<label class="command-label">检查端口:</label>
<el-input v-model="item.lives_healthCheckPort" placeholder="请输入端口" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">端口范围: 1~65535</span>
</div>
<div class="check-method-container">
<label class="command-label">请求路径:</label>
<el-input v-model="item.lives_healthCheckPath" placeholder="请输入路径" style="width: 200px;margin-left: 20px"></el-input>
</div>
</div>
<!-- TCP选项时显示 -->
<div v-else-if="item.livenessselectedCheckMethod === 'tcp'">
<div class="check-method-container">
<label class="command-label">检查端口:</label>
<el-input v-model="item.lives_healthCheckPort" placeholder="请输入端口" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">端口范围: 1~65535</span>
</div>
</div>
<!-- Exec选项时显示 -->
<div v-else-if="item.livenessselectedCheckMethod === 'exec'">
<div class="execution-command-container">
<label class="command-label">执行命令:</label>
<el-input v-model="item.livenessCommand" placeholder="请输入命令" style="width: 200px;margin-left: 20px"></el-input>
</div>
</div>
<!-- 启动延时始终显示 -->
<div class="check-method-container">
<label class="command-label">启动延时:</label>
<el-input v-model="item.livenessStartDelay" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:0~60秒</span>
</div>
<!-- 响应超时始终显示 -->
<div class="check-method-container">
<label class="command-label">响应延时:</label>
<el-input v-model="item.livenessResDelay" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:2~60秒</span>
</div>
<!-- 间隔时间始终显示 -->
<div class="check-method-container">
<label class="command-label">间隔时间:</label>
<el-input v-model="item.livenessIntervalTime" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:2~300秒</span>
</div>
<!-- 健康阈值始终显示 -->
<div class="check-method-container">
<label class="command-label">健康阈值:</label>
<el-input disabled v-model="item.livenessHealththreshold" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:1次</span>
</div>
<!-- 不健康阈值始终显示 -->
<div class="check-method-container">
<label class="command-label">不健康阈值:</label>
<el-input v-model="item.livenessUnHealththreshold" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:1~10次</span>
</div>
</div>
</div>
<!-- 就绪性检查 -->
<div>
<el-checkbox v-model="item.isReadinessCheckActive">就绪检查</el-checkbox>
<div v-if="item.isReadinessCheckActive">
<div class="livenessdiv">
<div class="check-method-container">
<label class="method-label">检查方法:</label>
<el-select v-model="item.ReadinessSelectedCheckMethod" placeholder="请选择">
<el-option label="HTTP" value="http"></el-option>
<el-option label="TCP" value="tcp"></el-option>
<el-option label="Exec" value="exec"></el-option>
</el-select>
</div>
<!-- HTTP选项时显示 -->
<div v-if="item.ReadinessSelectedCheckMethod === 'http'">
<div class="check-method-container">
<label class="method-label">检查协议:</label>
<el-select v-model="item.readnessSelectedProtocol" placeholder="请选择">
<el-option label="HTTP" value="http"></el-option>
<el-option label="HTTPS" value="https"></el-option>
</el-select>
</div>
<div class="check-method-container">
<label class="command-label">检查端口:</label>
<el-input v-model="item.readnessHealthCheckPort" placeholder="请输入端口" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">端口范围: 1~65535</span>
</div>
<div class="check-method-container">
<label class="command-label">请求路径:</label>
<el-input v-model="item.readnessHealthCheckPath" placeholder="请输入路径" style="width: 200px;margin-left: 20px"></el-input>
</div>
</div>
<!-- TCP选项时显示 -->
<div v-else-if="item.ReadinessSelectedCheckMethod === 'tcp'">
<div class="check-method-container">
<label class="command-label">检查端口:</label>
<el-input v-model="item.readnessHealthCheckPort" placeholder="请输入端口" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">端口范围: 1~65535</span>
</div>
</div>
<!-- Exec选项时显示 -->
<div v-else-if="item.ReadinessSelectedCheckMethod === 'exec'">
<div class="execution-command-container">
<label class="command-label">执行命令:</label>
<el-input v-model="item.readnessCommand" placeholder="请输入命令" style="width: 200px;margin-left: 20px"></el-input>
</div>
</div>
<!-- 启动延时始终显示 -->
<div class="check-method-container">
<label class="command-label">启动延时:</label>
<el-input v-model="item.readnessStartDelay" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:0~60秒</span>
</div>
<!-- 响应超时始终显示 -->
<div class="check-method-container">
<label class="command-label">响应延时:</label>
<el-input v-model="item.readnessResDelay" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:2~60秒</span>
</div>
<!-- 间隔时间始终显示 -->
<div class="check-method-container">
<label class="command-label">间隔时间:</label>
<el-input v-model="item.readnessIntervalTime" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:2~300秒</span>
</div>
<!-- 健康阈值始终显示 -->
<div class="check-method-container">
<label class="command-label">健康阈值:</label>
<el-input v-model="item.readnessHealththreshold" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:1~10次</span>
</div>
<!-- 不健康阈值始终显示 -->
<div class="check-method-container">
<label class="command-label">不健康阈值:</label>
<el-input v-model="item.readnessUnHealththreshold" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:1~10次</span>
</div>
</div>
</div>
</div>
</div>
</el-form-item>
<el-form-item label="副本数:" prop="replica" class="form-item-with-help">
<div class="input-containername">
<el-input-number v-model="createForm.replicas" :min="1" @change="changeReplicas" />
</div>
</el-form-item>
</div>
</div>
<div class="actions" v-if="!item.collapsed">
<el-button v-if="item.editMode" @click="saveInstanceItem(index)">保存</el-button>
<el-button @click="removeInstanceItem(index)" :disabled="instanceItems.length === 1">删除</el-button>
</div>
</div>
</div>
</div>
</el-form-item>
<el-button type="primary" @click="addInstanceItem" class="add-button">
添加新实例
</el-button>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="createDialogVisible = false">取消</el-button>
<el-button type="primary" @click="createDeployment">创建</el-button>
</span>
</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>
</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, DELETE_DEPLOYMENT,
DELETE_NAMESPACE, DEPLOYMENT_DETAIL, DEPLOYMENT_LIST,
NAMESPACE_DETAIL,
NAMESPACE_LIST, RESTART_DEPLOYMENT, UPDATE_DEPLOYMENT, UPDATE_DEPLOYMENTSCALE,
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";
import {value} from "lodash/seq.js";
const extensions = [javascript(),oneDark]
const dialogSelectedNamespace = ref(''); // 对话框的v-model
const scaleDialogVisible= ref(false)
const replicaCount = ref(1)
const currentRow = ref(null)
const openScaleDiglog = (row)=>{
replicaCount.value = row.Replicas
currentRow.value = row
scaleDialogVisible.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({
deploymentname: '',
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 createDeployment = async ()=> {
const labels = formItems.value.reduce((acc, item) => {
if (item.key && item.value) {
acc[item.key] = item.value;
}
return acc;
}, {});
console.log("Instance Items before sending to backend:", instanceItems.value);
instanceItems.value.forEach(item => {
console.log("Readiness Port for item:", item.readnessHealthCheckPort);
console.log("Parsed Port:", parseInt(item.readnessHealthCheckPort));
// 确认 parseInt 不是返回 NaN
if (isNaN(parseInt(item.readnessHealthCheckPort))) {
console.error("Invalid port number: ", item.readnessHealthCheckPort);
}
});
console.log("label = =================",labels)
console.log(instanceItems.value)
console.log(createForm.value.replicas)
const createParameters = ref({
"namespace": createForm.value.namespace,
"deploymentName": createForm.value.deploymentname,
"podName": createForm.value.deploymentname,
"label": labels,
"replicas": createForm.value.replicas,
"containers": instanceItems.value.map(item => ({
// 如果是空字符串则设置为 false
"name": item.name,
"image": item.image,
"version": item.image_tag,
// "ports": item.ports, // 确保这个字段是一个 int32 数组
"limits_cpu": item.cpu_limit,
"limit_memory": item.memory_limit,
"requests_cpu": item.cpu_request,
"requests_memory": item.memory_request,
"work_space": item.workspace,
"command": item.cmd,
"args": item.args.split(','), // 前提是 args 是以空格分隔的字符串
"readiness_healthCheck": item.isReadinessCheckActive || false,
"liveness_healthCheck": item.isLivenessCheckActive || false,
"healthcheckTypeLive": mapHealthCheckType(item.livenessselectedCheckMethod),
"healthcheckTypeRead": mapHealthCheckType(item.ReadinessSelectedCheckMethod),
"resource": {
},
"livenessProbe": {
"path": item.lives_healthCheckPath,
"port": parseInt(item.lives_healthCheckPort),
"cmd": item.livenessCommand,
"initialDelaySecond": parseInt(item.livenessStartDelay),
"timeoutSeconds": parseInt(item.livenessResDelay),
"periodSeconds": parseInt(item.livenessIntervalTime),
"successThreshold": parseInt(item.livenessHealththreshold),
"failureThreshold": parseInt(item.livenessUnHealththreshold),
},
"readinessProbe": {
"path": item.readnessHealthCheckPath,
"port": parseInt(item.readnessHealthCheckPort),
"cmd": item.readnessCommand,
"initialDelaySecond": parseInt(item.readnessStartDelay),
"timeoutSeconds": parseInt(item.readnessResDelay),
"periodSeconds": parseInt(item.readnessIntervalTime),
"successThreshold": parseInt(item.readnessHealththreshold),
"failureThreshold": parseInt(item.readnessUnHealththreshold),
},
}))
});
console.log(createParameters.value)
// 发送 createParameters.value 到后端...
try {
const resps = await CREATE_DEPLOYMENT(createParameters.value);
console.log(resps)
if (resps.code === 200){
createDialogVisible.value = false
ElMessage.success("创建成功")
getDeployData()
}else {
ElMessage.error(resps.data.message)
}
} catch (e) {
console.error(e);
return []; // 出错时返回空数组
}
console.log(createForm.value)
console.log("标签:",formItems.value)
console.log("~~~~~~~~~~~~~~~~")
console.log(instanceItems.value)
}
// 调用 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();
};
// 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('nspageSize',val)
getNSData()
}
const handleInput = _.debounce(async () => {
await search();
}, 500); // 使用 lodash 的 debounce 函数来防抖,防止搜索操作太频繁
const updateDeploy = async () =>{
try {
const updatedJson = yaml2obj.load(yamlContent.value)
const jsonString = JSON.stringify(updatedJson);
console.log("?????????",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('nscurrentPage', val); // 将当前页保存在 localStorage
getNSData()
}
const viewYAML = (row) => {
console.log('显示YAML信息', row.deploymentName);
// 这里可以编写显示YAML信息的逻辑
// 假设提供了 `getYAMLContent` 方法来获取 YAML 内容
// 直接使用传入的 row 作为 YAML 内容
getDeployDetailData(row.deploymentName)
};
const confirmDelete = (row) => {confirmDelete
ElMessageBox.confirm(
`确定要删除Deployment "${row.deploymentName}" 吗?`,
'警告',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
).then(async () => {
const deleteNSParameters = ref({
deploymentName: row.deploymentName,
namespace: selectedNamespace.value,
})
console.log(deleteNSParameters.value)
try {
const resp = await DELETE_DEPLOYMENT(deleteNSParameters.value)
console.log("————————————————",resp)
ElMessage({
type: 'success',
message: `Deployment "${row.deploymentName}" 已被删除`
});
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([
{
deploymentName: "",
Labels: '',
Status: '',
Images: '',
createTime:"",
Operation:"",
Replicas: '',
},
// ...其他数据项
]);
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 DEPLOYMENT_LIST(searchParameters);
if (resp && resp.data && Array.isArray(resp.data.Items)) {
tableData.value = resp.data.Items.map((item) => {
// 计算运行中的副本数
const runningReplicas = item.status.replicas - (item.status.unavailableReplicas || 0);
const expectedReplicas = item.spec.replicas;
const showLoading = runningReplicas !== expectedReplicas; // 加载图标仅在期望和运行副本数不匹配时显示
return {
deploymentName: item.metadata.name,
Labels: formatLabels(item.metadata.labels),
showLoading: showLoading, // 添加 showLoading 标志
// 更新状态显示为“运行副本数/期望副本数”格式
Status: `${runningReplicas}/${item.spec.replicas}`,
Images: item.spec.template.spec.containers.map(container => container.image),
createTime: formatDateTime(item.metadata.creationTimestamp),
// 其他属性...
};
});
}
// 更新部署计数
deploymentCount.value = resp.data.total;
} catch (e) {
console.log(e);
}
};
const refresh = () => {
getDeployData()
console.log('刷新表格数据');
};
const updateParameters = reactive({
deploymentName: "",
namespace: selectedNamespace,
content: ""
})
const updateDeployData = async ()=>{
try {
console.log("你吗的",updateParameters.content)
const resp = await UPDATE_DEPLOYMENT(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 DEPLOYMENT_LIST(deploymentParameters);
if (resp && resp.data && Array.isArray(resp.data.Items)) {
tableData.value = resp.data.Items.map((item) => {
// 计算运行中的副本数
const runningReplicas = item.status.replicas - (item.status.unavailableReplicas || 0);
const expectedReplicas = item.spec.replicas;
const showLoading = runningReplicas !== expectedReplicas; // 加载图标仅在期望和运行副本数不匹配时显示
return {
deploymentName: item.metadata.name,
Labels: formatLabels(item.metadata.labels),
showLoading: showLoading, // 添加 showLoading 标志
// 更新状态显示为“运行副本数/期望副本数”格式
Status: `${runningReplicas}/${item.spec.replicas}`,
Images: item.spec.template.spec.containers.map(container => container.image),
createTime: formatDateTime(item.metadata.creationTimestamp),
Replicas: item.spec.replicas
// 其他属性...
};
});
}
// 更新部署计数
deploymentCount.value = resp.data.total;
} catch (e) {
console.log(e);
}
};
const getDeployDetailData = async (deployName)=>{
try {
const DeploymentDetailparams = reactive({
deploymentName: deployName,
namespace: selectedNamespace.value
})
console.log("ddddddddd",DeploymentDetailparams)
const resp = await DEPLOYMENT_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>
2、pod开发
(1)功能
列表、详情、更新、删除、日志、终端
<template>
<div class="container">
<!-- 第一部分: 头部 -->
<div class="h-6" />
<el-menu
:default-active="activeIndex"
class="el-menu-demo"
mode="horizontal"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
@select="handleSelect"
>
<el-menu-item index="1">Pod管理</el-menu-item>
<el-menu-item index="2">日志</el-menu-item>
<!-- <el-menu-item index="3">详情</el-menu-item>-->
<el-menu-item index="3">YAML</el-menu-item>
</el-menu>
<div class="header shadow" v-if="activeIndex ==='1'">
<div class="left-group">
<el-button type="text" @click="goBack">
<el-icon style="font-size: 20px"><ArrowLeft /></el-icon>
</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="header shadow" v-if="activeIndex ==='3'">
<div class="left-group">
<el-button type="text" @click="goBack">
<el-icon style="font-size: 20px"><ArrowLeft /></el-icon>
</el-button>
</div>
</div>
<div class="header shadow" v-if="activeIndex ==='2'">
<div class="left-group">
<el-button type="text" @click="goBack">
<el-icon style="font-size: 20px"><ArrowLeft /></el-icon>
</el-button>
<!-- 选择器容器 -->
<div class="selectors">
<!-- 第一个选择器 -->
<el-select v-model="selectedValue1" placeholder="请选择" class="selector" @change="handlePodChange">
<el-option
v-for="option in tableData"
:key="option.podName"
:label="option.podName"
:value="option.podName"
>
</el-option>
</el-select>
<!-- 第二个选择器 -->
<el-select v-model="selectedValue2" placeholder="请选择" class="selector" @change="handleContainers">
<el-option
v-for="name in containerList"
:key="name"
:label="name"
:value="name">
</el-option>
</el-select>
<!-- 第三个选择器 -->
<el-select v-model="selectedLines" placeholder="请选择" class="selector">
<el-option
v-for="option in linesOptions"
:key="option.value"
:label="option.label"
:value="option.value">
</el-option>
</el-select>
</div>
</div>
</div>
<!-- 日志容器 -->
<div class="log-container" v-if="activeIndex ==='2'">
<codemirror
v-model="rawLogData"
placeholder=""
:style="{ height: editorHeight }"
v-bind="logOptions"
:extensions="extensions"
disabled="true"
/>
</div>
<!-- 日志容器 -->
<div class="log-container" v-if="activeIndex ==='3'">
<codemirror
v-model="yamlContent"
placeholder=""
:style="{ height: editorHeight }"
v-bind="logOptions"
:extensions="extensions"
/>
</div>
<!-- 第二部分: 表格 -->
<div class="table-wrapper shadow" v-if="activeIndex ==='1'">
<el-table :data="tableData" style="width: 100%" row-key="podName" >
<el-table-column type="expand">
<template #default="{ row }">
<div class="parent-container">
<div class="expanded-content">
<el-table :data="row.expanded?.slice(1)" style="width: 100%" class="row">
<el-table-column
class-name="text-center"
v-for="(label, prop) in row.expanded[0]"
:key="prop"
:label="label"
:prop="prop"
align="center"
header-align="center">
</el-table-column>
</el-table>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="podName" label="Pod名" width="180px" align="center">
<template #default="{ row }">
<div>
<p style="color: #4795EE; margin: 0;">{{ row.podName }}</p>
</div>
</template>
</el-table-column>
<el-table-column prop="Status" label="状态" width="200px" align="center">
<template #default="{ row }">
<div>
<el-tooltip class="item" effect="dark" placement="top" :content="row.FailedMessage">
<el-tag class="ml-2" :type="getTagType(row.Status)">{{ row.Status }}</el-tag>
</el-tooltip>
</div>
</template>
</el-table-column>
<el-table-column prop="HostIp" label="实例所在节点IP" align="center" width="200px">
<template #default="{ row }">
<div style="display: flex; align-items: center;justify-content: center;height: 100%">
{{ row.HostIp }}
<img v-if="row.showLoading" src="@/assets/load.svg" class="loading-icon" alt="loading"/>
</div>
</template>
</el-table-column>
<el-table-column prop="podIp" label="实例IP" align="center" width="200px">
<template #default="{ row }">
<div style="display: flex; align-items: center;justify-content: center;height: 100%">
{{ row.podIp }}
<img v-if="row.showLoading" src="@/assets/load.svg" class="loading-icon" alt="loading"/>
</div>
</template>
</el-table-column>
<el-table-column prop="RunningTime" label="运行时间" align="center" width="155px">
<template #default="{ row }">
<div>
<el-tag class="ml-2" type="warning">{{row.RunningTime}}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" align="center" width="155px">
<template #default="{ row }">
<div>
<el-tag class="ml-2" type="warning">{{row.createTime}}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="restartCount" label="重启次数" align="center" width="155px">
<template #default="{ row }">
<div>
<el-tag class="ml-2" type="warning">{{row.restartCount}}</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" plain @click="viewYAML(row)">
<el-icon><EditPen /></el-icon>
<span style="vertical-align: middle"> YAML </span>
</el-button>
<el-button size="small" color="#F58D79" 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=podCount
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="logOptions"
: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>
</div>
</template>
<script setup>
import {
DAEMONSET_DETAIL,
DAEMONSET_POD_LIST,
DELETE_DEPLOYMENT, DELETE_POD, DEPLOYMENT_DETAIL,
DEPLOYMENT_LIST,
DEPLOYMENT_POD_LIST,
GET_NS_POD_COUNT,
NAMESPACE_LIST, POD_CONTAINERS, POD_DETAIL,
POD_LIST, POD_LOGS, STATEFULSET_POD_LIST
} from "../../../api/k8s.js";
import {computed, nextTick, onBeforeMount, reactive, ref, watch} from "vue";
import { Codemirror } from 'vue-codemirror'
import router from "@/router/index.js";
import {ElMessage, ElMessageBox} from "element-plus";
import json2yaml from "json2yaml";
import {javascript} from "@codemirror/lang-javascript";
import {oneDark} from "@codemirror/theme-one-dark";
import _ from "lodash";
const extensions = [javascript(),oneDark]
const activeIndex = ref('1')
import { useRoute } from 'vue-router';
const handleSelect = (key, keyPath) => {
console.log(key, keyPath)
activeIndex.value = key; // 更新当前选中的菜单项
if (key === '1') {
getPodsData(); // 当选中Pod管理时,调用getPodsData函数
}else if (key === '3'){
getPodYamlData()
}
}
const selectedPodName = ref(null); // 用于双向绑定选中的Pod名称
// 假设这是从某处获取的日志字符串
const rawLogData = ref('暂无日志');
// 将原始日志字符串分割成行数组
const logLines = computed(() => rawLogData.value.split('\n'));
const currentPage = ref(1)
const namespaces = ref([]); // 用于存储命名空间列表
const selectedNamespace = ref('default'); // 用于存储用户选择的命名空间
let pageSize = ref(5)
const small = ref(false)
const background = ref(false)
const disabled = ref(false)
// 在 <script setup> 中
const { type,deploymentName, namespace } = defineProps({
type: String,
deploymentName: String,
namespace: String
});
const route = useRoute();
// 假设有三组选择器选项
const selectorOptions1 = ref([
{ label: '选项 1.1', value: '1.1' },
{ label: '选项 1.2', value: '1.2' },
// ...其他选项
]);
const selectorOptions2 = ref([
{ label: '选项 2.1', value: '2.1' },
{ label: '选项 2.2', value: '2.2' },
// ...其他选项
]);
const selectorOptions3 = ref([
{ label: '选项 3.1', value: '3.1' },
{ label: '选项 3.2', value: '3.2' },
// ...其他选项
]);
// 第一个选择器变化时触发的函数
function handlePodChange(newValue) {
// 当第一个选择器的值变化时,调用getPodDetailData函数
console.log("================================")
console.log(podN.value)
if (podN.value !== ""){
getContainerLogs()
}
getPodDetailData(newValue);
}
const handleInput = _.debounce(async () => {
await search();
}, 500); // 使用 lodash 的 debounce 函数来防抖,防止搜索操作太频繁
function handleContainers(newValue) {
// 当第一个选择器的值变化时,调用getPodDetailData函数
console.log("================================")
getContainerLogs(newValue);
}
// 为每个选择器定义模型来持有选中的值
const selectedValue1 = ref(null);
const selectedValue2 = ref(null);
const selectedLines = ref(50);
const linesOptions = [
{ label: '50行', value: 50 },
{ label: '100行', value: 100 },
{ label: '300行', value: 300 },
{ label: '500行', value: 500 },
{ label: '1000行', value: 1000 },
];
const logOptions = ref({
tabSize: 4,
mode: { name: 'yaml', json: true },
theme: 'base16-light',
styleActiveLine: true,
lineNumbers: true,
line: true,
readOnly: true,
indentWithTabs: true,
smartIndent: true,
indentUnit: 10,
foldgutter: true,
gutters: [
'CodeMirror-linenumbers',
'CodeMirror-foldgutter',
'CodeMirror-lint-markers'
],
lineWrapping: true, // 代码折叠
foldGutter: true,
matchBrackets: true, // 括号匹配
autoCloseBrackets: true,
showCursorWhenSelecting: true,
cursorHeight: 0.85,
})
// 计算编辑器高度
const editorHeight = computed(() => {
const lineHeight = 20; // 假设每行的高度
return `${selectedLines.value * lineHeight}px`;
});
// 假设这是你的数据源
const state = reactive({
tableData: [
// 这里放入你的表格数据
],
// 更多数据或状态...
});
const search = async () => {
console.log('执行搜索:', searchText.value);
console.log(type)
try {
if (type == 'daemonset'){
const searchParameters = reactive({
page_size: pageSize,
page_number: currentPage,
filterName: searchText.value,
daemonsetName: deploymentName,
namespace: namespace
})
console.log(searchParameters)
const resp = await DAEMONSET_POD_LIST(searchParameters);
console.log("牛逼:::",resp)
// // 初始化expandedData数组并添加表头
// 遍历每个 Pod 并构造其展开信息
const newTableData = resp.data.items?.map(item => {
const conditionsMessage = item.status.conditions?.map(condition => condition.message).join(", ") || "";
// 表头数据,将直接作为第一个元素添加到每个expanded中
const headerData = {
containerName: '容器名称',
image: '镜像版本号',
podIp: 'PodIP',
restartCount: '重启次数',
status: '状态'
};
// 构造容器状态信息
// 处理containerStatuses,以显示restartCount和state
const podExpData = (item.status.containerStatuses || item.spec.containers.map(container => ({
name: container.name,
image: container.image,
// 如果没有containerStatuses,设置restartCount和state为"-"
restartCount: '-',
state: { status: '-' }
}))).map(container => {
let status = '-';
let conditionMessage = '';
// 解析容器状态
if (container.state) {
if (container.state.running) {
status = '运行中';
} else if (container.state.waiting) {
status = '等待中';
conditionMessage = container.state.waiting.message || conditionsMessage;
} else if (container.state.terminated) {
status = '已终止';
conditionMessage = container.state.terminated.message || conditionsMessage;
}
} else {
// 如果容器还没有状态(可能是pod还在Pending阶段),使用Pod的conditions信息
conditionMessage = conditionsMessage;
}
return {
containerName: container.name || '-',
image: container.image || '-',
podIp: item.status.podIP || '-',
restartCount: container.restartCount !== undefined ? container.restartCount.toString() : '-',
status: status,
conditionMessage: conditionMessage // 存储条件消息,以便于前端显示提示
};
});
podExpData.unshift(headerData);
const failureMessage = getFailureMessage(item);
// 构造表格数据项,并包含展开属性
return {
// ...其他属性...
FailedMessage: failureMessage,
podName: item.metadata.name,
Status: item.status.phase,
HostIp: item.status.hostIP,
podIp: item.status.podIP,
RunningTime: item.status.startTime ?calculateRunningTime(item.status.startTime): '0天0时',
restartCount: calculateTotalRestartCount(item),
createTime: formatDateTime(item.metadata.creationTimestamp),
expanded: podExpData, // 添加expanded字段
};
});
console.log("================================",newTableData)
tableData.value = newTableData
// 更新部署计数
podCount.value = resp.data.total;
}else if (type === "statefulset"){
const searchParameters = reactive({
page_size: pageSize,
page_number: currentPage,
filterName: searchText.value,
statefulsetName: deploymentName,
namespace: namespace
})
const resp = await STATEFULSET_POD_LIST(searchParameters);
console.log("牛逼:::", resp)
// // 初始化expandedData数组并添加表头
// 遍历每个 Pod 并构造其展开信息
const newTableData = resp.data.items?.map(item => {
const conditionsMessage = item.status.conditions?.map(condition => condition.message).join(", ") || "";
// 表头数据,将直接作为第一个元素添加到每个expanded中
const headerData = {
containerName: '容器名称',
image: '镜像版本号',
podIp: 'PodIP',
restartCount: '重启次数',
status: '状态'
};
// 构造容器状态信息
// 处理containerStatuses,以显示restartCount和state
const podExpData = (item.status.containerStatuses || item.spec.containers.map(container => ({
name: container.name,
image: container.image,
// 如果没有containerStatuses,设置restartCount和state为"-"
restartCount: '-',
state: {status: '-'}
}))).map(container => {
let status = '-';
let conditionMessage = '';
// 解析容器状态
if (container.state) {
if (container.state.running) {
status = '运行中';
} else if (container.state.waiting) {
status = '等待中';
conditionMessage = container.state.waiting.message || conditionsMessage;
} else if (container.state.terminated) {
status = '已终止';
conditionMessage = container.state.terminated.message || conditionsMessage;
}
} else {
// 如果容器还没有状态(可能是pod还在Pending阶段),使用Pod的conditions信息
conditionMessage = conditionsMessage;
}
return {
containerName: container.name || '-',
image: container.image || '-',
podIp: item.status.podIP || '-',
restartCount: container.restartCount !== undefined ? container.restartCount.toString() : '-',
status: status,
conditionMessage: conditionMessage // 存储条件消息,以便于前端显示提示
};
});
podExpData.unshift(headerData);
const failureMessage = getFailureMessage(item);
// 构造表格数据项,并包含展开属性
return {
// ...其他属性...
FailedMessage: failureMessage,
podName: item.metadata.name,
Status: item.status.phase,
HostIp: item.status.hostIP,
podIp: item.status.podIP,
RunningTime: item.status.startTime ? calculateRunningTime(item.status.startTime) : '0天0时',
restartCount: calculateTotalRestartCount(item),
createTime: formatDateTime(item.metadata.creationTimestamp),
expanded: podExpData, // 添加expanded字段
};
});
console.log("================================", newTableData)
tableData.value = newTableData
podCount.value = resp.data.total;
}
else if (type == 'deployment'){
const searchParameters = reactive({
page_size: pageSize,
page_number: currentPage,
filterName: searchText.value,
deploymentName: deploymentName,
namespace: namespace
})
const resp = await DEPLOYMENT_POD_LIST(searchParameters);
console.log("牛逼:::", resp)
// // 初始化expandedData数组并添加表头
// 遍历每个 Pod 并构造其展开信息
const newTableData = resp.data.items?.map(item => {
const conditionsMessage = item.status.conditions?.map(condition => condition.message).join(", ") || "";
// 表头数据,将直接作为第一个元素添加到每个expanded中
const headerData = {
containerName: '容器名称',
image: '镜像版本号',
podIp: 'PodIP',
restartCount: '重启次数',
status: '状态'
};
// 构造容器状态信息
// 处理containerStatuses,以显示restartCount和state
const podExpData = (item.status.containerStatuses || item.spec.containers.map(container => ({
name: container.name,
image: container.image,
// 如果没有containerStatuses,设置restartCount和state为"-"
restartCount: '-',
state: {status: '-'}
}))).map(container => {
let status = '-';
let conditionMessage = '';
// 解析容器状态
if (container.state) {
if (container.state.running) {
status = '运行中';
} else if (container.state.waiting) {
status = '等待中';
conditionMessage = container.state.waiting.message || conditionsMessage;
} else if (container.state.terminated) {
status = '已终止';
conditionMessage = container.state.terminated.message || conditionsMessage;
}
} else {
// 如果容器还没有状态(可能是pod还在Pending阶段),使用Pod的conditions信息
conditionMessage = conditionsMessage;
}
return {
containerName: container.name || '-',
image: container.image || '-',
podIp: item.status.podIP || '-',
restartCount: container.restartCount !== undefined ? container.restartCount.toString() : '-',
status: status,
conditionMessage: conditionMessage // 存储条件消息,以便于前端显示提示
};
});
podExpData.unshift(headerData);
const failureMessage = getFailureMessage(item);
// 构造表格数据项,并包含展开属性
return {
// ...其他属性...
FailedMessage: failureMessage,
podName: item.metadata.name,
Status: item.status.phase,
HostIp: item.status.hostIP,
podIp: item.status.podIP,
RunningTime: item.status.startTime ? calculateRunningTime(item.status.startTime) : '0天0时',
restartCount: calculateTotalRestartCount(item),
createTime: formatDateTime(item.metadata.creationTimestamp),
expanded: podExpData, // 添加expanded字段
};
});
console.log("================================", newTableData)
tableData.value = newTableData
podCount.value = resp.data.total;
}
} catch (e) {
console.log(e);
}
}
const confirmDelete = (row) => {confirmDelete
ElMessageBox.confirm(
`确定要删除Pod"${row.podName}" 吗?`,
'警告',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
).then(async () => {
const deletePodParameters = reactive({
podName: row.podName,
namespace: namespace,
})
try {
const resp = await DELETE_POD(deletePodParameters)
console.log("————————————————",resp)
ElMessage({
type: 'success',
message: `Pod "${row.podName}" 已被删除`
});
getPodsData()
}catch (e){
console.log(e)
ElMessage.error(`删除失败: ${e}`);
}
}).catch(() => {
});
};
const getTagType = (status) => {
return status === 'Running' ? 'success' : 'danger';
};
const dialogVisible = ref(false);
let yamlContent = ref('');
var podCount = ref(0)
const namespaceAllParameters = reactive({
page_size: 100000,
page_number: currentPage.value,
namespaces: selectedNamespace.value
})
const refresh = () => {
getPodsData()
console.log('刷新表格数据');
};
const updateParameters = reactive({
podName: "",
namespace: namespace,
content: ""
})
const goBack = ()=>{
router.go(-1)
}
const handleSizeChange = (val) => {
console.log(`${val} items per page`)
namespaceAllParameters.page_size = val
localStorage.setItem('nspageSize',val)
getPodsData()
}
const viewYAML = (row) => {
console.log('显示YAML信息', row.podName);
// 这里可以编写显示YAML信息的逻辑
// 假设提供了 `getYAMLContent` 方法来获取 YAML 内容
// 直接使用传入的 row 作为 YAML 内容
};
const getPodYamlData = async ()=>{
console.log(deploymentName,"---------------????-----------------")
try {
const PodDetailparams = reactive({
daemonSetName: deploymentName,
namespace: namespace
})
const resp = await DAEMONSET_DETAIL(PodDetailparams)
console.log("yaml =======",json2yaml.stringify(resp.data))
yamlContent.value = json2yaml.stringify(resp.data); // 确保将数据赋值给 yamlContent
updateParameters.content = yamlContent.value
}catch (e){
console.log(e)
console.log("你吗")
}
}
let containerList = ref([])
let podN = ref('')
const getPodDetailData = async (podName)=>{
try {
const PodDetailparams = reactive({
podName: podName,
namespace: namespace
})
podN = podName
console.log("ddddddddd",PodDetailparams)
const resp = await POD_CONTAINERS(PodDetailparams)
console.log("cccccccc",resp)
containerList = resp.data.map((item) => item.name);
// 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("你吗")
}
}
const getContainerLogs = async (containerName)=>{
try {
const PodDetailparams = reactive({
podName: podN,
namespace: namespace,
containerName: containerName
})
console.log("ddddddddd",PodDetailparams)
const resp = await POD_LOGS(PodDetailparams)
rawLogData.value = resp.data
// 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("你吗")
}
}
const handleCurrentChange = (val) => {
console.log(`current page: ${val}`)
currentPage.value = val
namespaceAllParameters.page_number = val
localStorage.setItem('nscurrentPage', val); // 将当前页保存在 localStorage
getPodsData()
}
const searchText = ref('');
const tableData = ref([
{
podName: "",
Status: '',
HostIp: '-',
podIp: '-',
RunningTime: '',
creationTimestamp:'',
restartCount: '',
createTime:"",
Operation:"",
expanded:{},
FailedMessage: '',
},
// ...其他数据项
]);
const podParameters = reactive({
page_size: pageSize,
page_number: currentPage,
deploymentName: deploymentName,
namespace: namespace,
})
const calculateRunningTime = (startTime) => {
const now = new Date();
const startedAt = new Date(startTime);
const diff = now - startedAt;
const diffDays = Math.floor(diff / (1000 * 60 * 60 * 24));
const diffHours = Math.floor((diff / (1000 * 60 * 60)) % 24);
return `${diffDays}天${diffHours}时`;
};
const getPodsData = async () => {
console.log(type,"typeaaaaa================================================================")
console.log(podParameters,'--------------------------------')
try {
if (type == 'daemonset'){
const podParameterss = reactive({
page_size: pageSize,
page_number: currentPage,
daemonsetName: deploymentName,
namespace: namespace,
})
const resp = await DAEMONSET_POD_LIST(podParameterss);
console.log("牛逼:::",resp)
// // 初始化expandedData数组并添加表头
// 遍历每个 Pod 并构造其展开信息
const newTableData = resp.data.items?.map(item => {
const conditionsMessage = item.status.conditions?.map(condition => condition.message).join(", ") || "";
// 表头数据,将直接作为第一个元素添加到每个expanded中
const headerData = {
containerName: '容器名称',
image: '镜像版本号',
podIp: 'PodIP',
restartCount: '重启次数',
status: '状态'
};
// 构造容器状态信息
// 处理containerStatuses,以显示restartCount和state
const podExpData = (item.status.containerStatuses || item.spec.containers.map(container => ({
name: container.name,
image: container.image,
// 如果没有containerStatuses,设置restartCount和state为"-"
restartCount: '-',
state: { status: '-' }
}))).map(container => {
let status = '-';
let conditionMessage = '';
// 解析容器状态
if (container.state) {
if (container.state.running) {
status = '运行中';
} else if (container.state.waiting) {
status = '等待中';
conditionMessage = container.state.waiting.message || conditionsMessage;
} else if (container.state.terminated) {
status = '已终止';
conditionMessage = container.state.terminated.message || conditionsMessage;
}
} else {
// 如果容器还没有状态(可能是pod还在Pending阶段),使用Pod的conditions信息
conditionMessage = conditionsMessage;
}
return {
containerName: container.name || '-',
image: container.image || '-',
podIp: item.status.podIP || '-',
restartCount: container.restartCount !== undefined ? container.restartCount.toString() : '-',
status: status,
conditionMessage: conditionMessage // 存储条件消息,以便于前端显示提示
};
});
podExpData.unshift(headerData);
const failureMessage = getFailureMessage(item);
// 构造表格数据项,并包含展开属性
return {
// ...其他属性...
FailedMessage: failureMessage,
podName: item.metadata.name,
Status: item.status.phase,
HostIp: item.status.hostIP,
podIp: item.status.podIP,
RunningTime: item.status.startTime ?calculateRunningTime(item.status.startTime): '0天0时',
restartCount: calculateTotalRestartCount(item),
createTime: formatDateTime(item.metadata.creationTimestamp),
expanded: podExpData, // 添加expanded字段
};
});
console.log("================================",newTableData)
tableData.value = newTableData
// 更新部署计数
podCount.value = resp.data.total;
return
}else if (type == 'statefulset'){
const podParameters = reactive({
page_size: pageSize,
page_number: currentPage,
statefulsetName: deploymentName,
namespace: namespace,
})
const resp = await STATEFULSET_POD_LIST(podParameters);
console.log("牛逼:::",resp)
// // 初始化expandedData数组并添加表头
// 遍历每个 Pod 并构造其展开信息
const newTableData = resp.data.items?.map(item => {
const conditionsMessage = item.status.conditions?.map(condition => condition.message).join(", ") || "";
// 表头数据,将直接作为第一个元素添加到每个expanded中
const headerData = {
containerName: '容器名称',
image: '镜像版本号',
podIp: 'PodIP',
restartCount: '重启次数',
status: '状态'
};
// 构造容器状态信息
// 处理containerStatuses,以显示restartCount和state
const podExpData = (item.status.containerStatuses || item.spec.containers.map(container => ({
name: container.name,
image: container.image,
// 如果没有containerStatuses,设置restartCount和state为"-"
restartCount: '-',
state: { status: '-' }
}))).map(container => {
let status = '-';
let conditionMessage = '';
// 解析容器状态
if (container.state) {
if (container.state.running) {
status = '运行中';
} else if (container.state.waiting) {
status = '等待中';
conditionMessage = container.state.waiting.message || conditionsMessage;
} else if (container.state.terminated) {
status = '已终止';
conditionMessage = container.state.terminated.message || conditionsMessage;
}
} else {
// 如果容器还没有状态(可能是pod还在Pending阶段),使用Pod的conditions信息
conditionMessage = conditionsMessage;
}
return {
containerName: container.name || '-',
image: container.image || '-',
podIp: item.status.podIP || '-',
restartCount: container.restartCount !== undefined ? container.restartCount.toString() : '-',
status: status,
conditionMessage: conditionMessage // 存储条件消息,以便于前端显示提示
};
});
podExpData.unshift(headerData);
const failureMessage = getFailureMessage(item);
// 构造表格数据项,并包含展开属性
return {
// ...其他属性...
FailedMessage: failureMessage,
podName: item.metadata.name,
Status: item.status.phase,
HostIp: item.status.hostIP,
podIp: item.status.podIP,
RunningTime: item.status.startTime ?calculateRunningTime(item.status.startTime): '0天0时',
restartCount: calculateTotalRestartCount(item),
createTime: formatDateTime(item.metadata.creationTimestamp),
expanded: podExpData, // 添加expanded字段
};
});
console.log("================================",newTableData)
tableData.value = newTableData
// 更新部署计数
podCount.value = resp.data.total;
return
} else if (type == 'deployment'){
const resp = await DEPLOYMENT_POD_LIST(podParameters);
console.log("牛逼:::",resp)
// // 初始化expandedData数组并添加表头
// 遍历每个 Pod 并构造其展开信息
const newTableData = resp.data.items?.map(item => {
const conditionsMessage = item.status.conditions?.map(condition => condition.message).join(", ") || "";
// 表头数据,将直接作为第一个元素添加到每个expanded中
const headerData = {
containerName: '容器名称',
image: '镜像版本号',
podIp: 'PodIP',
restartCount: '重启次数',
status: '状态'
};
// 构造容器状态信息
// 处理containerStatuses,以显示restartCount和state
const podExpData = (item.status.containerStatuses || item.spec.containers.map(container => ({
name: container.name,
image: container.image,
// 如果没有containerStatuses,设置restartCount和state为"-"
restartCount: '-',
state: { status: '-' }
}))).map(container => {
let status = '-';
let conditionMessage = '';
// 解析容器状态
if (container.state) {
if (container.state.running) {
status = '运行中';
} else if (container.state.waiting) {
status = '等待中';
conditionMessage = container.state.waiting.message || conditionsMessage;
} else if (container.state.terminated) {
status = '已终止';
conditionMessage = container.state.terminated.message || conditionsMessage;
}
} else {
// 如果容器还没有状态(可能是pod还在Pending阶段),使用Pod的conditions信息
conditionMessage = conditionsMessage;
}
return {
containerName: container.name || '-',
image: container.image || '-',
podIp: item.status.podIP || '-',
restartCount: container.restartCount !== undefined ? container.restartCount.toString() : '-',
status: status,
conditionMessage: conditionMessage // 存储条件消息,以便于前端显示提示
};
});
podExpData.unshift(headerData);
const failureMessage = getFailureMessage(item);
// 构造表格数据项,并包含展开属性
return {
// ...其他属性...
FailedMessage: failureMessage,
podName: item.metadata.name,
Status: item.status.phase,
HostIp: item.status.hostIP,
podIp: item.status.podIP,
RunningTime: item.status.startTime ?calculateRunningTime(item.status.startTime): '0天0时',
restartCount: calculateTotalRestartCount(item),
createTime: formatDateTime(item.metadata.creationTimestamp),
expanded: podExpData, // 添加expanded字段
};
});
console.log("================================",newTableData)
tableData.value = newTableData
// 更新部署计数
podCount.value = resp.data.total;
}
} catch (e) {
console.log(e);
}
};
function getStatus(state) {
if (!state) return '未知';
if (state.running) {
return '运行中';
} else if (state.waiting) {
return `等待中 (${state.waiting.reason})`;
} else if (state.terminated) {
return `已终止 (${state.terminated.reason})`;
} else {
return '未知';
}
}
const getFailureMessage = (pod) => {
// 假设失败信息在条件列表中
if (pod.status && pod.status.conditions) {
const unschedulableCondition = pod.status.conditions.find(condition => condition.reason === 'Unschedulable');
return unschedulableCondition ? unschedulableCondition.message : 'No failure message';
}
return 'No failure message';
};
const calculateTotalRestartCount = (pod) => {
if (pod.status && Array.isArray(pod.status.containerStatuses)) {
return pod.status.containerStatuses.reduce((total, containerStatus) => {
return total + (containerStatus.restartCount || 0);
}, 0);
}
return '-';
};
// 设置watcher来监听selectedNamespace的改变
watch(selectedNamespace, (newNamespace) => {
// 更新deploymentParameters中的namespace
podParameters.namespace = newNamespace;
// 重新调用getDeployData以获取新命名空间的部署数据
getPodsData();
});
const formatDateTime = (dateTimeString) => {
return dateTimeString.replace('T', ' ').replace('Z', '');
};
onBeforeMount( async ()=> {
// 尝试从 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);
// }
getPodsData()
// 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;
}
.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; /* 调整按钮之间的间隔 */
}
.paginations {
margin-top: 10px;
margin-left: 20px;
margin-bottom: 10px;
}
.parent-container {
display: flex; /* 设置父容器为flex布局 */
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100%; /* 父容器需要有确定的高度 */
background-color: #F3F4F7;
}
.expanded-content {
width: 95%;
border: 1px solid #dcdcdc; /* 表格四周的外边框 */
background-color: white;
margin-top: 10px;
margin-bottom: 10px;
}
.row {
display: flex;
}
.cell {
flex: 1; /* 让所有单元格平均分配空间 */
text-align: center;
padding: 10px; /* 单元格内边距 */
border-top: none; /* 默认去除顶部边框 */
}
/* 只给第一行的最底部添加边框 */
.row:first-child .cell {
border-bottom: 1px solid #dcdcdc;
}
/* CSS 类来居中表格单元格内容 */
.text-center {
text-align: center;
}
::v-deep .text-center {
text-align: center !important;
}
/* 设置选择器的布局 */
.selectors {
display: flex; /* 使用flex布局 */
align-items: center; /* 垂直居中对齐 */
margin-left: 20px; /* 距离返回按钮的左边距 */
}
/* 设置选择器的样式 */
.selector {
margin-right: 20px; /* 选择器之间的右边距 */
}
/* 移除最后一个选择器的右边距 */
.selector:last-child {
margin-right: 0;
}
/* 日志行样式 */
.log-line {
display: flex;
}
/* 行号样式 */
.line-number {
color: #999;
min-width: 50px; /* 为行号保留足够宽度 */
text-align: right; /* 行号右对齐 */
padding-right: 15px; /* 行号和内容之间的间距 */
}
/* 日志内容样式 */
.line-content {
flex: 1;
white-space: pre; /* 保留空格和格式 */
}
.log-container {
height: 700px;
}
</style>
3、DaemonSet开发
(1)功能
列表、详情、更新、删除
<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="goToCreateDeployment()">
<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="daemonsetName" label="Daemonset名" width="180px" align="center">
<template #default="{ row }">
<div>
<span style="vertical-align: middle; color: #1395FF; cursor: pointer;" @click="handleRowClick(row)">
{{ row.daemonsetName }}
</span>
</div>
</template>
</el-table-column>
<el-table-column prop="Labels" label="标签" width="200px" 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="Pod数量 (运行/期望)" align="center" width="200px">
<template #default="{ row }">
<div style="display: flex; align-items: center;justify-content: center;height: 100%">
{{ row.Status }}
<img v-if="row.showLoading" src="@/assets/load.svg" class="loading-icon" alt="loading"/>
</div>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" align="center" width="155px">
<template #default="{ row }">
<div>
<el-tag class="ml-2" type="warning">{{row.createTime}}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="Images" label="镜像" align="center" width="180px">
<template #default="{ row }">
<div>
<el-tooltip
v-for="(images, index) in row.Images"
:key="index"
class="item"
effect="dark"
:content="images"
placement="top"
>
<el-tag class="ml-2" type="success">{{ images }}</el-tag>
</el-tooltip>
</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="openScaleDiglog(row)">-->
<!-- <el-icon><Tickets /></el-icon>-->
<!-- <span style="vertical-align: middle"> 扩缩 </span>-->
<!-- </el-button>-->
<!-- <el-button size="small" color="#F58D79" :dark="isDark" plain @click="restartDeployment(row)">-->
<!-- <el-icon><Tickets /></el-icon>-->
<!-- <span style="vertical-align: middle"> 重启 </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="创建Daemonset" v-model="createDialogVisible" width="60%">
<el-form ref="form" :model="createForm" label-position="left" label-width="180px">
<div class="input-with-help">
<el-form-item label="daemonset名:" prop="name" class="form-item-with-help">
<div class="input-deployname">
<el-input v-model="createForm.daemonsetName" placeholder="请输入daemonset名"></el-input>
<p class="help-text">
最长63个字符,只能包含小写字母、数字及分隔符("-"),且必须以小写字母开头,数字或小写字母结尾
</p>
</div>
</el-form-item>
</div>
<el-form-item label="描述:" prop="description" style="margin: 10px;margin-left: 20px">
<el-input
type="textarea"
v-model="createForm.description"
placeholder="请输入描述信息"
class="custom-textarea"
></el-input>
</el-form-item>
<el-form-item label="标签:" prop="labels" class="form-item-with-help">
<div v-for="(item, index) in formItems" :key="item.id" class="form-item">
<el-input
placeholder="Key"
v-model="item.key"
class="input-item"
:disabled="!item.editable"
></el-input>
<span> = </span>
<el-input
placeholder="Value"
v-model="item.value"
class="input-item"
></el-input>
<el-button @click="removeFormItem(index)" :disabled="!item.editable">
<el-icon><CircleClose /></el-icon>
</el-button>
</div>
<!-- 将添加新行的按钮移动到循环外部 -->
</el-form-item>
<div>
<el-button
type="primary"
@click="() => addFormItem(item)"
class="add-button"
>
添加新标签
</el-button>
</div>
<el-form-item label="命名空间:" prop="namespace" style="margin: 10px">
<el-select v-model="createForm.namespace" placeholder="请选择">
<el-option
v-for="namespace in namespaces"
:key="namespace"
:label="namespace"
:value="namespace">
</el-option>
</el-select>
</el-form-item>
<el-form-item class="custom-form-item" label="实例内容器:" style="margin: 10px">
<div class="container">
<div v-for="(item, index) in instanceItems" :key="item.id" class="item-container">
<div class="content-container" :class="{ 'collapsed': item.collapsed }">
<div class="info" v-if="item.collapsed">
<span>{{ item.name }}/</span> <!-- 显示名称 -->
<span>{{ item.image }}:{{ item.image_tag }}</span> <!-- 显示镜像 -->
</div>
<div class="actions" v-if="item.collapsed">
<el-button @click="expandInstanceItem(index)">编辑</el-button>
<el-button @click="removeInstanceItem(index)" :disabled="instanceItems.length === 1">删除</el-button>
</div>
<div class="info" v-else>
<!-- <span >{{ item.name }}</span>-->
<div class="input-with-help" v-if="!item.collapsed">
<el-form-item label="名称:" prop="name" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.name" placeholder="请输入容器名"></el-input>
<p class="help-text">
最长63个字符,只能包含小写字母、数字及分隔符("-"),且不能以分隔符开头或结尾
</p>
</div>
</el-form-item>
<el-form-item label="镜像:" prop="image" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.image" placeholder=""></el-input>
</div>
</el-form-item>
<el-form-item label="镜像版本:" prop="image_tag" class="form-item-with-resource">
<div class="input-containername">
<el-input v-model="item.image_tag" placeholder=""></el-input>
</div>
</el-form-item>
<el-form-item label="CPU/内存限制:" prop="form-item-with-help" class="form-item-resource">
<div class="input-container-cpu">
<label class="cpu-label">CPU:</label>
<div class="input-row">
<el-input v-model="item.cpu_request" >
<template #prepend>request</template>
</el-input>
<el-input v-model="item.cpu_limit" >
<template #prepend>limit</template>
</el-input>
<label >m</label>
</div>
</div>
<div class="input-container-memory">
<label class="cpu-label">内存:</label>
<div class="input-row">
<el-input v-model="item.memory_request">
<template #prepend>request</template>
</el-input>
<el-input v-model="item.memory_limit">
<template #prepend>limit</template>
</el-input>
<label >MiB</label>
</div>
</div>
</el-form-item>
<el-form-item label="工作目录:" prop="workspace" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.workspace" placeholder=""></el-input>
<p class="help-text">
指定容器运行后的工作目录
</p>
</div>
</el-form-item>
<el-form-item label="运行命令:" prop="cmd" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.cmd" placeholder=""></el-input>
<p class="help-text">
控制容器运行的输入命令
</p>
</div>
</el-form-item>
<el-form-item label="运行参数:" prop="args" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.args" placeholder=""></el-input>
<p class="help-text">
传递给容器运行命令的输入参数
</p>
</div>
</el-form-item>
<!-- HTML -->
<el-form-item label="容器健康检查" prop="health" class="form-item-with-help">
<div class="input-healthContainer">
<el-checkbox v-model="item.isLivenessCheckActive">存活检查</el-checkbox>
<div v-if="item.isLivenessCheckActive">
<div class="livenessdiv">
<div class="check-method-container">
<label class="method-label">检查方法:</label>
<el-select v-model="item.livenessselectedCheckMethod" placeholder="请选择">
<el-option label="HTTP" value="http"></el-option>
<el-option label="TCP" value="tcp"></el-option>
<el-option label="Exec" value="exec"></el-option>
</el-select>
</div>
<!-- HTTP选项时显示 -->
<div v-if="item.livenessselectedCheckMethod === 'http'">
<div class="check-method-container">
<label class="method-label">检查协议:</label>
<el-select v-model="item.lives_selectedProtocol" placeholder="请选择">
<el-option label="HTTP" value="http"></el-option>
<el-option label="HTTPS" value="https"></el-option>
</el-select>
</div>
<div class="check-method-container">
<label class="command-label">检查端口:</label>
<el-input v-model="item.lives_healthCheckPort" placeholder="请输入端口" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">端口范围: 1~65535</span>
</div>
<div class="check-method-container">
<label class="command-label">请求路径:</label>
<el-input v-model="item.lives_healthCheckPath" placeholder="请输入路径" style="width: 200px;margin-left: 20px"></el-input>
</div>
</div>
<!-- TCP选项时显示 -->
<div v-else-if="item.livenessselectedCheckMethod === 'tcp'">
<div class="check-method-container">
<label class="command-label">检查端口:</label>
<el-input v-model="item.lives_healthCheckPort" placeholder="请输入端口" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">端口范围: 1~65535</span>
</div>
</div>
<!-- Exec选项时显示 -->
<div v-else-if="item.livenessselectedCheckMethod === 'exec'">
<div class="execution-command-container">
<label class="command-label">执行命令:</label>
<el-input v-model="item.livenessCommand" placeholder="请输入命令" style="width: 200px;margin-left: 20px"></el-input>
</div>
</div>
<!-- 启动延时始终显示 -->
<div class="check-method-container">
<label class="command-label">启动延时:</label>
<el-input v-model="item.livenessStartDelay" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:0~60秒</span>
</div>
<!-- 响应超时始终显示 -->
<div class="check-method-container">
<label class="command-label">响应延时:</label>
<el-input v-model="item.livenessResDelay" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:2~60秒</span>
</div>
<!-- 间隔时间始终显示 -->
<div class="check-method-container">
<label class="command-label">间隔时间:</label>
<el-input v-model="item.livenessIntervalTime" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:2~300秒</span>
</div>
<!-- 健康阈值始终显示 -->
<div class="check-method-container">
<label class="command-label">健康阈值:</label>
<el-input disabled v-model="item.livenessHealththreshold" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:1次</span>
</div>
<!-- 不健康阈值始终显示 -->
<div class="check-method-container">
<label class="command-label">不健康阈值:</label>
<el-input v-model="item.livenessUnHealththreshold" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:1~10次</span>
</div>
</div>
</div>
<!-- 就绪性检查 -->
<div>
<el-checkbox v-model="item.isReadinessCheckActive">就绪检查</el-checkbox>
<div v-if="item.isReadinessCheckActive">
<div class="livenessdiv">
<div class="check-method-container">
<label class="method-label">检查方法:</label>
<el-select v-model="item.ReadinessSelectedCheckMethod" placeholder="请选择">
<el-option label="HTTP" value="http"></el-option>
<el-option label="TCP" value="tcp"></el-option>
<el-option label="Exec" value="exec"></el-option>
</el-select>
</div>
<!-- HTTP选项时显示 -->
<div v-if="item.ReadinessSelectedCheckMethod === 'http'">
<div class="check-method-container">
<label class="method-label">检查协议:</label>
<el-select v-model="item.readnessSelectedProtocol" placeholder="请选择">
<el-option label="HTTP" value="http"></el-option>
<el-option label="HTTPS" value="https"></el-option>
</el-select>
</div>
<div class="check-method-container">
<label class="command-label">检查端口:</label>
<el-input v-model="item.readnessHealthCheckPort" placeholder="请输入端口" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">端口范围: 1~65535</span>
</div>
<div class="check-method-container">
<label class="command-label">请求路径:</label>
<el-input v-model="item.readnessHealthCheckPath" placeholder="请输入路径" style="width: 200px;margin-left: 20px"></el-input>
</div>
</div>
<!-- TCP选项时显示 -->
<div v-else-if="item.ReadinessSelectedCheckMethod === 'tcp'">
<div class="check-method-container">
<label class="command-label">检查端口:</label>
<el-input v-model="item.readnessHealthCheckPort" placeholder="请输入端口" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">端口范围: 1~65535</span>
</div>
</div>
<!-- Exec选项时显示 -->
<div v-else-if="item.ReadinessSelectedCheckMethod === 'exec'">
<div class="execution-command-container">
<label class="command-label">执行命令:</label>
<el-input v-model="item.readnessCommand" placeholder="请输入命令" style="width: 200px;margin-left: 20px"></el-input>
</div>
</div>
<!-- 启动延时始终显示 -->
<div class="check-method-container">
<label class="command-label">启动延时:</label>
<el-input v-model="item.readnessStartDelay" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:0~60秒</span>
</div>
<!-- 响应超时始终显示 -->
<div class="check-method-container">
<label class="command-label">响应延时:</label>
<el-input v-model="item.readnessResDelay" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:2~60秒</span>
</div>
<!-- 间隔时间始终显示 -->
<div class="check-method-container">
<label class="command-label">间隔时间:</label>
<el-input v-model="item.readnessIntervalTime" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:2~300秒</span>
</div>
<!-- 健康阈值始终显示 -->
<div class="check-method-container">
<label class="command-label">健康阈值:</label>
<el-input v-model="item.readnessHealththreshold" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:1~10次</span>
</div>
<!-- 不健康阈值始终显示 -->
<div class="check-method-container">
<label class="command-label">不健康阈值:</label>
<el-input v-model="item.readnessUnHealththreshold" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:1~10次</span>
</div>
</div>
</div>
</div>
</div>
</el-form-item>
<el-form-item label="副本数:" prop="replica" class="form-item-with-help">
<div class="input-containername">
<el-input-number v-model="createForm.replicas" :min="1" @change="changeReplicas" />
</div>
</el-form-item>
</div>
</div>
<div class="actions" v-if="!item.collapsed">
<el-button v-if="item.editMode" @click="saveInstanceItem(index)">保存</el-button>
<el-button @click="removeInstanceItem(index)" :disabled="instanceItems.length === 1">删除</el-button>
</div>
</div>
</div>
</div>
</el-form-item>
<el-button type="primary" @click="addInstanceItem" class="add-button">
添加新实例
</el-button>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="createDialogVisible = false">取消</el-button>
<el-button type="primary" @click="createDeployment">创建</el-button>
</span>
</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>
</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_DAEMONSET,
CREATE_DEPLOYMENT,
CREATE_NAMESPACE, DAEMONSET_DETAIL, DAEMONSET_LIST, DELETE_DAEMONSET, DELETE_DEPLOYMENT,
DELETE_NAMESPACE, DEPLOYMENT_DETAIL, DEPLOYMENT_LIST,
NAMESPACE_DETAIL,
NAMESPACE_LIST, RESTART_DEPLOYMENT, UPDATE_DAEMONSET, UPDATE_DEPLOYMENT, UPDATE_DEPLOYMENTSCALE,
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";
import {value} from "lodash/seq.js";
const extensions = [javascript(),oneDark]
const dialogSelectedNamespace = ref(''); // 对话框的v-model
const scaleDialogVisible= ref(false)
const replicaCount = ref(1)
const currentRow = ref(null)
const openScaleDiglog = (row)=>{
replicaCount.value = row.Replicas
currentRow.value = row
scaleDialogVisible.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)
getDaemonsetData()
} catch (e) {
console.error(e);
return []; // 出错时返回空数组
}
// 在这里编写更新逻辑
scaleDialogVisible.value = false; // 关闭对话框
}
const handleRowClick = (row) => {
router.push({
name: 'Pod',
params: { type: 'daemonset',deploymentName: row.daemonsetName ,namespace: selectedNamespace.value}
});
};
const createDialogVisible = ref(false);
const createForm = ref({
daemonsetName: '',
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 createDeployment = async ()=> {
const labels = formItems.value.reduce((acc, item) => {
if (item.key && item.value) {
acc[item.key] = item.value;
}
return acc;
}, {});
console.log("Instance Items before sending to backend:", instanceItems.value);
instanceItems.value.forEach(item => {
console.log("Readiness Port for item:", item.readnessHealthCheckPort);
console.log("Parsed Port:", parseInt(item.readnessHealthCheckPort));
// 确认 parseInt 不是返回 NaN
if (isNaN(parseInt(item.readnessHealthCheckPort))) {
console.error("Invalid port number: ", item.readnessHealthCheckPort);
}
});
console.log("label = =================",labels)
console.log(instanceItems.value)
console.log(createForm.value.replicas)
const createParameters = ref({
"namespace": createForm.value.namespace,
"daemonsetName": createForm.value.daemonsetName,
"podName": createForm.value.daemonsetName,
"label": labels,
"replicas": createForm.value.replicas,
"containers": instanceItems.value.map(item => ({
// 如果是空字符串则设置为 false
"name": item.name,
"image": item.image,
"version": item.image_tag,
// "ports": item.ports, // 确保这个字段是一个 int32 数组
"limits_cpu": item.cpu_limit,
"limit_memory": item.memory_limit,
"requests_cpu": item.cpu_request,
"requests_memory": item.memory_request,
"work_space": item.workspace,
"command": item.cmd,
"args": item.args.split(','), // 前提是 args 是以空格分隔的字符串
"readiness_healthCheck": item.isReadinessCheckActive || false,
"liveness_healthCheck": item.isLivenessCheckActive || false,
"healthcheckTypeLive": mapHealthCheckType(item.livenessselectedCheckMethod),
"healthcheckTypeRead": mapHealthCheckType(item.ReadinessSelectedCheckMethod),
"resource": {
},
"livenessProbe": {
"path": item.lives_healthCheckPath,
"port": parseInt(item.lives_healthCheckPort),
"cmd": item.livenessCommand,
"initialDelaySecond": parseInt(item.livenessStartDelay),
"timeoutSeconds": parseInt(item.livenessResDelay),
"periodSeconds": parseInt(item.livenessIntervalTime),
"successThreshold": parseInt(item.livenessHealththreshold),
"failureThreshold": parseInt(item.livenessUnHealththreshold),
},
"readinessProbe": {
"path": item.readnessHealthCheckPath,
"port": parseInt(item.readnessHealthCheckPort),
"cmd": item.readnessCommand,
"initialDelaySecond": parseInt(item.readnessStartDelay),
"timeoutSeconds": parseInt(item.readnessResDelay),
"periodSeconds": parseInt(item.readnessIntervalTime),
"successThreshold": parseInt(item.readnessHealththreshold),
"failureThreshold": parseInt(item.readnessUnHealththreshold),
},
}))
});
console.log(createParameters.value)
// 发送 createParameters.value 到后端...
try {
const resps = await CREATE_DAEMONSET(createParameters.value);
console.log(resps)
if (resps.code === 200){
createDialogVisible.value = false
ElMessage.success("创建成功")
getDaemonsetData()
}else {
ElMessage.error(resps.data.message)
}
} catch (e) {
console.error(e);
return []; // 出错时返回空数组
}
console.log(createForm.value)
console.log("标签:",formItems.value)
console.log("~~~~~~~~~~~~~~~~")
console.log(instanceItems.value)
}
// 调用 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();
};
// 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}" 已重启`
});
getDaemonsetData()
}catch (e){
console.log(e)
ElMessage.error(`重启失败: ${e}`);
}
}).catch(() => {
});
}
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 updateDeploy = async () =>{
try {
const updatedJson = yaml2obj.load(yamlContent.value)
const jsonString = JSON.stringify(updatedJson);
console.log("?????????",updatedJson)
updateParameters.deploymentName = currentDeployName.value
updateParameters.content = jsonString
await updateDeployData()
await getDaemonsetData()
}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.daemonsetName);
// 这里可以编写显示YAML信息的逻辑
// 假设提供了 `getYAMLContent` 方法来获取 YAML 内容
// 直接使用传入的 row 作为 YAML 内容
getDeployDetailData(row.daemonsetName)
};
const confirmDelete = (row) => {confirmDelete
ElMessageBox.confirm(
`确定要删除Daemonset "${row.daemonsetName}" 吗?`,
'警告',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
).then(async () => {
const deleteNSParameters = ref({
daemonSetName: row.daemonsetName,
namespace: selectedNamespace.value,
})
console.log(deleteNSParameters.value)
try {
const resp = await DELETE_DAEMONSET(deleteNSParameters.value)
console.log("————————————————",resp)
ElMessage({
type: 'success',
message: `daemonset "${row.daemonsetName}" 已被删除`
});
getDaemonsetData()
}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([
{
daemonsetName: "",
Labels: '',
Status: '',
Images: '',
createTime:"",
Operation:"",
Replicas: '',
},
// ...其他数据项
]);
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 DAEMONSET_LIST(deploymentParameters);
if (resp && resp.data && Array.isArray(resp.data.items)) {
tableData.value = resp.data.items.map((item) => {
const statuss = item.status;
const specs = item.spec;
// 运行中的副本数
const runningReplicas = statuss.currentNumberScheduled;
// 无法用的副本数
const unavailableReplicas = statuss.numberUnavailable;
// 是否展示加载图标 - 如果还有Pods未在运行状态,则显示加载图标
const showLoading = statuss.desiredNumberScheduled !== statuss.currentNumberScheduled;
return {
daemonsetName: item.metadata.name,
Labels: formatLabels(item.metadata.labels),
showLoading: showLoading, // 添加 showLoading 标志
// 更新状态显示为“运行副本数/期望副本数”格式
Status: `${runningReplicas - (unavailableReplicas || 0)}/${runningReplicas}`,
Images: item.spec.template.spec.containers.map(container => container.image),
createTime: formatDateTime(item.metadata.creationTimestamp),
Replicas: item.spec.replicas
// 其他属性...
};
});
}
console.log(tableData.value,"wocao================================================================")
// 更新部署计数
deploymentCount.value = resp.data.count;
} catch (e) {
console.log(e);
}
};
const refresh = () => {
getDaemonsetData()
console.log('刷新表格数据');
};
const updateParameters = reactive({
deploymentName: "",
namespace: selectedNamespace,
content: ""
})
const updateDeployData = async ()=>{
try {
console.log("你吗的",updateParameters.content)
const resp = await UPDATE_DAEMONSET(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以获取新命名空间的部署数据
getDaemonsetData();
});
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 getDaemonsetData = async () => {
try {
const resp = await DAEMONSET_LIST(deploymentParameters);
if (resp && resp.data && Array.isArray(resp.data.items)) {
tableData.value = resp.data.items.map((item) => {
const statuss = item.status;
const specs = item.spec;
// 运行中的副本数
const runningReplicas = statuss.currentNumberScheduled;
// 无法用的副本数
const unavailableReplicas = statuss.numberUnavailable;
// 是否展示加载图标 - 如果还有Pods未在运行状态,则显示加载图标
const showLoading = statuss.desiredNumberScheduled !== statuss.currentNumberScheduled;
return {
daemonsetName: item.metadata.name,
Labels: formatLabels(item.metadata.labels),
showLoading: showLoading, // 添加 showLoading 标志
// 更新状态显示为“运行副本数/期望副本数”格式
Status: `${runningReplicas - (unavailableReplicas || 0)}/${runningReplicas}`,
Images: item.spec.template.spec.containers.map(container => container.image),
createTime: formatDateTime(item.metadata.creationTimestamp),
Replicas: item.spec.replicas
// 其他属性...
};
});
}
console.log(tableData.value,"wocao================================================================")
// 更新部署计数
deploymentCount.value = resp.data.count;
} catch (e) {
console.log(e);
}
};
const getDeployDetailData = async (daemonsetName)=>{
try {
const daemonsetDetailparams = reactive({
daemonSetName: daemonsetName,
namespace: selectedNamespace.value
})
console.log("ddddddddd",daemonsetDetailparams)
const resp = await DAEMONSET_DETAIL(daemonsetDetailparams)
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(() => {
getDaemonsetData();
}, 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);
}
getDaemonsetData()
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>
4、statefulset开发
<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="goToCreateDeployment()">
<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="statefulsetName" label="Statefulset名" width="180px" align="center">
<template #default="{ row }">
<div>
<span style="vertical-align: middle; color: #1395FF; cursor: pointer;" @click="handleRowClick(row)">
{{ row.statefulsetName }}
</span>
</div>
</template>
</el-table-column>
<el-table-column prop="Labels" label="标签" width="200px" 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="Pod数量 (运行/期望)" align="center" width="200px">
<template #default="{ row }">
<div style="display: flex; align-items: center;justify-content: center;height: 100%">
{{ row.Status }}
<img v-if="row.showLoading" src="@/assets/load.svg" class="loading-icon" alt="loading"/>
</div>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" align="center" width="155px">
<template #default="{ row }">
<div>
<el-tag class="ml-2" type="warning">{{row.createTime}}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="Images" label="镜像" align="center" width="180px">
<template #default="{ row }">
<div>
<el-tooltip
v-for="(images, index) in row.Images"
:key="index"
class="item"
effect="dark"
:content="images"
placement="top"
>
<el-tag class="ml-2" type="success">{{ images }}</el-tag>
</el-tooltip>
</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="openScaleDiglog(row)">-->
<!-- <el-icon><Tickets /></el-icon>-->
<!-- <span style="vertical-align: middle"> 扩缩 </span>-->
<!-- </el-button>-->
<!-- <el-button size="small" color="#F58D79" :dark="isDark" plain @click="restartDeployment(row)">-->
<!-- <el-icon><Tickets /></el-icon>-->
<!-- <span style="vertical-align: middle"> 重启 </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="创建StatefulSet" v-model="createDialogVisible" width="60%">
<el-form ref="form" :model="createForm" label-position="left" label-width="180px">
<div class="input-with-help">
<el-form-item label="StatefulSet名:" prop="name" class="form-item-with-help">
<div class="input-deployname">
<el-input v-model="createForm.StatefulSetName" placeholder="请输入StatefulSet名"></el-input>
<p class="help-text">
最长63个字符,只能包含小写字母、数字及分隔符("-"),且必须以小写字母开头,数字或小写字母结尾
</p>
</div>
</el-form-item>
</div>
<el-form-item label="描述:" prop="description" style="margin: 10px;margin-left: 20px">
<el-input
type="textarea"
v-model="createForm.description"
placeholder="请输入描述信息"
class="custom-textarea"
></el-input>
</el-form-item>
<el-form-item label="标签:" prop="labels" class="form-item-with-help">
<div v-for="(item, index) in formItems" :key="item.id" class="form-item">
<el-input
placeholder="Key"
v-model="item.key"
class="input-item"
:disabled="!item.editable"
></el-input>
<span> = </span>
<el-input
placeholder="Value"
v-model="item.value"
class="input-item"
></el-input>
<el-button @click="removeFormItem(index)" :disabled="!item.editable">
<el-icon><CircleClose /></el-icon>
</el-button>
</div>
<!-- 将添加新行的按钮移动到循环外部 -->
</el-form-item>
<div>
<el-button
type="primary"
@click="() => addFormItem(item)"
class="add-button"
>
添加新标签
</el-button>
</div>
<el-form-item label="命名空间:" prop="namespace" style="margin: 10px">
<el-select v-model="createForm.namespace" placeholder="请选择">
<el-option
v-for="namespace in namespaces"
:key="namespace"
:label="namespace"
:value="namespace">
</el-option>
</el-select>
</el-form-item>
<el-form-item class="custom-form-item" label="实例内容器:" style="margin: 10px">
<div class="container">
<div v-for="(item, index) in instanceItems" :key="item.id" class="item-container">
<div class="content-container" :class="{ 'collapsed': item.collapsed }">
<div class="info" v-if="item.collapsed">
<span>{{ item.name }}/</span> <!-- 显示名称 -->
<span>{{ item.image }}:{{ item.image_tag }}</span> <!-- 显示镜像 -->
</div>
<div class="actions" v-if="item.collapsed">
<el-button @click="expandInstanceItem(index)">编辑</el-button>
<el-button @click="removeInstanceItem(index)" :disabled="instanceItems.length === 1">删除</el-button>
</div>
<div class="info" v-else>
<!-- <span >{{ item.name }}</span>-->
<div class="input-with-help" v-if="!item.collapsed">
<el-form-item label="名称:" prop="name" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.name" placeholder="请输入容器名"></el-input>
<p class="help-text">
最长63个字符,只能包含小写字母、数字及分隔符("-"),且不能以分隔符开头或结尾
</p>
</div>
</el-form-item>
<el-form-item label="镜像:" prop="image" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.image" placeholder=""></el-input>
</div>
</el-form-item>
<el-form-item label="镜像版本:" prop="image_tag" class="form-item-with-resource">
<div class="input-containername">
<el-input v-model="item.image_tag" placeholder=""></el-input>
</div>
</el-form-item>
<el-form-item label="CPU/内存限制:" prop="form-item-with-help" class="form-item-resource">
<div class="input-container-cpu">
<label class="cpu-label">CPU:</label>
<div class="input-row">
<el-input v-model="item.cpu_request" >
<template #prepend>request</template>
</el-input>
<el-input v-model="item.cpu_limit" >
<template #prepend>limit</template>
</el-input>
<label >m</label>
</div>
</div>
<div class="input-container-memory">
<label class="cpu-label">内存:</label>
<div class="input-row">
<el-input v-model="item.memory_request">
<template #prepend>request</template>
</el-input>
<el-input v-model="item.memory_limit">
<template #prepend>limit</template>
</el-input>
<label >MiB</label>
</div>
</div>
</el-form-item>
<el-form-item label="工作目录:" prop="workspace" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.workspace" placeholder=""></el-input>
<p class="help-text">
指定容器运行后的工作目录
</p>
</div>
</el-form-item>
<el-form-item label="运行命令:" prop="cmd" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.cmd" placeholder=""></el-input>
<p class="help-text">
控制容器运行的输入命令
</p>
</div>
</el-form-item>
<el-form-item label="运行参数:" prop="args" class="form-item-with-help">
<div class="input-containername">
<el-input v-model="item.args" placeholder=""></el-input>
<p class="help-text">
传递给容器运行命令的输入参数
</p>
</div>
</el-form-item>
<!-- HTML -->
<el-form-item label="容器健康检查" prop="health" class="form-item-with-help">
<div class="input-healthContainer">
<el-checkbox v-model="item.isLivenessCheckActive">存活检查</el-checkbox>
<div v-if="item.isLivenessCheckActive">
<div class="livenessdiv">
<div class="check-method-container">
<label class="method-label">检查方法:</label>
<el-select v-model="item.livenessselectedCheckMethod" placeholder="请选择">
<el-option label="HTTP" value="http"></el-option>
<el-option label="TCP" value="tcp"></el-option>
<el-option label="Exec" value="exec"></el-option>
</el-select>
</div>
<!-- HTTP选项时显示 -->
<div v-if="item.livenessselectedCheckMethod === 'http'">
<div class="check-method-container">
<label class="method-label">检查协议:</label>
<el-select v-model="item.lives_selectedProtocol" placeholder="请选择">
<el-option label="HTTP" value="http"></el-option>
<el-option label="HTTPS" value="https"></el-option>
</el-select>
</div>
<div class="check-method-container">
<label class="command-label">检查端口:</label>
<el-input v-model="item.lives_healthCheckPort" placeholder="请输入端口" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">端口范围: 1~65535</span>
</div>
<div class="check-method-container">
<label class="command-label">请求路径:</label>
<el-input v-model="item.lives_healthCheckPath" placeholder="请输入路径" style="width: 200px;margin-left: 20px"></el-input>
</div>
</div>
<!-- TCP选项时显示 -->
<div v-else-if="item.livenessselectedCheckMethod === 'tcp'">
<div class="check-method-container">
<label class="command-label">检查端口:</label>
<el-input v-model="item.lives_healthCheckPort" placeholder="请输入端口" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">端口范围: 1~65535</span>
</div>
</div>
<!-- Exec选项时显示 -->
<div v-else-if="item.livenessselectedCheckMethod === 'exec'">
<div class="execution-command-container">
<label class="command-label">执行命令:</label>
<el-input v-model="item.livenessCommand" placeholder="请输入命令" style="width: 200px;margin-left: 20px"></el-input>
</div>
</div>
<!-- 启动延时始终显示 -->
<div class="check-method-container">
<label class="command-label">启动延时:</label>
<el-input v-model="item.livenessStartDelay" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:0~60秒</span>
</div>
<!-- 响应超时始终显示 -->
<div class="check-method-container">
<label class="command-label">响应延时:</label>
<el-input v-model="item.livenessResDelay" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:2~60秒</span>
</div>
<!-- 间隔时间始终显示 -->
<div class="check-method-container">
<label class="command-label">间隔时间:</label>
<el-input v-model="item.livenessIntervalTime" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:2~300秒</span>
</div>
<!-- 健康阈值始终显示 -->
<div class="check-method-container">
<label class="command-label">健康阈值:</label>
<el-input disabled v-model="item.livenessHealththreshold" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:1次</span>
</div>
<!-- 不健康阈值始终显示 -->
<div class="check-method-container">
<label class="command-label">不健康阈值:</label>
<el-input v-model="item.livenessUnHealththreshold" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:1~10次</span>
</div>
</div>
</div>
<!-- 就绪性检查 -->
<div>
<el-checkbox v-model="item.isReadinessCheckActive">就绪检查</el-checkbox>
<div v-if="item.isReadinessCheckActive">
<div class="livenessdiv">
<div class="check-method-container">
<label class="method-label">检查方法:</label>
<el-select v-model="item.ReadinessSelectedCheckMethod" placeholder="请选择">
<el-option label="HTTP" value="http"></el-option>
<el-option label="TCP" value="tcp"></el-option>
<el-option label="Exec" value="exec"></el-option>
</el-select>
</div>
<!-- HTTP选项时显示 -->
<div v-if="item.ReadinessSelectedCheckMethod === 'http'">
<div class="check-method-container">
<label class="method-label">检查协议:</label>
<el-select v-model="item.readnessSelectedProtocol" placeholder="请选择">
<el-option label="HTTP" value="http"></el-option>
<el-option label="HTTPS" value="https"></el-option>
</el-select>
</div>
<div class="check-method-container">
<label class="command-label">检查端口:</label>
<el-input v-model="item.readnessHealthCheckPort" placeholder="请输入端口" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">端口范围: 1~65535</span>
</div>
<div class="check-method-container">
<label class="command-label">请求路径:</label>
<el-input v-model="item.readnessHealthCheckPath" placeholder="请输入路径" style="width: 200px;margin-left: 20px"></el-input>
</div>
</div>
<!-- TCP选项时显示 -->
<div v-else-if="item.ReadinessSelectedCheckMethod === 'tcp'">
<div class="check-method-container">
<label class="command-label">检查端口:</label>
<el-input v-model="item.readnessHealthCheckPort" placeholder="请输入端口" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">端口范围: 1~65535</span>
</div>
</div>
<!-- Exec选项时显示 -->
<div v-else-if="item.ReadinessSelectedCheckMethod === 'exec'">
<div class="execution-command-container">
<label class="command-label">执行命令:</label>
<el-input v-model="item.readnessCommand" placeholder="请输入命令" style="width: 200px;margin-left: 20px"></el-input>
</div>
</div>
<!-- 启动延时始终显示 -->
<div class="check-method-container">
<label class="command-label">启动延时:</label>
<el-input v-model="item.readnessStartDelay" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:0~60秒</span>
</div>
<!-- 响应超时始终显示 -->
<div class="check-method-container">
<label class="command-label">响应延时:</label>
<el-input v-model="item.readnessResDelay" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:2~60秒</span>
</div>
<!-- 间隔时间始终显示 -->
<div class="check-method-container">
<label class="command-label">间隔时间:</label>
<el-input v-model="item.readnessIntervalTime" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:2~300秒</span>
</div>
<!-- 健康阈值始终显示 -->
<div class="check-method-container">
<label class="command-label">健康阈值:</label>
<el-input v-model="item.readnessHealththreshold" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:1~10次</span>
</div>
<!-- 不健康阈值始终显示 -->
<div class="check-method-container">
<label class="command-label">不健康阈值:</label>
<el-input v-model="item.readnessUnHealththreshold" style="width: 130px;margin-left: 20px"></el-input>
<span class="port-range">范围:1~10次</span>
</div>
</div>
</div>
</div>
</div>
</el-form-item>
<el-form-item label="副本数:" prop="replica" class="form-item-with-help">
<div class="input-containername">
<el-input-number v-model="createForm.replicas" :min="1" @change="changeReplicas" />
</div>
</el-form-item>
</div>
</div>
<div class="actions" v-if="!item.collapsed">
<el-button v-if="item.editMode" @click="saveInstanceItem(index)">保存</el-button>
<el-button @click="removeInstanceItem(index)" :disabled="instanceItems.length === 1">删除</el-button>
</div>
</div>
</div>
</div>
</el-form-item>
<el-button type="primary" @click="addInstanceItem" class="add-button">
添加新实例
</el-button>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="createDialogVisible = false">取消</el-button>
<el-button type="primary" @click="createDeployment">创建</el-button>
</span>
</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>
</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_STATEFULSET, DELETE_DEPLOYMENT,
DELETE_NAMESPACE, DEPLOYMENT_DETAIL, DEPLOYMENT_LIST,
NAMESPACE_DETAIL,
NAMESPACE_LIST, RESTART_DEPLOYMENT, STATEFULSET_DETAIL, STATEFULSET_LIST, UPDATE_DEPLOYMENT, UPDATE_DEPLOYMENTSCALE,
UPDATE_NAMESPACE, UPDATE_STATEFULSET
} 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";
import {value} from "lodash/seq.js";
const extensions = [javascript(),oneDark]
const dialogSelectedNamespace = ref(''); // 对话框的v-model
const scaleDialogVisible= ref(false)
const replicaCount = ref(1)
const currentRow = ref(null)
const openScaleDiglog = (row)=>{
replicaCount.value = row.Replicas
currentRow.value = row
scaleDialogVisible.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:'statefulset',deploymentName: row.statefulsetName ,namespace: selectedNamespace.value}
});
};
const createDialogVisible = ref(false);
const createForm = ref({
StatefulSetName: '',
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 createDeployment = async ()=> {
const labels = formItems.value.reduce((acc, item) => {
if (item.key && item.value) {
acc[item.key] = item.value;
}
return acc;
}, {});
console.log("Instance Items before sending to backend:", instanceItems.value);
instanceItems.value.forEach(item => {
console.log("Readiness Port for item:", item.readnessHealthCheckPort);
console.log("Parsed Port:", parseInt(item.readnessHealthCheckPort));
// 确认 parseInt 不是返回 NaN
if (isNaN(parseInt(item.readnessHealthCheckPort))) {
console.error("Invalid port number: ", item.readnessHealthCheckPort);
}
});
console.log("label = =================",labels)
console.log(instanceItems.value)
console.log(createForm.value.replicas)
const createParameters = ref({
"namespace": createForm.value.namespace,
"statefulset_name": createForm.value.StatefulSetName,
"podName": createForm.value.StatefulSetName,
"label": labels,
"replicas": createForm.value.replicas,
"containers": instanceItems.value.map(item => ({
// 如果是空字符串则设置为 false
"name": item.name,
"image": item.image,
"version": item.image_tag,
// "ports": item.ports, // 确保这个字段是一个 int32 数组
"limits_cpu": item.cpu_limit,
"limit_memory": item.memory_limit,
"requests_cpu": item.cpu_request,
"requests_memory": item.memory_request,
"work_space": item.workspace,
"command": item.cmd,
"args": item.args.split(','), // 前提是 args 是以空格分隔的字符串
"readiness_healthCheck": item.isReadinessCheckActive || false,
"liveness_healthCheck": item.isLivenessCheckActive || false,
"healthcheckTypeLive": mapHealthCheckType(item.livenessselectedCheckMethod),
"healthcheckTypeRead": mapHealthCheckType(item.ReadinessSelectedCheckMethod),
"resource": {
},
"livenessProbe": {
"path": item.lives_healthCheckPath,
"port": parseInt(item.lives_healthCheckPort),
"cmd": item.livenessCommand,
"initialDelaySecond": parseInt(item.livenessStartDelay),
"timeoutSeconds": parseInt(item.livenessResDelay),
"periodSeconds": parseInt(item.livenessIntervalTime),
"successThreshold": parseInt(item.livenessHealththreshold),
"failureThreshold": parseInt(item.livenessUnHealththreshold),
},
"readinessProbe": {
"path": item.readnessHealthCheckPath,
"port": parseInt(item.readnessHealthCheckPort),
"cmd": item.readnessCommand,
"initialDelaySecond": parseInt(item.readnessStartDelay),
"timeoutSeconds": parseInt(item.readnessResDelay),
"periodSeconds": parseInt(item.readnessIntervalTime),
"successThreshold": parseInt(item.readnessHealththreshold),
"failureThreshold": parseInt(item.readnessUnHealththreshold),
},
}))
});
console.log(createParameters.value)
// 发送 createParameters.value 到后端...
try {
const resps = await CREATE_STATEFULSET(createParameters.value);
console.log(resps)
if (resps.code === 200){
createDialogVisible.value = false
ElMessage.success("创建成功")
getDeployData()
}else {
ElMessage.error(resps.data.message)
}
} catch (e) {
console.error(e);
return []; // 出错时返回空数组
}
console.log(createForm.value)
console.log("标签:",formItems.value)
console.log("~~~~~~~~~~~~~~~~")
console.log(instanceItems.value)
}
// 调用 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();
};
// 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('nspageSize',val)
getNSData()
}
const handleInput = _.debounce(async () => {
await search();
}, 500); // 使用 lodash 的 debounce 函数来防抖,防止搜索操作太频繁
const updateDeploy = async () =>{
try {
const updatedJson = yaml2obj.load(yamlContent.value)
const jsonString = JSON.stringify(updatedJson);
console.log("?????????",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('nscurrentPage', val); // 将当前页保存在 localStorage
getNSData()
}
const viewYAML = (row) => {
console.log('显示YAML信息', row.statefulsetName);
// 这里可以编写显示YAML信息的逻辑
// 假设提供了 `getYAMLContent` 方法来获取 YAML 内容
// 直接使用传入的 row 作为 YAML 内容
getDeployDetailData(row.statefulsetName)
};
const confirmDelete = (row) => {confirmDelete
ElMessageBox.confirm(
`确定要删除Deployment "${row.deploymentName}" 吗?`,
'警告',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
).then(async () => {
const deleteNSParameters = ref({
deploymentName: row.deploymentName,
namespace: selectedNamespace.value,
})
console.log(deleteNSParameters.value)
try {
const resp = await DELETE_DEPLOYMENT(deleteNSParameters.value)
console.log("————————————————",resp)
ElMessage({
type: 'success',
message: `Deployment "${row.deploymentName}" 已被删除`
});
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([
{
statefulsetName: "",
Labels: '',
Status: '',
Images: '',
createTime:"",
Operation:"",
Replicas: '',
},
// ...其他数据项
]);
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 STATEFULSET_LIST(searchParameters);
if (resp && resp.data && Array.isArray(resp.data.items)) {
tableData.value = resp.data.items.map((item) => {
// 计算运行中的副本数
const runningReplicas = item.status.replicas - (item.status.unavailableReplicas || 0);
const expectedReplicas = item.spec.replicas;
const showLoading = runningReplicas !== expectedReplicas; // 加载图标仅在期望和运行副本数不匹配时显示
return {
statefulsetName: item.metadata.name,
Labels: formatLabels(item.metadata.labels),
showLoading: showLoading, // 添加 showLoading 标志
// 更新状态显示为“运行副本数/期望副本数”格式
Status: `${runningReplicas}/${item.spec.replicas}`,
Images: item.spec.template.spec.containers.map(container => container.image),
createTime: formatDateTime(item.metadata.creationTimestamp),
Replicas: item.spec.replicas
// 其他属性...
};
});
}
// 更新部署计数
deploymentCount.value = resp.data.count;
} catch (e) {
console.log(e);
}
};
const refresh = () => {
getDeployData()
console.log('刷新表格数据');
};
const updateParameters = reactive({
deploymentName: "",
namespace: selectedNamespace,
content: ""
})
const updateDeployData = async ()=>{
try {
console.log("你吗的",updateParameters.content)
const resp = await UPDATE_STATEFULSET(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 STATEFULSET_LIST(deploymentParameters);
if (resp && resp.data && Array.isArray(resp.data.items)) {
tableData.value = resp.data.items.map((item) => {
// 计算运行中的副本数
const runningReplicas = item.status.replicas - (item.status.unavailableReplicas || 0);
const expectedReplicas = item.spec.replicas;
const showLoading = runningReplicas !== expectedReplicas; // 加载图标仅在期望和运行副本数不匹配时显示
return {
statefulsetName: item.metadata.name,
Labels: formatLabels(item.metadata.labels),
showLoading: showLoading, // 添加 showLoading 标志
// 更新状态显示为“运行副本数/期望副本数”格式
Status: `${runningReplicas}/${item.spec.replicas}`,
Images: item.spec.template.spec.containers.map(container => container.image),
createTime: formatDateTime(item.metadata.creationTimestamp),
Replicas: item.spec.replicas
// 其他属性...
};
});
}
// 更新部署计数
deploymentCount.value = resp.data.count;
} catch (e) {
console.log(e);
}
};
const getDeployDetailData = async (statefulName)=>{
try {
const StatefulsetDetailparams = reactive({
statefulset_name: statefulName,
namespace: selectedNamespace.value
})
console.log("ddddddddd",StatefulsetDetailparams)
const resp = await STATEFULSET_DETAIL(StatefulsetDetailparams)
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!
评论