python+django自动化部署日志采用‌WebSocket前端实时展示

一、开发环境搭建和配置

# channels是一个用于在Django中实现WebSocket、HTTP/2和其他异步协议的库。
pip install channels

#channels-redis是一个用于在Django Channels中使用Redis作为后台存储的库。它可以用于处理#WebSocket连接的持久化和消息传递。
pip install channels-redis


#安装 docker(此处采用 mac安装)
brew install --cask docker

二、django应用模块目录

deployments
├── Consumers.py
├── __init__.py
├── __pycache__
│   ├── Consumers.cpython-313.pyc
│   ├── __init__.cpython-313.pyc
│   ├── admin.cpython-313.pyc
│   ├── apps.cpython-313.pyc
│   ├── models.cpython-313.pyc
│   ├── routing.cpython-313.pyc
│   └── views.cpython-313.pyc
├── admin.py
├── apps.py
├── migrations
│   ├── 0001_initial.py
│   ├── __init__.py
│   └── __pycache__
│       ├── 0001_initial.cpython-313.pyc
│       └── __init__.cpython-313.pyc
├── models.py
├── routing.py
├── tests.py
└── views.py

三、django模块相关代码

***************************/operation/settings.py***************************
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [
                "redis://:xx@xx:6379/0",
            ],
        },
    },
}



INSTALLED_APPS = [
    'deployments',
    'channels'
]

ASGI_APPLICATION = 'operation.asgi.application'
***************************/operation/asgi.py***************************



"""
ASGI config for operation project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/

 Django项目支持ASGI的入口点,ASGI是一个Python标准,用于异步Web服务器、
 Web框架和Web应用之间的通信,它允许你编写异步的Django视图和其他组件,以提
  高应用的性能和响应能力,部署时需要用到

  部署的时候才用到
"""

import os

from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack

# 设置 DJANGO_SETTINGS_MODULE 环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'operation.settings')

# 获取 ASGI 应用
django_asgi_app = get_asgi_application()

# 延迟导入其他模块
from deployments import routing  # 这里是你需要延迟导入的模块


application = ProtocolTypeRouter({
    "http": django_asgi_app,
    "websocket": AuthMiddlewareStack(
        URLRouter(
            routing.websocket_urlpatterns
        )
    )
})
************************/deployments/routing.py************************
from django.urls import path


from . import Consumers

websocket_urlpatterns = [
    path('ws/cicd_progress/', Consumers.ChatConsumer.as_asgi())
]
************************/deployments/views.py***************************

import asyncio
import json
import logging
import os
import subprocess

import git
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from git import Repo

from deployments.models import Deployment
from operation import settings
from operation.common.enum.BuildProjectEnum import BuildProjectEnum
from operation.common.enum.ResponeCodeEnum import ResponseCodeEnum
from operation.common.exception.BusinessException import BusinessException
from operation.common.utils.CommonResult import CommonResult
from operation.common.utils.PageUtils import paginate_queryset
from asgiref.sync import sync_to_async

# Create your views here.


"""
****************************创建 deployments API视图
"""

"""
 获取应用列表
"""


@csrf_exempt
def getDeploymentsList(request):
    try:
        if request.method == 'POST':
            json_data = request.body
            data = json.loads(json_data)
            page = data.get('page', 1)
            page_size = data.get('page_size', 10)
        else:
            raise BusinessException(ResponseCodeEnum.METHOD_ERROR.message, ResponseCodeEnum.METHOD_ERROR.code)

        deployment_list = Deployment.objects.all().order_by('created_at')
        deployment_lists, pagination_info = paginate_queryset(deployment_list, page, page_size)
        deployment_lists = [Deployment.to_dict() for Deployment in deployment_lists]
        logging.info(type(deployment_lists))
        return JsonResponse(CommonResult.success_pagination(deployment_lists, pagination_info),
                            json_dumps_params={'ensure_ascii': False})
    except BusinessException as e:
        return JsonResponse(CommonResult.error(e.code, e.message), json_dumps_params={'ensure_ascii': False})


"""
    增加应用
"""


@csrf_exempt
def addDeployment(request):
    if request.method == "GET":
        return BusinessException(ResponseCodeEnum.METHOD_ERROR.message, ResponseCodeEnum.METHOD_ERROR.code)
    try:
        json_data = request.body
        data = json.loads(json_data)
        name = data.get('name')
        if name is None:
            raise BusinessException(ResponseCodeEnum.PARAMS_ERROR.message, ResponseCodeEnum.PARAMS_ERROR.code)

        # 保存应用
        Deployment.objects.create(
            name=name,
            status=BuildProjectEnum.NOT_STARTED.code
        )
        return JsonResponse(CommonResult.success_data(None), json_dumps_params={'ensure_ascii': False})
    except BusinessException as e:
        return JsonResponse(CommonResult.error(e.code, e.message), json_dumps_params={'ensure_ascii': False})


