k8s管理系统项目前端-cluster

一、namespace

<template>
  <div class="container">
    <!-- 第一部分: 头部 -->
    <div class="header shadow">
      <div class="left-group">
        <el-button  type="primary" @click="goToCreateNamespace()">
          <el-icon><EditPen /></el-icon>
          <span style="vertical-align: middle"> 创建 </span>
        </el-button>
        <el-input placeholder="请输入内容" v-model="searchText" class="search-input" @input="handleInput">
          <template #append>
            <el-button type="primary" @click="search()">
              <el-icon><Search /></el-icon>
            </el-button>
          </template>
        </el-input>
      </div>
      <div class="right-group">
        <el-button  @click="refresh">
          <el-icon><Refresh /></el-icon>
          <span> 刷新 </span>
        </el-button>
      </div>
    </div>

    <!-- 第二部分: 表格 -->
    <div class="table-wrapper shadow">
      <el-table :data="tableData" style="width: 100%">
        <el-table-column prop="nsName" label="Namespace名" width="180" align="center">
          <template #default="{ row }">
            <div>
              <p style="color: #4795EE; margin: 0;">{{ row.nsName }}</p>
            </div>
          </template>
        </el-table-column>
        <el-table-column prop="Labels" label="标签" width="230" align="center">
          <template #default="{ row }">
            <div>
              <el-tooltip
                  v-for="(label, index) in row.Labels"
                  :key="index"
                  class="item"
                  effect="dark"
                  :content="label"
                  placement="top"
              >
                <el-tag class="ml-2" type="success">{{ label }}</el-tag>
              </el-tooltip>
            </div>
          </template>
        </el-table-column>
        <el-table-column prop="Status" label="状态" align="center">
          <template #default="{ row }">
            <div :class="{'status-active': row.Status === 'Active', 'status-inactive': row.Status !== 'Active'}">
              {{ row.Status }}
            </div>
          </template>

        </el-table-column>
        <el-table-column prop="createTime" label="创建时间" align="center">
          <template #default="{ row }">
            <div>
              <el-tag class="ml-2" type="warning">{{row.createTime}}</el-tag>
            </div>
          </template>
        </el-table-column>
        <el-table-column prop="Operation" label="操作" align="center">
          <template #default="{ row }">
            <el-button color="#5AABFF" :dark="isDark" plain @click="viewYAML(row)">
              <el-icon><EditPen /></el-icon>
              <span style="vertical-align: middle"> YAML </span>
            </el-button>
            <el-button :disabled="systemNamespaces.includes(row.nsName)" color="#F58D79" :dark="isDark" plain @click="confirmDelete(row)">
              <el-icon><Tickets /></el-icon>
              <span style="vertical-align: middle"> 删除 </span>
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <el-pagination
          v-model:current-page="currentPage"
          v-model:page-size="pageSize"
          :page-sizes="[5, 10, 20, 30]"
          :small="small"
          :disabled="disabled"
          :background="background"
          layout="total, sizes, prev, pager, next, jumper"
          :total=namespaceCount
          class="paginations"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
      />
    </div>

    <el-dialog title="YAML信息" v-model="dialogVisible" width="45%" top="5%">

      <codemirror
          v-model="yamlContent"
          placeholder="请输入代码..."
          :style="{ height: '100%' }"
          v-bind="cmOptions"
          :extensions="extensions"
      />
      <template #footer>
                <span class="dialog-footer">
                    <el-button @click="dialogVisible = false">取 消</el-button>
                    <el-button  type="primary" @click="updateNS()">更 新</el-button>
                </span>
      </template>
    </el-dialog>
    <el-dialog title="创建命名空间" v-model="createDialogVisible" width="30%">
      <el-form label-position="right" label-width="100px">
        <el-form-item label="名称">
          <el-input v-model="newNamespace.name" ref="nameInput" placeholder="请输入名称" style="width: 200px"></el-input>
        </el-form-item>
        <div style="margin-left: 100px; color: #606266; margin-top: -10px; margin-bottom: 20px;">
          最长63个字符,只能包含小写字母、数字及分隔符("-"),且必须以小写字母开头,数字或小写字母结尾
        </div>
        <el-form-item label="描述">
          <el-input
              type="textarea"
              v-model="newNamespace.description"
              placeholder="请输入描述"
          ></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
    <el-button @click="createDialogVisible = false">取消</el-button>
    <el-button type="primary" @click="createNamespace">创建</el-button>
  </span>
    </el-dialog>
  </div>
