Initial YakPanel commit

This commit is contained in:
Niranjan
2026-04-07 02:04:22 +05:30
commit 2826d3e7f3
5359 changed files with 1390724 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
# ------------------------------
# Docker模型 - Docker应用
# ------------------------------
import public
import os
import time
import json
import re
from btdockerModelV2 import dk_public as dp
from btdockerModelV2.dockerBase import dockerBase
from public.validate import Param
class main(dockerBase):
def __init__(self):
pass
# 2024/2/20 下午 4:31 获取/搜索docker应用的列表
def get_app_list(self, get=None):
'''
@name 获取docker应用的列表
@author wzz <2024/2/20 下午 4:32>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
from btdockerModelV2 import registryModel as dr
dr.main().registry_list(get)
from panelPlugin import panelPlugin
pp = panelPlugin()
get.type = 10
# get.type = 16 # dev docker
# get.type = 14 # www docker
get.force = get.force if "force" in get and get.force else 0
if not hasattr(get, "query"):
get.query = ""
get.tojs = "soft.get_list"
# softList = pp.get_soft_list(get)
if get.query != "":
get.row = 1000
softList = pp.get_soft_list(get)
softList['list'] = self.struct_list(softList['list'])
softList['list'] = pp.get_page(softList['list']['data'], get)
else:
softList = pp.get_soft_list(get)
return public.return_message(0, 0, softList['list'])
except Exception as e:
# public.print_log("1111111111 进方法")
return public.return_message(-1, 0, e)
# 2024/2/20 下午 4:47 处理云端软件列表只需要list中type=13的数据
def struct_list(self, softList: dict):
'''
@name 处理云端软件列表只需要list中type=13的数据
@param softList:
@return:
'''
new_list = []
for i in softList['data']:
# if i['type'] == 14: # www docker
# if i['type'] == 16: # dev docker
if i['type'] == 10:
new_list.append(i)
softList['data'] = new_list
return softList

View File

@@ -0,0 +1,186 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
import os
import time
import traceback
import gettext
_ = gettext.gettext
# ------------------------------
# Docker模型
# ------------------------------
import public
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):
# 2023/12/22 上午 9:56 备份指定容器的mount volume
def backup_volume(self, get):
'''
@name 备份指定容器的mount volume
@author wzz <2023/12/22 上午 11:19>
@param "data":{"container_id":"容器ID"}
@return dict{"status":True/False,"msg":"提示信息"}
'''
# 校验参数
try:
get.validate([
Param('container_id').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:
client = dp.docker_client()
container = client.containers.get(get.container_id)
volume_list = container.attrs["Mounts"]
volume_list = [v["Source"] for v in volume_list]
if not volume_list:
return public.return_message(-1, 0,
public.lang("There is no volume to back up"))
backup_path = "/www/backup/btdocker/volumes/{}".format(container.name)
if not os.path.exists(backup_path):
os.makedirs(backup_path, 0o755)
import subprocess
public.ExecShell("echo -n > {}".format(self._backup_log))
for v in volume_list:
backup_name = os.path.basename(v)
# 2023/12/22 上午 10:34 每个压缩包命名都用v的目录名如果是文件则用文件名
tar_name = "{}_{}_{}.tar.gz".format(
container.name,
backup_name,
time.strftime("%Y%m%d_%H%M%S", time.localtime())
)
backup_file = os.path.join(backup_path, tar_name)
source_path = os.path.dirname(v)
cmd = "cd {} && tar zcvf {} {}".format(source_path, backup_file, backup_name)
cmd = ("nohup echo 'To start backing up {} of container {}, it may take more than 1-5 minutes...' >> {};"
"{} >> {} 2>&1 &&"
"echo 'bt_successful' >> {} || echo 'bt_failed' >> {} &"
.format(
container.name,
tar_name,
self._backup_log,
cmd,
self._backup_log,
self._backup_log,
self._backup_log,
))
subprocess.Popen(cmd, shell=True)
# 2023/12/22 下午 12:17 添加到数据库
dp.sql('dk_backup').add(
'type,name,container_id,container_name,filename,size,addtime',
(3, tar_name, container.id, container.name, backup_file, 0, time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime()
))
)
public.WriteLog("Docker module", "The {} of the backup container {} succeeds".format(container.name, tar_name))
return public.return_message(0, 0, public.lang("The backup task was created successfully."))
except Exception as e:
print(traceback.format_exc())
return public.return_message(-1, 0, public.lang("Failed to create a backup task {}", str(e)))
# 2023/12/22 上午 11:23 获取指定容器的备份列表
def get_backup_list(self, get):
'''
@name 获取指定容器的备份列表
@param "data":{"container_id":"容器ID"}
@return list[dict{"":""}]
'''
# 校验参数
try:
get.validate([
Param('container_id').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:
# 2023/12/22 下午 12:24 从数据库中获取已备份的指定容器
backup_list = dp.sql('dk_backup').where('container_id=?', (get.container_id,)).field(
'name,container_id,container_name,filename,size,addtime'
).select()
for l in backup_list:
if not os.path.exists(l['filename']):
l['size'] = 0
l['ps'] = 'file does not exist'
continue
l['size'] = os.path.getsize(l['filename'])
l['ps'] = 'local backup'
return public.return_message(0, 0, backup_list)
except Exception as e:
print(traceback.format_exc())
return public.return_message(0, 0, [])
# 2023/12/22 下午 2:25 删除指定容器的备份
def remove_backup(self, get):
'''
@name 删除指定容器的备份
@param "data":{"container_id":"容器ID","container_name":"容器名","name":"文件名"}
@return dict{"status":True/False,"msg":"提示信息"}
'''
# 校验参数
try:
get.validate([
Param('container_id').Require().String(),
Param('container_name').Require().String(),
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))
try:
# 2023/12/22 下午 2:26 从数据库中删除指定容器的备份
dp.sql('dk_backup').where('container_id=? and name=?', (get.container_id, get.name)).delete()
# 2023/12/22 下午 2:27 删除本地备份文件
backup_path = "/www/backup/btdocker/volumes/{}".format(get.container_name)
file_path = os.path.join(backup_path, get.name)
if not os.path.exists(file_path):
return public.return_message(0, 0, public.lang("successfully delete"))
os.remove(file_path)
return public.return_message(0, 0, public.lang("successfully delete"))
except Exception as e:
print(traceback.format_exc())
return public.return_message(-1, 0, public.lang("Failed to delete the file, reason: {}", get.name, str(e)))
def get_pull_log(self, get):
"""
获取镜像拉取日志websocket
@param get:
@return:
"""
get.wsLogTitle = "Start container directory backup, please wait..."
get._log_path = self._backup_log
return self.get_ws_log(get)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
{
"https://docker.m.daocloud.io": "Third party image accelerator"
}

View File

@@ -0,0 +1,11 @@
{
"docker.io": "docker official mirror site",
"swr.cn-north-4.myhuaweicloud.com": "Huawei Cloud Mirror Station (North China-Beijing 4)",
"ccr.ccs.tencentyun.com": "Tencent Cloud Mirror Station",
"registry.cn-hongkong.aliyuncs.com": "Alibaba Cloud Mirror Station (Hong Kong)",
"registry.ap-southeast-1.aliyuncs.com": "Alibaba Cloud Mirror Station (Singapore)",
"registry.us-west-1.aliyuncs.com": "Alibaba Cloud Mirror Station (Silicon Valley, USA)",
"registry.eu-west-1.aliyuncs.com": "Alibaba Cloud Mirror Station (London, UK)",
"registry.eu-central-1.aliyuncs.com": "Alibaba Cloud Mirror Station (Frankfurt, Germany)",
"registry.ap-northeast-1.aliyuncs.com": "Alibaba Cloud Mirror Station (Japan)"
}

View File

@@ -0,0 +1 @@
1710901654

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,316 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
import time
# ------------------------------
# Docker模型
# ------------------------------
from datetime import datetime, timezone, timedelta
import json
import os
import db
# from class_v2.dk_db import db
import public
from public.validate import Param
# db_path = "/www/server/panel/data/db/docker.db"
db_path = "/www/server/panel/data/docker.db"
def check_db():
if not os.path.exists(db_path) or os.path.getsize(db_path) == 0:
execstr = "wget -O {} {}/install/src/docker_en.db".format(db_path, public.get_url())
public.ExecShell(execstr)
def sql(table):
check_db()
with db.Sql() as sql:
sql.dbfile(db_path)
return sql.table(table)
# 实例化docker
def docker_client(url="unix:///var/run/docker.sock"):
"""
目前仅支持本地服务器
:param url: unix:///var/run/docker.sock
:return:
"""
try:
import docker
except:
public.ExecShell("btpip install --upgrade docker")
import docker
try:
client = docker.DockerClient(base_url=url)
if client:
return client
except Exception as e:
#如果Docker服务是Active状态但连接失败说明SDK版本不兼容尝试自动升级
if public.ExecShell("systemctl is-active docker")[0].strip() == "active":
public.ExecShell("btpip install docker==7.1.0")
# 留出升级时间
time.sleep(5)
public.restart_panel()
public.print_log(public.get_error_info())
return False
def docker_client_low(url="unix:///var/run/docker.sock"):
"""
docker 低级接口
:param url:
:return:
"""
try:
import docker
except:
public.ExecShell("btpip install --upgrade docker")
import docker
try:
client = docker.APIClient(base_url=url)
return client
except docker.errors.DockerException:
return False
# 取CPU类型
def get_cpu_count():
import re
with open('/proc/cpuinfo', 'r') as f:
cpuinfo = f.read()
rep = r"processor\s*:"
tmp = re.findall(rep, cpuinfo)
if not tmp:
return 0
return len(tmp)
def set_kv(kv_str):
"""
将键值字符串转为对象
:param data:
:return:
"""
if not kv_str:
return None
res = kv_str.split('\n')
data = dict()
for i in res:
i = i.strip()
if not i:
continue
if i.find('=') == -1:
continue
if i.find('=') > 1:
k, v = i.split('=', 1)
data[k] = v
continue
k, v = i.split('=')
data[k] = v
return data
def get_mem_info():
# 取内存信息
import psutil
mem = psutil.virtual_memory()
memInfo = int(mem.total)
return memInfo
def byte_conversion(data):
data = data.lower() # 将数据转换为小写字母形式
if "tib" in data:
return float(data.replace('tib', '')) * 1024 * 1024 * 1024 *1024
if "gib" in data:
return float(data.replace('gib', '')) * 1024 * 1024 * 1024
elif "mib" in data:
return float(data.replace('mib', '')) * 1024 * 1024
elif "kib" in data:
return float(data.replace('kib', '')) * 1024
elif "tb" in data:
return float(data.replace('tb', '')) * 1024 * 1024 * 1024 *1024
elif "gb" in data:
return float(data.replace('gb', '')) * 1024 * 1024 * 1024
elif "mb" in data:
return float(data.replace('mb', '')) * 1024 * 1024
elif "kb" in data:
return float(data.replace('kb', '')) * 1024
elif "b" in data:
return float(data.replace('b', ''))
else:
return False
def bytes_to_human_readable(bytes_num):
"""
将字节数转换为人类可读的格式KB、MB、GB等
:param bytes_num: 字节数
:return: 格式化后的字符串 xxx mb
"""
suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
index = 0
while bytes_num >= 1024 and index < len(suffixes) - 1:
bytes_num /= 1024.0
index += 1
return "{:.2f} {}".format(bytes_num, suffixes[index])
def log_docker(generator, task_name):
__log_path = '/tmp/dockertmp.log'
while True:
try:
output = generator.__next__()
try:
output = json.loads(output)
if 'status' in output:
output_str = "{}\n".format(output['status'])
public.writeFile(__log_path, output_str, 'a+')
except:
public.writeFile(__log_path, public.get_error_info(), 'a+')
if 'stream' in output:
output_str = output['stream']
public.writeFile(__log_path, output_str, 'a+')
except StopIteration:
public.writeFile(__log_path, f'{task_name} complete.', 'a+')
break
except ValueError:
public.writeFile(__log_path, f'Error parsing output from {task_name}: {output}', 'a+')
except Exception as e:
public.writeFile(__log_path, f'Error from {task_name}: {e}', 'a+')
break
def docker_conf():
"""
解析docker配置文件
KEY=VAULE
KEY1=VALUE1
:return:
"""
docker_conf = public.readFile("{}/data/docker.conf".format(public.get_panel_path()))
if not docker_conf:
return {"SAVE": 30}
data = dict()
for i in docker_conf.split("\n"):
if not i:
continue
k, v = i.split("=")
if k == "SAVE":
v = int(v)
data[k] = v
return data
def get_process_id(pname, cmd_line):
import psutil
pids = psutil.pids()
for pid in pids:
try:
p = psutil.Process(pid)
if p.name() == pname and cmd_line in p.cmdline():
return pid
except:
pass
return False
def write_log(log_data):
public.WriteLog("Docker", log_data)
def check_socket(port):
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
location = ("127.0.0.1", int(port))
result_of_check = s.connect_ex(location)
s.close()
if result_of_check == 0:
return True
else:
return False
def download_file(url, filename):
'''
下载方法
@param url:
@param filename:
@return:
'''
return public.ExecShell(f"wget -O {filename} {url} --no-check-certificate")
def convert_timezone_str_to_iso8601(timestamp_str):
# 解析时间字符串为 datetime 对象
dt = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S %z %Z')
# 转换时区为 UTC
dt_utc = dt.astimezone(timezone.utc)
# 格式化为 ISO 8601 格式
iso8601_str = dt_utc.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
return iso8601_str
def timestamp_to_string(timestamp):
# 将时间戳转换为 datetime 对象
dt_object = datetime.fromtimestamp(timestamp)
# 格式化为字符串
formatted_string = dt_object.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
return formatted_string
def rename(name: str):
"""
重命名容器名,兼容中文命名
@param name:
@return:
"""
try:
if name[:4] != 'q18q':
return name
config_path = "{}/config/name_map.json".format(public.get_panel_path())
config_data = json.loads(public.readFile(config_path))
name_l = name.split('_')
if name_l[0] in config_data.keys():
name_l[0] = config_data[name_l[0]]
return '_'.join(name_l)
except:
return name
def convert_timezone_str_to_timestamp(timestamp_str: str):
import re
# 解析时间字符串为 2024-05-16T06:18:23.915547557-04:00 时间戳
timestamp_str = re.sub(r'\.\d+', '', timestamp_str)
date_formats = ("%Y-%m-%dT%H:%M:%S%z", "%Y-%m-%d %H:%M:%S %z %Z")
dt = None
for format_str in date_formats:
try:
dt = datetime.strptime(timestamp_str, format_str)
break
except ValueError:
continue
if dt is None:
return None
# 转换时区为 UTC然后转换为时间戳
dt_utc = dt.astimezone(timezone.utc)
timestamp = dt_utc.timestamp()
return timestamp

View File

@@ -0,0 +1,297 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author:
# -------------------------------------------------------------------
# ------------------------------
# 容器启动顺序
# ------------------------------
import copy
import json
import os.path
import sys
import time
import subprocess
import psutil
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
import public
from btdockerModelV2.containerModel import main as docker
from btdockerModelV2.dockerSock import container
from btdockerModelV2 import dk_public as dp
class main():
GROUP_PATH = "/www/server/panel/data/docker_groups.json"
def __init__(self):
if not os.path.exists(self.GROUP_PATH):
public.WriteFile(self.GROUP_PATH, "[]")
# 添加组信息
def add_group(self, get):
try:
if not hasattr(get, "group_name"):
return public.return_message(-1, 0, public.lang("Missing parameters name"))
if not hasattr(get, "container_info"):
return public.return_message(-1, 0, public.lang("Missing parameters container_info"))
interval = 30 if not get.interval else int(get.interval)
container_info = get.container_info.split(',') if get.container_info else []
from uuid import uuid4
group_id = uuid4().hex[::2]
file_data = self.__readFile()
for group in file_data:
if group["group_name"] == get.group_name.strip():
return public.return_message(-1, 0, public.lang("The grouping already exists"))
for temp in container_info:
if temp in group["order"]:
return public.return_message(-1, 0, public.lang("Container {} has been added by group {}!",temp, group['group_name']))
pdata = {
"id": group_id,
"group_name": get.group_name.strip(),
"interval": interval,
"order": container_info,
}
file_data.append(pdata)
public.writeFile(self.GROUP_PATH, json.dumps(file_data))
return public.return_message(0, 0, public.lang("Added successfully"))
except Exception as e:
return public.return_message(-1, 0, public.lang("Add failed:{}",e))
# 编辑组信息
def update_group(self, get):
try:
if not hasattr(get, "id"):
return public.return_message(-1, 0, public.lang("Missing parameters id"))
interval = 30 if not get.interval else int(get.interval)
container_info = get.container_info.split(",") if get.container_info else []
file_data = self.__readFile()
for group in file_data:
if group["id"] == get.id.strip():
group["group_name"] = get.group_name.strip()
group["interval"] = interval
group["order"] = container_info
break
public.writeFile(self.GROUP_PATH, json.dumps(file_data))
return public.return_message(0, 0, public.lang("Edit Success"))
except Exception as e:
return public.return_message(-1, 0, public.lang("Edit failed:{}",e))
# 删除组信息
def del_group(self, get):
try:
if not hasattr(get, "id"):
return public.return_message(-1, 0, public.lang("Missing parameters id"))
file_data = self.__readFile()
for index, data in enumerate(file_data):
if data["id"] == get.id.strip():
del file_data[index]
break
public.writeFile(self.GROUP_PATH, json.dumps(file_data))
return public.return_message(0, 0, public.lang("Deleted successfully"))
except Exception as e:
return public.return_message(-1, 0, public.lang("Deletion failed:{}",e))
# 获取组信息
def get_group(self, get):
file_data = self.__readFile()
# 拿配置文件数据
for group in file_data:
group["status"] = 0
# 标记文件
status_file = "/tmp/{}.json".format(group["id"])
try:
info = json.loads(public.readFile(status_file))
except:
info = {
"group_id": group["id"],
"status": 0,
"start_failed": ""
}
group["status"] = info["status"]
group["start_failed"] = info["start_failed"]
return public.returnResult(True, data=file_data)
# 从组启动/停止容器
def group_status(self, group_id, status):
sk_container = container.dockerContainer()
container_list = sk_container.get_container()
if status not in ["start", "stop"]:
return public.return_message(-1, 0, public.lang("status Parameter error"))
# 状态启动中
status_file = "/tmp/{}.json".format(group_id)
group_data = {
"group_id": group_id,
"status": 0,
"start_failed": ""
}
if status == "start":
group_data["status"] = 2
else:
group_data["status"] = 4
public.writeFile(status_file, json.dumps(group_data))
try:
get = public.dict_obj()
file_data = self.__readFile()
# 获取对应ID的信息
group_info = self.__group_info(group_id, file_data)
if group_info:
# 获取容器列表名称列表
container_order = group_info["order"]
# 依次启动容器
for containers in container_order:
get.id = containers
get.status = status
# 顺序开启并且容器已经开启了
if status == "start" and self.__is_running(containers, container_list):
continue
result = docker().set_container_status(get)
if result["status"] == -1 and status == "start":
# 写入失败标记文件 # 启动失败直接返回
group_data["status"] = 3
# 用于记录启动失败的容器
group_data["start_failed"] = containers
public.writeFile(status_file, json.dumps(group_data))
return public.return_message(-1, 0, public.lang("Container [{}] operation failed",containers))
# 启动时间间隔
if len(container_order) > 1:
time.sleep(int(group_info["interval"]))
if status == "start":
group_data["status"] = 1
elif status == "stop":
group_data["status"] = 0
public.writeFile(status_file, json.dumps(group_data))
return public.return_message(0, 0, public.lang("Operation successful"))
except Exception as e:
return public.return_message(-1, 0, public.lang("Operation failed:{}",e))
# 脚本调用
def script_group(self, get):
pid_file = "/tmp/docker_groups.pid"
try:
p = psutil.Process(public.readFile(pid_file))
if p.is_running():
return "The operation is running. Please wait for the previous operation to complete."
except:
pass
if os.path.exists(pid_file):
os.remove(pid_file)
file_data = self.__readFile()
for group in file_data:
if group['id'] == get.id.strip():
public.ExecShell(
"nohup /www/server/panel/pyenv/bin/python3 /www/server/panel/script/set_docker_groups.py {} {} &> /tmp/docker_groups.log & \n"
"echo $! > {} ".format(group['id'], get.status, pid_file)
)
return public.return_message(0, 0, public.lang("Container start order {}",get.status))
# 顺序启动 / 停止 分组
def modify_group_status(self, get):
try:
group_ids = [i for i in get.id.split(",")]
status = get.status.strip()
if status not in ["start", "stop"]:
return public.return_message(-1, 0, public.lang("Invalid state!"))
for group_id in group_ids:
get.id = group_id
return self.script_group(get)
except Exception as e:
return public.return_message(-1, 0, public.lang("Operation failed:{}",e))
# 获取Json文件内容
def __readFile(self):
try:
return json.loads(public.readFile(self.GROUP_PATH)) if public.readFile(self.GROUP_PATH) else []
except:
return []
# 返回指定组信息
def get_info(self, get):
if not hasattr(get, "id"):
return public.return_message(-1, 0, public.lang("Missing parameters id"))
return public.returnResult(True, data=self.__group_info(get.id, self.__readFile()))
# 获取指定组信息 内部使用
def __group_info(self, group_id, filedata):
result = None
for result in filedata:
if group_id == result["id"]:
break
return result
# 容器是否正在运行
def __is_running(self, container_name, container_list):
container_status = {dp.rename(temp['Names'][0].replace("/", "")): temp["State"] for temp in container_list}
if container_name in container_status and container_status[container_name] == "running":
return True
else:
return False
def docker_list(self, get):
try:
# 获取容器列表和运行状态
sk_container = container.dockerContainer()
container_list = sk_container.get_container()
filedata = self.__readFile()
# 构建容器状态列表
container_status_list = []
# 构建分组容器字典
group_containers = {group_data["group_name"]: group_data["order"] for group_data in filedata}
# 遍历每个容器并获取状态
for temp in container_list:
container_name = dp.rename(temp['Names'][0].replace("/", "")) # 假设容器信息中包含名称字段
container_status = {
"name": container_name,
"status": temp["State"],
"group": ""
}
for group_name, group_order in group_containers.items():
if container_name in group_order:
container_status["group"] = group_name
break
container_status_list.append(container_status)
return public.returnResult(True, data=container_status_list)
except Exception as e:
return public.returnResult(True, data=[])

View File

@@ -0,0 +1,459 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: zhengweibiao
# -------------------------------------------------------------------
# ------------------------------
# 容器项目编排
# ------------------------------
import os, sys, re, json, shutil, psutil, time
import datetime
import public
from btdockerModelV2.containerModel import main as docker
import subprocess
class main():
next_id = 1 # 将 next_id 设为类变量,而不是实例变量
def __init__(self):
# self.next_id = 1
pass
def load_project_data(self):
json_file = "/www/server/panel/class_v2/btdockerModelV2/docker_project_groups.json"
try:
with open(json_file, 'r') as f:
return json.load(f)
except FileNotFoundError:
data = []
with open(json_file, 'w') as f:
json.dump(data, f)
return data
except Exception as e:
return None
def write_to_json(self, data):
json_file = "/www/server/panel/class_v2/btdockerModelV2/docker_project_groups.json"
try:
with open(json_file, 'w') as f:
json.dump(data, f)
return True
except Exception as e:
print("写入失败!{}".format(e))
return False
def get_project_groups(self, get):
data = self.load_project_data()
if data is None:
return public.returnMsg(False, "获取配置文件失败")
# 获取项目列表
project_list = docker().get_list(get)
# 更新每个项目组的状态
for group in data:
# 检查项目排序是否为空
if not group['order']:
group['status'] = 0 # 如果项目排序为空,状态为停止
continue
all_running = True # 假设所有的项目都在运行
for project in group['projects']:
for p in project_list['container_list']:
if p['name'] == project['project_name']:
if p['status']!="running": # 如果项目没有运行
all_running = False
break
if not all_running:
break
if all_running:
group['status'] = 1 # 如果所有的项目都在运行,状态为启动
else:
group['status'] = 0
return public.returnMsg(True, data)
def get_group_data(self, get):
try:
data = self.load_project_data()
if data is None:
return None, public.returnMsg(False, "获取配置文件失败!")
project_list = docker().get_list(get)
for group in data:
if group['id'] == int(get.id):
for project in group['projects']:
print(project)
# print(project_list['container_list'])
for p in project_list['container_list']:
if p['name'] == project['project_name']:
print(3333)
print(p)
project['status'] = p['status']
return group, None
return None, public.ReturnMsg(False, "项目不存在!")
except Exception as e:
return None, public.returnMsg(False, "获取失败!" + str(e))
def get_project_details(self, get):
print(33333)
group, error_msg = self.get_group_data(get)
if error_msg:
return error_msg
group['projects'].sort(
key=lambda x: group['order'].index(x['project_name']))
return public.returnMsg(True, group['projects'])
def get_project_group_details(self, get):
group, error_msg = self.get_group_data(get)
if error_msg:
return error_msg
return public.returnMsg(True, group)
def add_project_group(self, get):
try:
data = self.load_project_data()
if data is None:
return public.returnMsg(False, "获取配置文件失败")
# 检查是否已存在
for group in data:
if group['group_name'] == get.group_name:
return public.returnMsg(False, "项目已存在!")
# 添加新的项目
new_group = {
"id": self.next_id, # 使用 next_id 作为新的 id
"group_name": get.group_name,
# "status": 0,
"interval": 30,
"projects": [],
"order": [],
}
data.append(new_group)
# 更新 next_id
main.next_id += 1
if not self.write_to_json(data):
return public.returnMsg(False, "写入失败!")
return public.returnMsg(True, "项目添加成功!")
except Exception as e:
return public.returnMsg(False, "添加失败!" + str(e))
def edit_project_order(self, get):
try:
data = self.load_project_data()
if data is None:
return public.returnMsg(False, "获取配置文件失败")
# 找到指定的项目组
for group in data:
if group['id']==int(get.id):
new_order=get.order.split(',')
if sorted(new_order) != sorted(group['order']):
return public.returnMsg(False, "无效的容器顺序!")
group['order']=new_order
if not self.write_to_json(data):
return public.returnMsg(False, "写入失败!")
return public.returnMsg(True,"容器顺序修改成功!")
return public.returnMsg(False,"项目不存在!")
except Exception as e:
return public.returnMsg(False,"修改失败:"+str(e))
def edit_group_interval(self, get):
try:
data = self.load_project_data()
if data is None:
return public.returnMsg(False, "获取配置文件失败")
# 找到指定的项目
for group in data:
if group['id'] == int(get.id):
# 检查新的项目顺序是否有效
group['interval'] = get.interval
break
else:
return public.returnMsg(False, "项目不存在!")
if not self.write_to_json(data):
return public.returnMsg(False, "写入失败!")
return public.returnMsg(True, "项目时间间隔修改成功!")
except Exception as e:
return public.returnMsg(False, "修改失败!" + str(e))
def start_projects_in_order(self, get):
try:
data = self.load_project_data()
if data is None:
return public.returnMsg(False, "获取配置文件失败")
project_list=docker().get_list(get)['container_list']
for group in data:
if group['id']==int(get.id):
# 如果pid存在并且进程仍在运行那么就不允许用户再次启动项目
if 'start_pid' in group and self.is_process_running(group['start_pid']):
return public.returnMsg(False,"正在依次启动容器中!")
project_order=group['order']
running_projects=[project for project in project_order if self.is_project_running(project,project_list)]
if running_projects and not get.get("force_stop",False):
return public.returnMsg(False,"以下容器正在运行:{}。是否允许先强制停止再启动?您也可以选择自己手动停止运行中的容器!".format(",".join(running_projects)))
with open('/dev/null','w') as devnull:
process=subprocess.Popen(['btpython','/www/server/panel/script/set_docker_project_groups.py','--id',str(group['id']),"--action","start"],stdout=devnull,stderr=devnull)
pid=process.pid
group['start_pid']=pid
if not self.write_to_json(data):
return public.returnMsg(False, "写入失败!")
return public.returnMsg(True, "开始依次启动容器!")
except Exception as e:
return public.returnMsg(False, "启动失败!"+str(e))
def is_process_running(self,pid):
try:
os.kill(pid,0)
except OSError:
return False
else:
return True
def is_project_running(self,project_name,project_list):
for project in project_list:
if project['name']==project_name and project['status']=="running":
return True
return False
def stop_projects_in_order(self, get):
try:
data = self.load_project_data()
if data is None:
return public.returnMsg(False, "获取配置文件失败")
for group in data:
if group['id']==int(get.id):
# 如果pid存在并且进程仍在运行那么就不允许用户再次停stop_pid止项目
if 'stop_pid' in group and self.is_process_running(group['stop_pid']):
return public.returnMsg(False,"正在依次停止容器中!")
with open("/dev/null","w") as devnull:
process=subprocess.Popen(['btpython','/www/server/panel/script/set_docker_project_groups.py','--id',str(group['id']),"--action","stop"],stdout=devnull,stderr=devnull)
pid=process.pid
group['stop_pid']=pid
if not self.write_to_json(data):
return public.returnMsg(False, "写入失败!")
return public.returnMsg(True,"容器开始按顺序停止")
except Exception as e:
return public.returnMsg(False, "停止失败!" + str(e))
def start_group(self, args_id):
try:
get = public.dict_obj()
data = self.load_project_data()
if data is None:
return public.returnMsg(False, "获取配置文件失败")
# 找到指定的项目
for group in data:
if group['id'] == int(args_id):
# 获取项目排序
project_order = group['order']
# 停止所有项目
for project_name in project_order:
container_id = None
for project in group['projects']:
if project['project_name'] == project_name:
container_id = project['project_id']
break
if container_id:
# print()
# docker().set_container_status(public.dict_obj({
# "id": container_id,
# "status": "stop"
# }))
print(33333333333333)
get.status = "stop"
get.id = container_id
docker().set_container_status(get)
# time.sleep(30)
# 依次启动项目
for project_name in project_order:
container_id = None
for project in group['projects']:
if project['project_name'] == project_name:
container_id = project['project_id']
break
if container_id:
# print()
# docker().set_container_status(public.dict_obj({
# "id": container_id,
# "status": "stop"
# }))
get.status = "start"
get.id = container_id
start_result=docker().set_container_status(get)
if not start_result['status']:
return start_result # 如果启动失败,立即返回错误信息
# 暂停指定的时间间隔
time.sleep(int(group['interval']))
if not self.is_process_running(group['pid']):
# 删除pid
del group['pid']
if not self.write_to_json(data):
return public.returnMsg(False, "写入失败!")
except Exception as e:
print("启动失败!" + str(e))
def stop_group(self, args_id):
try:
get = public.dict_obj()
data = self.load_project_data()
if data is None:
return public.returnMsg(False, "获取配置文件失败")
# 找到指定的项目
for group in data:
if group['id'] == int(args_id):
# 获取项目排序
project_order = group['order']
# 停止所有项目
for project_name in project_order:
container_id = None
for project in group['projects']:
if project['project_name'] == project_name:
container_id = project['project_id']
break
if container_id:
get.status = "stop"
get.id = container_id
docker().set_container_status(get)
except Exception as e:
print("启动失败!" + str(e))
def modify_group_status(self, get):
group_ids=[int(id) for id in get.id.split(",")]
for group_id in group_ids:
get.id=group_id
if get.status=="1":
return self.start_projects_in_order(get)
elif get.status=="0":
return self.stop_projects_in_order(get)
else:
return public.returnMsg(False,"无效的状态!")
def add_project_to_group(self, get):
try:
data = self.load_project_data()
if data is None:
return public.returnMsg(False, "获取配置文件失败")
# 检查项目是否已被其他项目组添加
for group in data:
for project in group['projects']:
if project['project_name']==get.project_name:
return public.returnMsg(False,"容器 {} 已经被项目组 {} 添加了!".format(get.project_name,group['group_name']))
for group in data:
if group['id']==int(get.id):
new_project={
"project_id":get.project_id,
"project_name":get.project_name,
}
group['projects'].append(new_project)
group['order'].append(get.project_name)
break
if not self.write_to_json(data):
return public.returnMsg(False, "写入失败!")
return public.returnMsg(True, "容器添加成功!")
except Exception as e:
return public.returnMsg(False, "添加失败!" + str(e))
def remove_project_from_group(self, get):
try:
data = self.load_project_data()
if data is None:
return public.returnMsg(False, "获取配置文件失败")
# 将 get.project_name 分割成一个列表
project_names=get.project_name.split(",")
# 找到指定的项目
for group in data:
if group['id']==int(get.id):
# 删除指定的项目组
group['projects']=[project for project in group['projects'] if project['project_name'] not in project_names]
# 同时更新order列表移除已删除的项目名称
group['order'] = [project_name for project_name in group['order'] if project_name not in project_names]
break
else:
return public.returnMsg(False,"项目不存在!")
# 将更新后的数据写回文件
if not self.write_to_json(data):
return public.returnMsg(False, "写入失败!")
return public.returnMsg(True, "容器删除成功!")
except Exception as e:
return public.returnMsg(False, "删除失败!" + str(e))
def delete_project_group(self, get):
try:
data = self.load_project_data()
if data is None:
return public.returnMsg(False, "获取配置文件失败")
# 将 get.id 分割成一个列表
group_ids=[int(id) for id in get.id.split(",")]
# 找到并删除指定的项目
data=[group for group in data if group['id'] not in group_ids]
# 将更新后的数据写回文件
if not self.write_to_json(data):
return public.returnMsg(False, "写入失败!")
return public.returnMsg(True, "项目删除成功!")
except Exception as e:
return public.returnMsg(False, "删除失败!" + str(e))

View File

@@ -0,0 +1,117 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
# ------------------------------
# Docker模型
# ------------------------------
import public, os
from btdockerModelV2 import dk_public as dp
class dockerBase(object):
def __init__(self):
# self._db_path = "/www/server/panel/data/db/docker.db"
self._db_path = "/www/server/panel/data/docker.db"
self._backup_log = '/tmp/backup.log'
self._log_path = '/tmp/dockertmp.log'
self._rCmd_log = '/tmp/dockerRun.log'
self._url = "unix:///var/run/docker.sock"
self.compose_path = "{}/data/compose".format(public.get_panel_path())
self.aes_key = "btdockerModel_QWERAS"
self.moinitor_lock = "/tmp/bt_docker_monitor.lock"
def get_ws_log(self, get):
"""
获取日志websocket
@param get:
@return:
"""
if not hasattr(get, "_ws"):
return True
import time
sum = 0
with open(get._log_path, "r") as file:
position = file.tell()
get._ws.send(public.lang("{}\r\n",get.wsLogTitle))
while True:
current_position = file.tell()
line = file.readline()
if current_position > position:
file.seek(position)
new_content = file.read(current_position - position)
if "nohup" not in new_content:
for i in new_content.split('\n'):
if i == "": continue
get._ws.send(i.strip("\n") + "\r\n")
position = current_position
if "bt_successful" in line:
get._ws.send(public.lang("successful\r\n"))
del get._ws
break
elif "bt_failed" in line:
get._ws.send(public.lang("failed\r\n"))
del get._ws
break
if sum > 0:
sum = 0
else:
sum += 1
if sum >= 6000:
get._ws.send(public.lang("\r\nNo response for more than 10 minutes!\r\n"))
break
time.sleep(0.1)
return True
# 命令行创建 拉取容器
def run_cmd(self, get):
"""
命令行创建运行容器(docker run / docker pull),需要做危险命令校验,存在危险命令则不执行
@param get:
@return:
"""
import re
if not hasattr(get, 'cmd'):
return public.return_message(-1, 0, public.lang("Please pass in cmd"))
if "docker run" not in get.cmd and "docker pull" not in get.cmd:
return public.return_message(-1, 0, public.lang("Only docker run or docker pull commands can be executed"))
danger_cmd = ['rm', 'rmi', 'kill', 'stop', 'pause', 'unpause', 'restart', 'update', 'exec', 'init',
'shutdown', 'reboot', 'chmod', 'chown', 'dd', 'fdisk', 'killall', 'mkfs', 'mkswap', 'mount',
'swapoff', 'swapon', 'umount', 'userdel', 'usermod', 'passwd', 'groupadd', 'groupdel',
'groupmod', 'chpasswd', 'chage', 'usermod', 'useradd', 'userdel', 'pkill']
danger_symbol = ['&', '&&', '||', '|', ';']
for d in danger_cmd:
if get.cmd.startswith(d) or re.search(r'\s{}\s'.format(d), get.cmd):
return public.return_message(-1, 0, public.lang("Dangerous command exists: [ {}], execution is not allowed!", d))
for d in danger_symbol:
if d in get.cmd:
return public.return_message(-1, 0, public.lang("Dangerous symbol exists: [ {}], execution is not allowed!", d))
os.system("echo -n > {}".format(self._rCmd_log))
os.system("nohup {} >> {} 2>&1 && echo 'bt_successful' >> {} || echo 'bt_failed' >> {} &".format(
get.cmd,
self._rCmd_log,
self._rCmd_log,
self._rCmd_log,
))
return public.return_message(0, 0, public.lang("The command has been executed!"))

View File

@@ -0,0 +1,143 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
# docker模型sock 封装库 容器库
# -------------------------------------------------------------------
import socket
import select
import json
import public
from btdockerModelV2.dockerSock.sockBase import base
class dockerContainer(base):
def __init__(self):
super(dockerContainer, self).__init__()
# 2024/3/13 上午 11:20 获取所有容器列表
def get_container(self):
'''
@name 获取所有容器列表
@author wzz <2024/3/13 上午 10:54>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/containers/json?all=1".format(self._sock, self.get_api_version()))[0])
except Exception as e:
try:
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
for c in c_list:
if not c.endswith("/curl"): continue
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/containers/json?all=1".format(c, self._sock, self.get_api_version()))
if not err: return json.loads(res)
return []
except:
return []
# 2024/3/28 下午 11:37 获取指定容器的inspect
def get_container_inspect(self, container_id: str):
'''
@name 获取指定容器的inspect
@param container_id: 容器id
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/containers/{}/json"
.format(self._sock, self.get_api_version(), container_id))[0])
except Exception as e:
try:
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
for c in c_list:
if not c.endswith("/curl"): continue
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/containers/{}/json".format(c, self._sock, self.get_api_version(), container_id))
if not err: return json.loads(res)
return []
except:
return []
# 2025/1/18 11:05 构造返回日志
def structure_docker_logs(self, log_data: str) -> list:
"""
@name: 清理 Docker 日志并转换为 JSON 格式
@param: raw_logs: 原始日志字符串
@return: 日志对象列表
"""
formatted_logs = []
# 逐行处理日志
for line in log_data.split('\n'):
line = line.strip()
if line:
# 只处理包含日期时间戳的行,去除其它无用的字符
parts = line.split(' ', 1) # 按第一个空格分割时间戳和日志内容
if len(parts) > 1:
timestamp = parts[0]
message = parts[1]
formatted_logs.append(message)
# 返回格式化后的日志
return "\n".join(formatted_logs)
# 2025/1/18 10:26 获取容器日志
def get_container_logs(self, container_id: str, options: dict) -> list:
'''
@name 获取容器日志
@param container_id: 容器id
@return dict{"status":True/False,"msg":"提示信息"}
'''
# 定义 Docker API 请求路径
url = f"/containers/{container_id}/logs?stdout=true&stderr=true&timestamps=true"
if options:
if "since" in options and options["since"] != "":
url += "&since={}".format(options['since'])
if "until" in options and options["until"] != "":
url += "&until={}".format(options['until'])
if "tail" in options and options["tail"] != "":
url += "&tail={}".format(options['tail'])
# 设置 Unix socket 地址
unix_socket = "/var/run/docker.sock"
# 连接到 Docker 的 UNIX socket
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client_socket:
client_socket.settimeout(1)
client_socket.connect(unix_socket)
# 构建 HTTP 请求
http_request = f"GET {url} HTTP/1.1\r\n"
http_request += "Host: localhost\r\n"
http_request += "Accept: application/json\r\n"
http_request += "Content-Type: application/json\r\n"
http_request += "Connection: close\r\n\r\n"
# 发送请求
client_socket.sendall(http_request.encode())
# 接收响应
response = b""
while True:
data = client_socket.recv(4096)
if not data:
break
response += data
# 从响应中分割出 HTTP 头部和响应体
headers, body = response.split(b"\r\n\r\n", 1)
# 只保留日志部分,解码并忽略无法解码的字符
log_data = body.decode('utf-8', errors='replace')
result = self.structure_docker_logs(log_data)
# 返回日志内容
return result

View File

@@ -0,0 +1,108 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
# docker模型sock 封装库 镜像库
# -------------------------------------------------------------------
import json
import public
from btdockerModelV2.dockerSock.sockBase import base
class dockerImage(base):
def __init__(self):
super(dockerImage, self).__init__()
# 2024/3/13 上午 11:20 获取所有镜像列表
def get_images(self):
'''
@name 获取所有镜像列表
@author wzz <2024/3/13 上午 10:54>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/images/json?all=1"
.format(self._sock, self.get_api_version()))[0])
except Exception as e:
try:
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
for c in c_list:
if not c.endswith("/curl"): continue
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/images/json?all=1".format(c, self._sock, self.get_api_version()))
if not err: return json.loads(res)
return []
except:
return []
# 2023/12/13 上午 11:08 镜像搜索
def search(self, name):
'''
@name 镜像搜索
@author wzz <2023/12/13 下午 3:41>
@param 参数名<数据类型> 参数描述
@return 数据类型
'''
try:
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/images/search?term={}"
.format(self._sock, self.get_api_version(), name))[0],)
except Exception as e:
try:
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
for c in c_list:
if not c.endswith("/curl"): continue
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/images/search?term={}".format(c, self._sock, self.get_api_version(), name))
if not err: return json.loads(res)
return []
except:
return []
# 2024/4/1 下午 2:47 image load
def load_image(self, path):
'''
@name 加载镜像
@param path <str> 镜像名称
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
return json.loads(public.ExecShell("curl -s --unix-socket {} -X POST http:/{}/images/load -H \"Content-Type: application/x-tar\" --upload-file {}".format(self._sock, self.get_api_version(), path))[0])
except Exception as e:
return {}
# 2024/4/16 上午11:39 获取指定image的inspect信息
def inspect(self, image):
'''
@name 获取指定image的inspect信息
@param image <str> 镜像名称
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/images/{}/json"
.format(self._sock, self.get_api_version(), image))[0])
except Exception as e:
try:
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
for c in c_list:
if not c.endswith("/curl"): continue
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/images/{}/json".format(c, self._sock, self.get_api_version(), image))
if not err: return json.loads(res)
return {}
except:
return {}
# 2024/10/31 22:43 清理所有构建缓存
def build_prune(self):
'''
@name 清理所有构建缓存
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
return json.loads(public.ExecShell("curl -s --unix-socket {} -X POST http:/{}/build/prune?all=1"
.format(self._sock, self.get_api_version()))[0])
except Exception as e:
return False

View File

@@ -0,0 +1,91 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
# docker模型sock 封装库 网络库
# -------------------------------------------------------------------
import json
import public
from btdockerModelV2.dockerSock.sockBase import base
class dockerNetWork(base):
def __init__(self):
super(dockerNetWork, self).__init__()
# 2024/11/27 14:31 list network
def list_network(self):
'''
@name list network
'''
try:
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/networks".format(self._sock, self.get_api_version()))[0])
except Exception as e:
try:
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
for c in c_list:
if not c.endswith("/curl"): continue
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/networks".format(c, self._sock, self.get_api_version()))
if not err: return json.loads(res)
return []
except:
return []
# 2024/11/27 14:33 inspect a network
def inspect_network(self, get):
'''
@name inspect a network
'''
try:
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/networks/{}".format(self._sock, self.get_api_version(), get.id))[0])
except Exception as e:
try:
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
for c in c_list:
if not c.endswith("/curl"): continue
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/networks/{}".format(c, self._sock, self.get_api_version(), get.id))
if not err: return json.loads(res)
return {}
except:
return {}
# 2024/11/27 14:34 remove network
def remove_network(self, get):
'''
@name remove network
'''
try:
return json.loads(public.ExecShell("curl -s -X DELETE --unix-socket {} http:/{}/networks/{}".format(self._sock, self.get_api_version(), get.id))[0])
except Exception as e:
try:
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
for c in c_list:
if not c.endswith("/curl"): continue
res, err = public.ExecShell("{} -s -X DELETE --unix-socket {} http:/{}/networks/{}".format(c, self._sock, self.get_api_version(), get.id))
if not err: return json.loads(res)
return {}
except:
return {}
# 2024/11/27 14:34 create network
def create_network(self, get):
'''
@name create network
'''
try:
return json.loads(public.ExecShell("curl -s -X POST --unix-socket {} http:/{}/networks/create -H \"Content-Type: application/json\" -d '{}'".format(self._sock, self.get_api_version(), json.dumps(get.post_data)))[0])
except Exception as e:
try:
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
for c in c_list:
if not c.endswith("/curl"): continue
res, err = public.ExecShell("{} -s -X POST --unix-socket {} http:/{}/networks/create -H \"Content-Type: application/json\" -d '{}'".format(c, self._sock, self.get_api_version(), json.dumps(get.post_data)))
if not err: return json.loads(res)
return {}
except:
return {}

View File

@@ -0,0 +1,19 @@
class dockerSock(object):
def __init__(self):
self._sock = "/var/run/docker.sock"
self._url = "unix://{}".format(self._sock)
self._api_version = "/127.0.0.1"
def get_sock(self):
return self._sock
def get_url(self):
return self._url
def get_api_version(self):
return self._api_version
class base(dockerSock):
def __init__(self):
super(base, self).__init__()

View File

@@ -0,0 +1,39 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
# docker模型sock 封装库 系统信息库
# -------------------------------------------------------------------
import json
import public
from btdockerModelV2.dockerSock.sockBase import base
class dockerSystem(base):
def __init__(self):
super(dockerSystem, self).__init__()
# 2024/12/16 17:47 获取系统信息
def get_system_info(self):
'''
@name 获取系统信息
'''
try:
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/info".format(self._sock, self.get_api_version()))[0])
except Exception as e:
try:
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
for c in c_list:
if not c.endswith("/curl"): continue
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/info".format(c, self._sock, self.get_api_version()))
if not err: return json.loads(res)
return []
except:
return []

View File

@@ -0,0 +1,41 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
# docker模型sock 封装库 存储库
# -------------------------------------------------------------------
import json
import public
from btdockerModelV2.dockerSock.sockBase import base
class dockerVolume(base):
def __init__(self):
super(dockerVolume, self).__init__()
# 2024/3/13 上午 11:20 获取所有存储卷列表
def get_volumes(self):
'''
@name 获取所有存储卷列表
@author wzz <2024/3/13 上午 10:54>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/volumes"
.format(self._sock, self.get_api_version()))[0])
except Exception as e:
try:
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
for c in c_list:
if not c.endswith("/curl"): continue
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/volumes".format(c, self._sock, self.get_api_version()))
if not err: return json.loads(res)
return []
except:
return []