"""
    *********部署角本
    报错: You cannot call this from an async context - use a thread or sync_to_async.
    解决方案:1.使用 sync_to_async  2.使用 使用线程
"""
# 定义全局变量 progress
progress = 0


async def cicd_execute(project_id, project_name, send_progress, send_news):
    global progress  # 声明 progress 为全局变量
    try:
        # 每次点击发布时,重置 progress 为0
        progress = 0

        # 1.初始化阶段
        await init_project(project_id, project_name, send_progress)

        # 2.构建阶段
        await build_and_deploy_project(project_id, project_name, send_progress, send_news)

        # 3.运行阶段
        await run_project(project_id, project_name, send_progress,send_news)
    except BusinessException as e:
        logging.info(f"发生系统内部异常: {e}")
        return

"""
    1.初始化阶段
"""


async def init_project(project_id, project_name, send_progress):
    global progress  # 声明 progress 为全局变量
    try:
        logging.info("=================开始初始化 项目 %s" % (project_name))
        logging.info("初始化中......")

        # 更新状态为 "初始化中" 记录进度值
        progress += 20
        await update_deployment_status(project_id, project_name, BuildProjectEnum.INITIALIZING.code, progress)

        # 发送事件
        await send_progress(progress, BuildProjectEnum.INITIALIZING.code, ResponseCodeEnum.SUCCESS.status_code,
                            ResponseCodeEnum.SUCCESS.message)

        projectName = project_name
        projectId = project_id
        if projectName is None or projectId is None:
            raise BusinessException(ResponseCodeEnum.PARAMS_ERROR.message, ResponseCodeEnum.PARAMS_ERROR.code)

        # 环境变量配置
        set_environment_variables()

        # 加载仓库 拉取代码
        await clone_or_pull_repo(project_name)

        # 更新状态为 "初始化成功" 记录进度值
        progress += 20
        await update_deployment_status(project_id, project_name, BuildProjectEnum.INITIAL_SUCCESS.code, progress)

        # 发送事件
        await send_progress(progress, BuildProjectEnum.INITIAL_SUCCESS.code, ResponseCodeEnum.SUCCESS.status_code,
                            ResponseCodeEnum.SUCCESS.message)
        logging.info("初始化成功......")

    except BusinessException as e:
        logging.info("初始化失败......")
        # 更新状态为 "初始化失败"
        await update_deployment_status(project_id, project_name, BuildProjectEnum.INITIAL_FAILURE.code, progress)
        # 发送事件
        await send_progress(progress, BuildProjectEnum.INITIAL_FAILURE.code,
                            ResponseCodeEnum.INTERNAL_SERVER_ERROR.status_code,
                            ResponseCodeEnum.INTERNAL_SERVER_ERROR.message)
        # 抛出异常
        raise BusinessException(ResponseCodeEnum.INTERNAL_SERVER_ERROR.message,
                                ResponseCodeEnum.INTERNAL_SERVER_ERROR.code)


"""
    2.构建阶段
"""
async def build_and_deploy_project(project_id, project_name, send_progress, send_news):
    global progress
    logging.info("=================构建 项目 %s" % (project_name))
    logging.info("部署中......")
    try:
        # 更新状态为 "部署中" 记录进度值 60
        progress += 20
        await update_deployment_status(project_id, project_name, BuildProjectEnum.DEPLOYMENT_IN.code, progress)

        # 发送事件
        await send_progress(progress, BuildProjectEnum.DEPLOYMENT_IN.code, ResponseCodeEnum.SUCCESS.status_code,
                            ResponseCodeEnum.SUCCESS.message)

        projectName_image = project_name + "-image"
        projectName_dir = os.path.join(settings.PROJECT_REPO_DIR, project_name)
        await clean_old_containers(projectName_image)
        await clean_old_images(projectName_image)
        os.chdir(projectName_dir)
        logging.info(f"当前工作目录: {os.getcwd()}")


        # 执行 maven 打包
        await execute_maven_build(send_news)
        logging.info("mvn clean install -DskipTests 执行完成......")

        # 构建 image
        await build_docker_image(projectName_image,send_news)
        logging.info("docker build -t , projectName_image . 执行完成")

        # 更新状态为 "部署成功" 记录进度值 80
        progress += 20
        await update_deployment_status(project_id, project_name, BuildProjectEnum.DEPLOYMENT_SUCCESS.code, progress)

        # 发送事件
        await send_progress(progress, BuildProjectEnum.DEPLOYMENT_SUCCESS.code, ResponseCodeEnum.SUCCESS.status_code,
                            ResponseCodeEnum.SUCCESS.message)
        logging.info("部署成功......")

    except Exception as ex:
        # 更新状态为 "部署失败"
        logging.info("部署失败......")
        await update_deployment_status(project_id, project_name, BuildProjectEnum.DEPLOYMENT_FAILURE.code, progress)
        # 发送事件
        await send_progress(progress, BuildProjectEnum.DEPLOYMENT_FAILURE.code,
                            ResponseCodeEnum.INTERNAL_SERVER_ERROR.status_code,
                            ResponseCodeEnum.INTERNAL_SERVER_ERROR.message)

        # 抛出异常
        raise BusinessException(ResponseCodeEnum.INTERNAL_SERVER_ERROR.message,ResponseCodeEnum.INTERNAL_SERVER_ERROR.code)


