# coding: utf-8 # ------------------------------------------------------------------- # YakPanel # ------------------------------------------------------------------- # Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved. # ------------------------------------------------------------------- # Author: wzz # ------------------------------------------------------------------- # ------------------------------ # docker模型 # ------------------------------ import json import os import sys import time if "/www/server/panel/class" not in sys.path: sys.path.insert(0, "/www/server/panel/class") os.chdir("/www/server/panel") import public from mod.project.docker.app.appManageMod import AppManage # from mod.project.docker.runtime.runtimeManage import RuntimeManage # from mod.project.docker.sites.sitesManage import SitesManage from mod.project.docker.app.sub_app.ollamaMod import OllamaMod from mod.project.docker.apphub.apphubManage import AppHub from btdockerModelV2 import dk_public as dp class main(AppManage, OllamaMod): def __init__(self): super(main, self).__init__() OllamaMod.__init__(self) # 2024/6/26 下午5:49 获取所有已部署的项目列表 def get_project_list(self, get): ''' @name 获取所有已部署的项目列表 @author wzz <2024/6/26 下午5:49> @param "data":{"参数名":""} <数据类型> 参数描述 @return dict{"status":True/False,"msg":"提示信息"} ''' try: if self.def_name is None: self.set_def_name(get.def_name) if hasattr(get, '_ws') and hasattr(get._ws, 'btws_get_project_list'): return while True: compose_list = self.ls(get) if len(compose_list) == 0: if hasattr(get, '_ws'): get._ws.send(json.dumps(self.wsResult( True, data=[], ))) stacks_info = dp.sql("stacks").select() compose_project = [] for j in compose_list: t_status = j["Status"].split(",") container_count = 0 for ts in t_status: container_count += int(ts.split("(")[1].split(")")[0]) j_name = j['Name'] if "bt_compose_" in j_name: config_path = "{}/config/name_map.json".format(public.get_panel_path()) name_map = json.loads(public.readFile(config_path)) if j_name in name_map: j_name = name_map[j_name] else: j_name = j_name.replace("bt_compose_", "") tmp = { "id": None, "name": j_name, "status": "1", "path": j['ConfigFiles'], "template_id": None, "time": None, "remark": "", "run_status": j['Status'].split("(")[0].lower(), "container_count": container_count, } for i in stacks_info: if public.md5(i['name']) in j['Name']: tmp["name"] = i['name'] tmp["run_status"] = j['Status'].split("(")[0].lower() tmp["template_id"] = i['template_id'] tmp["time"] = i['time'] tmp["remark"] = i["remark"] tmp["id"] = i['id'] break if i['name'] == j['Name']: tmp["run_status"] = j['Status'].split("(")[0].lower() tmp["template_id"] = i['template_id'] tmp["time"] = i['time'] tmp["remark"] = i["remark"] tmp["id"] = i['id'] break if tmp["time"] is None: if os.path.exists(j['ConfigFiles']): get.path = j['ConfigFiles'] compose_ps = self.ps(get) if len(compose_ps) > 0 and "CreatedAt" in compose_ps[0]: tmp["time"] = dp.convert_timezone_str_to_timestamp(compose_ps[0]['CreatedAt']) compose_project.append(tmp) if hasattr(get, '_ws'): setattr(get._ws, 'btws_get_project_list', True) get._ws.send(json.dumps(self.wsResult( True, data=sorted(compose_project, key=lambda x: x["time"] if x["time"] is not None else float('-inf'), reverse=True), ))) time.sleep(2) except Exception as e: return public.return_message(-1, 0, str(e)) # 2026/2/26 下午8:55 获取指定compose.yml的docker-compose ps 增加异常处理和类型检查,防止WebSocket推送过程中出现错误导致循环崩溃 def get_project_ps(self, get): ''' @name 获取指定 compose.yml 的 docker-compose ps 实时状态(WebSocket 推送) @author wzz <2026/2/26> @param get.path: compose 项目路径 @return dict {"status": True/False, "msg": "..."} ''' try: if self.def_name is None: self.set_def_name(get.def_name) # 确保 path 存在 if not hasattr(get, 'path') or not get.path: return public.return_message(-1, 0, "Missing 'path' parameter") ws_flag_attr = f'btws_get_project_ps_{get.path}' # 防止重复订阅 if hasattr(get, '_ws') and hasattr(get._ws, ws_flag_attr): result = self.wsResult(True, data=[]) try: get._ws.send(json.dumps(result)) except: pass return result from btdockerModelV2.dockerSock import container sk_container = container.dockerContainer() while True: try: compose_list = self.ps(get) # 强制确保是 list if not isinstance(compose_list, list): compose_list = [] # 发送空结果并退出(无容器) if len(compose_list) == 0: if hasattr(get, '_ws'): try: get._ws.send(json.dumps(self.wsResult(True, data=[]))) except: pass break # 处理每个容器 for l in compose_list: if not isinstance(l, dict): continue # 补全 Image if "Image" not in l: l["Image"] = "" if l.get("ID"): try: inspect = sk_container.get_container_inspect(l["ID"]) l["Image"] = inspect.get("Config", {}).get("Image", "") except: pass # 忽略 inspect 失败 # 补全 Ports 字符串 if "Ports" not in l: l["Ports"] = "" publishers = l.get("Publishers") if publishers and isinstance(publishers, list): for p in publishers: if not isinstance(p, dict): continue url = p.get("URL", "") target = p.get("TargetPort", "") proto = p.get("Protocol", "") pub_port = p.get("PublishedPort", "") if url == "": l["Ports"] += f"{target}/{proto}," else: l["Ports"] += f"{url}:{pub_port}->{target}/{proto}," # 构造结构化 ports(兼容 containerModel.struct_container_ports) ports_data = {} publishers = l.get("Publishers") if publishers and isinstance(publishers, list): for port in publishers: if not isinstance(port, dict): continue key = f"{port.get('TargetPort', '')}/{port.get('Protocol', '')}" host_ip = port.get("URL", "") host_port = str(port.get("PublishedPort", "")) entry = {"HostIp": host_ip, "HostPort": host_port} if key not in ports_data: ports_data[key] = [entry] if host_ip else None elif ports_data[key] is not None: ports_data[key].append(entry) l["ports"] = ports_data # 推送数据 if hasattr(get, '_ws'): setattr(get._ws, ws_flag_attr, True) try: get._ws.send(json.dumps(self.wsResult(True, data=compose_list))) except: # WebSocket 已断开,退出循环 break time.sleep(2) except Exception: # 内部循环异常,安全退出 break return self.wsResult(True, data=[]) except Exception as e: return public.return_message(-1, 0, str(e)) # 2024/11/11 14:34 获取所有正在运行的容器信息和已安装的应用信息 def get_some_info(self, get): ''' @name 获取所有正在运行的容器信息和已安装的应用信息 ''' get.type = get.get("type", "container") if not get.type in ("container", "app"): return public.return_message(-1, 0, public.lang("Only container and app types are supported")) if get.type == "container": from btdockerModelV2.dockerSock import container sk_container = container.dockerContainer() sk_container_list = sk_container.get_container() data = [] for container in sk_container_list: if not "running" in container["State"]: continue port_list = [] for p in container["Ports"]: if not "PublicPort" in p: continue if not p["PublicPort"] in port_list: port_list.append(p["PublicPort"]) data.append({ "id": container["Id"], "name": container["Names"][0].replace("/", ""), "status": container["State"], "image": container["Image"], "created_time": container["Created"], "ports": port_list, }) return public.return_message(0, 0, data) else: get.row = 10000 installed_apps = self.get_installed_apps(get)['message'] not_allow_category = ("Database", "System") if installed_apps and installed_apps.get('data', []): for app in installed_apps["data"]: if not "running" in app["status"]: installed_apps["data"].remove(app) if app["apptype"] in not_allow_category: installed_apps["data"].remove(app) if app in installed_apps["data"] else None # return public.returnResult(status=installed_apps["status"], data=installed_apps["data"]) return public.return_message(0, 0, installed_apps) def generate_apphub(self, get): ''' @name 解析外部应用列表 @author csj <2025/7/9> @return dict{"status":True/False,"msg":"提示信息"} ''' return AppHub().generate_apphub(get) def create_app(self,get): ''' @name 创建应用 @author csj <2025/7/9> @return dict{"status":True/False,"msg":"提示信息"} ''' if get.get("appid","0") == "-1": # 从apphub创建应用 self.templates_path = os.path.join(AppHub.hub_home_path, "templates") self.apps_json_file = os.path.join(AppHub.hub_home_path, "apps.json") version = get.get("version","latest") app_name = get.get("app_name","") if not os.path.exists(self.templates_path): os.makedirs(self.templates_path) #/www/dk_project/dk_app/apphub/apphub/templates/app_name/version app_version_path = os.path.join(AppHub.hub_home_path, app_name, version) if not os.path.exists(app_version_path): return public.return_message(-1, 0, public.lang("Version {} for applying {} does not exist", (version, app_name))) # /www/dk_project/dk_app/apphub/apphub/templates/app_name app_template_path = os.path.join(self.templates_path, app_name) public.ExecShell("\cp -r {} {}".format(app_version_path,app_template_path)) return super().create_app(get) def get_apphub_config(self, get): ''' @name 获取apphub配置 @author csj <2025/7/9> @return dict{"status":True/False,"data":{}} ''' return public.return_message(0, 0, AppHub.get_config()) def set_apphub_git(self,get): ''' @name 设置外部应用的git地址 @author csj <2025/7/9> @param get: git_url, git_branch, user, password ''' if not hasattr(get, 'git_url') or not get.git_url: return public.return_message(-1, 0, public.lang("GIT ADDRESS IS NOT SET")) if not hasattr(get, 'git_branch') or not get.git_branch: return public.return_message(-1, 0, public.lang("The branch name is not set")) return AppHub().set_apphub_git(get) def import_git_apphub(self,get): ''' @name 从git导入外部应用 @author csj <2025/7/9> ''' return AppHub().import_git_apphub(get) def install_apphub(self,get): ''' @name 安装apphub所需环境 @author csj <2025/7/9> ''' return AppHub().install_apphub(get) def import_zip_apphub(self,get): ''' @name 从zip包导入外部应用 @author csj <2025/7/9> @param get: sfile: zip文件路径 ''' if not hasattr(get, 'sfile') or not get.sfile: return public.return_message(-1, 0, public.lang("The zip file path is not set")) return AppHub().import_zip_apphub(get) def parser_zip_apphub(self,get): ''' @name 解析zip包 @author csj <2025/7/9> @param get: sfile: zip文件路径 @return dict{"status":True/False,"data":[]} ''' if not hasattr(get, 'sfile') or not get.sfile: return public.return_message(-1, 0, public.lang("Please select the file path")) app_list = [] files = get.sfile.split(',') for sfile in files: get.sfile = sfile apps = AppHub().parser_zip_apphub(get) app_list.extend(apps) public.print_log(app_list) return public.return_message(0, 0, app_list) if __name__ == '__main__': pass