</template>

<script setup>
import {nextTick, onBeforeMount, reactive, ref} from 'vue';
import {
  CREATE_NAMESPACE,
  DELETE_NAMESPACE,
  NAMESPACE_DETAIL,
  NAMESPACE_LIST,
  UPDATE_NAMESPACE
} from "../../../api/k8s.js";
import { Codemirror } from 'vue-codemirror'
import { javascript} from  '@codemirror/lang-javascript'
// import { javascript } from 'codemirror/lang-javascript'
 import { oneDark } from '@codemirror/theme-one-dark'

import _ from 'lodash';
import json2yaml from 'json2yaml'
import yaml2obj from 'js-yaml';
import {ElMessage} from "element-plus";
import router from "@/router/index.js";
import { ElMessageBox } from 'element-plus';
import axios from "axios";

 const extensions = [javascript(),oneDark]

const createDialogVisible = ref(false);

const newNamespace = reactive({
  name: '',
  description: ''
});
let currentPage = ref(1)
let pageSize = ref(5)
const small = ref(false)
const background = ref(false)
const disabled = ref(false)
const dialogVisible = ref(false);
const currentNsName = ref('');
let yamlContent = ref('');


const goToCreateNamespace = () => {
  createDialogVisible.value = true
};
const createNamespace = async () => {

  const nameInput = newNamespace.name.trim();
  const nameRegex = /^[a-z]([-a-z0-9]*[a-z0-9])?$/;

  // 如果名称不符合长度限制或正则表达式规则
  if (nameInput.length > 63 || !nameRegex.test(nameInput)) {
    ElMessage.error('名称格式不正确');
    return;
  }

  try {
    const resp = await CREATE_NAMESPACE(newNamespace)
    ElMessage.success("创建成功")
    createDialogVisible.value = false;
    // 如果验证通过,关闭弹窗
    // 清空表单
    newNamespace.name = '';
    newNamespace.description = '';
    console.log('创建命名空间', newNamespace);
    getNSData()
  }catch (e){
    console.log(e)
    ElMessage.error(`${e}`)

  }
  // 发送创建命名空间请求的逻辑...
};

//编辑器配置
const cmOptions = {
  // 语言及语法模式
  mode: 'text/yaml',
  // 主题
  theme: 'monokai',
  lint: true,
  // 显示行数
  lineNumbers: true,
  smartIndent: true, //智能缩进
  indentUnit: 4, // 智能缩进单元长度为 4 个空格
  styleActiveLine: true, // 显示选中行的样式
  matchBrackets: true, //每当光标位于匹配的方括号旁边时,都会使其高亮显示
  readOnly: false,
  lineWrapping: true //自动换行
}

const handleSizeChange = (val) => {
  console.log(`${val} items per page`)
  namespaceAllParameters.page_size = val
  localStorage.setItem('nspageSize',val)
  getNSData()
}
const handleInput = _.debounce(async () => {
  await search();
}, 500); // 使用 lodash 的 debounce 函数来防抖,防止搜索操作太频繁
const updateNS = async () =>{
  try {

    const updatedJson = yaml2obj.load(yamlContent.value)
    const jsonString = JSON.stringify(updatedJson);

    console.log("?????????",updatedJson)
    updateParameters.name = currentNsName.value
    updateParameters.content = jsonString
    await updateNSData()
  }catch (e){
    ElMessage.error(`${e}`)
  }
}
const onChange = (val)=> {
  yamlContent.value = val
}