"""
    3.运行阶段
"""
async def run_project(project_id, project_name, send_progress,send_news):
    global progress
    logging.info("=================启动 项目 %s" % (project_name))
    logging.info("启动中......")
    try:
        # 更新状态为 "启动中" 记录进度值 70
        progress += 10
        await update_deployment_status(project_id, project_name, BuildProjectEnum.STARTING.code, progress)

        # 发送事件
        await send_progress(progress, BuildProjectEnum.STARTING.code, ResponseCodeEnum.SUCCESS.status_code,
                            ResponseCodeEnum.SUCCESS.message)

        projectName_image = project_name + "-image"

        await run_docker_container(projectName_image,send_news)

        # 更新状态为 "启动成功" 记录进度值 80
        progress += 10
        await update_deployment_status(project_id, project_name, BuildProjectEnum.STARTED_SECCESS.code, progress)

        # 发送事件
        await send_progress(progress, BuildProjectEnum.STARTED_SECCESS.code, ResponseCodeEnum.SUCCESS.status_code,
                            ResponseCodeEnum.SUCCESS.message)
        logging.info("启动成功......")
    except Exception as ex:
        # 更新状态为 "启动失败"
        logging.info("启动失败......")
        await update_deployment_status(project_id, project_name, BuildProjectEnum.STARTED_FAILURE.code, progress)

        # 发送事件
        await send_progress(progress, BuildProjectEnum.STARTED_FAILURE.code,
                            ResponseCodeEnum.INTERNAL_SERVER_ERROR.status_code,
                            ResponseCodeEnum.INTERNAL_SERVER_ERROR.message)

        # 抛出异常
        raise BusinessException(ResponseCodeEnum.INTERNAL_SERVER_ERROR.message,
                                ResponseCodeEnum.INTERNAL_SERVER_ERROR.code)



"""
    加载环境变量
"""


def set_environment_variables():
    logging.info("加载环境变量......")
    os.environ['PATH'] = settings.MAVEN_PATH + os.environ['PATH']
    jdk_path = settings.JDK_PATH
    os.environ['JAVA_HOME'] = jdk_path
    os.environ['PATH'] = f"{jdk_path}/bin:{os.environ['PATH']}"
    logging.info("环境变量加载成功......")


"""
    加载仓库 拉取代码
"""


async def clone_or_pull_repo(project_name):
    logging.info("拉取仓库代码......")
    project_repo_url = settings.PROJECT_REPO_URL
    project_repo_dir = settings.PROJECT_REPO_DIR
    if not os.path.exists(project_repo_dir):
        await sync_to_async(Repo.clone_from)(project_repo_url, project_repo_dir)
    else:
        project_repo = await sync_to_async(Repo)(project_repo_dir)
        await sync_to_async(project_repo.remotes.origin.pull)()
    logging.info("拉取仓库代码成功......")


"""
    清理旧容器
"""
async def clean_old_containers(projectName_image):
    cid = await sync_to_async(subprocess.check_output)(
        ["docker", "ps", "-a", "-q", "--filter", f"ancestor={projectName_image}"])
    if cid:
        # 去除末尾的换行符并转换为字符串
        cid_str = cid.decode().strip()
        logging.info("=================cid: %s" % (cid_str))
        logging.info(f"清理旧容器 docker rm -f {cid_str}")
        await sync_to_async(subprocess.run)(["docker", "rm", "-f", cid_str])