View File

@@ -0,0 +1,60 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: zouhw <zhw@yakpanel.com>
# -------------------------------------------------------------------
# ------------------------------
# Docker模型
# ------------------------------
import public
import dk_public as dp
class main:
# 获取docker主机列表
def get_list(self,args=None):
info = dp.sql("hosts").select()
for i in info:
if dp.docker_client(i['url']):
i['status'] = True
else:
i['status'] = False
return info
# 添加docker主机
def add(self,args):
"""
:param url 连接主机的url
:param remark 主机备注
:return:
"""
import time
host_lists = self.get_list()
for h in host_lists:
if h['url'] == args.url:
return public.returnMsg(False, public.lang("The host already exists"))
# 测试连接
if not dp.docker_client(args.url):
return public.returnMsg(False, public.lang("Failed to connect to the server, please check if docker is started"))
pdata = {
"url": args.url,
"remark": public.xsssec(args.remark),
"time": int(time.time())
}
dp.write_log("Add host [{}] successful".format(args.url))
dp.sql('hosts').insert(pdata)
return public.returnMsg(True, public.lang("Add docker host successfully"))
def delete(self,args):
"""
:param id 连接主机id
:return:
"""
data = dp.sql('hosts').where('id=?',args(args.id,)).find()
dp.sql('hosts').delete(id=args.id)
dp.write_log("Delete host [{}] successful".format(data['url']))
return public.returnMsg(True, public.lang("Delete host successfully"))

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,129 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: zouhw <zhw@yakpanel.com>
# -------------------------------------------------------------------
# ------------------------------
# Docker模型
# ------------------------------
import sys
import threading
import time
sys.path.insert(0, "/www/server/panel/class_v2/")
sys.path.insert(1, "/www/server/panel/")
import public
from btdockerModelV2 import dk_public as dp
from btdockerModelV2 import containerModel as dc
from btdockerModelV2 import statusModel as ds
from btdockerModelV2 import imageModel as di
from public.validate import Param
class main:
__save_date = None
__day_sec = 86400
def __init__(self, save_date):
if not save_date:
self.__save_date = 30
else:
self.__save_date = save_date
def docker_client(self, url):
return dp.docker_client(url)
def get_all_host_stats(self, fun):
"""
获取所有主机信息并获取该主机下的容器状态
:param fun: 需要调用的方法,用于获取并记录容器状态
:return:
"""
hosts = dp.sql('hosts').select()
for i in hosts:
t = threading.Thread(target=fun, args=(i,))
t.setDaemon(True)
t.start()
# 获取所有docker容器的状态信息
def container_status_for_all_hosts(self):
"""
获取所有服务器的容器数量
:return:
"""
# while True:
args = public.to_dict_obj({})
container_list = dc.main().get_list(args)['message']
for c in container_list['container_list']:
args.id = c['id']
args.write = 1
args.save_date = self.__save_date
ds.main().stats(args)
# time.sleep(60)
# 获取所有服务器的容器数量
def container_count(self):
# while True:
hosts = dp.sql('hosts').select()
n = 0
for i in hosts:
args = public.to_dict_obj({})
args.url = i['url']
container_list = dc.main().get_list(args)['message']
n += len(container_list)
pdata = {
"time": int(time.time()),
"container_count": n
}
expired = time.time() - (self.__save_date * self.__day_sec)
dp.sql("container_count").where("time<?", (expired,)).delete()
dp.sql("container_count").insert(pdata)
# time.sleep(60)
def image_for_all_host(self):
# while True:
hosts = dp.sql('hosts').select()
num = 0
size = 0
for i in hosts:
args = public.to_dict_obj({})
args.url = i['url']
res = di.main().image_for_host(args)['message']
num += res['num']
size += res['size']
pdata = {
"time": int(time.time()),
"num": num,
"size": int(size)
}
expired = time.time() - (self.__save_date * self.__day_sec)
dp.sql("image_infos").where("time<?", (expired,)).delete()
dp.sql("image_infos").insert(pdata)
# time.sleep(60)
def monitor():
# 获取所有容器信息
while True:
save_date = dp.docker_conf()['SAVE']
m = main(save_date)
m.get_all_host_stats(m.container_status_for_all_hosts)
# 开始获取容器总数
t = threading.Thread(target=m.container_count)
t.setDaemon(True)
t.start()
# 获取镜像详情
t = threading.Thread(target=m.image_for_all_host)
t.setDaemon(True)
t.start()
time.sleep(60)
# condition=threading.Condition()
# condition.acquire()
# condition.wait()
if __name__ == "__main__":
monitor()