const handleCurrentChange = (val) => {
  console.log(`current page: ${val}`)
  currentPage.value = val
  namespaceAllParameters.page_number = val
  localStorage.setItem('nscurrentPage', val); // 将当前页保存在 localStorage

  getNSData()
}
const viewYAML = (row) => {
  console.log('显示YAML信息', row);
  // 这里可以编写显示YAML信息的逻辑
  // 假设提供了 `getYAMLContent` 方法来获取 YAML 内容
  // 直接使用传入的 row 作为 YAML 内容
  getNsDetailData(row.nsName)
};
const confirmDelete =  (row) => {
  ElMessageBox.confirm(
      `确定要删除命名空间 "${row.nsName}" 吗?`,
      '警告',
      {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning',
      }
  ).then(async () => {
    const deleteNSParameters = reactive({
      name: row.nsName,
    })
    try {
      const name = row.nsName
      console.log("nameaaaaaa : ",name)
      const resp = await DELETE_NAMESPACE(name,deleteNSParameters)
      console.log("————————————————",resp)
      ElMessage({
        type: 'success',
        message: `命名空间 "${row.nsName}" 已被删除`
      });
      getNSData()

    }catch (e){
      console.log(e)
      ElMessage.error(`删除失败: ${e}`);
    }
  }).catch(() => {
    ElMessage({
      type: 'info',
      message: '已取消删除'
    });
  });
};
const deleteNSData = async (row)=>{



}


const formatDateTime = (dateTimeString) => {
  return dateTimeString.replace('T', ' ').replace('Z', '');
};
// 示例数据和方法
const searchText = ref('');
const tableData = ref([
  {
    nsName: "",
    Labels: '',
    Status: '',
    createTime:"",
    Operation:""
  },
  // ...其他数据项
]);

const search = async () => {
  console.log('执行搜索:', searchText.value);
  const searchParameters = reactive({
    page_size: pageSize,
    page_number: currentPage,
    filterName: searchText.value
  })
  try {
    const resp = await NAMESPACE_LIST(searchParameters)
    if (resp && resp.data && Array.isArray(resp.data.Items)) {
      tableData.value = resp.data.Items.map((item) => ({
        nsName: item.metadata.name,
        Labels: formatLabels(item.metadata.labels),
        Status: item.status.phase,
        createTime: formatDateTime(item.metadata.creationTimestamp),
        Operation: "",
        // 其他属性...
      }));
    }
    console.log(resp)
  }catch (e){
    console.log(e)
  }
};

const refresh = () => {
  getNSData()
  console.log('刷新表格数据');
};
const updateParameters = reactive({
  name: "",
  content: ""

})
const updateNSData = async ()=>{
  try {
    console.log("你吗的",updateParameters.content)
    const resp = await UPDATE_NAMESPACE(updateParameters)
    if (resp.code !== 200){
      ElMessage.error(resp.message)
      return
    }
    ElMessage.success("更新成功")
    dialogVisible.value = false

  }catch (e){
    console.log(e)
    ElMessage.error(`${e}`)

  }
}
const namespaceAllParameters = reactive({
  page_size: pageSize,
  page_number: currentPage,
})
var namespaceCount = ref(0)
// 在这里创建一个方法来将 Labels 对象转换为一个字符串数组
const formatLabels = (labelsMap) => {
  return Object.entries(labelsMap).map(([key, value]) => `${key}: ${value}`);
};
const systemNamespaces = ['default', 'kube-system', 'kube-public', 'kube-node-lease'];

const getNSData = async ()=>{
  try {
    const resp = await NAMESPACE_LIST(namespaceAllParameters)
    if (resp && resp.data && Array.isArray(resp.data.Items)) {
      tableData.value = resp.data.Items.map((item) => ({
        nsName: item.metadata.name,
        Labels: formatLabels(item.metadata.labels),
        Status: item.status.phase,
        createTime: formatDateTime(item.metadata.creationTimestamp),
        isSystem: systemNamespaces.includes(item.metadata.name), // 添加这个属性
        // 其他属性...
      }));
    }

    namespaceCount.value = resp.data.total
    console.log(resp)
  }catch (e){
    console.log(e)
  }
}
const getNsDetailData = async (nsName)=>{
  try {
    console.log(nsName)
    const params = reactive({
      name: nsName,
    })
    const resp = await NAMESPACE_DETAIL(params)

    console.log("yaml =======",json2yaml.stringify(resp.data))
    yamlContent.value = json2yaml.stringify(resp.data); // 确保将数据赋值给 yamlContent
    currentNsName.value = resp.data.metadata.name
    updateParameters.content = yamlContent.value
    await  nextTick()
    dialogVisible.value = true;
    console.log(yamlContent.value)
    console.log("握草")
  }catch (e){
    console.log(e)
    console.log("你吗")
  }
}
onBeforeMount( ()=> {
  // 尝试从 localStorage 中读取状态
  const savedPageSize = localStorage.getItem('nspageSize');
  const savedCurrentPage = localStorage.getItem('nscurrentPage');

  // 如果存在则更新到响应式变量中
  if (savedPageSize) {
    pageSize.value = parseInt(savedPageSize, 10);
  }
  if (savedCurrentPage) {
    currentPage.value = parseInt(savedCurrentPage, 10);
  }
  getNSData()
  // getNSAllData()

});
</script>