"""
    清理旧镜像
"""
async def clean_old_images(projectName_image):
    iid = await sync_to_async(subprocess.check_output)(["docker", "images", "-q", projectName_image])
    if iid:
        # 去除末尾的换行符并转换为字符串
        iid_str = iid.strip()
        logging.info("=================iid: %s" % (iid_str))
        logging.info(f"清理旧镜像 docker rmi -f {iid_str}")
        await sync_to_async(subprocess.run)(["docker", "rmi", "-f", iid_str])


"""
    maven 打包构建
"""
async def execute_maven_build(send_news):
    # 使用 asyncio.create_subprocess_exec 创建异步进程
    processObject = await asyncio.create_subprocess_exec(
        'mvn', 'clean', 'install', '-DskipTests',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )
    logging.info("发送maven打包构建执行日志......")
    await deployment_log_output(processObject, send_news, ResponseCodeEnum.SUCCESS)


"""
    构建镜像
"""
async def build_docker_image(projectName_image, send_news):
    logging.info(f"=================构建镜像 docker build -t {projectName_image} .")
    # 使用 asyncio.create_subprocess_exec 来异步执行命令
    imageProcessObject = await asyncio.create_subprocess_exec(
        'docker', 'build', '-t', projectName_image, '.',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )
    logging.info("发送构建镜像执行日志......")
    await deployment_log_output(imageProcessObject, send_news, ResponseCodeEnum.SUCCESS)


"""
    运行 docker
"""
async def run_docker_container(projectName_image, send_news):
    logging.info(f"=================运行 docker run -d -p 8084:8084 {projectName_image} .")
    containerRunProcessObject = await asyncio.create_subprocess_exec(
        'docker', 'run', '-d', '-p', '8084:8084', projectName_image, '.',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )
    logging.info("发送启动docker执行日志......")
    # 获取容器 ID
    stdout, stderr = await containerRunProcessObject.communicate()
    container_id = stdout.decode('utf-8').strip()

    # 异步运行 docker logs 命令
    containerRunlogprocess = await asyncio.create_subprocess_exec(
        'docker', 'logs', '-f', '-n', '100', container_id,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )

    # 异步读取容器日志
    asyncio.create_task(deployment_log_output(containerRunlogprocess, send_news, ResponseCodeEnum.SUCCESS))




"""
    更新model
"""
async def update_deployment_status(project_id, project_name, status, progress):
    await sync_to_async(Deployment.update_deployment_model)(project_id, project_name, status=status, progress=progress)


"""
读取mvn 执行命令
    定义一个异步的 函数 deployment_log_output
    async def:协程(协程是由用户程序控制的) 可以暂停和恢复执行
    部署日志模块记录

    processObject: 进程对象
    send_news: 处理消息的方法
    buildProjectEnum:构建项目的 Enum类
    responseCodeEnum: 响应Enum类
    progress: 进度值
"""
async def deployment_log_output(processObject, send_news, responseCodeEnum: ResponseCodeEnum):
    try:
        # 使用 async for 循环读取日志
        async for line in processObject.stdout:
            if not line:
                break
            # 发送日志
            await send_news(line.decode('utf-8'), responseCodeEnum)
            logging.info(f"发送日志信息成功: %s" % (line.decode('utf-8')))
    finally:
        # 等待子进程执行完毕
        await processObject.wait()







************************/deployments/Consumers.py***************************
import os
from typing import Any

import django
import logging

from channels.generic.websocket import AsyncWebsocketConsumer
import json

from deployments.views import cicd_execute
from operation.common.enum.ResponeCodeEnum import ResponseCodeEnum

# 手动设置 DJANGO_SETTINGS_MODULE
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'operation.settings')

# 配置 Django
django.setup()

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        logging.info("connect")
        await self.accept()

    async def disconnect(self, close_code):
        pass


    """
    Exception inside application: You cannot call this from an async context - use a thread or sync_to_async.
    Traceback (most recent call last):
    这个错误提示表明你在异步上下文中调用了同步代码,这在 Django Channels 中是不允许的。
    你需要将同步代码转换为异步代码,
    或者使用 sync_to_async 包装同步代码。
    """
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        id = text_data_json['id']
        project_name = text_data_json['projectName']

        # 调用CI/CD执行函数
        await self.channel_layer.send(
            self.channel_name,
            {
                'type': 'execute_cicd',
                'id': id,
                'project_name': project_name,
            }
        )

    async def execute_cicd(self, event):
        id = event['id']
        project_name = event['project_name']

        # 调用CI/CD执行函数
        await cicd_execute(id, project_name, self.send_progress,self.send_news)

    """
        发送 进度
    """
    async def send_progress(self, progress,status,code,message):
        await self.send(text_data=json.dumps({
            'type': 'progress',
            'code': code,
            'message': message,
            'data': {
                'progress': progress,  #进度值
                'status': status        # 状态
            }
        }))


    """
        发送 执行日志
        : 类型注解,用于明确指定 responseCodeEnum 参数的类型是 ResponseCodeEnum
    """
    async def send_news(self,logInfo: Any,responseCodeEnum:ResponseCodeEnum):
        await self.send(text_data=json.dumps({
            'type': 'news',
            'code': responseCodeEnum.code,
            'message': responseCodeEnum.message,
            'data': {
                'logInfo':logInfo   #日志信息
            }
        }))

