Files
yakpanel-core/mod/project/docker/comMod.py
2026-04-07 02:04:22 +05:30

398 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 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