<style scoped>
.container {
  margin: 10px;
}

.header {
  display: flex;
  align-items: center;
  justify-content: space-between; /* 添加此属性对子元素进行分散对齐 */
  margin-bottom: 0px;
  gap: 10px;
  padding: 10px;
  background: #FFF;
  border: 2px solid #ebeef5;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}

.search-input {
  /*flex-grow: 1;*/
  width: 200px;
}

.table-wrapper {
  background: #FFF;
  border: 2px solid #ebeef5; /* 浅色边框线 */
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.paginations {
  margin-top: 10px;
  margin-left: 20px;
  margin-bottom: 10px;
}
/* 左侧组合样式,创建按钮和搜索框靠在一起 */
.left-group {
  display: flex;
  align-items: center;
  gap: 10px; /* You can adjust the gap as needed */
}

/* 右侧刷新按钮 */
.right-group {
  display: flex;
  align-items: center;
}
.yaml-content {
  background-color: #f5f5f5;
  border-left: 3px solid #4795EE;
  padding: 15px;
  white-space: pre-wrap;
  text-align: left;
  margin: 20px 0;
  overflow-x: auto;
}
.status-active {
  color: #67C23A;
}

.status-inactive {
  color: red;
}
.dialog-footer {
  /*text-align: right;*/
  display: flex;
  justify-content: flex-end;
  padding: 8px;
}
</style>

二、node开发

<template>
  <div class="container">
    <!-- 第一部分: 头部 -->
    <div class="header shadow">
      <div class="left-group">
      <el-button disabled type="primary">
        <el-icon><EditPen /></el-icon>
        <span style="vertical-align: middle"> 创建 </span>
      </el-button>
      <el-input placeholder="请输入内容" v-model="searchText" class="search-input" @input="handleInput">
        <template #append>
          <el-button type="primary"  @click="search()">
            <el-icon><Search /></el-icon>
          </el-button>
        </template>
      </el-input>
      </div>
      <div class="right-group">
      <el-button  @click="refresh">
        <el-icon><Refresh /></el-icon>
        <span> 刷新 </span>
      </el-button>
      </div>
    </div>

    <!-- 第二部分: 表格 -->
    <div class="table-wrapper shadow">
      <el-table :data="tableData" style="width: 100%">
        <el-table-column prop="nodeName" label="Node名" width="180" align="center">
          <template #default="{ row }">
            <div>
              <p style="color: #4795EE; margin: 0;">{{ row.nodeName }}</p>
              <p style="color: #aaa; margin: 0;">{{ row.hostName }}</p>
            </div>
          </template>
        </el-table-column>
        <el-table-column prop="Specifications" label="规格" width="180" align="center">
          <template #default="{ row }">
            <div>
              <el-tag class="ml-2" type="success">{{row.Specifications}}</el-tag>
            </div>
          </template>
        </el-table-column>
        <el-table-column prop="PodCIDR" label="POD-CIDR" align="center"></el-table-column>
        <el-table-column prop="version" label="版本" align="center"></el-table-column>
        <el-table-column prop="createTime" label="创建时间" align="center">
          <template #default="{ row }">
            <div>
              <el-tag class="ml-2" type="warning">{{row.createTime}}</el-tag>
            </div>
          </template>
        </el-table-column>
        <el-table-column prop="Operation" label="操作" align="center">
          <template #default="{ row }">
            <el-button color="#5AABFF" :dark="isDark" plain @click="viewYAML(row)">
              <el-icon><EditPen /></el-icon>
              <span style="vertical-align: middle"> YAML </span>
            </el-button>
            <el-button color="#5AABFF" :dark="isDark" plain>
              <el-icon><Tickets /></el-icon>
              <span style="vertical-align: middle"> 详情 </span>
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <el-pagination
          v-model:current-page="currentPage"
          v-model:page-size="pageSize"
          :page-sizes="[5, 10, 20, 30]"
          :small="small"
          :disabled="disabled"
          :background="background"
          layout="total, sizes, prev, pager, next, jumper"
          :total=nodeCount
          class="paginations"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
      />
    </div>
    <el-dialog title="YAML信息" v-model="dialogVisible" width="45%" top="5%">
      <codemirror
          v-model="yamlContent"
          placeholder="请输入代码..."
          :style="{ height: '100%' }"
          v-bind="cmOptions"
          :extensions="extensions"
      />

      <template #footer>
                <span class="dialog-footer">
                    <el-button @click="dialogVisible = false">取 消</el-button>
                    <el-button disabled type="primary" >更 新</el-button>
                </span>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
import {nextTick, onBeforeMount, reactive, ref} from 'vue';
import {NAMESPACE_LIST, NODE_DETAIL_LIST, NODE_LIST} from "../../../api/k8s.js";
import { Codemirror } from "vue-codemirror";
import json2yaml from 'json2yaml'
import idea from "vue-codemirror"
import {javascript} from "@codemirror/lang-javascript";
import {oneDark} from "@codemirror/theme-one-dark";
import _ from "lodash";

const extensions = [javascript(),oneDark]
// import 'codemirror/mode/yaml/yaml.js'
// import 'codemirror/theme/idea.css'
let currentPage = ref(1)
let pageSize = ref(5)
const small = ref(false)
const background = ref(false)
const disabled = ref(false)
const isLoading = ref(true)
const dialogVisible = ref(false);
const currentYAMLContent = ref('');
let yamlContent = ref('');
//编辑器配置
const cmOptions = {
  // 语言及语法模式
  mode: 'text/yaml',
      // 主题
      theme: 'idea',
      // 显示行数
      lineNumbers: true,
      smartIndent: true, //智能缩进
      indentUnit: 4, // 智能缩进单元长度为 4 个空格
      styleActiveLine: true, // 显示选中行的样式
      matchBrackets: true, //每当光标位于匹配的方括号旁边时,都会使其高亮显示
      readOnly: false,
      lineWrapping: true //自动换行
}
const handleInput = _.debounce(async () => {
  await search();
}, 500); // 使用 lodash 的 debounce 函数来防抖,防止搜索操作太频繁
const handleSizeChange = (val) => {
  console.log(`${val} items per page`)
  nodeParameters.page_size = val
  localStorage.setItem('nodepageSize', val); // 将页面大小保存在 localStorage

  getNodeData()
}
const onChange = (val)=> {
  yamlContent = val
}
// 转换Ki到G的函数
const kiToG = (ki) => {
  const kiValue = parseInt(ki.replace('Ki', '')); // 移除Ki并转换为整数
  return (kiValue / (1024 * 1024)).toFixed(2) + 'G'; // 转换为G并保留两位小数
};
const handleCurrentChange = (val) => {
  console.log(`current page: ${val}`)
  currentPage.value = val
  localStorage.setItem('nodecurrentPage', val); // 将当前页保存在 localStorage

  nodeParameters.page_number = val
  getNodeData()
}
const viewYAML = (row) => {
  console.log('显示YAML信息', row);
  // 这里可以编写显示YAML信息的逻辑
  // 假设提供了 `getYAMLContent` 方法来获取 YAML 内容
  // 直接使用传入的 row 作为 YAML 内容
  getNodeDetailData(row.nodeName)
};

const viewDetails = (row) => {
  console.log('显示详情', row);
  // 这里可以编写显示详情信息的逻辑
};
const formatDateTime = (dateTimeString) => {
  return dateTimeString.replace('T', ' ').replace('Z', '');
};
// 示例数据和方法
const searchText = ref('');
const tableData = ref([
  {

  },
  // ...其他数据项
]);

const search = async () => {
  console.log('执行搜索:', searchText.value);
  const searchParameters = reactive({
    page_size: pageSize,
    page_number: currentPage,
    filterName: searchText.value,
    isMaster: 3
  })
  try {
    const resp = await NODE_LIST(searchParameters)
    if (resp && resp.data && Array.isArray(resp.data.Items)) {
      tableData.value = resp.data.Items.map((item) => ({
        nodeName: item.metadata.name,
        hostName: item.metadata.labels['kubernetes.io/hostname'],
        Specifications: item.status.capacity.cpu + "核" + kiToG(item.status.capacity.memory),
        PodCIDR: item.spec.podCIDR,
        version: item.status.nodeInfo.kubeletVersion,
        createTime: formatDateTime(item.metadata.creationTimestamp),
        Operation: "hha",
        // 其他属性...
      }));
    }
    console.log(resp)
  }catch (e){
    console.log(e)
  }
};

const refresh = () => {
  console.log('刷新表格数据');
};

const nodeParameters = reactive({
  page_size: pageSize,
  page_number: currentPage,
  isMaster: 3,
})
var nodeCount = ref(0)
const getNodeData = async ()=>{
  try {
    const resp = await NODE_LIST(nodeParameters)
    if (resp && resp.data && Array.isArray(resp.data.Items)) {
      tableData.value = resp.data.Items.map((item) => ({
        nodeName: item.metadata.name,
        hostName: item.metadata.labels['kubernetes.io/hostname'],
        Specifications: item.status.capacity.cpu + "核" + kiToG(item.status.capacity.memory),
        PodCIDR: item.spec.podCIDR,
        version: item.status.nodeInfo.kubeletVersion,
        createTime: formatDateTime(item.metadata.creationTimestamp),
        Operation: "hha",
        // 其他属性...
      }));
    }
    nodeCount.value = resp.data.total

    console.log(resp)
    // for (const respKey of resp.data.Items) {
    //   console.log(respKey)
    //
    // }

  }catch (e){
    console.log(e)
  }
}
const getNodeDetailData = async (nodeName)=>{
  try {
    console.log(nodeName)
    const params = reactive({
      nodeName: nodeName,
    })
    const resp = await NODE_DETAIL_LIST(params)
    console.log("0000000000:",resp)

    console.log("yaml =======",json2yaml.stringify(resp.data))
    yamlContent.value = json2yaml.stringify(resp.data); // 确保将数据赋值给 yamlContent

    await  nextTick()
    dialogVisible.value = true;


  }catch (e){
    console.log(e)
  }
}
const getNodeAllData = async ()=>{
  const nodeAllParameters = reactive({
    page_size: 100000,
    page_number: 1,
    isMaster: 3,
  })
  try {
    const resp = await NODE_LIST(nodeAllParameters)
    console.log(resp)
    nodeCount.value = resp.data.total
    // for (const respKey of resp.data.Items) {
    //   console.log(respKey)
    //
    // }
  }catch (e){
    console.log(e)
  }
}
onBeforeMount( ()=> {
  const savedPageSize = localStorage.getItem('nodepageSize');
  const savedCurrentPage = localStorage.getItem('nodecurrentPage');

  if (savedPageSize && !isNaN(savedPageSize)) {
    pageSize.value = Number(savedPageSize);
  }

  if (savedCurrentPage && !isNaN(savedCurrentPage)) {
    currentPage.value = Number(savedCurrentPage);
  }
  getNodeData()

});
</script>

<style scoped>
.container {
  margin: 10px;
}

.header {
  display: flex;
  align-items: center;
  justify-content: space-between; /* 添加此属性对子元素进行分散对齐 */
  margin-bottom: 0px;
  gap: 10px;
  padding: 10px;
  background: #FFF;
  border: 2px solid #ebeef5;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}

.search-input {
  /*flex-grow: 1;*/
  width: 200px;
}

.table-wrapper {
  background: #FFF;
  border: 2px solid #ebeef5; /* 浅色边框线 */
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.paginations {
  margin-top: 10px;
  margin-left: 20px;
  margin-bottom: 10px;
}
/* 左侧组合样式,创建按钮和搜索框靠在一起 */
.left-group {
  display: flex;
  align-items: center;
  gap: 10px; /* You can adjust the gap as needed */
}

/* 右侧刷新按钮 */
.right-group {
  display: flex;
  align-items: center;
}
.yaml-content {
  background-color: #f5f5f5;
  border-left: 3px solid #4795EE;
  padding: 15px;
  white-space: pre-wrap;
  text-align: left;
  margin: 20px 0;
  overflow-x: auto;
}

.dialog-footer {
  text-align: right;
}
</style>