四、启动django项目 (需要支持WebSocket或其他异步通信,使用daphne启动)

"""
     和项目交互基本上都是基于这个文件,一般都是在终端输入python3 manage.py [子命令]
     manage.py输入python3 manage.py help查看更多
     启动项目  python3 manage.py runserver 
     创建app模块 python3 manage.py startapp app
     生成迁移文件,描述如何将模型更改应用到数据库中 python3 manage.py makemigrations
     将这些文件迁移应用到数据库中 python3 manage.py migrate
     
     如果你需要支持WebSocket或其他异步通信,使用daphne operation.asgi:application。
    如果你只需要简单的HTTP服务,使用python manage.py runserver。
    
    export DJANGO_SETTINGS_MODULE=your_project_name.settings

"""
#安装Daphne
pip install daphne

#终端输入
daphne operation.asgi:application

五、前端代码

<template>
  <div class="app-container">
    <el-card shadow="always">
      <el-form ref="searchForm" :inline="true" :model="searchMap" style="margin-top: 20px">
        <el-form-item>
          <el-button type="primary" icon="el-icon-search" @click="searchLog('searchForm')">搜索</el-button>
          <el-button type="primary" icon="el-icon-clear" @click="resetForm('searchForm')">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <el-card shadow="always">
      <template>
        <el-button size="mini" icon="el-icon-plus" type="primary" @click="openAddDrawer()">
          新增
        </el-button>
      </template>
    </el-card>

    <el-row :gutter="24">
      <el-col :span="24">
        <el-card shadow="always">
          <div>
            <el-table ref="multipleTable" v-loading="listLoading" :data="getDeploymentListDto" border fit highlight-current-row style="width: 100%;" class="tb-edit">
              <el-table-column prop="id" label="应用id" width="180px" align="center"></el-table-column>
              <el-table-column prop="name" label="应用名称" width="180px" align="center"></el-table-column>
              <el-table-column prop="status" label="状态" width="180px" align="center" :formatter="statusFormatter"></el-table-column>
              <el-table-column prop="created_at" label="创建时间" width="180px" align="center"></el-table-column>
              <el-table-column prop="actions" label="操作">
                <template slot-scope="scope">
                  <el-button type="text" @click="redeploy(scope.row)">重新部署</el-button>
                </template>
              </el-table-column>
              <!-- <el-table-column label="部署进度">
                <template slot-scope="scope">
                  <el-progress :percentage="scope.row.progress" v-if="scope.row.progress"></el-progress>
                </template>
              </el-table-column> -->
              <el-table-column label="部署进度">
                <template slot-scope="scope">
                  <div style="display: flex; align-items: center;">
                    <el-progress :percentage="scope.row.progress" v-if="scope.row.progress" style="flex: 1;"></el-progress>
                    <el-button type="text" @click="viewLogs(scope.row)" style="margin-left: 10px;">查看日志</el-button>
                  </div>
                </template>
              </el-table-column>

            </el-table>
          </div>
          <div class="block">
            <el-pagination :current-page="currentPage" :page-sizes="[5, 10, 15, 20]" :page-size="5" layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
          </div>
        </el-card>
      </el-col>
    </el-row>

    <!--新增项目 Drawer 层-->
    <el-drawer title="增加应用" :visible.sync="addDrawerVisible" :direction="direction" size="50%">
      <el-card shadow="always">
        <el-form ref="addProjectForm" :model="addProjectForm" status-icon :rules="rules" label-width="100px" class="demo-ruleForm">
          <el-form-item label="应用名称" prop="name">
            <el-input v-model="addProjectForm.name" placeholder="请输入应用名称" />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="addProjectSubmitForm('addProjectForm')">
              保存
            </el-button>
          </el-form-item>
        </el-form>
      </el-card>
    </el-drawer>


    <!-- 日志弹出层 -->
    <el-drawer title="日志信息" :visible.sync="viewLogDrawerVisible" :direction="direction" size="50%">
      <el-card shadow="always">
        <el-scrollbar ref="logScrollbar" style="height: 800px;"> <!--设置滚动区域的高度-->
          <div v-html="logContent"></div>
        </el-scrollbar>
      </el-card>
    </el-drawer>


  </div>