View File

@@ -0,0 +1,384 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
import docker.errors
import gettext
_ = gettext.gettext
# ------------------------------
# Docker模型
# ------------------------------
import public
from btdockerModelV2 import dk_public as dp
from btdockerModelV2.dockerBase import dockerBase
from public.validate import Param
class main(dockerBase):
def docker_client(self, url):
return dp.docker_client(url)
def get_network_id(self, get):
"""
asdf
@param get:
@return:
"""
networks = self.docker_client(self._url).networks
network = networks.get(get.id)
return network.attrs
def get_host_network(self, get):
"""
获取服务器的docker网络
:param get:
:return:
"""
try:
client = self.docker_client(self._url)
if not client:
return public.return_message(-1, 0, [])
networks = client.networks
network_attr = self.get_network_attr(networks)
data = list()
for attr in network_attr:
get.id = attr["Id"]
c_result = self.get_network_id(get)
subnet = ""
gateway = ""
subnetv6 = ""
gatewayv6 = ""
if attr["IPAM"]["Config"]:
if "Subnet" in attr["IPAM"]["Config"][0]:
subnet = attr["IPAM"]["Config"][0]["Subnet"]
if "Gateway" in attr["IPAM"]["Config"][0]:
gateway = attr["IPAM"]["Config"][0]["Gateway"]
if len(attr["IPAM"]["Config"]) > 1:
if "Subnet" in attr["IPAM"]["Config"][1]:
subnetv6 = attr["IPAM"]["Config"][1]["Subnet"]
if "Gateway" in attr["IPAM"]["Config"][1]:
gatewayv6 = attr["IPAM"]["Config"][1]["Gateway"]
tmp = {
"id": attr["Id"],
"name": attr["Name"],
"time": dp.convert_timezone_str_to_timestamp(attr["Created"]),
"driver": attr["Driver"],
"subnet": subnet,
"gateway": gateway,
"subnetv6": subnetv6,
"gatewayv6": gatewayv6,
"labels": attr["Labels"],
"used": 1 if c_result["Containers"] else 0,
"containers": c_result["Containers"],
}
data.append(tmp)
return public.return_message(0, 0, sorted(data, key=lambda x: x['time'], reverse=True))
except Exception as e:
err = str(e)
if "Connection reset by peer" in err:
return public.return_message(-1, 0, public.lang("The docker service is running abnormally, please restart and try again!"))
return public.return_message(-1, 0, [])
def get_network_attr(self, networks):
network = networks.list()
return [i.attrs for i in network]
def add(self, get):
"""
:param name 网络名称
:param driver bridge/ipvlan/macvlan/overlay
:param options Driver options as a key-value dictionary
:param subnet '124.42.0.0/16'
:param gateway '124.42.0.254'
:param iprange '124.42.0.0/24'
:param labels Map of labels to set on the network. Default None.
:param remarks 备注
:param get:
:return:
"""
# {"name": "23sdff223f", "driver": "overlay", "options": "", "subnet": "192.168.13.0/24",
# "gateway": "192.168.13.1", "iprange": "192.168.13.0/24", "labels": ""}
# 校验参数
try:
get.validate([
Param('name').Require().String(),
Param('subnet').Require(),
Param('gateway').Require(),
Param('iprange').Require(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
import docker
# 传参 给默认值
subnet = get.get("subnet", "")
gateway = get.get("gateway", "")
iprange = get.get("iprange", "")
subnet_v6 = get.get("subnet_v6", "")
gateway_v6 = get.get("gateway_v6", "")
v6_status = get.get("status/d", 0)
ipam_pool4 = docker.types.IPAMPool(
subnet=subnet,
gateway=gateway,
iprange=iprange
)
if v6_status != 0:
ipam_pool6 = docker.types.IPAMPool(
subnet=subnet_v6,
gateway=gateway_v6,
)
ipam_config = docker.types.IPAMConfig(
pool_configs=[ipam_pool4, ipam_pool6]
)
else:
ipam_config = docker.types.IPAMConfig(
pool_configs=[ipam_pool4]
)
try:
self.docker_client(self._url).networks.create(
name=get.name,
options=dp.set_kv(get.options),
driver=get.driver, # 使用用户指定的网络驱动类型
ipam=ipam_config,
enable_ipv6=v6_status,
)
except docker.errors.APIError as e:
print(str(e))
if "failed to allocate gateway" in str(e):
return public.return_message(-1, 0,
public.lang("The gateway setting is wrong, Please enter a gateway that matches the subnet: {}", get.subnet))
if "invalid CIDR address" in str(e):
return public.return_message(-1, 0, public.lang("Subnet address format error, please enter for example: 172.16.0.0/16"))
if "invalid Address SubPool" in str(e):
return public.return_message(-1, 0,
public.lang("IP range format error, please enter the appropriate IP range for this subnet:", get.subnet))
if "Pool overlaps with other one on this address space" in str(e):
return public.return_message(-1, 0, public.lang("IP range [ {}] already exists!", get.subnet))
if "kernel version failed to meet the minimum ipvlan kernel requirement" in str(e):
return public.return_message(-1, 0, public.lang("The system kernel version is too low, please update the kernel or choose another network mode"))
if "not a swarm manager" in str(e):
return public.return_message(-1, 0, public.lang("The current node is not a Swarm and needs to be configured before it can be used"))
return public.return_message(-1, 0, public.lang("Failed to add network! {}", str(e)))
dp.write_log("Added network [{}] [{}] successful!".format(get.name, get.iprange))
return public.return_message(0, 0, public.lang("Added network successfully!"))
def del_network(self, get):
"""
:param id
:param get:
:return:
"""
# 校验参数
try:
get.validate([
Param('id').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:
networks = self.docker_client(self._url).networks.get(get.id)
attrs = networks.attrs
if attrs['Name'] in ["bridge", "none"]:
return public.return_message(-1, 0, public.lang("The system default network cannot be deleted!"))
networks.remove()
dp.write_log("Delete network [{}] successfully!".format(attrs['Name']))
return public.return_message(0, 0, public.lang("successfully delete!"))
except docker.errors.APIError as e:
if " has active endpoints" in str(e):
return public.return_message(-1, 0, public.lang("The network cannot be deleted while it is in use!"))
return public.return_message(-1, 0, public.lang("Delete failed! {}", str(e)))
def prune(self, get):
"""
删除无用的网络
:param get:
:return:
"""
try:
res = self.docker_client(self._url).networks.prune()
if not res['NetworksDeleted']:
return public.return_message(-1, 0, public.lang("There are no useless networks!"))
dp.write_log("Delete useless network successfully!")
return public.return_message(0, 0, public.lang("successfully delete!"))
except docker.errors.APIError as e:
return public.return_message(-1, 0, public.lang("Delete failed! {}", str(e)))
def disconnect(self, get):
"""
断开某个容器的网络
:param id
:param container_id
:param get:
:return:
"""
# 校验参数
try:
get.validate([
Param('id').Require().String(),
Param('container_id').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:
get.id = get.get("id/s", "")
get.container_id = get.get("container_id/s", "")
if get.id == "":
return public.return_message(-1, 0, public.lang("Network ID cannot be empty"))
if get.container_id == "":
return public.return_message(-1, 0, public.lang("Container ID cannot be empty"))
networks = self.docker_client(self._url).networks.get(get.id)
networks.disconnect(get.container_id)
dp.write_log("Network disconnection [{}] successful!".format(get.id))
return public.return_message(0, 0, public.lang("Network disconnection was successful!"))
except docker.errors.APIError as e:
if "No such container" in str(e):
return public.return_message(-1, 0, public.lang("Container ID: {}, does not exist!", get.container_id))
if "network" in str(e) and "Not Found" in str(e):
return public.return_message(-1, 0, public.lang("Network ID: {}, does not exist!", get.id))
return public.return_message(-1, 0, public.lang("Network disconnection failed! {}", str(e)))
def connect(self, get):
"""
连接到指定网络
:param id
:param container_id
:param get:
:return:
"""
# 校验参数
try:
get.validate([
Param('id').Require().String(),
Param('container_id').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:
networks = self.docker_client(self._url).networks.get(get.id)
networks.connect(get.container_id)
dp.write_log("Network connection [{}] successful!".format(get.id))
return public.return_message(0, 0, public.lang("Network connection successful!"))
except docker.errors.APIError as e:
if "No such container" in str(e):
return public.return_message(-1, 0, public.lang("Container ID: {}, does not exist!", get.container_id))
if "network" in str(e) and "Not Found" in str(e):
return public.return_message(-1, 0, public.lang("Network ID: {}, does not exist!", get.id))
return public.return_message(-1, 0, public.lang("Failed to connect to network! {}", str(e)))
# 2024/11/27 14:37 创建网络
def create_network(self, get):
'''
@name 创建网络
'''
get.subnet = get.get("subnet", "")
get.gateway = get.get("gateway", "")
get.iprange = get.get("iprange", "")
get.subnet_v6 = get.get("subnet_v6", "")
get.gateway_v6 = get.get("gateway_v6", "")
get.v6_status = get.get("status", 0)
get.name = get.get("name", None)
if get.name is None: return public.return_message(-1, 0, public.lang("The network name cannot be empty!"))
get.driver = get.get("driver", "bridge")
get.options = get.get("options", "")
ipam_pool4 = {}
ipam_pool6 = {}
if get.subnet != "":
if get.gateway == "": return public.return_message(-1, 0, public.lang("Gateways can't be empty!"))
if get.iprange == "": return public.return_message(-1, 0, public.lang("IP ranges cannot be empty!"))
ipam_pool4 = {
"subnet": get.subnet,
"gateway": get.gateway,
"iprange": get.iprange
}
if get.v6_status != 0:
ipam_pool6 = {
"subnet": get.subnet_v6,
"gateway": get.gateway_v6,
}
if not ipam_pool4 and not ipam_pool6:
get.ipam = None
elif not ipam_pool6:
get.ipam = {
"Driver": "default",
"Config": [{
"Subnet": get.subnet,
"Gateway": get.gateway,
"IPRange": get.iprange
}]
}
else:
get.ipam = {
"Driver": "default",
"Config": [{
"Subnet": get.subnet,
"Gateway": get.gateway,
"IPRange": get.iprange
}, {
"Subnet": get.subnet_v6,
"Gateway": get.gateway_v6,
}]
}
get.post_data = {
"name": get.name,
"options": None,
"driver": get.driver,
"ipam": get.ipam,
"enable_ipv6": bool(get.v6_status),
}
from btdockerModelV2.dockerSock import network
sk_network = network.dockerNetWork()
create_network = sk_network.create_network(get)
if not create_network:
return public.return_message(-1, 0, public.lang("Creating a network failed!"))
if "message" in create_network:
if "already exists" in create_network["message"]:
return public.return_message(-1, 0, public.lang("Network name: [{}] already exists!".format(get.name)))
return public.return_message(-1, 0, create_network["message"])
return public.return_message(0, 0, public.lang("Create a network successfully!"))

View File

@@ -0,0 +1,518 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
# ------------------------------
# Docker模型
# ------------------------------
import public
import os
import time
import json
import re
from btdockerModelV2 import dk_public as dp
from btdockerModelV2 import setupModel as ds
from btdockerModelV2 import volumeModel as dv
from btdockerModelV2.dockerBase import dockerBase
from public.validate import Param
class main(dockerBase):
compose_path = "{}/data/compose".format(public.get_panel_path())
project_path = "/www/dk_project"
templates_path = "{}/templates".format(project_path)
config_path = "{}/config".format(public.get_panel_path())
info_path = "{}/docker_project_info.json".format(config_path)
__first_pl = "{}/first.pl".format(project_path)
def __init__(self):
self.log_file = "/tmp/dk_project_run.log"
self.docker_setup = ds.main()
if not os.path.exists(self.templates_path): os.system("mkdir -p {}".format(self.templates_path))
self.compose_cmd = "/usr/bin/docker-compose" if self.docker_setup.check_docker_compose_service()[0] \
else "/usr/local/bin/docker-compose"
def __check_conf(self, filename):
'''
验证配置文件是否可执行
@param filename: docker-compose.yml文件路劲
@return:
'''
return public.ExecShell("{} -f {} config".format(self.compose_cmd, filename))
def sync_item(self, get):
'''
同步官方可以一键部署的项目
@param get: 空对象
@return:
'''
os.remove(self.info_path)
project_info = self._get_project_list(get)
failed_list = []
successes_list = []
for info in project_info:
if info["server_name"]:
down_project_yml = self.__download_project_yml(info["server_name"])
if not down_project_yml["status"]:
failed_list.append(info["server_name"])
continue
successes_list.append(info["server_name"])
data = [{"successes": len(successes_list), "server_name": successes_list},
{"failed": len(failed_list), "server_name": failed_list}]
return public.return_message(0, 0, data)
def __first_sync_item(self, project_info):
'''
同步官方可以一键部署的项目
@param get: 空对象
@return:
'''
failed_list = []
successes_list = []
for info in project_info:
if info["server_name"]:
down_project_yml = self.__download_project_yml(info["server_name"])
if not down_project_yml["status"]:
failed_list.append(info["server_name"])
continue
successes_list.append(info["server_name"])
data = [{"successes": len(successes_list), "server_name": successes_list},
{"failed": len(failed_list), "server_name": failed_list}]
return data
def get_project_list(self, get):
'''
获取支持一键部署的项目列表
@param get:
@return:
'''
project_info = self._get_project_list(get)
return public.return_message(0, 0, project_info)
def _get_project_list(self, get):
'''
获取支持一键部署的项目列表
@param get:
@return:
'''
project_info = []
try:
if not os.path.exists(self.info_path):
down_info = self.__download_info(self.info_path)
if not down_info["status"]:
return project_info
project_info = json.loads(public.readFile(self.info_path))
project_info.sort(key=lambda x: x["sort"])
if not os.path.exists(self.__first_pl):
sync_result = self.__first_sync_item(project_info)
for result in sync_result:
if result.get("successes") and result["successes"] <= 0:
return project_info
public.ExecShell("echo \"first\" > {}".format(self.__first_pl))
except Exception as e:
project_info = []
return project_info
def __get_docker_status(self, args):
'''
获取docker安装和启动状态
@param args:
@return:
'''
return {
"installed": self.docker_setup.check_docker_compose_service(),
"service_status": self.docker_setup.get_service_status()
}
def __download_info(self, info_path):
'''
下载版本信息: info.json
@param info_path: string info.json文件的路劲
@return:
'''
url = "{}/install/lib/docker_project/docker_project_info.json".format(public.get_url())
dp.download_file(url, info_path)
if os.path.exists(info_path):
return public.return_message(0, 0, public.lang("info.json is downloaded!"))
return public.return_message(-1, 0, public.lang("The info.json download failed!"))
def __download_project_yml(self, server_name):
'''
下载指定项目压缩包
@param server_name: string 模板名称,如nextcloud
@return:
'''
try:
path = "{}/{}".format(self.templates_path, server_name)
filename = "{}/{}.tar.gz".format(self.templates_path, server_name)
compose_file = "{}/docker-compose.yml".format(path)
url = "{}/install/lib/docker_project/templates/{}.tar.gz".format(public.get_url(), server_name)
dp.download_file(url, filename)
if not os.path.exists(filename):
return public.return_message(-1, 0, public.lang("{} Download failed, please resync!", server_name))
if os.path.getsize(filename) == 0:
os.remove(filename)
return public.return_message(-1, 0, public.lang("{} Download failed, please resync!", server_name))
self.__tar_x_yml(server_name, path, filename)
if os.path.exists(compose_file):
check_conf = self.__check_conf(compose_file)
if check_conf[1]:
return public.return_message(-1, 0, public.lang("{}yml file test failed,{}", server_name, check_conf[1]))
return public.return_message(0, 0, public.lang("{} Download completed!", server_name))
except:
return public.return_message(-1, 0, public.lang("{} Download failed, please resync!", server_name))
def __tar_x_yml(self, server_name, path=None, filename=None):
'''
解压项目模板方法
@param server_name: 模板名称,如nextcloud
@param path: 项目模板路劲,如/www/dk_project/templates/nextcloud
@param filename: 项目模板压缩包,如/www/dk_project/templates/nextcloud.tar.gz
@return:
'''
tar_result = public.ExecShell("tar xvf {} -C {}".format(filename, self.templates_path))
if tar_result[1]:
os.remove(path)
os.remove(filename)
return public.return_message(-1, 0, public.lang("{} Decompression failed", server_name))
return public.return_message(0, 0, public.lang("{} extracted successfully", server_name))
def create_project_volume(self, server_name, project_name, dir_names, volume_path):
'''
创建指定项目的数据存储卷
@param volume_path:
@param project_name: string
@param dir_names: list [dir_name,dir_name,...]
@return:
'''
args = public.dict_obj()
args.url = "unix:///var/run/docker.sock"
# volumes = dv.main().get_volume_list(args)
# {'status': True, 'msg': {'volume': [], 'installed': True, 'service_status': True}}
# if volumes['status']:
# volumes = volumes['msg']['volume']
# else:
# volumes = list()
# volume的值,一个list: []
for dir_name in dir_names:
# # 如果已经存在就跳过
# for volume in volumes:
# if dir_name == volume["Name"]:
# continue
if volume_path == "":
path = "{}/projects/{}/data/{}".format(self.project_path, project_name, dir_name)
else:
path = "{}/data/{}".format(volume_path, dir_name)
is_mkdir = public.ExecShell("mkdir -p {}".format(path))
if is_mkdir[1]: return public.return_message(-1, 0, public.lang("Directory creation failed for the following reasons: {}", is_mkdir[1]))
args.name = "{}_{}_{}".format(project_name, server_name, dir_name)
args.driver = "local"
args.driver_opts = {'type': 'none', 'device': path, 'o': 'bind'}
args.labels = {}
dv.main().add(args)
return public.return_message(0, 0, public.lang("The storage volume has been created"))
def get_project(self, get):
'''
获取指定一键部署项目的配置信息
@param get: get.server_name
@return:
'''
# 校验参数
try:
get.validate([
Param('server_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))
try:
server_name = getattr(get, "server_name")
info_path = "{}/{}/conf.json".format(self.templates_path, server_name)
project_info = json.loads(public.readFile(info_path))
volume_placeholder = "Default: {}/projects/ your project name /data/".format(self.project_path)
total_sum = len(project_info)
volume_path = {"id": total_sum + 1, "sort": total_sum + 1, "type": "string",
"key": "VOLUME_PATH", "value": "", "placeholder": volume_placeholder,
"ps": "Data storage directory"}
project_info.append(volume_path)
except:
project_info = []
return public.return_message(0, 0, project_info)
def _get_project(self, get):
'''
获取指定一键部署项目的配置信息
@param get: get.server_name
@return:
'''
try:
server_name = getattr(get, "server_name")
info_path = "{}/{}/conf.json".format(self.templates_path, server_name)
project_info = json.loads(public.readFile(info_path))
volume_placeholder = "Default: {}/projects/ your project name /data/".format(self.project_path)
total_sum = len(project_info)
volume_path = {"id": total_sum + 1, "sort": total_sum + 1, "type": "string",
"key": "VOLUME_PATH", "value": "", "placeholder": volume_placeholder,
"ps": "Data storage directory"}
project_info.append(volume_path)
except:
project_info = []
return project_info
def __get_server_ps(self, project_conf, conf_key):
'''
获取对应服务名的标题
@param project_conf:
@param conf_key:
@return:
'''
get = public.dict_obj()
for conf in project_conf:
if conf["key"] == "SERVER_NAME":
get.server_name = conf["value"]
server_conf = self._get_project(get)
for server in server_conf:
if conf_key == server["key"]:
return server["ps"]
return conf_key
def get_project_logs(self, get):
"""
获取一键部署日志websocket
@param get:
@return:
"""
get.wsLogTitle = "Please wait to execute the command..."
print(self.log_file)
get._log_path = self.log_file
return self.get_ws_log(get)
def create_project(self, get):
'''
创建一键部署的项目
@param get:
@return:
'''
# {"project_conf": [{"key": "PROJECT_NAME", "value": "sdfasdf"}, {"key": "PORT", "value": "8180"},
# {"key": "DB_ROOT_PASS", "value": "bt_nextcloud"}, {"key": "DB_NAME", "value": "nextcloud"},
# {"key": "DB_USER", "value": "nextcloud"}, {"key": "DB_PASS", "value": "bt_nextcloud"},
# {"key": "VOLUME_PATH", "value": "/www/dk_project/projects/sdfasdf"},
# {"key": "REMARK", "value": "SDFADSF"}, {"key": "SERVER_NAME", "value": "nextcloud"},
# {"key": "VOLUMES", "value": ["nextcloud", "db"]}]}
# 校验参数
try:
get.validate([
Param('project_conf').Require().List(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
project_conf = getattr(get, "project_conf")
remark = ""
for conf in project_conf:
if conf["key"] != "REMARK" and type(conf["value"]) != list:
if re.search(r'\s', conf["value"]):
server_ps = self.__get_server_ps(project_conf, conf["key"])
return public.return_message(-1, 0, public.lang("{} cannot contain Spaces", server_ps))
if conf["key"] != "VOLUME_PATH" and conf["key"] != "REMARK":
if conf["value"] == "":
server_ps = self.__get_server_ps(project_conf, conf["key"])
return public.return_message(-1, 0, public.lang("{} cannot be null!", server_ps))
if conf["key"].upper() == "PROJECT_NAME": project_name = conf["value"].strip()
if conf["key"].upper() == "VOLUME_PATH": project_volume = conf["value"].strip()
if conf["key"].upper() == "SERVER_NAME": server_name = conf["value"].strip()
if conf["key"].upper() == "VOLUMES": # VOLUMES = list
volumes = conf["value"]
if conf["key"].upper() == "PORT":
if dp.check_socket(conf["value"]):
return public.return_message(-1, 0, public.lang("Server port [ {}] is occupied, please change to another port!", conf['value']))
project_port = conf["value"]
if conf["key"] == "REMARK": remark = conf["value"]
config_path = "{}/config/name_map.json".format(public.get_panel_path())
if not os.path.exists(config_path):
public.writeFile(config_path, json.dumps({}))
if public.readFile(config_path) == '':
public.writeFile(config_path, json.dumps({}))
name_map = json.loads(public.readFile(config_path))
name_str = 'q18q' + public.GetRandomString(10).lower()
name_map[name_str] = project_name
project_name = name_str
public.writeFile(config_path, json.dumps(name_map))
server_dir = "{}/{}".format(self.templates_path, server_name)
project_dir = "{}/projects/{}/{}_{}".format(self.project_path, project_name, project_name, server_name)
public.set_module_logs('docker_project', 'create_project', 1)
check_result = self.__create_dir(project_dir, project_name, server_name, server_dir)
# todo 修改返回内容 只取msg 测试是否取到
if not check_result["status"]:
return public.return_message(-1, 0, check_result["msg"])
self.__write_config(project_dir, project_name, server_name, project_conf)
self.create_project_volume(server_name, project_name, volumes, project_volume)
run_result = self.__project_run(project_dir, project_name)
if run_result["status"]:
self.__add_sql(project_dir, project_name, server_name, remark)
dp.write_log("One-click deployment project [{}] successful!".format(server_name))
return public.return_message(-1, 0, self.__return_msg(project_port))
return public.return_message(-1, 0, run_result)
# return public.return_message(-1, 0, run_result["msg"])
def __project_run(self, project_dir, project_name):
'''
运行项目
@param project_dir: 项目运行目录
@param server_name: 服务名称
@return:
'''
filename = "{}/docker-compose.yml".format(project_dir)
check_result = self.__check_conf(filename)
if check_result[1]:
return public.return_message(-1, 0, public.lang("Project startup failed {}", check_result[1]))
public.ExecShell("echo -n > {}".format(self.log_file))
public.ExecShell("nohup {} -f {}/docker-compose.yml up -d >> {} 2>&1 &&"
" echo 'bt_successful' >> {} || echo 'bt_failed' >> {} &"
.format(
self.compose_cmd,
project_dir,
self.log_file,
self.log_file,
self.log_file
))
return public.return_message(0, 0, public.lang("Start creating the project"))
def __create_dir(self, project_dir, project_name, server_name, server_dir):
'''
创建项目目录
@param project_dir: 项目目录
@param project_name: 项目名称
@param server_dir: 服务源目录
@return:
'''
if self.__check_repeat(project_dir, project_name, server_name):
return public.return_message(-1, 0, public.lang("{} already exists, please change the project name", project_name))
mk_result = public.ExecShell("mkdir -p {}".format(project_dir))
if mk_result[1]: return public.return_message(-1, 0, public.lang("User project directory failed to create,details: {}", mk_result[1]))
cp_result = public.ExecShell("cp -a {}/. {}/".format(server_dir, project_dir))
if cp_result[1]: return public.return_message(-1, 0, public.lang("Failed to copy project directory. Details: {}", cp_result[1]))
return public.return_message(0, 0, public.lang(""))
def __add_sql(self, project_dir, project_name, server_name, remark):
'''
添加项目到docker数据库中
@param project_dir: 项目路劲
@param project_name: 项目名称
@return:
'''
pdata = {
"name": public.xsssec("{}_{}".format(project_name, server_name)),
"status": "1",
"path": "{}/docker-compose.yml".format(project_dir),
"template_id": "",
"time": time.time(),
"remark": public.xsssec(remark)
}
dp.sql("stacks").insert(pdata)
def __return_msg(self, project_port):
'''
创建成功后返回给用户的数据
@param project_port:
@return:
'''
server_ip = public.get_server_ip()
local_ip = public.GetLocalIp()
data = {"protocol": "http", "server_ip": server_ip, "local_ip": local_ip,
"port": project_port}
return public.return_message(0, 0, data)
def __check_repeat(self, project_dir, project_name, server_name):
'''
检查是否存在相同项目
@param project_dir: 项目路劲
@return:
'''
# if os.path.exists(project_dir):
# return True
stacks_info = dp.sql("stacks").where("name=?", ("{}_{}".format(project_name, server_name),)).find()
if stacks_info:
return True
return False
def __write_config(self, project_dir, project_name, server_name, project_conf):
'''
写配置文件
@param project_dir: 用户项目目录
@param project_name: 项目名称
@param server_name: 服务名称如nextcloud
@param project_conf: 新的配置文件内容
@return:
'''
old_env_path = "{}/{}/.env".format(self.templates_path, server_name)
new_env_path = "{}/.env".format(project_dir)
env_conf = ""
if not os.path.exists(old_env_path):
public.ExecShell("echo > {}".format(old_env_path))
with open(old_env_path) as env:
lines = env.readlines()
# 取旧文件转字典
old_dict = {}
for line in lines:
if "=" in line:
temp = line.split("=")
old_dict[temp[0]] = temp[1]
# 新数据转字典
new_dict = {}
for conf in project_conf:
if conf["key"] == "VOLUME_PATH":
project_volume = conf["value"]
if "Default path" in project_volume:
conf["value"] = "{}/{}/data/".format(self.project_path, project_name)
continue
if conf["key"] == "VOLUMES": continue
new_dict[conf["key"].upper()] = conf["value"]
# 旧字典更新新字典的内容
old_dict.update(new_dict)
# 拼接成新的环境变量文件
for key, value in old_dict.items():
env_conf += "{}={}\n".format(key, value.strip())
public.writeFile(new_env_path, env_conf)
return True
def sync_compose_template(self, server_name):
'''
同步模板到项目模板页面
@param server_name: 模板名称
@return:
'''
data = dp.sql("templates").where("name=?", (server_name,)).find()
# if data: dp.sql("templates").delete(id=data["id"])
if data: return
pdata = {
"name": server_name,
"remark": "YakPanel Docker Quick Deployment templates only [Do not delete them and use them separately to create projects]",
"path": "{}/{}/docker-compose.yml".format(self.templates_path, server_name)
}
dp.sql("templates").insert(pdata)
dp.write_log("Add template [{}] successful!".format(server_name))

View File

@@ -0,0 +1,307 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
import json
import os
import traceback
from datetime import datetime
import public
from btdockerModelV2 import dk_public as dp
from btdockerModelV2.dockerBase import dockerBase
from public.validate import Param
class main(dockerBase):
# 2023/12/27 下午 2:56 创建容器反向代理
def create_proxy(self, get):
'''
@name 创建容器反向代理
@author wzz <2023/12/27 下午 2:57>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
# 校验参数
try:
get.validate([
Param('domain').Require(),
Param('container_port').Require(),
Param('container_name').Require(),
Param('container_id').Require(),
Param('privateKey').Require(),
Param('certPem').Require(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, ex)
# try:
if not (os.path.exists('/etc/init.d/nginx') or os.path.exists('/etc/init.d/httpd')):
return public.return_message(-1, 0, public.lang("nginx or apache server was not detected, please install one first!"))
# if not hasattr(get, 'domain'):
# return public.return_message(-1, 0, public.lang("parameter error"))
#
# if not hasattr(get, 'container_port'):
# return public.return_message(-1, 0, public.lang("parameter error"))
self.siteName = get.domain.strip()
self.check_table_dk_sites()
if dp.sql('dk_sites').where('container_id=?', (get.container_id,)).order('id desc').find():
self.close_proxy(get)
# 2024/2/23 下午 12:05 如果其他地方有这个域名,则禁止添加
newpid = public.M('domain').where("name=? and port=?", (self.siteName, 80)).getField('pid')
if newpid:
result = public.M('sites').where("id=?", (newpid,)).find()
if result:
return public.return_message(-1, 0, public.lang("Project Type [{}] Existing Domain: {}", result['project_type'],self.siteName))
self.container_port = get.container_port
if not dp.check_socket(self.container_port):
return public.return_message(-1, 0, public.lang("Server port [ {}] is not used, please enter the port in use to reverse!", self.container_port))
# todo mod的反向代理
from mod.project.proxy.comMod import main as proxyMod
pMod = proxyMod()
try:
args = public.to_dict_obj({
"proxy_pass": "http://127.0.0.1:{}".format(self.container_port),
"proxy_type": "http",
"domains": self.siteName,
"proxy_host": "$http_host",
"remark": "Reverse proxy for container [{}]".format(get.container_name),
})
# 改返回
create_result = pMod.create(args)
if create_result['status'] == -1:
return create_result
# if not create_result['status']:
# return public.returnResult(False, create_result['msg'])
if hasattr(get, "privateKey") and hasattr(get, "certPem") and get.privateKey != "" and get.certPem != "":
args.site_name = self.siteName
args.key = get.privateKey
args.csr = get.certPem
# 改返回
ssl_result = pMod.set_ssl(args)
# if not ssl_result['status']:
if ssl_result['status'] == -1:
result = public.M('sites').where("name=?", (self.siteName,)).find()
args.id = result['id']
args.siteName = self.siteName
args.remove_path = 1
pMod.delete(args)
return ssl_result
# return public.returnResult(False, ssl_result['msg'])
self.sitePath = '/www/wwwroot/' + self.siteName
site_pid = dp.sql('dk_sites').add(
'name,path,ps,addtime,container_id,container_name,container_port',
(self.siteName, self.sitePath, 'Reverse proxy for container [{}]'.format(get.container_name),
datetime.now().strftime("%Y-%m-%d %H:%M:%S"), get.container_id, get.container_name, self.container_port)
)
if not site_pid:
return public.return_message(-1, 0, public.lang("Add failure, database cannot be written!"))
domain_id = dp.sql('dk_domain').where('id=?', (site_pid,)).find()
if not domain_id:
dp.sql('dk_domain').add(
'pid,name,addtime',
(site_pid, self.siteName, datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
)
return public.return_message(0, 0, public.lang("successfully added!"))
except Exception as e:
result = public.M('sites').where("name=?", (self.siteName,)).find()
args = public.to_dict_obj({
"id": result['id'],
"siteName": self.siteName,
"remove_path": 1,
})
pMod.delete(args)
return public.return_message(-1, 0, public.lang('Add failed with error: {}',str(e)))
# 2024/1/2 下午 5:34 获取容器的反向代理信息
def get_proxy_info(self, get):
'''
@name 获取容器的反向代理信息
@author wzz <2024/1/2 下午 5:34>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
# 校验参数
try:
get.validate([
Param('container_id').Require(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, ex)
try:
# if not hasattr(get, 'container_id'):
# return public.return_message(-1, 0, public.lang("parameter error"))
self.check_table_dk_sites()
container_id = get.container_id
from btdockerModelV2 import containerModel as dc
get.id = container_id
container_info = dc.main().get_container_info(get)
proxy_port = []
proxy_info = {
"proxy_port": proxy_port,
"ssl": False,
"status": False,
}
# public.print_log("container_info: {}".format(container_info))
if container_info["status"] == -1:
proxy_port = []
else:
container_info = container_info['message']
for key, value in container_info['NetworkSettings']['Ports'].items():
if value:
proxy_port.append(value[0]['HostPort'])
try:
proxy_info_data = dp.sql('dk_sites').where('container_id=?', (container_id,)).order('id desc').find()
if not proxy_info_data:
return public.return_message(0, 0, proxy_info)
proxy_info = proxy_info_data
site_result = public.M('sites').where("name=?", (proxy_info['name'],)).find()
if not site_result:
dp.sql('dk_sites').where('container_id=?', (container_id,)).delete()
dp.sql('dk_domain').where('pid=?', (proxy_info['id'],)).delete()
return public.return_message(0, 0, proxy_info)
path = '/www/server/panel/vhost/cert/' + proxy_info['name']
conf_file = '/www/server/panel/vhost/nginx/' + proxy_info['name'] + '.conf'
csrpath = path + "/fullchain.pem"
keypath = path + "/privkey.pem"
proxy_info["ssl"] = False
if os.path.exists(csrpath) and os.path.exists(keypath):
try:
conf = public.readFile(conf_file)
if conf:
if (conf.find("ssl_certificate") != -1):
proxy_info["ssl"] = True
proxy_info['cert'] = public.readFile(csrpath)
proxy_info['key'] = public.readFile(keypath)
except:
proxy_info['cert'] = ""
proxy_info['key'] = ""
proxy_info["status"] = True
proxy_info['proxy_port'] = proxy_port
except:
pass
return public.return_message(0, 0, proxy_info)
except Exception as ex:
public.print_log(traceback.format_exc())
public.print_log("error: {}".format(ex))
return public.return_message(-1, 0, ex)
# 2024/1/2 下午 5:43 关闭容器的反向代理
def close_proxy(self, get):
'''
@name 关闭容器的反向代理
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
# 校验参数
try:
get.validate([
Param('container_id').Require().String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, ex)
try:
# if not hasattr(get, 'container_id'):
# return public.return_message(-1, 0, public.lang("parameter error!"))
container_id = get.container_id
proxy_info = dp.sql('dk_sites').where('container_id=?', (container_id,)).order('id desc').find()
if not proxy_info:
return public.return_message(-1, 0, public.lang("No reverse proxy information was detected!"))
newpid = public.M('domain').where("name=? and port=?", (proxy_info["name"], 80)).getField('pid')
if not newpid:
return public.return_message(-1, 0, public.lang("No reverse proxy information was detected!"))
# 删除站点
public.M('sites').where("name=?", (proxy_info['name'],)).delete()
public.M('domain').where("name=?", (proxy_info['name'],)).delete()
# 删除数据库记录
dp.sql('dk_sites').where('container_id=?', (container_id,)).delete()
dp.sql('dk_domain').where('pid=?', (proxy_info['id'],)).delete()
return public.return_message(0, 0, public.lang("successfully delete!"))
except:
return public.return_message(-1, 0, traceback.format_exc())
# 2024/1/2 下午 5:57 获取指定域名的证书内容
def get_cert_info(self, get):
'''
@name 获取指定域名的证书内容
@author wzz <2024/1/2 下午 5:58>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
if not hasattr(get, 'cert_name'): return public.return_message(-1, 0, public.lang("parameter error!"))
cert_name = get.cert_name
# 2024/1/3 下午 4:50 处理通配符域名,将*.spider.com替换成spider.com
if cert_name.startswith('*.'):
cert_name = cert_name.replace('*.', '')
if not os.path.exists('/www/server/panel/vhost/ssl/{}'.format(cert_name)):
return public.return_message(-1, 0, public.lang("Certificate does not exist!"))
cert_data = {}
cert_data['cert_name'] = cert_name
cert_data['cert'] = public.readFile('/www/server/panel/vhost/ssl/{}/fullchain.pem'.format(cert_name))
cert_data['key'] = public.readFile('/www/server/panel/vhost/ssl/{}/privkey.pem'.format(cert_name))
cert_data['info'] = json.loads(
public.readFile('/www/server/panel/vhost/ssl/{}/info.json'.format(cert_name)))
return public.return_message(0, 0, cert_data)
except:
return public.return_message(-1, 0, traceback.format_exc())
def check_table_dk_domain(self):
'''
@name 检查并创建表
@return dict{"status":True/False,"msg":"提示信息"}
'''
if not dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'dk_domain')).count():
dp.sql('dk_domain').execute(
"CREATE TABLE `dk_domain` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `pid` INTEGER, `name` TEXT, `addtime` TEXT )",
()
)
def check_table_dk_sites(self):
'''
@name 检查并创建表
@return dict{"status":True/False,"msg":"提示信息"}
'''
if not dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'dk_sites')).count():
dp.sql('dk_sites').execute(
"CREATE TABLE `dk_sites` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `path` TEXT, `status` TEXT DEFAULT 1, `ps` TEXT, `addtime` TEXT, `type_id` integer DEFAULT 111, `edate` integer DEFAULT '0000-00-00', `project_type` STRING DEFAULT 'dk_proxy', `container_id` TEXT DEFAULT '', `container_name` TEXT DEFAULT '', `container_port` TEXT DEFAULT '')",
()
)

View File

@@ -0,0 +1,323 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: zouhw <zhw@yakpanel.com>
# -------------------------------------------------------------------
# ------------------------------
# Docker模型
# ------------------------------
import json
import public
from btdockerModelV2 import dk_public as dp
from btdockerModelV2.dockerBase import dockerBase
from public.validate import Param
class main(dockerBase):
def docker_client(self, url):
return dp.docker_client(url)
def add(self, args):
"""
添加仓库
:param registry 仓库URL docker.io
:param name
:parma username
:parma password
:param namespace 仓库命名空间
:param remark 备注
:param args:
:return:
"""
# {"registry": "docker.io", "name": "wzznb", "username": "akaishuichi", "password": "xiuyi999..",
# "namespace": "akaishuichi", "remark": "wzz_docker_io"}
# 校验参数
try:
args.validate([
Param('name').Require().String(),
Param('username').Require().String(),
Param('password').Require().String(),
Param('namespace').Require().String(),
Param('remark').String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
# 验证登录
if not args.registry:
args.registry = "docker.io"
res = self.login(self._url, args.registry, args.username, args.password)
if not res['status']:
return public.return_message(-1, 0, res['msg'])
r_list = self.registry_list(args)
if len(r_list) > 0:
for r in r_list:
if "reg_name" in r:
if r['reg_name'] == args.name and r["reg_url"] == args.registry and r['username'] == args.username:
return public.return_message(-1, 0, public.lang("Repository information already exists!"))
if r['name'] == args.name and r["reg_url"] == args.registry and r['username'] == args.username:
return public.return_message(-1, 0, public.lang("Repository information already exists!"))
pdata = {
"reg_name": args.name,
"url": args.registry,
"namespace": args.namespace,
"username": public.aes_encrypt(args.username, self.aes_key),
"password": public.aes_encrypt(args.password, self.aes_key),
"remark": public.xsssec(args.remark)
}
aa = dp.sql("registry").insert(pdata)
dp.write_log("Added repository [{}] [{}] success!".format(args.name, args.registry))
return public.return_message(0, 0, public.lang("successfully added!"))
def edit(self, args):
"""
编辑仓库
:param registry 仓库URL docker.io
:param id 仓库id
:parma username
:parma password
:param namespace
:param remark
:param args:
:return:
"""
# 校验参数
try:
args.validate([
Param('id').Require().Integer(),
Param('username').Require().String(),
Param('password').Require().String(),
Param('namespace').Require().String(),
Param('remark').String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
# 验证登录
# if str(args.id) == "1":
# return public.return_message(-1, 0, public.lang("[Official Docker repository] Not editable!"))
if not args.registry:
args.registry = "docker.io"
# 2023/12/13 上午 11:40 处理加密的编辑
try:
is_encrypt = False
res = self.login(self._url, args.registry, args.username, args.password)
if not res['status']:
res = self.login(
self._url,
args.registry,
public.aes_decrypt(args.username, self.aes_key),
public.aes_decrypt(args.password, self.aes_key)
)
if not res['status']:
return public.return_message(-1, 0, res['msg'])
is_encrypt = True
except Exception as e:
if "binascii.Error: Incorrect padding" in str(e):
return public.return_message(-1, 0, public.lang("Editing failed! Reason: Account password decryption failed! Please delete the repository and add it again"))
return public.return_message(-1, 0, public.lang("Editing failed! Reason:{}", e))
res = dp.sql("registry").where("id=?", (args.id,)).find()
if not res:
return public.return_message(-1, 0, public.lang("This repository could not be found"))
pdata = {
"reg_name": args.name,
"url": args.registry,
"username": public.aes_encrypt(args.username, self.aes_key) if is_encrypt is False else args.username,
"password": public.aes_encrypt(args.password, self.aes_key) if is_encrypt is False else args.password,
"namespace": args.namespace,
"remark": args.remark
}
dp.sql("registry").where("id=?", (args.id,)).update(pdata)
dp.write_log("Edit repository [{}][{}] Success!".format(args.name, args.registry))
return public.return_message(0, 0, public.lang("Edit success!"))
def remove(self, args):
"""
删除某个仓库
:param id
:param rags:
:return:
"""
# 校验参数
try:
args.validate([
Param('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))
# if str(args.id) == "1":
# return public.return_message(-1, 0, public.lang("[Official Docker repository] can not be removed!"))
data = dp.sql("registry").where("id=?", (args.id)).find()
if len(data) < 1:
return public.return_message(0, 0, public.lang("Delete failed,The repository id may not exist!"))
dp.sql("registry").where("id=?", (args.id,)).delete()
dp.write_log("Delete repository [{}][{}] Success!".format(data['name'], data['url']))
return public.return_message(0, 0, public.lang("Successfully deleted!"))
def registry_list(self, get):
"""
获取仓库列表
:return:
"""
self.check_table_dk_registry()
db_obj = dp.sql("registry")
# 2024/1/3 下午 6:00 检测数据库是否存在并且表健康
search_result = db_obj.where('id=? or name=?', (1, "Docker public repository")).select()
# if db_obj.ERR_INFO:
# return []
if len(search_result) == 0:
dp.sql("registry").insert({
"name": "Docker public repository",
"url": "docker.io",
"username": "",
"password": "",
"namespace": "",
"remark": "Docker public repository"
})
if "error: no such table: registry" in search_result or len(search_result) == 0:
# public.ExecShell("mv -f /www/server/panel/data/docker.db /www/server/panel/data/db/docker.db")
public.ExecShell("mv -f /www/server/panel/data/db/docker.db /www/server/panel/data/docker.db")
dp.check_db()
res = dp.sql("registry").select()
if not isinstance(res, list):
res = []
for r in res:
if "reg_name" not in r: continue
if r["name"] == "" or not r["name"] or r["name"] is None:
r["name"] = r["reg_name"]
return res
# 改返回
def registry_listV2(self, get):
"""
获取仓库列表
:return:
"""
res = self.registry_list(get)
return public.return_message(0, 0, res)
# 2024/5/24 下午6:09 设置备注
def set_remark(self, get):
'''
@name 设置备注
@param "data":{"id":"仓库ID","remark":"备注"}
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
get.remark = get.get("remark/s", "")
if get.remark != "":
get.remark = public.xssencode2(get.remark)
dp.sql("registry").where("id=?", (get.id,)).setField("remark", get.remark)
return public.return_message(0, 0, public.lang("Setup Successful!"))
except Exception as e:
return public.return_message(-1, 0, public.lang("Setup failed!{}",str(e)))
def get_com_registry(self, get):
"""
获取常用仓库列表
@param get:
@return:
"""
com_registry_file = "{}/class/btdockerModelV2/config/com_registry.json".format(public.get_panel_path())
try:
com_registry = json.loads(public.readFile(com_registry_file))
except:
com_registry = {
"docker.io": "Docker public repository",
"swr.cn-north-4.myhuaweicloud.com": "Huawei Cloud mirror station",
"ccr.ccs.tencentyun.com": "Tencent cloud mirror station",
"registry.cn-hangzhou.aliyuncs.com": "Alibaba Cloud Mirror Station (Hangzhou)"
}
return public.return_message(0, 0, com_registry)
# todo 检查字段新增reg_name
def registry_info(self, get):
'''
@name 获取指定仓库的信息
@author wzz <2024/5/24 下午3:16>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
registry_info = dp.sql("registry").where("reg_name=? and url=?", (get.name, get.url)).find()
if not registry_info:
registry_info = dp.sql("registry").where("name=? and url=?", (get.name, get.url)).find()
return registry_info
def login(self, url, registry, username, password):
"""
仓库登录测试
:param args:
:return:
"""
try:
res = self.docker_client(url).login(
registry=registry,
username=username,
password=password,
reauth=False
)
return public.returnMsg(True, str(res))
except:
error_info = public.get_error_info()
return public.returnMsg(False, public.lang("Login test failed! Reason: {}", error_info))
# except docker.errors.APIError as e:
# if "authentication required" in str(e):
# return public.returnMsg(False, public.lang("Login test failed! Reason: May be account password error, please check!"))
# if "unauthorized: incorrect username or password" in str(e):
# return public.returnMsg(False, public.lang("Login test failed! Reason: May be account password error, please check!"))
# return public.returnMsg(False, public.lang("Login test failed! Reason: {}", e))
def check_table_dk_registry(self):
'''
@name 检查表registry 字段 reg_name remark是否存在
@return dict{"status":True/False,"msg":"提示信息"}
'''
# if public.M('sqlite_master').where('type=? AND name=?', ('table', 'docker_log_split')).count():
# create_table_str = public.M('sqlite_master').where('type=? AND name=?', ('table', 'docker_log_split')).getField('sql')
#
# if dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'docker_log_split')).count():
# create_table_str = dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'docker_log_split')).getField('sql')
create_table_str = dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'registry')).getField('sql')
if create_table_str and 'reg_name' not in create_table_str:
dp.sql('registry').execute('ALTER TABLE `registry` ADD COLUMN `reg_name` VARCHAR default "";')
if create_table_str and 'remark' not in create_table_str:
dp.sql('registry').execute('ALTER TABLE `registry` ADD COLUMN `remark` VARCHAR default "";')

View File

@@ -0,0 +1,84 @@
#coding: utf-8
#-------------------------------------------------------------------
# YakPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: zouhw <zhw@yakpanel.com>
#-------------------------------------------------------------------
#------------------------------
# Docker模型
#------------------------------
import dk_public as dp
import time
class main:
def get_status(self,args):
"""
start_time
stop_time
:param args:
:return:
"""
data = dict()
# 容器总数
data['container_count'] = self.__get_container_count(args)
# 镜像信息,镜像总数,占用空间大小
data['image_info'] = dp.sql("image_infos").where("time>=? and time<=?",(args.start_time,args.stop_time)).select()
# 主机信息
data['host'] = len(dp.sql('hosts').select())
# 1小时内容器占用资源前三平均值
data['container_top'] = {"cpu":self.__get_cpu_avg(),"mem":self.__get_mem_avg()}
return data
def __get_container_count(self,args):
count = dp.sql('container_count').where("time>=? and time<=?", (args.start_time, args.stop_time)).select()
if not count:
return 0
return count[-1]
def __get_mem_avg(self):
now = int(time.time())
start_time = now - 3600
data = dp.sql("mem_stats").where("time>=? and time<=?",(start_time,now)).select()
containers = list()
info = dict()
# 获取容器ID
for d in data:
containers.append(d['container_id'])
# 获取每个容器1小时内的cpu使用率总和
containers = set(containers)
for c in containers:
num = 0
usage = 0
for d in data:
if d['container_id'] == c:
num += 1
usage += float(d['usage'])
if num != 0:
info[c] = usage / num
return info
def __get_cpu_avg(self):
now = int(time.time())
start_time = now - 3600
data = dp.sql("cpu_stats").where("time>=? and time<=?",(start_time,now)).select()
containers = list()
info = dict()
# 获取容器ID
for d in data:
containers.append(d['container_id'])
# 获取每个容器1小时内的cpu使用率总和
containers = set(containers)
for c in containers:
num = 0
cpu_usage = 0
for d in data:
if d['container_id'] == c:
num += 1
cpu_usage += float(0 if d['cpu_usage'] == '0.0' else d['cpu_usage'])
if num != 0:
info[c] = cpu_usage / num
return info

View File

@@ -0,0 +1,136 @@
# coding: utf-8
import sys, os
os.chdir('/www/server/panel/')
sys.path.insert(0, "class/")
import PluginLoader
import public
import time
def clear_hosts():
"""
@name 清理 hosts 中面板官网节点记录
@return:
"""
if public.is_self_hosted():
return
remove = 0
try:
import requests
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
url = 'https://wafapi2.yakpanel.com/api/ip/info_json'
res = requests.post(url, verify=False)
if res.status_code == 404:
remove = 1
elif res.status_code == 200 or res.status_code == 400:
res = res.json()
if res != "[]":
remove = 1
except:
result = public.ExecShell("curl -sS --connect-timeout 3 -m 60 -k https://wafapi2.yakpanel.com/api/ip/info_json")[0]
if result != "[]":
remove = 1
hosts_file = '/etc/hosts'
if remove == 1 and os.path.exists(hosts_file):
public.ExecShell('sed -i "/www\\.yakpanel\\.com/d" /etc/hosts && sed -i "/api\\.yakpanel\\.com/d" /etc/hosts && sed -i "/bt\\.cn/d" /etc/hosts')
def flush_cache():
'''
@name 更新缓存
@author hwliang
@return void
'''
try:
# start_time = time.time()
res = PluginLoader.get_plugin_list(1)
spath = '{}/data/pay_type.json'.format(public.get_panel_path())
public.downloadFile(public.get_url() + '/install/lib/pay_type.json', spath)
import plugin_deployment
plugin_deployment.plugin_deployment().GetCloudList(None)
# timeout = time.time() - start_time
if 'ip' in res and res['ip']:
pass
else:
if isinstance(res, dict) and not 'msg' in res: res['msg'] = 'Connection failure!'
except:
pass
def flush_php_order_cache():
"""
更新软件商店php顺序缓存
@return:
"""
spath = '{}/data/php_order.json'.format(public.get_panel_path())
public.downloadFile(public.get_url() + '/install/lib/php_order.json', spath)
def flush_msg_json():
"""
@name 更新消息json
"""
try:
spath = '{}/data/msg.json'.format(public.get_panel_path())
public.downloadFile(public.get_url() + '/linux/panel/msg/msg.json', spath)
except:
pass
def flush_docker_project_info():
'''
@name 更新docker_project版本信息
@author wzz
@return void
'''
msg = "docker_projcet version information"
try:
# start_time = time.time()
res = PluginLoader.get_plugin_list(1)
config_path = f"{public.get_panel_path()}/config"
spath = f"{config_path}/docker_project_info.json"
url = "/install/lib/docker_project/docker_project_info.json"
public.downloadFile(f"{public.get_url()}{url}", spath)
import plugin_deployment
plugin_deployment.plugin_deployment().GetCloudList(None)
# timeout = time.time() - start_time
if 'ip' in res and res['ip']:
pass
else:
if isinstance(res, dict) and not 'msg' in res: res['msg'] = 'Connection failure!'
except:
pass
# 2024/3/20 上午 11:09 更新docker_hub镜像排行数据
def flush_docker_hub_repos():
'''
@name 更新docker_hub镜像排行数据
@author wzz <2024/3/20 上午 11:09>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
public.ExecShell("/www/server/panel/pyenv/bin/python3 /www/server/panel/class_v2/btdockerModelV2/script/syncreposdb.py")
if __name__ == '__main__':
tip_date_tie = '/tmp/.fluah_time'
if os.path.exists(tip_date_tie):
last_time = int(public.readFile(tip_date_tie))
timeout = time.time() - last_time
if timeout < 600:
print("Execution interval is too short, exit - {}!".format(timeout))
sys.exit()
clear_hosts()
flush_cache()
flush_php_order_cache()
flush_msg_json()
flush_docker_project_info()
flush_docker_hub_repos()
public.writeFile(tip_date_tie, str(int(time.time())))

View File

@@ -0,0 +1,92 @@
#!/www/server/panel/pyenv/bin/python3
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
# docker模型sock 封装库 镜像库
# -------------------------------------------------------------------
import os
import sys
import time
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
import public
if "/www/server/panel/class_v2" not in sys.path:
sys.path.insert(0, "/www/server/panel/class_v2")
import btdockerModelV2.dk_public as dp
db_file = '{}/class_v2/btdockerModelV2/config/docker_hub_repos.db'.format(public.get_panel_path())
last_update_pl = "{}/class_v2/btdockerModelV2/config/docker_hub_last_update.pl".format(public.get_panel_path())
# 2024/3/20 上午 9:47 获取docker hub最新的镜像排行数据
def get_docker_hub_repos():
'''
@name 获取docker hub最新的镜像排行数据
@author wzz <2024/3/20 上午 9:47>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
url = "{}/src/docker/docker_hub_repos.db".format(public.get_url())
dp.download_file(url, db_file)
if not os.path.exists(db_file):
return public.returnMsg(False, "info.json download failed")
# 写一个最后更新的标记文件,里面有时间戳
public.writeFile(last_update_pl, str(int(time.time())))
return
except Exception as e:
if os.path.exists('data/debug.pl'):
print(public.get_error_info())
public.print_log(public.get_error_info())
# 2024/3/20 上午 9:34 如果当前时间减去这个时间戳大于30天就执行 get_docker_hub_repos
def check_last_update():
'''
@name 如果当前时间减去这个时间戳大于30天就执行 get_docker_hub_repos
@author wzz <2024/3/20 上午 9:46>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
if os.path.exists(last_update_pl):
last_update_time = int(public.readFile(last_update_pl))
if int(time.time()) - last_update_time > 2592000:
public.ExecShell("rm -f {}".format(db_file))
public.ExecShell("rm -f {}".format(last_update_pl))
get_docker_hub_repos()
if os.path.exists(db_file) and (os.path.getsize(db_file) == 0 or os.path.getsize(db_file) < 80):
public.ExecShell("rm -f {}".format(db_file))
public.ExecShell("rm -f {}".format(last_update_pl))
get_docker_hub_repos()
if not os.path.exists(db_file):
public.ExecShell("rm -f {}".format(last_update_pl))
get_docker_hub_repos()
else:
if not os.path.exists(db_file):
get_docker_hub_repos()
if os.path.exists(db_file) and (os.path.getsize(db_file) == 0 or os.path.getsize(db_file) < 80):
public.ExecShell("rm -f {}".format(db_file))
public.ExecShell("rm -f {}".format(last_update_pl))
get_docker_hub_repos()
except Exception as e:
public.ExecShell("rm -f {}".format(db_file))
public.ExecShell("rm -f {}".format(last_update_pl))
if os.path.exists('data/debug.pl'):
print(public.get_error_info())
public.print_log(public.get_error_info())
check_last_update()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,317 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
import gettext
import json
_ = gettext.gettext
import time
# ------------------------------
# Docker模型
# ------------------------------
import public
from btdockerModelV2 import dk_public as dp
from btdockerModelV2.dockerBase import dockerBase
from public.validate import Param
class main(dockerBase):
__stats_tmp = dict()
__docker = None
def docker_client(self, url):
if not self.__docker:
self.__docker = dp.docker_client(url)
return self.__docker
def io_stats(self, stats, write=None):
drive_io = stats['blkio_stats']['io_service_bytes_recursive']
if drive_io:
if len(drive_io) <= 2:
try:
now = drive_io[0]['value']
self.__stats_tmp['read_total'] = now
except:
self.__stats_tmp['read_total'] = 0
try:
now = drive_io[1]['value']
self.__stats_tmp['write_total'] = now
except:
self.__stats_tmp['write_total'] = 0
else:
try:
now = drive_io[0]['value'] + drive_io[2]['value']
self.__stats_tmp['read_total'] = now
except:
self.__stats_tmp['read_total'] = 0
try:
now = drive_io[1]['value'] + drive_io[3]['value']
self.__stats_tmp['write_total'] = now
except:
self.__stats_tmp['write_total'] = 0
if write:
self.__stats_tmp['container_id'] = stats['id']
self.write_io(self.__stats_tmp)
def net_stats(self, stats, cache, write=None):
try:
net_io = stats['networks']['eth0']
net_io_old = cache['networks']['eth0']
except:
self.__stats_tmp['rx_total'] = 0
self.__stats_tmp['rx'] = 0
self.__stats_tmp['tx_total'] = 0
self.__stats_tmp['tx'] = 0
if write:
self.__stats_tmp['container_id'] = stats['id']
self.write_net(self.__stats_tmp)
return
time_now = stats["time"]
time_old = cache["time"]
try:
now = net_io["rx_bytes"]
self.__stats_tmp['rx_total'] = now
old = net_io_old["rx_bytes"]
self.__stats_tmp['rx'] = int((now - old) / (time_now - time_old))
except:
self.__stats_tmp['rx_total'] = 0
self.__stats_tmp['rx'] = 0
try:
now = net_io["tx_bytes"]
old = net_io_old["tx_bytes"]
self.__stats_tmp['tx_total'] = now
self.__stats_tmp['tx'] = int((now - old) / (time_now - time_old))
except:
self.__stats_tmp['tx_total'] = 0
self.__stats_tmp['tx'] = 0
if write:
self.__stats_tmp['container_id'] = stats['id']
self.write_net(self.__stats_tmp)
# return data
def mem_stats(self, stats, write=None):
mem = stats['memory_stats']
try:
self.__stats_tmp['limit'] = mem['limit']
self.__stats_tmp['usage_total'] = mem['usage']
if 'cache' not in mem['stats']:
mem['stats']['cache'] = 0
self.__stats_tmp['usage'] = mem['usage'] - mem['stats']['cache']
self.__stats_tmp['cache'] = mem['stats']['cache']
# data['mem_useage'] = round(mem['usage'] * 100 / data['limit'],2)
except:
# return public.get_error_info()
self.__stats_tmp['limit'] = 0
self.__stats_tmp['usage'] = 0
self.__stats_tmp['cache'] = 0
self.__stats_tmp['usage_total'] = 0
# data['mem_useage'] = 0
if write:
self.__stats_tmp['container_id'] = stats['id']
self.write_mem(self.__stats_tmp)
# return data
def cpu_stats(self, stats, write=None):
# cpu_limit = dp.sql('container').where("c_id=?",(stats['id'],)).find()
# if cpu_limit:
# cpu_limit = cpu_limit['cpu_limit']
# else:
# cpu_limit = 1
try:
cpu = stats['cpu_stats']['cpu_usage']['total_usage'] - stats[
'precpu_stats']['cpu_usage']['total_usage']
except:
cpu = 0
try:
system = stats['cpu_stats']['system_cpu_usage'] - stats[
'precpu_stats']['system_cpu_usage']
except:
system = 0
try:
self.__stats_tmp['online_cpus'] = stats['cpu_stats']['online_cpus']
except:
self.__stats_tmp['online_cpus'] = 0
if cpu > 0 and system > 0:
self.__stats_tmp['cpu_usage'] = round(
(cpu / system) * 100 * self.__stats_tmp['online_cpus'], 2)
else:
self.__stats_tmp['cpu_usage'] = 0.0
if write:
self.__stats_tmp['container_id'] = stats['id']
self.write_cpu(self.__stats_tmp)
# return data
def stats(self, args):
"""
获取某个容器的cpu内存网络io磁盘io.
:param url
:param id
:param args:
:return:
"""
# {"id": "d58097084d43324643efde5cc8d30643901c27366d35238801a2119509352ab7", "dk_status": "running"}
try:
args.validate([
Param('id').Require().String(),
Param('dk_status').Require().String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, ex)
try:
container = self.docker_client(self._url).containers.get(args.id)
stats = container.stats(decode=None, stream=False)
stats['time'] = time.time()
cache = public.cache_get('stats')
if not cache:
cache = stats
public.cache_set('stats', stats)
write = None
if hasattr(args, "write"):
write = args.write
self.__stats_tmp['expired'] = time.time() - (args.save_date * 86400)
stats['id'] = args.id
import json
from pygments import highlight, lexers, formatters
formatted_json = json.dumps(stats, indent=3)
colorful_json = highlight(formatted_json.encode('utf-8'), lexers.JsonLexer(), formatters.TerminalFormatter())
# print(colorful_json)
self.io_stats(stats, write)
self.net_stats(stats, cache, write)
self.cpu_stats(stats, write)
self.mem_stats(stats, write)
public.cache_set('stats', stats)
self.__stats_tmp['detail'] = stats
if 'dk_status' in args and args.dk_status != 'running':
self.__stats_tmp['read_total'] = 0
self.__stats_tmp['write_total'] = 0
return public.return_message(0, 0, self.__stats_tmp)
except Exception as ex:
if "No such container" in str(ex):
return public.return_message(-1, 0, public.lang("The container does not exist, please refresh the browser and try again!"))
return public.return_message(-1, 0, public.lang('Failed to get container status: ' + str(ex)))
def top(self, get):
"""
获取容器内进程信息
@param get:
@return:
"""
container = self.docker_client(self._url).containers.get(get.id)
return public.return_message(0, 0, container.top())
def write_cpu(self, data):
pdata = {
"time": time.time(),
"cpu_usage": data['cpu_usage'],
"online_cpus": data['online_cpus'],
"container_id": data['container_id']
}
dp.sql("cpu_stats").where("time<?", (self.__stats_tmp['expired'],)).delete()
dp.sql("cpu_stats").insert(pdata)
def write_io(self, data):
pdata = {
"time": time.time(),
"write_total": data['write_total'],
"read_total": data['read_total'],
"container_id": data['container_id']
}
dp.sql("io_stats").where("time<?", (self.__stats_tmp['expired'],)).delete()
dp.sql("io_stats").insert(pdata)
def write_net(self, data):
pdata = {
"time": time.time(),
"tx_total": data['tx_total'],
"rx_total": data['rx_total'],
"tx": data['tx'],
"rx": data['rx'],
"container_id": data['container_id']
}
dp.sql("net_stats").where("time<?", (self.__stats_tmp['expired'],)).delete()
dp.sql("net_stats").insert(pdata)
def write_mem(self, data):
pdata = {
"time": time.time(),
"mem_limit": data['limit'],
"cache": data['cache'],
"usage": data['usage'],
"usage_total": data['usage_total'],
"container_id": data['container_id']
}
dp.sql("mem_stats").where("time<?", (self.__stats_tmp['expired'],)).delete()
dp.sql("mem_stats").insert(pdata)
# 获取某服务器容器总数
def get_container_count(self, args):
return public.return_message(0, 0, len(self.docker_client(self._url).containers.list()))
# 获取监控容器资源并记录每分钟
# 获取Docker总览信息
def get_docker_system_info(self, get):
result = {
"containers": {"usage": "-", "total": "-", "size": "-"},
"images": {"usage": "-", "total": "-", "size": "-"},
"volumes": {"usage": "-", "total": "-", "size": "-"},
"networks": {"usage": 0, "total": 0},
"composes": {"usage": 0, "total": 0},
"mirrors": 0,
}
try:
# 获取 mirrors
system_info = self.docker_client(self._url).info()
result["mirrors"] = len(system_info.get("RegistryConfig", {}).get("IndexConfigs", {}))
# 解析 docker system df
dfs, err = public.ExecShell("docker system df --format json")
for line in dfs.strip().split('\n'):
line = line.strip()
if not line:
continue
try:
df = json.loads(line)
df_type = df.get("Type")
if df_type == "Images":
result["images"]["usage"] = df.get("Active", 0)
result["images"]["total"] = df.get("TotalCount", 0)
result["images"]["size"] = df.get("Size", "0B")
elif df_type == "Containers":
result["containers"]["usage"] = df.get("Active", 0)
result["containers"]["total"] = df.get("TotalCount", 0)
result["containers"]["size"] = df.get("Size", "0B")
elif df_type == "Local Volumes":
result["volumes"]["usage"] = df.get("Active", 0)
result["volumes"]["total"] = df.get("TotalCount", 0)
result["volumes"]["size"] = df.get("Size", "0B")
except Exception as e:
public.print_log('Docker', 'Parse docker system df failed: {}, error: {}'.format(line, str(e)))
continue
# Networks
network_info = self.docker_client(self._url).networks.list()
result["networks"]["total"] = len(network_info)
# Compose
from mod.project.docker.composeMod import main as Compose
dk_compose = Compose()
compose_list = dk_compose.ls(get)
result["composes"]["total"] = len(compose_list)
except Exception as e:
public.print_log('Docker', 'Get system info failed: {}'.format(str(e)))
return public.return_message(0, 0, result)

View File

@@ -0,0 +1,158 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
# ------------------------------
# Docker模型
# ------------------------------
import docker.errors
import public
from btdockerModelV2 import dk_public as dp
from btdockerModelV2.dockerBase import dockerBase
from public.validate import Param
class main(dockerBase):
def docker_client(self, url):
return dp.docker_client(url)
def get_volume_container_name(self, volume_detail, container_list):
'''
拼接对应的容器名与卷名
@param volume_detail: 卷字典
@param container_list: 容器详情列表
@return:
'''
try:
for container in container_list:
if not container['Mounts']:
continue
for mount in container['Mounts']:
if "Name" not in mount:
continue
if volume_detail['Name'] == mount['Name']:
volume_detail['container'] = container['Names'][0].replace("/", "")
if 'container' not in volume_detail:
volume_detail['container'] = ''
except:
volume_detail['container'] = ''
return volume_detail
def get_volume_list(self, args):
"""
:param self._url: 链接docker的URL
:return:
"""
try:
data = list()
from btdockerModelV2.dockerSock import volume
sk_volume = volume.dockerVolume()
volume_list = sk_volume.get_volumes()
from btdockerModelV2.dockerSock import container
sk_container = container.dockerContainer()
container_list = sk_container.get_container()
if "Volumes" in volume_list and type(volume_list["Volumes"]) == list:
for v in volume_list["Volumes"]:
v["CreatedAt"] = dp.convert_timezone_str_to_timestamp(v["CreatedAt"])
data.append(self.get_volume_container_name(v, container_list))
return public.return_message(0, 0, sorted(data, key=lambda x: x['CreatedAt'], reverse=True))
else:
return public.return_message(0, 0, [])
except Exception as e:
return public.return_message(-1, 0, [])
def add(self, args):
"""
添加一个卷
:param name
:param driver local
:param driver_opts (dict) Driver options as a key-value dictionary
:param labels str
:return:
"""
try:
args.driver_opts = args.get("driver_opts", "")
args.labels = args.get("labels", "")
if args.driver_opts != "":
args.driver_opts = dp.set_kv(args.driver_opts)
if args.labels != "":
args.labels = dp.set_kv(args.labels)
if len(args.name) < 2:
return public.return_message(-1, 0, public.lang("Volume names can be no less than 2 characters long!"))
self.docker_client(self._url).volumes.create(
name=args.name,
driver=args.driver,
driver_opts=args.driver_opts if args.driver_opts else None,
labels=args.labels if args.labels != "" else None
)
dp.write_log("Add storage volume [{}] success!".format(args.name))
return public.return_message(0, 0, public.lang("successfully added!"))
except docker.errors.APIError as e:
if "volume name is too short, names should be at least two alphanumeric characters" in str(e):
return public.return_message(-1, 0, public.lang("Volume names can be no less than 2 characters long!"))
if "volume name" in str(e):
return public.return_message(-1, 0, public.lang("Volume name already exists!"))
return public.return_message(-1, 0, public.lang("addition failed {}", e))
except Exception as e:
if "driver_opts must be a dictionary" in str(e):
return public.return_message(-1, 0, public.lang("Driver option tags must be dictionary/key-value pairs!"))
return public.return_message(-1, 0, public.lang("Add failed! {}", e))
def remove(self, args):
"""
删除一个卷
:param name volume name
:param args:
:return:
"""
# 校验参数
try:
args.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))
try:
obj = self.docker_client(self._url).volumes.get(args.name)
obj.remove()
dp.write_log("Delete volume [{}] successful!".format(args.name))
return public.return_message(0, 0, public.lang("successfully delete"))
except docker.errors.APIError as e:
if "volume is in use" in str(e):
return public.return_message(-1, 0, public.lang("The storage volume is in use and cannot be deleted!"))
if "no such volume" in str(e):
return public.return_message(-1, 0, public.lang("The storage volume does not exist!"))
return public.return_message(-1, 0, public.lang("Delete failed! {}", e))
def prune(self, args):
"""
删除无用的卷
:param args:
:return:
"""
try:
res = self.docker_client(self._url).volumes.prune()
if not res['VolumesDeleted']:
return public.return_message(-1, 0, public.lang("No useless storage volumes!"))
dp.write_log("Delete useless storage volume successfully!")
return public.return_message(0, 0, public.lang("successfully delete!"))
except docker.errors.APIError as e:
return public.return_message(-1, 0, public.lang("Delete failed! {}", e))