# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------

# ------------------------------
# Docker模型
# ------------------------------
import json
import os
import time
import gettext
_ = gettext.gettext

import public
import traceback
from btdockerModelV2 import containerModel as dc
from btdockerModelV2 import dk_public as dp
from btdockerModelV2.dockerBase import dockerBase
from public.validate import Param


class main(dockerBase):

    def get_docker_compose_version(self):
        try:
            import subprocess
            result = subprocess.run(["docker-compose", "version", "--short"], capture_output=True, text=True)
            version_str = result.stdout.strip()
            major, minor, patch = map(int, version_str.split('.'))
            return major, minor, patch
        except Exception as e:
            print("Error:", e)
            return None

    # 验证配置文件
    def check_conf(self, path):
        # 2024/3/21 下午 5:58 检测path是否存在中文，如果有就false return
        if public.check_chinese(path):
            return public.return_message(-1, 0, public.lang("The file path cannot contain Chinese characters!"))

        # 2024/3/20 下午 4:33 获取docker-compose的版本，如果大于v2.24.7则不需要检测，比如是v2.25，解决高版本的docker-compose
        version = self.get_docker_compose_version()
        if version and version > (2, 24, 7):
            tmpfile = public.md5(path)
            public.ExecShell(r"\cp -r {} /tmp/{}.yml".format(path, public.md5(path)))
            public.ExecShell("sed -i '/version:/d' /tmp/{}.yml".format(tmpfile))
            path = "/tmp/{}.yml".format(tmpfile)

        shell = "/usr/bin/docker-compose -f {} config".format(path)
        a, e = public.ExecShell(shell)
        if e and "setlocale: LC_ALL: cannot change locale (en_US.UTF-8)" not in e:
            return public.return_message(-1, 0, public.lang("Detection failed: {}", e))
        return public.return_message(0, 0, public.lang("Detection passes!"))

    # 用引导方式创建模板
    def add_template_gui(self, get):
        """
        用引导方式创建模板
        :param name                     模板名
        :param description              模板描述
        :param data                     模板内容 {"version":3,"services":{...}...}
        :param get:
        模板文件参数：
        version 2/3version
            2: 仅支持单机
            3：支持单机和多机模式
        services:
            多个容器的集合
            下一层执行服务名
            如web1,服务名下面指定服务的变量
            web1:
                build: .                    基于dockerfile构建一个镜像
                image: nginx                服务所使用的镜像为nginx
                container_name: "web"       容器名
                depends_on:                 该服务在db服务启动后再启动
                  - db
                ports:
                  - "6061:80"               将容器的80端口映射到主机的6061端口
                networks:
                  - frontend                该容器所在的网络
                deploy:                     指定与部署和运行服务相关的配置(在使用 swarm时才会生效)
                  replicas: 6               6个副本
                  update_config:
                    parallelism: 2
                    delay: 10s
                  restart_policy:
                    condition: on-failure
        其他详细描述可以参考 https://docs.docker.com/compose/compose-file/compose-file-v3
        :return:
        """
        import yaml
        path = "{}/template".format(self.compose_path)
        file = "{}/{}.yaml".format(path, get.name)
        if not os.path.exists(path):
            os.makedirs(path)
        data = json.loads(get.data)
        yaml.dump(data, file)

    def get_template_kw(self, get):
        data = {
            "version": "",
            "services": {
                "server_name_str": {  # 用户输入
                    "build": {
                        "context": "str",
                        "dockerfile": "str",
                        "get": [],
                        "cache_from": [],
                        "labels": [],
                        "network": "str",
                        "shm_size": "str",
                        "target": "str"
                    },
                    "cap_add": "",
                    "cap_drop": "",
                    "cgroup_parent": "str",
                    "command": "str",
                    "configs": {
                        "my_config_str": []
                    },
                    "container_name": "str",
                    "credential_spec": {
                        "file": "str",
                        "registry": "str"
                    },
                    "depends_on": [],
                    "deploy": {
                        "endpoint_mode": "str",
                        "labels": {
                            "key": "value"
                        },
                        "mode": "str",
                        "placement": [{"key": "value"}],
                        "max_replicas_per_node": "int",
                        "replicas": "int",
                        "resources": {
                            "limits": {
                                "cpus": "str",
                                "memory": "str",
                            },
                            "reservations": {
                                "cpus": "str",
                                "memory": "str",
                            },
                            "restart_policy": {
                                "condition": "str",
                                "delay": "str",
                                "max_attempts": "int",
                                "window": "str"
                            }
                        }
                    }
                }
            }
        }

    # 创建项目配置文件
    def add_template(self, get):
        """
        添加一个模板文件
        :param name                     模板名
        :param remark              模板描述
        :param data                     模板内容
        :param get:
        :return:
        """
        import re
        name = get.name
        if not re.search(r"^[\w\.\-]+$", name):
            return public.return_message(-1, 0, public.lang("Template names cannot contain special characters; only letters, numbers, underscores, dots, and underscores are supported"))

        template_list = self._template_list(get)
        for template in template_list:
            if name == template['name']:
                return public.return_message(-1, 0, public.lang("This template name already exists!"))

        path = "{}/{}/template".format(self.compose_path, name)
        file = "{}/{}.yaml".format(path, name)
        if not os.path.exists(path):
            os.makedirs(path)
        public.writeFile(file, get.data)

        check_res = self.check_conf(file)
        if check_res['status'] == -1:
            if os.path.exists(file):
                os.remove(file)
            return public.return_message(-1, 0, check_res['message'])

        pdata = {
            "name": name,
            "remark": public.xsssec(get.remark),
            "path": file
        }
        dp.sql("templates").insert(pdata)
        dp.write_log("Added template [{}] successfully!".format(name))
        public.set_module_logs('docker', 'add_template', 1)
        return public.return_message(0, 0, public.lang("Template added successfully!"))

    def edit_template(self, get):
        """
        :param id 模板id
        :param data 模板内容
        :param remark              模板描述
        :param get:
        :return:
        """
        template_info = dp.sql("templates").where("id=?", (get.id,)).find()
        if not template_info:
            return public.return_message(-1, 0, public.lang("Did not change the template!"))

        # if "data" not in get:
        #     return public.return_message(-1, 0, public.lang("Template content format error, please enter a valid docker-compose template!"))
        #
        # if "version" not in get.data:
        #     return public.return_message(-1, 0, public.lang("Template content format error, please enter a valid docker-compose template!"))

        public.writeFile(template_info['path'], get.data)
        check_res = self.check_conf(template_info['path'])
        if check_res['status'] == -1:
            return public.return_message(-1, 0, check_res['message'])
        pdata = {
            "name": get.name,
            "remark": public.xsssec(get.remark),
            "path": template_info['path']
        }
        dp.sql("templates").where("id=?", (get.id,)).update(pdata)
        dp.write_log("Edit template [{}] successful!".format(template_info['name']))
        return public.return_message(0, 0, public.lang("Modified template successfully!"))

    def get_template(self, get):
        """
        id 模板ID
        获取模板内容
        :return:
        """

        # 校验参数
        try:
            get.validate([
                Param('template_id').Require().Integer(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0,  str(ex))

        template_info = dp.sql("templates").where("id=?", (get.template_id,)).find()
        if not template_info:
            return public.return_message(-1, 0, public.lang("This template was not found!"))

        return public.return_message(0, 0, public.readFile(template_info['path']))

    def template_list(self, get):
        """
        获取所有模板
        :param get:
        :return:
        """
        template = dp.sql("templates").select()[::-1]
        if not isinstance(template, list):
            template = []

        return public.return_message(0, 0, template)

    # 内部调用 不改响应格式
    def _template_list(self, get):
        """
        获取所有模板
        :param get:
        :return:
        """
        template = dp.sql("templates").select()[::-1]
        if not isinstance(template, list):
            template = []

        return template
    #  检查模板是否被使用
    def check_use_template(self, get):
        """
        检查模板是否被使用
        :param get:
        :return:
        """
        template_info = dp.sql("stacks").where("template_id=?", (get.id,)).find()
        if not template_info:
            return public.return_message(-1, 0,  "")
        return public.return_message(0, 0, template_info["name"])

    def remove_template(self, get):
        """
        删除模板
        :param template_id
        :param get:
        :return:
        """
        # 校验参数
        try:
            get.validate([
                Param('template_id').Require().Integer(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0, str(ex))

        data = dp.sql("templates").where("id=?", (get.template_id,)).find()
        if not data:
            return public.return_message(-1, 0, public.lang("This template was not found!"))
        if os.path.exists(data['path']):
            # 删除模板文件 目录不处理
            try:
                os.remove(data['path'])
            except:
                pass

        get.id = get.template_id

        if self.check_use_template(get)['status']==0:
            # 模板已被使用
            if hasattr(get, "status"):
                template_info = dp.sql("stacks").where("template_id=?", (get.template_id,)).find()
                if os.path.exists(template_info["path"]):
                    public.ExecShell("/usr/bin/docker-compose -f {} down".format(template_info["path"]))
                else:
                    stdout, stderr = public.ExecShell("docker-compose ls --format json")
                    try:
                        info = json.loads(stdout)
                    except:
                        info = []
                    for i in info:
                        if i['Name'] == public.md5(template_info['name']):
                            public.ExecShell("/usr/bin/docker-compose -p {} down".format(i['Name']))
                            break
                dp.sql("stacks").delete(id=template_info["id"])

        dp.sql("templates").delete(id=get.template_id)
        dp.write_log("Delete template [{}] successfully!".format(data['name']))
        return public.return_message(0, 0, public.lang("successfully delete!"))

    def edit_project_remark(self, get):
        """
        编辑项目
        :param project_id 项目
        :param remark备注
        :param get:
        :return:
        """
        stacks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
        if not stacks_info:
            return public.return_message(-1, 0, public.lang("The item was not found!"))
        pdata = {
            "remark": public.xsssec(get.remark)
        }
        dp.write_log("Comment for project [{}] changed successfully [{}] --> [{}]!".format(stacks_info['name'],
                                                                                           stacks_info['remark'],
                                                                                           public.xsssec(get.remark)))
        dp.sql("stacks").where("id=?", (get.project_id,)).update(pdata)

    def edit_template_remark(self, get):
        """
        编辑项目
        :param templates_id 项目
        :param remark备注
        :param get:
        :return:
        """
        stacks_info = dp.sql("templates").where("id=?", (get.templates_id,)).find()
        if not stacks_info:
            return public.return_message(-1, 0, public.lang("The template was not found!"))
        pdata = {
            "remark": public.xsssec(get.remark)
        }
        dp.write_log(
            "Modify template [{}] Remark successful [{}] --> [{}]!".format(stacks_info['name'], stacks_info['remark'],
                                                                           public.xsssec(get.remark)))
        dp.sql("templates").where("id=?", (get.templates_id,)).update(pdata)

    def create_project_in_path(self, name, path):
        shell = "cd {} && /usr/bin/docker-compose -p {} up -d &> {}".format("/".join(path.split("/")[:-1]), name,
                                                                            self._log_path)
        public.ExecShell(shell)

    def create_project_in_file(self, project_name, file):
        project_path = "{}/{}".format(self.compose_path, project_name)
        project_file = "{}/docker-compose.yaml".format(project_path)
        if not os.path.exists(project_path):
            os.makedirs(project_path)
        template_content = public.readFile(file)
        public.writeFile(project_file, template_content)
        shell = "/usr/bin/docker-compose -p {} -f {} up -d &> {}".format(project_name, project_file, self._log_path)
        public.ExecShell(shell)

    def check_project_container_name(self, template_data, get):
        """
        检测模板文件中的容器名是否已经存在
        :return:
        """
        import re
        data = []
        template_container_name = re.findall("container_name\s*:\s*[\"\']+(.*)[\'\"]", template_data)
        # 调用容器列表接口 选择不改统一返回的
        container_list = dc.main()._get_list(get)

        container_list = container_list['container_list']
        for container in container_list:
            if container['name'] in template_container_name:
                data.append(container['name'])
        if data:
            return public.return_message(-1, 0, public.lang("The container name already exists!: <br>[{}]", ", ".join(data)))
        # 获取模板所使用的端口
        rep = r"(\d+):\d+"
        port_list = re.findall(rep, template_data)
        for port in port_list:
            if dp.check_socket(port):

                return public.return_message(-1, 0, public.lang("This port [{}] is already used by other templates", port))

    # 创建项目
    def create(self, get):
        """
        :param project_name         项目名
        :param remark          描述
        :param template_id             模板ID
        :param rags:
        :return:
        """
        # {"template_id": "13", "project_name": "dedf2f", "remark": "从本地添加asd"}
        # 校验参数
        try:
            get.validate([
                Param('template_id').Require().Integer(),
                Param('project_name').Require().String().Xss(),
                Param('remark').Require().String(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0, str(ex))

        try:
            project_name = public.md5(public.xsssec(get.project_name))
            if "template_id" not in get:
                return public.return_message(-1, 0, public.lang("Parameter error, please pass in template_id!"))
            template_id = get.template_id
            template_info = dp.sql("templates").where("id=?", template_id).find()
            if len(template_info) < 1:
                return public.return_message(-1, 0, public.lang("This template was not found, or file is corrupt!"))

            if not os.path.exists(template_info['path']):
                return public.return_message(-1, 0,  public.lang("Template file does not exist"))

            template_exist = dp.sql("stacks").where("template_id=?", (template_id,)).find()
            if template_exist:
                return public.return_message(-1, 0,
                                             public.lang("Template [{}] has been deployed by project: [{}], please change a template and try again!",
                                                 template_info['name'], template_exist['name']))

            name_exist = self.check_project_container_name(public.readFile(template_info['path']), get)
            if name_exist:
                return public.return_message(-1, 0, name_exist['message'])

            stacks_info = dp.sql("stacks").where("name=?", (public.xsssec(get.project_name))).find()
            if not stacks_info:
                pdata = {
                    "name": public.xsssec(get.project_name),
                    "status": "1",
                    "path": template_info['path'],
                    "template_id": template_id,
                    "time": time.time(),
                    "remark": public.xsssec(get.remark)
                }
                dp.sql("stacks").insert(pdata)
            else:
                return public.return_message(-1, 0, public.lang("The project name already exists!"))

            if template_info['add_in_path'] == 1:
                self.create_project_in_path(
                    project_name,
                    template_info['path']
                )
            else:
                self.create_project_in_file(
                    project_name,
                    template_info['path']
                )

            # 检查容器是否成功执行
            check_shell = "/usr/bin/docker-compose -p {} ps -q".format(project_name)
            container_ids, _ = public.ExecShell(check_shell)
            if not container_ids.strip():
                raise Exception("Docker deployment failed. Please check logs at: {}".format(self._log_path))

            dp.write_log("Project [{}] deployed successfully!".format(public.xsssec(get.project_name)))
            public.set_module_logs('docker', 'add_project', 1)
            return public.return_message(0, 0, public.lang("Successful deployment!"))
        except Exception as ex:
            dp.sql("stacks").where("name=?", (public.xsssec(get.project_name),)).delete()
            public.print_log(traceback.format_exc())
            return public.return_message(-1, 0, str(ex))

    def compose_project_list(self, get):
        """
        获取所有已部署的项目列表
        @param get:
        """
        compose_project = dp.sql("stacks").select()
        # public.print_log("部署项目  {}".format(compose_project))
        try:
            cmd_result = public.ExecShell("/usr/bin/docker-compose ls -a --format json")[0]
            if "Segmentation fault" in cmd_result:
                return public.return_message(-1, 0, public.lang("docker-compose is too low, please upgrade to the latest version!"))
            result = json.loads(cmd_result)
        except:
            result = []

        for i in compose_project:
            for j in result:
                if public.md5(i['name']) in j['Name']:
                    i['run_status'] = j['Status'].split("(")[0].lower()
                    break
                else:
                    i['run_status'] = "exited"

        return public.return_message(0, 0, compose_project)

    def project_container_count(self, get):
        """
        获取项目容器数量
        @param get:
        @return:
        """
        from btdockerModelV2.dockerSock import container
        sk_container = container.dockerContainer()
        sk_container_list = sk_container.get_container()

        stacks_info = dp.sql("stacks").select()
        net_info = []

        for i in stacks_info:
            count = 0
            for c in sk_container_list:
                if public.md5(i['name']) in c["Names"][0].replace("/", ""):
                    count += 1
                    continue

                if 'com.docker.compose.project' in c.keys():
                    if "com.docker.compose.project.config_files" in c:
                        if public.md5(i['name']) in c['com.docker.compose.project.config_files']:
                            count += 1
                            continue

                        if public.md5(i['name']) in public.md5(c['com.docker.compose.project.config_files']):
                            count += 1
                            continue

                if 'com.docker.compose.project' in c['Labels'].keys():
                    if public.md5(i['name']) in c['Labels']['com.docker.compose.project.config_files']:
                        count += 1
                        continue

                    if public.md5(i['name']) in public.md5(c['Labels']['com.docker.compose.project.config_files']):
                        count += 1
                        continue

            net_info.append(count)

        return public.return_message(0, 0, net_info)

    def get_compose_container(self, get):
        """
        目前仅支持本地 url: unix:///var/run/docker.sock
        """
        # 校验参数
        try:
            get.validate([
                Param('name').Require().String(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0, str(ex))

        from btdockerModelV2.dockerSock import container
        sk_container = container.dockerContainer()
        sk_container_list = sk_container.get_container()

        project_container_list = []
        for c in sk_container_list:
            if public.md5(get.name) in dp.rename(c["Names"][0].replace("/", "")):
                project_container_list.append(dc.main().struct_container_list(c))
                continue

            if 'com.docker.compose.project' in c.keys():
                if "com.docker.compose.project.config_files" in c:
                    if public.md5(get.name) in c['com.docker.compose.project.config_files']:
                        project_container_list.append(dc.main().struct_container_list(c))

                    if public.md5(get.name) in public.md5(c['com.docker.compose.project.config_files']):
                        project_container_list.append(dc.main().struct_container_list(c))

            if 'com.docker.compose.project' in c['Labels'].keys():
                if "com.docker.compose.project.config_files" in c['Labels']:
                    if public.md5(get.name) in c['Labels']['com.docker.compose.project.config_files']:
                        project_container_list.append(dc.main().struct_container_list(c))

                    if public.md5(get.name) in public.md5(c['Labels']['com.docker.compose.project.config_files']):
                        project_container_list.append(dc.main().struct_container_list(c))

        return public.return_message(0, 0, project_container_list)

    # 删除项目
    def remove(self, get):
        """
        project_id          数据库记录的项目ID
        :param get:
        :return:
        """
        # 校验参数
        try:
            get.validate([
                Param('project_id').Require().Integer(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0, str(ex))

        statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
        if not statcks_info:
            return public.return_message(-1, 0, public.lang("The project name was not found!"))
        container_name = public.ExecShell("docker ps --format \"{{.Names}}\"")
        if statcks_info['name'] in container_name[0]:
            shell = f"/usr/bin/docker-compose -p {statcks_info['name']} -f {statcks_info['path']} down &> {self._log_path}"
        else:
            shell = f"/usr/bin/docker-compose -p {public.md5(statcks_info['name'])} -f" \
                    f" {statcks_info['path']} down &> {self._log_path}"
        public.ExecShell(shell)
        dp.sql("stacks").delete(id=get.project_id)

        dp.write_log("Delete project [{}] success!".format(statcks_info['name']))
        return public.return_message(0, 0, public.lang("successfully delete!"))

    def prune(self, get):
        """
        删除所有没有容器的项目
        @param get:
        @return:
        """
        stacks_info = dp.sql("stacks").select()
        container_name = public.ExecShell("docker ps --format \"{{.Names}}\"")[0]
        # 正在运行的容器为空 直接返回结果
        if container_name == "":
            return public.return_message(0, 0, public.lang("The cleanup was successful!"))

        container_name = container_name.split("\n")

        # 数据库列表信息
        for i in stacks_info:
            # 2024/3/21 下午 6:26 如果i['name']在container_name[0]中，说明容器还在运行，不删除
            is_run = False
            docker_name = public.ExecShell("grep 'container_name' {}".format(i["path"]))[0]

            #  实际运行列表
            for j in container_name:
                if j == "": continue
                if public.md5(i['name']) in j or j in docker_name:
                    is_run = True
                    break

            if is_run: continue
            shell = "/usr/bin/docker-compose -f {} down &> {}".format(i['path'], self._log_path)
            public.ExecShell(shell)
            dp.sql("stacks").delete(id=i['id'])
            dp.write_log("Cleanup project [{}] successful!".format(i['name']))
        return public.return_message(0, 0, public.lang("Clean up successfully!"))


    def set_compose_status(self, get):
        """
        设置项目状态
        @param get:
        @return:
        """
        try:
            get.validate([

                Param('status').Require().String('in', ['start', 'stop','restart','pause','unpause','kill']),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0, str(ex))

        if get.status == 'start':
            data = self.start(get)

        elif get.status == 'stop':
            data = self.stop(get)
        elif get.status == 'restart':
            data = self.restart(get)
        elif get.status == 'pause':
            data = self.pause(get)
        elif get.status == 'unpause':
            data = self.unpause(get)
        else:
            data = self.kill(get)

        if data["status"]== -1:
            return public.return_message(0, 0, data['message'])
        else:
            return public.return_message(-1, 0, data['message'])
    # 项目状态执行命令调用
    def __host_temp(self, statcks_info, status):
        file_path = "{}/data/compose/{}/docker-compose.yaml".format(public.get_panel_path(), public.md5(statcks_info['name']))

        status_info = {
            "start": "start",
            "stop": "stop",
            "restart": "restart",
            "pause": "pause",
            "unpause": "Unpause",
            "kill": "kill",
        }

        # 本地添加模版
        if not os.path.exists(file_path):
            if os.path.exists(statcks_info["path"]):
                public.ExecShell("/usr/bin/docker-compose -f {} {} &> {}".format(statcks_info["path"], status, self._log_path))
            else:
                stdout, stderr = public.ExecShell("docker-compose ls --format json")
                try:
                    info = json.loads(stdout)
                except:
                    info = []
                if stdout:
                    for i in info:
                        if i['Name'] == statcks_info['name']:
                            public.ExecShell("docker-compose -p {} {} &> {}".format(i['Name'], status, self._log_path))
                            break
        else:
            shell = "/usr/bin/docker-compose -f {compose_path} {status} &> {_log_path}".format(
                compose_path="{}/data/compose/{}/docker-compose.yaml".format(
                    public.get_panel_path(),
                    public.md5(statcks_info['name'])),
                status=status,
                _log_path=self._log_path
            )

            a, e = public.ExecShell(shell)
            if e:
                return public.return_message(-1, 0,  public.lang("{}The project failed: {}",status_info[status], e))

    def kill(self, get):
        """
        强制停止项目
        @param get:
        @return:
        """
        if not hasattr(get, "project_id"):
            return public.return_message(-1, 0, public.lang("Missing parameter project_id!"))

        statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
        if not statcks_info:
            return public.return_message(-1, 0, public.lang("Project configuration not found!"))

        if not hasattr(get, "status"):
            return public.return_message(-1, 0, public.lang("The status parameter is missing!"))

        self.__host_temp(statcks_info, get.status)
        dp.write_log("Stopping project [{}] succeeded".format(statcks_info['name']))
        return public.return_message(0, 0, public.lang("Setup successful!"))

    def stop(self, get):
        """
        停止项目
        project_id          数据库记录的项目ID
        kill                强制停止项目 0/1
        :param get:
        :return:
        """
        if not hasattr(get, "project_id"):
            return public.return_message(-1, 0, public.lang("Missing parameter project_id!"))

        statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
        if not statcks_info:
            return public.return_message(-1, 0, public.lang("Project configuration not found!"))

        if not hasattr(get, "status"):
            return public.return_message(-1, 0, public.lang("The status parameter is missing!"))

        self.__host_temp(statcks_info, get.status)
        dp.write_log("Stopping project [{}] succeeded!".format(statcks_info['name']))
        return public.return_message(0, 0, public.lang("Setup successful!"))

    def start(self, get):
        """
        启动项目
        project_id          数据库记录的项目ID
        :param get:
        :return:
        """
        if not hasattr(get, "project_id"):
            return public.return_message(-1, 0, public.lang("Missing parameter project_id!"))

        statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
        if not statcks_info:
            return public.return_message(-1, 0, public.lang("Project configuration not found!"))

        if not hasattr(get, "status"):
            return public.return_message(-1, 0, public.lang("The status parameter is missing!"))

        self.__host_temp(statcks_info, get.status)
        dp.write_log("Start project [{}] successful!".format(statcks_info['name']))
        return public.return_message(0, 0, public.lang("Setup successful!"))

    def restart(self, get):
        """
        拉取项目内需要的镜像
        project_id          数据库记录的项目ID
        :param get:
        :return:
        """
        if not hasattr(get, "project_id"):
            return public.return_message(-1, 0, public.lang("Missing parameter project_id!"))

        statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
        if not statcks_info:
            return public.return_message(-1, 0, public.lang("Project configuration not found!"))

        if not hasattr(get, "status"):
            return public.return_message(-1, 0, public.lang("The status parameter is missing!"))

        self.__host_temp(statcks_info, get.status)
        dp.write_log("Restart project [{}] successfully!".format(statcks_info['name']))
        return public.return_message(0, 0, public.lang("Successfully set!"))

    def pull(self, get):
        """
        拉取模板内需要的镜像
        template_id          数据库记录的项目ID
        :param get:
        :return:
        """
        get.name = get.get("name", "")
        get.url = get.get("url", "docker.io")
        get.template_id = get.get("template_id", "")
        if get.template_id == "":
            return public.return_message(-1, 0, public.lang("Missing parameter template_id!"))

        statcks_info = dp.sql("templates").where("id=?", (get.template_id,)).find()
        if not statcks_info:
            return public.return_message(0, 0, public.lang("Didn't find the template!"))

        cmd = "/usr/bin/docker-compose -f {} pull >> {} 2>&1".format(statcks_info['path'], self._log_path)
        if get.name != "" and get.url != "docker.io":
            from btdockerModelV2 import registryModel as dr
            r_info = dr.main().registry_info(get)
            r_info['username'] = public.aes_decrypt(r_info['username'], self.aes_key)
            r_info['password'] = public.aes_decrypt(r_info['password'], self.aes_key)
            login_result = dr.main().login(self._url, r_info['url'], r_info['username'], r_info['password'])
            if not login_result["status"]:
                return login_result

            cmd = "docker login --username {} --password {} {} >> {} 2>&1 &&".format(
                r_info['username'],
                r_info['password'],
                r_info['url'],
                self._log_path
            ) + cmd

        os.system(
            "echo > {};nohup {} && echo 'bt_successful' >> {} "
            "|| echo 'bt_failed' >> {} &".format(
                self._log_path,
                cmd,
                self._log_path,
                self._log_path,
        ))
        dp.write_log("The image inside the template [{}] was pulled successfully  !".format(statcks_info['name']))
        return public.return_message(0, 0, public.lang("Pull successfully!"))

    def pause(self, get):
        """
        暂停项目
        project_id          数据库记录的项目ID
        :param get:
        :return:
        """
        if not hasattr(get, "project_id"):
            return public.return_message(-1, 0, public.lang("Missing parameter project_id!"))

        statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
        if not statcks_info:
            return public.return_message(-1, 0, public.lang("Project configuration not found!"))
        if not hasattr(get, "status"):
            return public.return_message(-1, 0, public.lang("The status parameter is missing!"))

        self.__host_temp(statcks_info, get.status)
        dp.write_log("Pause [{}] success!".format(statcks_info['name']))
        return public.return_message(0, 0, public.lang("Successfully set!"))

    def unpause(self, get):
        """
        取消暂停项目
        project_id          数据库记录的项目ID
        :param get:
        :return:
        """
        if not hasattr(get, "project_id"):
            return public.return_message(-1, 0, public.lang("Missing parameter project_id!"))

        statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
        if not statcks_info:
            return public.return_message(-1, 0, public.lang("Project configuration not found!"))

        if not hasattr(get, "status"):
            return public.return_message(-1, 0, public.lang("The status parameter is missing!"))

        self.__host_temp(statcks_info, get.status)
        dp.write_log("Unpause [{}] success!".format(statcks_info['name']))
        return public.return_message(0, 0, public.lang("Successfully set!"))

    def scan_compose_file(self, path, data):
        """
        递归扫描目录下的compose文件
        :param path 需要扫描的目录
        :param data 需要返回的数据 一个字典
        :param get:
        :return:
        """
        try:
            if not os.path.isdir(path):
                return data

            file_list = os.listdir(path)
            for file in file_list:
                current_path = os.path.join(path, file)
                # 判断是否是文件夹
                if os.path.isdir(current_path):
                    self.scan_compose_file(current_path, data)
                else:
                    if file == "docker-compose.yaml" or file == "docker-compose.yam" or file == "docker-compose.yml":
                        if "/www/server/panel/data/compose" in current_path:
                            continue
                        data.append(current_path)
                    if ".yaml" in file or ".yam" in file or ".yml" in file:
                        if "/www/server/panel/data/compose" in current_path:
                            continue
                        data.append(current_path)
        except:
            pass

        return data

    def get_compose_project(self, get):
        """
        :param path 需要获取的路径 是一个目录
        :param sub_dir 扫描子目录
        :param get:
        :return:
        """
        get.exists(["path", "sub_dir"])
        data = list()
        suffix = ["yaml", "yam", "yml"]
        if get.path == "/":
            return public.return_message(-1, 0, public.lang("Unable to scan the root directory"))

        if get.path[-1] == "/":
            get.path = get.path[:-1]

        if not os.path.exists(get.path):
            return public.return_message(-1, 0, public.lang("The path does not exist!"))

        if str(get.sub_dir) == "1":
            res = self.scan_compose_file(get.path, data)
            if not res:
                res = []
            else:
                tmp = list()
                p_name_tmp = list()
                for i in res:
                    if i.split(".")[1] not in suffix:
                        continue

                    project_name = i.split("/")[-1].split(".")[0]
                    if project_name in p_name_tmp:
                        project_name = "{}_{}".format(project_name, i.split("/")[-2])

                    tmp_data = {
                        "project_name": project_name,
                        "conf_file": "/".join(i.split("/")),
                        "remark": "Add locally"
                    }

                    tmp.append(tmp_data)
                    p_name_tmp.append(tmp_data['project_name'])
                res = tmp
                p_name_tmp.clear()
        else:
            yaml = "{}/docker-compose.yaml".format(get.path)
            yam = "{}/docker-compose.yam".format(get.path)
            yml = "{}/docker-compose.yml".format(get.path)
            if os.path.exists(yaml):
                res = [{
                    "project_name": get.path.split("/")[-1],
                    "conf_file": yaml,
                    "remark": "Add locally"
                }]
            elif os.path.exists(yam):
                res = [{
                    "project_name": get.path.split("/")[-1],
                    "conf_file": yam,
                    "remark": "Add locally"
                }]
            elif os.path.exists(yml):
                res = [{
                    "project_name": get.path.split("/")[-1],
                    "conf_file": yml,
                    "remark": "Add locally"
                }]
            else:
                res = list()

            if not os.path.isdir(get.path):
                return public.return_message(0, 0, res)

            dir_list = os.listdir(get.path)

            for i in dir_list:
                if i.rsplit(".")[-1] in suffix:
                    res.append({
                        "project_name": i.rsplit(".")[0],
                        "conf_file": "/".join(get.path.split("/") + [i]),
                        "remark": "Add locally"
                    })

        # 去重
        res = list({item['conf_file']: item for item in res}.values())

        return public.return_message(0, 0, res)

    # 从现有目录中添加模板
    def add_template_in_path(self, get):
        """
        :param template_list list [{"project_name":"pathtest_template","conf_file":"/www/dockerce/mysecent-project/docker-compose.yaml","remark":"描述描述"}]
        :param get:
        :return:
        """

        create_failed = dict()
        create_successfully = dict()
        for template in get.template_list:
            path = template['conf_file']
            name = template['project_name']
            remark = template['remark']
            exists = self._template_list(get)

            is_continue = False
            for i in exists:
                if name.strip() == i['name'].strip():
                    create_failed[name] = "Template already exists!"
                    is_continue = True
                    break

            if is_continue: continue

            if not os.path.exists(path):
                create_failed[name] = "This template was not found!"
                continue
            check_res = self.check_conf(path)
            if check_res['status'] == -1:
                create_failed[name] = "Template validation failed, possibly malformed!"
                continue
            pdata = {
                "name": name,
                "remark": remark,
                "path": path,
                "add_in_path": 1
            }
            dp.sql("templates").insert(pdata)
            create_successfully[name] = "Template added successfully!"

        for i in create_failed:
            if i in create_successfully:
                del (create_successfully[i])
            else:
                dp.write_log("Template added successfully from path [{}]!".format(i))
        if not create_failed and create_successfully:
            return public.return_message(0, 0,
                                         public.lang("Template added successfully: [{}]", ','.join(create_successfully)))
        elif not create_successfully and create_failed:
            return public.return_message(-1, 0,
                                         public.lang("Failed to add template: template name already exists or is incorrectly formatted [{}],Use docker-compose -f [specify compose.yml file] config to check", ','.join(create_failed)))

        return public.return_message(-1, 0,
                                     public.lang('These templates succeed: [{}]<br> These templates fail: the template name already exists or is incorrectly formatted [{}]',','.join(create_successfully), ','.join(create_failed)))

    def get_pull_log(self, get):
        """
        获取镜像拉取日志，websocket
        @param get:
        @return:
        """
        get.wsLogTitle = "Start to pull the template image, please wait..."
        get._log_path = self._log_path
        return self.get_ws_log(get)

    # 编辑项目    todo 根据删除适配
    def edit(self, get):
        """
        :param project_id: 要编辑的项目的ID
        :param project_name: 新的项目名
        :param remark: 新的描述
        :param template_id: 新的模板ID
        :return:
        """
        # {"project_id": 1, "template_id": 2, "project_name": "福达坊", "remark": ""}
        # 校验参数
        try:
            get.validate([
                Param('project_id').Require().Integer(),
                Param('project_name').Require().String(),
                Param('remark').String(),
                Param('template_id').Require().Integer(),
            ], [
                public.validate.trim_filter(),
            ])
        except Exception as ex:
            public.print_log("error info: {}".format(ex))
            return public.return_message(-1, 0, str(ex))

        # 删除旧的项目
        remove_result = self.remove(get)
        # if not remove_result['status']:
        #     return public.return_message(-1, 0, public.lang("Fail to modify!"))

        # 创建新的项目
        self.create(get)
        return public.return_message(0, 0, public.lang("Modify successfully!"))