</template>

<script>
  import {
    getDeploymentsList,
    viewCICD,
    addDeployment
  } from '@/api/deployment/deployment-request'
  import {
    message
  } from 'rhea-promise'
  import {
    ElScrollbar
  } from 'element-ui';

  export default {
    name: 'DeploymentPage',
    data() {
      return {
        getDeploymentListDto: [], // 数据传给list,列表渲染的数据
        total: 0,
        listLoading: true,
        dialogVisible: false,
        currentPage: 1,
        addDrawerVisible: false,
        viewLogDrawerVisible: false,
        direction: 'rtl',
        logContent: '',
        jsonstr: {
          "id": "",
          "projectName": ""
        },
        addProjectForm: {
          name: ""
        },
        rules: {
          name: [{
            required: true,
            message: '请输入应用名称',
            trigger: 'blur'
          }],
        },
        params: {
          page: 1, // 当前页码
          page_size: 5 // 每页显示数目
        },
        searchMap: {},
        socket: null
      }
    },
    mounted() {
      this.getDeploymentsList()
    },
    beforeDestroy() {
      if (this.socket) {
        this.socket.close()
      }
    },
    methods: {
      handleSizeChange(val) {
        console.log(`每页 ${val} 条`)
        this.params.page_size = val
        this.getDeploymentsList()
      },
      handleCurrentChange(val) {
        console.log(`当前页: ${val}`)
        this.params.page = val
        this.getDeploymentsList()
      },
      // 格式化 列
      statusFormatter(row, column, cellValue) {
        switch (cellValue) {
          case '0':
            return '未启动';
          case '1':
            return '初始化中';
          case '2':
            return '初始化成功';
          case '3':
            return '初始失败';
          case '4':
            return '部署中';
          case '5':
            return '部署成功';
          case '6':
            return '部署失败';
          case '7':
            return '启动中';
          case '8':
            return '启动成功';
          case '9':
            return '启动失败';
          default:
            return '未知状态';
        }


      },
      // 重置功能, element ui 提供的功能
      resetForm(formName) {
        console.log(this.$refs[formName].resetFields)
        this.$refs[formName].resetFields()
        this.getDeploymentsList()
      },
      searchLog(formName) {
        console.log(this.searchMap)
        this.getDeploymentsList()
      },
      //新增项目
      openAddDrawer() {
        this.addDrawerVisible = true; // 显示Drawer
      },
      viewLogs(row) {
        // 这里可以添加查看日志的逻辑
        // console.log('查看日志:', row);
        // // 例如,可以打开一个新的对话框或跳转到日志页面
        // this.$message.info(`查看日志: ${row.name}`);
        this.viewLogDrawerVisible = true
      },
      // 操作日志列表 ajax 请求
      getDeploymentsList() {
        // 目标需求:在历史查询列表页面中加入查询的转圈的loading加载动画。
        this.dataLoading = true
        console.log('请求参数:' + this.params)
        getDeploymentsList(this.params).then((res) => {
          console.log('响应:', res.data.data)
          this.getDeploymentListDto = res.data.data
          this.total = res.data.pagination.total

          setTimeout(() => { // 超过指定超时时间  关闭查询的转圈的loading加载动画
            this.listLoading = false
          }, 1.5 * 1000)
        })
      },
      addProjectSubmitForm(formName) {
        this.$refs[formName].validate((valid) => {
          if (valid) {
            this.addProjectInfo();
          } else {
            console.log('error submit!!');
            this.getDeploymentsList();
            return false;
          }
        });
      },
      //增加应用
      addProjectInfo() {
        this.dataLoading = true
        addDeployment(this.addProjectForm).then((res) => {
          console.log(res);
          this.addDrawerVisible = false; // 关闭抽屉
          this.getDeploymentsList();
          this.$refs.addOrgForm.resetFields();
          // this.$router.push('/organizationList'); // 假设列表页的路由路径是 /list
          setTimeout(() => {
            this.listLoading = false
          }, 1.5 * 1000)
        })
      },
      redeploy(row) {
        this.socket = new WebSocket('ws://localhost:8000/ws/cicd_progress/');
        this.jsonstr.id = row.id;
        this.jsonstr.projectName = row.name;


        this.socket.onopen = () => {
          this.socket.send(JSON.stringify(this.jsonstr));
        };

        this.socket.onmessage = (event) => {
          const data = JSON.parse(event.data);
          if (data.type === 'progress') {
            // 处理进度消息
            if (data.code !== 200) {
              // 处理错误消息
              row.status = data.data.status; // 更新当前状态
              alert(data.data.status)
              this.getDeploymentsList();
            } else {
              // 更新进度条
              const progress = data.data.progress; // 假设服务器返回的进度数据在progress字段中
              row.progress = progress; // 更新进度条的百分比
              row.status = data.data.status; //更新当前状态
              this.getDeploymentsList();
            }
          } else if (data.type === 'news') {
            // 处理日志消息
            this.logContent += `<p>${data.data.logInfo}</p>`; // 追加日志
            this.$nextTick(() => {
              this.scrollToBottom(); // 每次更新日志后滚动到底部
            });
          } else {
            console.error('Unknown message type:', data.type);
          }
        };

        this.socket.onclose = () => {
          console.log('WebSocket closed');
          row.status = '5'; // 假设部署成功后状态为5
        };

        this.socket.onerror = (error) => {
          console.error('WebSocket error:', error);
          row.status = '6'; // 假设部署失败后状态为6
          this.$message.error('WebSocket 连接错误'); // 显示错误消息
        };
      },
      scrollToBottom() {
        const scrollbar = this.$refs.logScrollbar.$refs.wrap;
        scrollbar.scrollTop = scrollbar.scrollHeight;
      },
    }
  }
</script>



<style>
</style>

六、效果

#启动前端项目 点击重新发布  点击查看日志
npm run dev

#maven 打包构建 日志显示

#运行 docker run 后   docker logs 日志显示

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/926697.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【小白学机器学习37】用numpy计算协方差cov(x,y) 和 皮尔逊相关系数 r(x,y)

目录 1 关于1个数组np.array&#xff08;1组数据&#xff09;如何求各种统计数据 2 关于2个数组np.array&#xff08;2组数据&#xff09;如何求数组的相关关系&#xff1f; 2.1 协方差公式和方差公式 2.2 协方差 公式 的相关说明 2.3 用np.cov(x,y,ddof0) 直接求协方差矩…

(超详细图文)PLSQL Developer 配置连接远程 Oracle 服务

1、下载配置文件 &#xff08;超详细图文详情&#xff09;Navicat 配置连接 Oracle-CSDN博客 将下载的文件解压到单独文件夹&#xff0c;如&#xff1a;D:\App\App_Java\Oracle\instantclient-basic-windows.x64-19.25.0.0.0dbru 2、配置 打开 PLSQL Developer&#xff0c;登…

学习视频超分辨率扩散模型中的空间适应和时间相干性(原文翻译)

文章目录 摘要1. Introduction2. Related Work3. Our Approach3.1. Video Upscaler3.2. Spatial Feature Adaptation Module3.3. Temporal Feature Alignment Module3.4. Video Refiner3.5. Training Strategy 4. Experiments4.1. Experimental Settings4.2. Comparisons with …

设计模式---建造者模式

建造者模式 一种创建型设计模式&#xff0c;它允许你一步一步地构建复杂对象。通过使用建造者模式&#xff0c;你可以将对象的构建过程与其表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。说白点就是&#xff0c;解决了构造函数创建对象的问题。 适用于那种构造函…

AtomicIntegerFieldUpdater能否降低内存

1. 代码如下&#xff1a; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger;public class AtomicIntegerTest {final AtomicInteger startPosition new AtomicInteger(0);final AtomicInteger wrotePosition new Atom…

element的el-table表格标题用css自定义是否必填,用添加伪类的方式标红色*

element的el-table表格标题用css自定义是否必填添加伪类红色 * 效果图如下&#x1f447; el-table组件的html部分 css部分 /deep/.el-table__header-wrapper{.el-table__header{.has-gutter tr .el-table__cell:nth-of-type(3) .cell:before{content: *;color:red}.has-gutte…

数据库的⽤户和权限管理

数据库的⽤户和权限管理 应⽤场景⽤户查看⽤户创建⽤户语法注意事项示例 修改密码语法示例 删除⽤户语法 权限与授权给⽤户授权语法⽰例 回收权限语法⽰例 应⽤场景 数据库服务安装成功后默认有⼀个root⽤户&#xff0c;可以新建和操纵数据库服务中管理的所有数据库。在真实的…

C语言数据结构-栈和队列

C语言数据结构-栈和队列 1.栈1.1概念与结构1.2栈的实现1.2.1结构体定义1.2.2初始化1.2.3销毁1.2.4入栈1.2.5出栈1.2.6取栈顶处的元素1.2.7获取栈中有效的个数 2.队列2.1概念与结构2.2队列的实现2.2.1结构体定义2.2.2入队列2.2.3判断是否为空2.2.4队列中的有效元素个数2.2.5删除…

[CTF/网络安全] 攻防世界 upload1 解题详析

[CTF/网络安全] 攻防世界 upload1 解题详析 考察文件上传&#xff0c;具体原理及姿势不再赘述。 姿势 在txt中写入一句话木马<?php eval($_POST[qiu]);?> 回显如下&#xff1a; 查看源代码&#xff1a; Array.prototype.contains function (obj) { var i this.…

TiDB 优化器丨执行计划和 SQL 算子解读最佳实践

作者&#xff1a; TiDB社区小助手 原文来源&#xff1a; https://tidb.net/blog/5edb7933 导读 在数据库系统中&#xff0c;查询优化器是数据库管理系统的核心组成部分&#xff0c;负责将用户的 SQL 查询转化为高效的执行计划&#xff0c;因而会直接影响用户体感的性能与稳…

监控视频汇聚平台:Liveweb视频监控管理平台方案详细介绍

Liveweb国标视频综合管理平台是一款以视频为核心的智慧物联应用平台。它基于分布式、负载均衡等流媒体技术进行开发&#xff0c;提供广泛兼容、安全可靠、开放共享的视频综合服务。该平台具备多种功能&#xff0c;包括视频直播、录像、回放、检索、云存储、告警上报、语音对讲、…

10个Word自动化办公脚本

在日常工作和学习中&#xff0c;我们常常需要处理Word文档&#xff08;.docx&#xff09;。 Python提供了强大的库&#xff0c;如python-docx&#xff0c;使我们能够轻松地进行文档创建、编辑和格式化等操作。本文将分享10个使用Python编写的Word自动化脚本&#xff0c;帮助新…

ROS VSCode调试方法

VSCode 调试 Ros文档 1.编译参数设置 cd catkin_ws catkin_make -DCMAKE_BUILD_TYPEDebug2.vscode 调试插件安装 可在扩展中安装(Ctrl Shift X): 1.ROS 2.C/C 3.C Intelliense 4.Msg Language Support 5.Txt Syntax 3.导入已有或者新建ROS工作空间 3.1 导入工作…

排序学习整理(1)

1.排序的概念及运用 1.1概念 排序&#xff1a;所谓排序&#xff0c;就是使⼀串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作&#xff0c;以便更容易查找、组织或分析数据。 1.2运用 购物筛选排序 院校排名 1.3常见排序算法 2.实…

TYUT设计模式大题

对比简单工厂&#xff0c;工厂方法&#xff0c;抽象工厂模式 比较安全组合模式和透明组合模式 安全组合模式容器节点有管理子部件的方法&#xff0c;而叶子节点没有&#xff0c;防止在用户在叶子节点上调用不适当的方法&#xff0c;保证了的安全性&#xff0c;防止叶子节点暴露…

指针与引用错题汇总

int *p[3]; // 定义一个包含 3 个指向 int 的指针的数组int a 10, b 20, c 30; p[0] &a; // p[0] 指向 a p[1] &b; // p[1] 指向 b p[2] &c; // p[2] 指向 c // 访问指针所指向的值 printf("%d %d %d\n", *p[0], *p[1], *p[2]); // 输出: 10 20 30…

uniapp中scrollview配合swiper实现一个简单的tab标签页

<template><view class"tab-container"><!-- Tab 标签滚动容器 --><scroll-view scroll-x"true" class"tab-scroll" scroll-with-animation"true"><view class"tab-list"><viewv-for"…

opengl 三角形

最后效果&#xff1a; OpenGL version: 4.1 Metal 不知道为啥必须使用VAO 才行。 #include <glad/glad.h> #include <GLFW/glfw3.h>#include <iostream> #include <vector>void framebuffer_size_callback(GLFWwindow *window, int width, int heigh…

Qml-TabBar类使用

Qml-TabBar类使用 TabBar的概述 TabBar继承于Container 由TabButton进行填充&#xff0c;可以与提供currentIndex属性的任何容器或布局控件一起使用&#xff0c;如StackLayout 或 SwipeView&#xff1b;contentHeight : real:TabBar的内容高度&#xff0c;用于计算标签栏的隐…

Windows常用DOS指令(附案例)

文章目录 1.dir 查看当前目录2.cd 进入指定目录3.md 创建指定目录4.cd> 创建指定文件5.rd 删除指定空目录6.del 删除指定文件7.copy 复制文件8.xcopy 批量复制9.ren 改名10.type 在命令行空窗口打开文件11.cls 清空DOS命令窗口12.chkdsk 检查磁盘使用情况13.time 显示和设置…