1961 lines
77 KiB
Python
1961 lines
77 KiB
Python
|
|
import hashlib
|
|||
|
|
import html
|
|||
|
|
import io
|
|||
|
|
import ipaddress
|
|||
|
|
import json
|
|||
|
|
import os
|
|||
|
|
import re
|
|||
|
|
import shutil
|
|||
|
|
import sys
|
|||
|
|
import threading
|
|||
|
|
import time
|
|||
|
|
import socket
|
|||
|
|
import platform
|
|||
|
|
from uuid import uuid4
|
|||
|
|
|
|||
|
|
import math
|
|||
|
|
import requests
|
|||
|
|
import simple_websocket
|
|||
|
|
import websocket
|
|||
|
|
from werkzeug.datastructures import FileStorage
|
|||
|
|
from datetime import datetime
|
|||
|
|
|
|||
|
|
if "/www/server/panel/class" not in sys.path:
|
|||
|
|
sys.path.insert(0, "/www/server/panel/class")
|
|||
|
|
|
|||
|
|
import public
|
|||
|
|
from typing import Optional, Tuple, List, Callable, Union, Any, Dict
|
|||
|
|
from .one_panel_api import OnePanelApiClient
|
|||
|
|
from mod.project.node.dbutil import Node, NodeAPPKey
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
from YakPanel import cache
|
|||
|
|
except:
|
|||
|
|
class _Cache:
|
|||
|
|
def __init__(self):
|
|||
|
|
self._data = {}
|
|||
|
|
|
|||
|
|
def get(self, key):
|
|||
|
|
return self._data.get(key, None)
|
|||
|
|
|
|||
|
|
def set(self, key, value, timeout):
|
|||
|
|
self._data[key] = value
|
|||
|
|
|
|||
|
|
|
|||
|
|
cache = _Cache()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _get_os_info() -> Tuple[str, str]:
|
|||
|
|
os_data = public.readFile("/etc/os-release")
|
|||
|
|
if not os_data:
|
|||
|
|
return "", ""
|
|||
|
|
|
|||
|
|
version = ["", ""]
|
|||
|
|
for line in os_data.split("\n"):
|
|||
|
|
if line.startswith("NAME="):
|
|||
|
|
name = line.split("=")[1].replace('"', "").strip()
|
|||
|
|
name_arr = name.split(" ")
|
|||
|
|
if len(name_arr) > 1:
|
|||
|
|
name = name_arr[0]
|
|||
|
|
version[0] = name
|
|||
|
|
elif line.startswith("VERSION="):
|
|||
|
|
ver = line.split("=")[1].replace('"', "").strip()
|
|||
|
|
ver_arr = ver.split(" ")
|
|||
|
|
if len(ver_arr) > 1:
|
|||
|
|
ver = ver_arr[0]
|
|||
|
|
version[1] = ver
|
|||
|
|
|
|||
|
|
version = " ".join(version)
|
|||
|
|
return version, platform.machine()
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ServerNode:
|
|||
|
|
is_local = False
|
|||
|
|
node_db_file = public.get_panel_path() + "/data/db/node.db"
|
|||
|
|
|
|||
|
|
def __init__(self, origin: str, api_key: str, app_key: str, remarks: str = "", timeout: int = 20):
|
|||
|
|
self.origin = origin
|
|||
|
|
self.api_key = api_key
|
|||
|
|
self.app_key: Optional[NodeAPPKey] = Node.parse_app_key(app_key)
|
|||
|
|
self.remarks = remarks
|
|||
|
|
self.timeout = timeout
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def node_server_ip(self):
|
|||
|
|
from urllib.parse import urlparse
|
|||
|
|
host = urlparse(self.origin).hostname
|
|||
|
|
try:
|
|||
|
|
if isinstance(host, str) and ipaddress.ip_address(host):
|
|||
|
|
return host
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
try:
|
|||
|
|
ip_address = socket.gethostbyname(host)
|
|||
|
|
return ip_address
|
|||
|
|
except socket.gaierror as e:
|
|||
|
|
print(f"Error: {e}")
|
|||
|
|
return host
|
|||
|
|
|
|||
|
|
def app_bind(self) -> Optional[str]:
|
|||
|
|
if self.app_key is None:
|
|||
|
|
return "App key parsing failed, please confirm key information again"
|
|||
|
|
version, arch = _get_os_info()
|
|||
|
|
# public.print_log(self.app_key.app_token)
|
|||
|
|
pdata = {
|
|||
|
|
"bind_token": self.app_key.app_token,
|
|||
|
|
"client_brand": "Yak-Panel",
|
|||
|
|
"client_model": "Linux" if not version else "Linux {} {}".format(version, arch)
|
|||
|
|
}
|
|||
|
|
header = {
|
|||
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|||
|
|
"User-Agent": "Yak-Panel/Node Manager"
|
|||
|
|
}
|
|||
|
|
bind_url = self.app_key.origin + "/check_bind"
|
|||
|
|
try:
|
|||
|
|
res = requests.post(bind_url, data=pdata, headers=header, verify=False, timeout=self.timeout)
|
|||
|
|
if res.status_code != 200:
|
|||
|
|
return "Binding request failed, returned status code:%s,响应信息为:%s" % (res.status_code, res.text)
|
|||
|
|
res_str = res.text.strip()
|
|||
|
|
if res_str == "0":
|
|||
|
|
return "Binding failed, please check if your key is correct"
|
|||
|
|
elif res_str == "1":
|
|||
|
|
return None
|
|||
|
|
else:
|
|||
|
|
return "Binding failed, error message is:{}".format(json.loads(res_str))
|
|||
|
|
except Exception as e:
|
|||
|
|
return "Network connection failed, unable to request to the target server"
|
|||
|
|
|
|||
|
|
def app_bind_status(self) -> Optional[str]:
|
|||
|
|
if self.app_key is None:
|
|||
|
|
return "App key parsing failed, please confirm key information again"
|
|||
|
|
pdata = {"bind_token": self.app_key.app_token}
|
|||
|
|
header = {
|
|||
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|||
|
|
"User-Agent": "Yak-Panel/Node Manager"
|
|||
|
|
}
|
|||
|
|
bind_url = self.app_key.origin + "/get_app_bind_status"
|
|||
|
|
try:
|
|||
|
|
resp = requests.post(bind_url, data=pdata, headers=header, verify=False, timeout=self.timeout)
|
|||
|
|
if not resp.status_code == 200:
|
|||
|
|
return "Error in obtaining binding status response status code. Please check if the node address and API are correct. The current status code is {},The return information is {}".format(
|
|||
|
|
resp.status_code, resp.text)
|
|||
|
|
res_str = resp.text.strip()
|
|||
|
|
if res_str == "0":
|
|||
|
|
return "Your device is not bound, please try to rebind it"
|
|||
|
|
elif res_str == "1":
|
|||
|
|
return None
|
|||
|
|
else:
|
|||
|
|
return "Request binding status failed with error message:{}".format(json.loads(res_str))
|
|||
|
|
except Exception as e:
|
|||
|
|
return "Failed to obtain binding status. Please check if the node app key is correct. The error message is:{}".format(str(e))
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def new_by_id(cls, node_id: int) -> Optional[Union['ServerNode', 'LocalNode', 'LPanelNode']]:
|
|||
|
|
data = public.S('node', cls.node_db_file).where("id = ?", (node_id,)).find()
|
|||
|
|
if not data or not isinstance(data, dict):
|
|||
|
|
return None
|
|||
|
|
return cls.new_by_data(data)
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def new_by_data(cls, node_data: dict):
|
|||
|
|
if node_data["api_key"] == "local" and node_data["app_key"] == "local":
|
|||
|
|
return LocalNode()
|
|||
|
|
|
|||
|
|
if node_data['lpver']:
|
|||
|
|
lp = LPanelNode(
|
|||
|
|
address=node_data['address'],
|
|||
|
|
api_key=node_data['api_key'],
|
|||
|
|
lpver=node_data['lpver'],
|
|||
|
|
timeout=node_data.get('timeout', 20)
|
|||
|
|
)
|
|||
|
|
lp.remarks = node_data['remarks']
|
|||
|
|
return lp
|
|||
|
|
if not node_data["api_key"] and not node_data["app_key"]:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
return cls(
|
|||
|
|
origin=node_data['address'],
|
|||
|
|
api_key=node_data['api_key'],
|
|||
|
|
app_key=node_data['app_key'],
|
|||
|
|
remarks=node_data['remarks'],
|
|||
|
|
timeout=node_data.get('timeout', 20)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def show_name(self) -> str:
|
|||
|
|
if self.remarks:
|
|||
|
|
return "{}({})".format(self.remarks, self.origin)
|
|||
|
|
return self.origin
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
def _get_node_ip(node_id: int) -> str:
|
|||
|
|
data = public.S('node', public.get_panel_path() + "/data/db/node.db").where("id = ?", (node_id,)).find()
|
|||
|
|
if not data or not isinstance(data, dict):
|
|||
|
|
return ""
|
|||
|
|
if data['address'].startswith("http"):
|
|||
|
|
from urllib.parse import urlparse
|
|||
|
|
host = urlparse(data['address']).hostname
|
|||
|
|
else:
|
|||
|
|
host = data['address']
|
|||
|
|
try:
|
|||
|
|
if isinstance(host, str) and ipaddress.ip_address(host):
|
|||
|
|
return host
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
try:
|
|||
|
|
ip_address = socket.gethostbyname(host)
|
|||
|
|
return ip_address
|
|||
|
|
except socket.gaierror as e:
|
|||
|
|
print(f"Error: {e}")
|
|||
|
|
return ""
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def get_node_ip(cls, node_id: int) -> Optional[str]:
|
|||
|
|
if public.S('node', cls.node_db_file).where("id=? AND app_key = 'local' AND api_key = 'local'", (node_id,)).count() > 0:
|
|||
|
|
return "127.0.0.1"
|
|||
|
|
cache_key = "mod_node_server_ip_{}".format(node_id)
|
|||
|
|
c_ip = cache.get(cache_key)
|
|||
|
|
if c_ip:
|
|||
|
|
return c_ip
|
|||
|
|
else:
|
|||
|
|
ip = cls._get_node_ip(node_id)
|
|||
|
|
if ip:
|
|||
|
|
cache.set(cache_key, ip, 86400)
|
|||
|
|
return ip
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def check_api_key(cls, node: Node) -> str:
|
|||
|
|
if not LPanelNode.check_api_key(node): # 如果1panel检查可以连接上,则标记为1panel并直接返回
|
|||
|
|
return ""
|
|||
|
|
|
|||
|
|
node = cls(node.address, node.api_key, node.app_key)
|
|||
|
|
data, err = node._request("/system", "GetSystemTotal")
|
|||
|
|
if err:
|
|||
|
|
return err
|
|||
|
|
if isinstance(data, dict) and "version" in data:
|
|||
|
|
return ""
|
|||
|
|
if isinstance(data, dict) and not data:return "Validation error:Please check if the node information is correct"
|
|||
|
|
return "Validation error:%s" % str(data)
|
|||
|
|
|
|||
|
|
def test_conn(self) -> str:
|
|||
|
|
data, err = self._request("/system", "GetSystemTotal")
|
|||
|
|
if err:
|
|||
|
|
return err
|
|||
|
|
if isinstance(data, dict) and "version" in data:
|
|||
|
|
return ""
|
|||
|
|
return "Validation error:%s" % str(data)
|
|||
|
|
|
|||
|
|
def version(self) -> Optional[str]:
|
|||
|
|
data, err = self._request("/system", "GetSystemTotal")
|
|||
|
|
if err:
|
|||
|
|
return None
|
|||
|
|
if isinstance(data, dict) and "version" in data:
|
|||
|
|
return data["version"]
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def get_net_work(self) -> Tuple[Optional[dict], str]:
|
|||
|
|
data, err = self._request("/system", "GetNetWork")
|
|||
|
|
if err:
|
|||
|
|
return None, err
|
|||
|
|
if isinstance(data, dict) and "cpu" in data and "mem" in data:
|
|||
|
|
return data, ""
|
|||
|
|
return None, "data in wrong format: %s" % str(data)
|
|||
|
|
|
|||
|
|
def get_tmp_token(self) -> Tuple[str, str]:
|
|||
|
|
data, err = self._request("/config", "get_tmp_token")
|
|||
|
|
if err:
|
|||
|
|
return "", err
|
|||
|
|
if isinstance(data, dict) and "status" in data:
|
|||
|
|
if data["status"] and "msg" in data:
|
|||
|
|
return data["msg"], ""
|
|||
|
|
return "", data.get("msg", str(data))
|
|||
|
|
if isinstance(data, dict) and "result" in data:
|
|||
|
|
return data["result"], ""
|
|||
|
|
return "", "data in wrong format: %s" % str(data)
|
|||
|
|
|
|||
|
|
def get_bt_params(self, other_data: dict = None) -> Dict:
|
|||
|
|
now = int(time.time() * 1000)
|
|||
|
|
other_data = other_data or {}
|
|||
|
|
if self.app_key:
|
|||
|
|
md5_panel_key = hashlib.md5(self.app_key.request_token.encode()).hexdigest()
|
|||
|
|
request_token = hashlib.md5("{}{}".format(now, md5_panel_key).encode()).hexdigest()
|
|||
|
|
other_data["request_token"] = request_token
|
|||
|
|
other_data["request_time"] = str(now)
|
|||
|
|
form_data = json.dumps(other_data)
|
|||
|
|
return {
|
|||
|
|
"client_bind_token": self.app_key.app_token,
|
|||
|
|
"form_data": public.aes_encrypt(form_data, self.app_key.app_key)
|
|||
|
|
}
|
|||
|
|
else:
|
|||
|
|
md5_panel_key = hashlib.md5(self.api_key.encode()).hexdigest()
|
|||
|
|
request_token = hashlib.md5("{}{}".format(now, md5_panel_key).encode()).hexdigest()
|
|||
|
|
other_data["request_token"] = request_token
|
|||
|
|
other_data["request_time"] = str(now)
|
|||
|
|
return other_data
|
|||
|
|
|
|||
|
|
def _request(self, path: str, action: str, pdata: dict = None) -> Tuple[Optional[any], str]:
|
|||
|
|
url = "{}/v2{}".format(self.origin, path)
|
|||
|
|
pdata = pdata or {}
|
|||
|
|
bt_p = self.get_bt_params({"action": action, **pdata})
|
|||
|
|
header = {
|
|||
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|||
|
|
"User-Agent": "Yak-Panel/Node Manager"
|
|||
|
|
}
|
|||
|
|
source_action=["DeleteDir","DeleteFile","CreateDir", "get_path_size"]
|
|||
|
|
try:
|
|||
|
|
resp = requests.post(url, data=bt_p, headers=header, verify=False, timeout=self.timeout)
|
|||
|
|
if not resp.status_code == 200:
|
|||
|
|
return None, "The response status code is incorrect. Please check if the node address and API are correct. The current response status code is {}".format(
|
|||
|
|
resp.status_code)
|
|||
|
|
if self.app_key:
|
|||
|
|
real_data = public.aes_decrypt(resp.text, self.app_key.app_key)
|
|||
|
|
if action in source_action:return json.loads(real_data), ""
|
|||
|
|
return json.loads(real_data).get('message',{}), ""
|
|||
|
|
if action in source_action:return resp.json(), ""
|
|||
|
|
return resp.json().get('message',{}), ""
|
|||
|
|
except Exception as e:
|
|||
|
|
# public.print_error()
|
|||
|
|
# return None, "请求节点失败,请检查节点地址和api是否正确,错误信息为:{}".format(str(e))
|
|||
|
|
if self.remarks:
|
|||
|
|
return None, "Request node [{}] failed, please check if the node address and API are correct".format(self.remarks)
|
|||
|
|
return None, "Node request failed, please check if the node address and API are correct"
|
|||
|
|
|
|||
|
|
def get_file_body(self, path: str) -> Tuple[Optional[str], str]:
|
|||
|
|
data, err = self._request("/files", "GetFileBody", pdata={"path": path})
|
|||
|
|
if err:
|
|||
|
|
return None, err
|
|||
|
|
if not isinstance(data, dict):
|
|||
|
|
if isinstance(data, dict) and "msg" in data:
|
|||
|
|
return None, data["msg"]
|
|||
|
|
return None, "data in wrong format"
|
|||
|
|
return data["data"], ""
|
|||
|
|
|
|||
|
|
def php_site_list(self) -> Tuple[Optional[List[dict]], str]:
|
|||
|
|
data, err = self._request("/mod/node/node/php_site_list", "", pdata={
|
|||
|
|
"type": -1,
|
|||
|
|
"p": 1,
|
|||
|
|
"limit": 1000,
|
|||
|
|
"table": "sites",
|
|||
|
|
"search": "",
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return None, err
|
|||
|
|
|
|||
|
|
if not isinstance(data, list):
|
|||
|
|
if isinstance(data, dict) and "msg" in data:
|
|||
|
|
return None, data["msg"]
|
|||
|
|
return None, "data in wrong format"
|
|||
|
|
|
|||
|
|
return data, ""
|
|||
|
|
|
|||
|
|
def create_php_site(self, site_name: str, port: int, **kwargs) -> Tuple[Optional[int], str]:
|
|||
|
|
path = "/www/wwwroot/{}".format(site_name)
|
|||
|
|
if port not in (80, 443):
|
|||
|
|
path = "/www/wwwroot/{}_{}".format(site_name, port)
|
|||
|
|
webname = {
|
|||
|
|
"domain": site_name if port in (80, 443) else "{}:{}".format(site_name, port),
|
|||
|
|
"domainlist": [],
|
|||
|
|
"count": 0
|
|||
|
|
}
|
|||
|
|
data, err = self._request("/site", "AddSite", pdata={
|
|||
|
|
"path": path,
|
|||
|
|
"ftp": "false",
|
|||
|
|
"type": "PHP",
|
|||
|
|
"type_id": "0",
|
|||
|
|
"ps": "{}【Load balancing node】".format(site_name),
|
|||
|
|
"port": str(port),
|
|||
|
|
"version": "00",
|
|||
|
|
"sql": "false",
|
|||
|
|
"webname": json.dumps(webname)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if err:
|
|||
|
|
return None, err
|
|||
|
|
if not isinstance(data, dict):
|
|||
|
|
return None, "data in wrong format"
|
|||
|
|
|
|||
|
|
if isinstance(data, dict) and "msg" in data and not data.get("status", True):
|
|||
|
|
return None, data["msg"]
|
|||
|
|
|
|||
|
|
site_id = data.get("siteId", None)
|
|||
|
|
if not isinstance(site_id, int):
|
|||
|
|
return None, "Data parsing error"
|
|||
|
|
return site_id, ""
|
|||
|
|
|
|||
|
|
def set_firewall_open(self, port: int, protocol: str = "tcp") -> Tuple[bool, str]:
|
|||
|
|
data, err = self._request("/firewall/com/set_port_rule", "", pdata={
|
|||
|
|
"protocol": "udp" if protocol == "udp" else "tcp",
|
|||
|
|
"port": str(port),
|
|||
|
|
"choose": "all",
|
|||
|
|
"types": "accept",
|
|||
|
|
"chain": "INPUT",
|
|||
|
|
"operation": "add",
|
|||
|
|
"strategy": "accept",
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return False, err
|
|||
|
|
|
|||
|
|
if isinstance(data, dict) and "msg" in data:
|
|||
|
|
msg = data["msg"]
|
|||
|
|
if "Port {} already exists".format(port) in msg:
|
|||
|
|
return True, ""
|
|||
|
|
elif "Setup successful" in msg:
|
|||
|
|
return True, ""
|
|||
|
|
return False, "Setup failed"
|
|||
|
|
|
|||
|
|
def add_domain(self, site_id: int, site_name: str, domain: str, port: int) -> Tuple[bool, str]:
|
|||
|
|
data, err = self._request("/site", "AddDomain", pdata={
|
|||
|
|
"domain": "{}:{}".format(domain, port),
|
|||
|
|
"webname": site_name,
|
|||
|
|
"id": str(site_id)
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return False, err
|
|||
|
|
|
|||
|
|
if isinstance(data, dict) and "domains" in data:
|
|||
|
|
for d in data["domains"]:
|
|||
|
|
if d["name"] == domain:
|
|||
|
|
if "Already bound" in d["msg"]:
|
|||
|
|
return True, ""
|
|||
|
|
elif "Added successfully" in d["msg"]:
|
|||
|
|
return True, ""
|
|||
|
|
return False, "Add failed, the target machine returned information as:{}".format(
|
|||
|
|
data.get("msg", str(data)) if isinstance(data, dict) else str(data))
|
|||
|
|
|
|||
|
|
def has_domain(self, site_id: int, domain: str) -> bool:
|
|||
|
|
data, err = self._request("/data", "getData", pdata={
|
|||
|
|
"table": "domain",
|
|||
|
|
"list": "True",
|
|||
|
|
"search": str(site_id)
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return False
|
|||
|
|
if not isinstance(data, list):
|
|||
|
|
return False
|
|||
|
|
for d in data:
|
|||
|
|
if d["name"] == domain:
|
|||
|
|
return True
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# mode -> cover: 覆盖,ignore: 跳过,rename:重命名
|
|||
|
|
def upload_file(self, filename: str, target_path: str, mode: str = "cover",
|
|||
|
|
call_log: Callable[[int, str], None] = None) -> str:
|
|||
|
|
if not os.path.isfile(filename):
|
|||
|
|
return "File: {} does not exist".format(filename)
|
|||
|
|
|
|||
|
|
target_file = os.path.join(target_path, os.path.basename(filename))
|
|||
|
|
exits, err = self.target_file_exits(target_file)
|
|||
|
|
if err:
|
|||
|
|
return err
|
|||
|
|
if exits and mode == "ignore":
|
|||
|
|
call_log(0, "File upload:{} -> {},The target file already exists, skip uploading".format(filename, target_file))
|
|||
|
|
return ""
|
|||
|
|
if exits and mode == "rename":
|
|||
|
|
upload_name = "{}_{}".format(os.path.basename(filename), public.md5(filename))
|
|||
|
|
call_log(0, "File upload:{} -> {},The target file already exists, it will be renamed to {}".format(filename, target_file, upload_name))
|
|||
|
|
else:
|
|||
|
|
upload_name = os.path.basename(filename)
|
|||
|
|
|
|||
|
|
if os.path.getsize(filename) > 1024 * 1024 * 5:
|
|||
|
|
return self._upload_big_file(filename, target_path, upload_name, call_log)
|
|||
|
|
else:
|
|||
|
|
return self._upload_little_file(filename, target_path, upload_name, call_log)
|
|||
|
|
|
|||
|
|
def target_file_exits(self, target_file: str) -> Tuple[bool, str]:
|
|||
|
|
data, err = self._request("/files", "upload_files_exists", pdata={
|
|||
|
|
"files": target_file,
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return False, err
|
|||
|
|
if not isinstance(data, list):
|
|||
|
|
return False, "data in wrong format: %s" % str(data)
|
|||
|
|
for f in data:
|
|||
|
|
if f["filename"] == target_file and f["exists"]:
|
|||
|
|
return True, ""
|
|||
|
|
return False, ""
|
|||
|
|
|
|||
|
|
def upload_check(self, target_file_list: List[str]) -> Tuple[List[dict], str]:
|
|||
|
|
data, err = self._request("/files", "upload_files_exists", pdata={
|
|||
|
|
"files": "\n".join(target_file_list),
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return [], err
|
|||
|
|
if not isinstance(data, list):
|
|||
|
|
if isinstance(data, dict):
|
|||
|
|
return [], data.get("msg", "data in wrong format")
|
|||
|
|
return [], "data in wrong format: %s" % str(data)
|
|||
|
|
return data, ""
|
|||
|
|
|
|||
|
|
def _upload_big_file(self, filename: str, target_path: str, upload_name: str,
|
|||
|
|
call_log: Callable[[int, str], None] = None) -> str:
|
|||
|
|
url = "{}/files".format(self.origin)
|
|||
|
|
bt_p = self.get_bt_params({"action": "upload"})
|
|||
|
|
header = {"User-Agent": "Yak-Panel/Node Manager"}
|
|||
|
|
try:
|
|||
|
|
fb = open(filename, 'rb')
|
|||
|
|
except Exception as e:
|
|||
|
|
public.print_error()
|
|||
|
|
return "File {} failed to open, please check file permissions, error message is:{}".format(filename, str(e))
|
|||
|
|
|
|||
|
|
file_size = os.path.getsize(filename)
|
|||
|
|
for i in range(0, file_size, 1024 * 1024 * 5):
|
|||
|
|
file_data = fb.read(1024 * 1024 * 5)
|
|||
|
|
files = {'blob': ('blob', file_data, 'application/octet-stream')}
|
|||
|
|
data = {
|
|||
|
|
'f_path': target_path,
|
|||
|
|
'f_name': upload_name,
|
|||
|
|
'f_size': file_size,
|
|||
|
|
'f_start': i,
|
|||
|
|
}
|
|||
|
|
data.update(bt_p)
|
|||
|
|
try:
|
|||
|
|
resp = requests.post(url, data=data, files=files, headers=header, verify=False, timeout=self.timeout)
|
|||
|
|
if not resp.status_code == 200:
|
|||
|
|
return "The response status code for uploading the file is incorrect. Please check if the node address and API are correct. The current status code is {}, and the return message is:{}".format(
|
|||
|
|
resp.status_code, resp.text)
|
|||
|
|
resp_text = resp.text
|
|||
|
|
resp_json = {}
|
|||
|
|
if self.app_key:
|
|||
|
|
resp_text = public.aes_decrypt(resp_text, self.app_key.app_key).strip()
|
|||
|
|
if not resp_text.isdecimal():
|
|||
|
|
resp_json = json.loads(resp_text)
|
|||
|
|
else:
|
|||
|
|
resp_json = resp.json()
|
|||
|
|
|
|||
|
|
if resp_text.isdecimal():
|
|||
|
|
up_p = int(resp_text)
|
|||
|
|
if up_p != i + len(file_data):
|
|||
|
|
return "Upload file failed, file block size is inconsistent"
|
|||
|
|
else:
|
|||
|
|
call_log(up_p * 100 // file_size, "File upload:{} -> {},The uploaded size is:{}".format(
|
|||
|
|
filename, upload_name, public.to_size(up_p)))
|
|||
|
|
elif "status" in resp_json:
|
|||
|
|
if not resp_json["status"]:
|
|||
|
|
return "Upload file failed with error message:{}".format(resp_json["msg"])
|
|||
|
|
else:
|
|||
|
|
call_log(100, "File upload:{} -> {},Upload successful".format(filename, upload_name))
|
|||
|
|
return ""
|
|||
|
|
else:
|
|||
|
|
return "Upload file failed, response message is:{}".format(resp_text)
|
|||
|
|
except Exception as e:
|
|||
|
|
public.print_error()
|
|||
|
|
return "Upload file: {} failed with error message:{}".format(filename, str(e))
|
|||
|
|
return ""
|
|||
|
|
|
|||
|
|
def _upload_little_file(self, filename: str, target_path: str, upload_name: str,
|
|||
|
|
call_log: Callable[[int, str], None] = None) -> str:
|
|||
|
|
url = "{}/files".format(self.origin)
|
|||
|
|
bt_p = self.get_bt_params({"action": "upload"})
|
|||
|
|
header = {"User-Agent": "Yak-Panel/Node Manager"}
|
|||
|
|
try:
|
|||
|
|
with open(filename, 'rb') as f:
|
|||
|
|
file_data = f.read()
|
|||
|
|
except Exception as e:
|
|||
|
|
public.print_error()
|
|||
|
|
return "File {} failed to open, please check file permissions, error message is:{}".format(filename, str(e))
|
|||
|
|
|
|||
|
|
file_size = os.path.getsize(filename)
|
|||
|
|
files = {'blob': ('blob', file_data, 'application/octet-stream')}
|
|||
|
|
data = {
|
|||
|
|
'f_path': target_path,
|
|||
|
|
'f_name': upload_name,
|
|||
|
|
'f_size': file_size,
|
|||
|
|
'f_start': 0
|
|||
|
|
}
|
|||
|
|
data.update(bt_p)
|
|||
|
|
try:
|
|||
|
|
resp = requests.post(url, data=data, files=files, headers=header, verify=False, timeout=self.timeout)
|
|||
|
|
if not resp.status_code == 200:
|
|||
|
|
return "The response status code for uploading the file is incorrect. Please check if the node address and API are correct. The current status code is {}, and the return message is:{}".format(
|
|||
|
|
resp.status_code, resp.text)
|
|||
|
|
resp_json = {}
|
|||
|
|
if self.app_key:
|
|||
|
|
resp_text = public.aes_decrypt(resp.text, self.app_key.app_key).strip()
|
|||
|
|
if not resp_text.isdecimal():
|
|||
|
|
resp_json = json.loads(resp_text)
|
|||
|
|
else:
|
|||
|
|
resp_json = resp.json()
|
|||
|
|
if not resp_json["status"]:
|
|||
|
|
return "Upload file failed with error message:{}".format(resp_json["msg"])
|
|||
|
|
return ""
|
|||
|
|
except Exception as e:
|
|||
|
|
public.print_error()
|
|||
|
|
return "Upload file: {} failed with error message:{}".format(filename, str(e))
|
|||
|
|
|
|||
|
|
def upload_proxy(self):
|
|||
|
|
try:
|
|||
|
|
from YakPanel import request
|
|||
|
|
f_name = request.form.get('f_name')
|
|||
|
|
f_path = request.form.get('f_path')
|
|||
|
|
f_size = request.form.get('f_size')
|
|||
|
|
f_start = request.form.get('f_start')
|
|||
|
|
blob_file: FileStorage = request.files.getlist('blob')[0]
|
|||
|
|
url = "{}/files".format(self.origin)
|
|||
|
|
bt_p = self.get_bt_params({"action": "upload"})
|
|||
|
|
header = {"User-Agent": "Yak-Panel/Node Manager"}
|
|||
|
|
files = {'blob': ('blob', blob_file.stream, 'application/octet-stream')}
|
|||
|
|
data = {
|
|||
|
|
'f_path': f_path,
|
|||
|
|
'f_name': f_name,
|
|||
|
|
'f_size': f_size,
|
|||
|
|
'f_start': f_start
|
|||
|
|
}
|
|||
|
|
data.update(bt_p)
|
|||
|
|
resp = requests.post(url, data=data, files=files, headers=header, verify=False, timeout=self.timeout)
|
|||
|
|
if not resp.status_code == 200:
|
|||
|
|
return "The response status code for uploading the file is incorrect. Please check if the node address and API are correct. The current status code is {}, and the return message is:{}".format(
|
|||
|
|
resp.status_code, resp.text)
|
|||
|
|
resp_text = resp.text
|
|||
|
|
resp_json = {}
|
|||
|
|
if self.app_key:
|
|||
|
|
resp_text = public.aes_decrypt(resp_text, self.app_key.app_key).strip()
|
|||
|
|
if not resp_text.isdecimal():
|
|||
|
|
resp_json = json.loads(resp_text)
|
|||
|
|
else:
|
|||
|
|
resp_json = resp.json()
|
|||
|
|
if resp_text.isdecimal():
|
|||
|
|
return int(resp_text)
|
|||
|
|
elif "status" in resp_json:
|
|||
|
|
return resp_json
|
|||
|
|
else:
|
|||
|
|
return {"status": False, "msg": "Upload file failed, response message is:{}".format(resp_text)}
|
|||
|
|
except Exception as e:
|
|||
|
|
public.print_error()
|
|||
|
|
return {"status": False, "msg": "Upload file failed with error message:{}".format(str(e))}
|
|||
|
|
|
|||
|
|
def remove_file(self, filename: str, is_dir: bool) -> dict:
|
|||
|
|
action = "DeleteDir" if is_dir else "DeleteFile"
|
|||
|
|
data, err = self._request("/files", action, pdata={
|
|||
|
|
"path": filename,
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return {"status": False, "msg": "Failed to delete file, error message is:{}".format(err)}
|
|||
|
|
|
|||
|
|
if isinstance(data, dict):
|
|||
|
|
return data
|
|||
|
|
return {"status": False, "msg": "Failed to delete file, response message is:{}".format(data)}
|
|||
|
|
|
|||
|
|
def create_dir(self, path: str) -> Tuple[Optional[Dict], str]:
|
|||
|
|
data, err = self._request("/files", action="CreateDir", pdata={
|
|||
|
|
"path": path,
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return None, "Failed to create directory, error message is:{}".format(err)
|
|||
|
|
if isinstance(data, dict):
|
|||
|
|
return data, ""
|
|||
|
|
return None, "Failed to create directory, response message is:{}".format(data)
|
|||
|
|
|
|||
|
|
def dir_walk(self, path: str) -> Tuple[List[dict], str]:
|
|||
|
|
data, err = self._request("/mod/node/file_transfer/dir_walk", "", pdata={
|
|||
|
|
"path": path,
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return [], err
|
|||
|
|
|
|||
|
|
if not isinstance(data, list):
|
|||
|
|
if isinstance(data, dict) and "msg" in data:
|
|||
|
|
return [], data["msg"]
|
|||
|
|
return [], "data in wrong format: %s" + str(data)
|
|||
|
|
|
|||
|
|
return data, ""
|
|||
|
|
|
|||
|
|
def filetransfer_version_check(self) -> str:
|
|||
|
|
ver = self.version()
|
|||
|
|
if not ver:
|
|||
|
|
return "Node {} connection error".format(self.show_name())
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
ver_list = [int(i) for i in ver.split(".")]
|
|||
|
|
# if self.app_key: # todo: 版本号检测
|
|||
|
|
# if ver_list[0] > 11 or (ver_list[0] == 11 and ver_list[1] >= 1):
|
|||
|
|
# return "Node {} version is lower than [11.1.0], unable to use app link, please upgrade node version".format(self.show_name())
|
|||
|
|
|
|||
|
|
if ver_list[0] >=8:
|
|||
|
|
return ""
|
|||
|
|
elif ver_list[0] == 7 and ver_list[1] >= 65:
|
|||
|
|
return ""
|
|||
|
|
elif ver_list[0] == 7 and ver_list[1] == 0 and ver_list[2] >= 30:
|
|||
|
|
return ""
|
|||
|
|
elif ver_list[0] ==2 and ver_list[1] >= 15:
|
|||
|
|
return ""
|
|||
|
|
return "Please upgrade the panel version to 【8.0.0/7.65.0/7.0.30/2.15.0】 or above before using it"
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
return "Please upgrade the panel version to 【8.0.0/7.65.0/7.0.30/2.15.0】 or above before using it"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def node_create_filetransfer_task(self, source_node: dict,
|
|||
|
|
target_node: dict,
|
|||
|
|
source_path_list: List[dict],
|
|||
|
|
target_path: str,
|
|||
|
|
created_by: str,
|
|||
|
|
default_mode: str = "cover") -> dict:
|
|||
|
|
data, err = self._request("/mod/node/file_transfer/node_create_filetransfer_task", "", pdata={
|
|||
|
|
"source_node": json.dumps(source_node),
|
|||
|
|
"target_node": json.dumps(target_node),
|
|||
|
|
"source_path_list": json.dumps(source_path_list),
|
|||
|
|
"target_path": target_path,
|
|||
|
|
"created_by": created_by,
|
|||
|
|
"default_mode": default_mode,
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return {"status": False, "msg": "Failed to create file transfer task, error message is:{}".format(err)}
|
|||
|
|
if isinstance(data, dict):
|
|||
|
|
return data
|
|||
|
|
else:
|
|||
|
|
return {"status": False, "msg": "Failed to create file transfer task, response message is:{}".format(data)}
|
|||
|
|
|
|||
|
|
def file_list(self, path: str, p: int, row: int, search: str) -> Tuple[Dict, str]:
|
|||
|
|
data, err = self._request("/files", "GetDirNew", pdata={
|
|||
|
|
"path": path,
|
|||
|
|
"p": p,
|
|||
|
|
"showRow": row,
|
|||
|
|
"search": search,
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return {}, err
|
|||
|
|
if not isinstance(data, dict):
|
|||
|
|
return {}, "data in wrong format: %s" + str(data)
|
|||
|
|
return data, ""
|
|||
|
|
|
|||
|
|
def get_transfer_status(self, task_id: int) -> dict:
|
|||
|
|
data, err = self._request("/mod/node/file_transfer/get_transfer_status", "", pdata={
|
|||
|
|
"task_id": task_id,
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return {"status": False, "msg": "Failed to retrieve the status of the file transfer task, error message is:{}".format(err)}
|
|||
|
|
if isinstance(data, dict):
|
|||
|
|
return data
|
|||
|
|
return {"status": False, "msg": "Failed to obtain file transfer task status, response message is:{}".format(data)}
|
|||
|
|
|
|||
|
|
def proxy_transfer_status(self, task_id: int, ws: simple_websocket.Server):
|
|||
|
|
err = self._proxy_websocket(
|
|||
|
|
call_data={
|
|||
|
|
"mod_name": "node",
|
|||
|
|
"sub_mod_name": "file_transfer",
|
|||
|
|
"def_name": "node_proxy_transfer_status",
|
|||
|
|
"callback": "node_proxy_transfer_status",
|
|||
|
|
"data": {
|
|||
|
|
"task_id": task_id,
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
uri="ws_modsoc",
|
|||
|
|
call_back=ws.send
|
|||
|
|
)
|
|||
|
|
if err:
|
|||
|
|
ws.send(json.dumps({
|
|||
|
|
"type": "error",
|
|||
|
|
"msg": "Failed to retrieve the status of the file transfer task, error message is:{}".format(err)
|
|||
|
|
}))
|
|||
|
|
|
|||
|
|
def _proxy_websocket(self, call_data: dict, uri: str, call_back: Callable[[Any],None]) -> str:
|
|||
|
|
from urllib.parse import urlencode, urlparse
|
|||
|
|
from ssl import CERT_NONE
|
|||
|
|
try:
|
|||
|
|
bt_p = self.get_bt_params()
|
|||
|
|
header = {"User-Agent": "Yak-Panel/Node Manager"}
|
|||
|
|
u = urlparse(self.origin)
|
|||
|
|
url = (
|
|||
|
|
"{}://{}/{}?{}".format(
|
|||
|
|
"ws" if u.scheme == "http" else "wss",
|
|||
|
|
u.netloc, uri, urlencode(bt_p)
|
|||
|
|
)
|
|||
|
|
)
|
|||
|
|
ws_req = websocket.WebSocket(sslopt={"cert_reqs": CERT_NONE}) # 忽略证书
|
|||
|
|
ws_req.connect(url, header=header)
|
|||
|
|
ws_req.send("{}") # 跳过x-http-tokn 验证
|
|||
|
|
ws_req.send(json.dumps(call_data)) # 发送调用数据
|
|||
|
|
|
|||
|
|
while True:
|
|||
|
|
data = ws_req.recv()
|
|||
|
|
if data == "{}":
|
|||
|
|
break
|
|||
|
|
if data:
|
|||
|
|
call_back(data)
|
|||
|
|
else:
|
|||
|
|
break
|
|||
|
|
ws_req.close()
|
|||
|
|
return ""
|
|||
|
|
except Exception as e:
|
|||
|
|
return str(e)
|
|||
|
|
|
|||
|
|
def proxy_transferfile_status(self, task_id: int, exclude_nodes: List[int], the_log_id: int, call_back: Callable[[Any],None]) -> str:
|
|||
|
|
return self._proxy_websocket(
|
|||
|
|
call_data={
|
|||
|
|
"mod_name": "node",
|
|||
|
|
"sub_mod_name": "executor",
|
|||
|
|
"def_name": "node_proxy_transferfile_status",
|
|||
|
|
"callback": "node_proxy_transferfile_status",
|
|||
|
|
"data": {
|
|||
|
|
"task_id": task_id,
|
|||
|
|
"exclude_nodes": exclude_nodes,
|
|||
|
|
"the_log_id": the_log_id
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
uri="ws_modsoc",
|
|||
|
|
call_back=call_back
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
def node_create_transfer_task(self, transfer_task_data:dict) -> dict:
|
|||
|
|
data, err = self._request(
|
|||
|
|
"/mod/node/executor/node_create_transfer_task", "", pdata={
|
|||
|
|
"transfer_task_data": json.dumps(transfer_task_data),
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
if err:
|
|||
|
|
return {"status": False, "msg": "Failed to create file transfer task, error message is:{}".format(err)}
|
|||
|
|
if isinstance(data, dict):
|
|||
|
|
return data
|
|||
|
|
else:
|
|||
|
|
return {"status": False, "msg": "Failed to create file transfer task, response message is:{}".format(data)}
|
|||
|
|
|
|||
|
|
def node_transferfile_status_history(self, transfer_task_id: int, only_error=True):
|
|||
|
|
data, err = self._request(
|
|||
|
|
"/mod/node/executor/node_transferfile_status_history", "", pdata={
|
|||
|
|
"task_id": transfer_task_id,
|
|||
|
|
"only_error": 1 if only_error else 0
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
if err:
|
|||
|
|
return {"status": False, "msg": "Failed to create file transfer task, error message is:{}".format(err)}
|
|||
|
|
if isinstance(data, dict):
|
|||
|
|
return data
|
|||
|
|
else:
|
|||
|
|
return {"status": False, "msg": "Failed to create file transfer task, response message is:{}".format(data)}
|
|||
|
|
|
|||
|
|
def download_proxy(self, filename: str):
|
|||
|
|
url = "{}/download".format(self.origin)
|
|||
|
|
bt_p = self.get_bt_params()
|
|||
|
|
header = {"User-Agent": "Yak-Panel/Node Manager"}
|
|||
|
|
try:
|
|||
|
|
resp = requests.get(url, params={"filename": filename}, data=bt_p, headers=header, stream=True, verify=False, timeout=self.timeout)
|
|||
|
|
if not resp.status_code == 200:
|
|||
|
|
return "The response status code for downloading the file is incorrect. Please check if the node address and API are correct. The current status code is {}, and the return message is:{}".format(
|
|||
|
|
resp.status_code, resp.text)
|
|||
|
|
|
|||
|
|
from flask import send_file, stream_with_context, Response
|
|||
|
|
filename = os.path.basename(filename)
|
|||
|
|
if resp.headers.get("Content-Disposition", "").find("filename=") != -1:
|
|||
|
|
filename = resp.headers.get("Content-Disposition", "").split("filename=")[1]
|
|||
|
|
|
|||
|
|
def generate():
|
|||
|
|
for chunk in resp.iter_content(chunk_size=1024 * 1024 * 5):
|
|||
|
|
if chunk:
|
|||
|
|
yield chunk
|
|||
|
|
|
|||
|
|
# 设置响应头
|
|||
|
|
headers = {
|
|||
|
|
'Content-Type': resp.headers.get('Content-Type', 'application/octet-stream'),
|
|||
|
|
'Content-Disposition': 'attachment; filename="{}"'.format(filename),
|
|||
|
|
'Content-Length': resp.headers.get('Content-Length', ''),
|
|||
|
|
'Accept-Ranges': 'bytes'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 使用 stream_with_context 确保请求上下文在生成器运行时保持活跃
|
|||
|
|
return Response(
|
|||
|
|
stream_with_context(generate()),
|
|||
|
|
headers=headers,
|
|||
|
|
direct_passthrough=True
|
|||
|
|
)
|
|||
|
|
except Exception as e:
|
|||
|
|
public.print_error()
|
|||
|
|
return "Download file: {} failed with error message:{}".format(filename, str(e))
|
|||
|
|
|
|||
|
|
def download_file(self, filename: str, target_path: str, mode: str,
|
|||
|
|
call_log: Callable[[int, str], None] = None) -> str:
|
|||
|
|
target_file = os.path.join(target_path, os.path.basename(filename))
|
|||
|
|
exits = os.path.exists(target_file)
|
|||
|
|
if exits and mode == "ignore":
|
|||
|
|
call_log(0, "File Download:{} -> {},The target file already exists, skip download".format(filename, target_file))
|
|||
|
|
return ""
|
|||
|
|
if exits and mode == "rename":
|
|||
|
|
download_name = "{}_{}".format(os.path.basename(filename), public.md5(filename))
|
|||
|
|
call_log(0, "File Download:{} -> {},The target file already exists, it will be renamed to {}".format(filename, target_file, download_name))
|
|||
|
|
else:
|
|||
|
|
download_name = os.path.basename(filename)
|
|||
|
|
|
|||
|
|
return self._download_file(filename, target_path, download_name, call_log)
|
|||
|
|
|
|||
|
|
def _download_file(self, filename: str, target_path: str, download_name: str,
|
|||
|
|
call_log: Callable[[int, str], None] = None) -> str:
|
|||
|
|
data, err = self.upload_check([filename])
|
|||
|
|
if err:
|
|||
|
|
return "Request file: {} status failed with error message:{}".format(filename, err)
|
|||
|
|
file_size: Optional[int] = None
|
|||
|
|
for i in data:
|
|||
|
|
if i["filename"] == filename and i["isfile"] == True:
|
|||
|
|
file_size = i["size"]
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
if file_size is None:
|
|||
|
|
return "File {} does not exist, skip download".format(filename)
|
|||
|
|
try:
|
|||
|
|
if not os.path.isdir(target_path):
|
|||
|
|
os.makedirs(target_path)
|
|||
|
|
except Exception as e:
|
|||
|
|
return "Failed to create folder {}, please check folder permissions, error message is:{}".format(target_path, str(e))
|
|||
|
|
|
|||
|
|
if file_size == 0:
|
|||
|
|
fp = open(os.path.join(target_path, download_name), "w")
|
|||
|
|
fp.close()
|
|||
|
|
return ""
|
|||
|
|
|
|||
|
|
tmp_file = os.path.join(target_path, "{}.{}".format(download_name, uuid4().hex))
|
|||
|
|
try:
|
|||
|
|
if not os.path.exists(target_path):
|
|||
|
|
os.makedirs(target_path)
|
|||
|
|
fb = open(tmp_file, 'wb')
|
|||
|
|
except Exception as e:
|
|||
|
|
return "Failed to create temporary file {}, please check folder permissions, error message is:{}".format(tmp_file, str(e))
|
|||
|
|
|
|||
|
|
url = "{}/download".format(self.origin)
|
|||
|
|
bt_p = self.get_bt_params()
|
|||
|
|
header = {"User-Agent": "Yak-Panel/Node Manager"}
|
|||
|
|
try:
|
|||
|
|
resp = requests.get(url, params={"filename": filename}, data=bt_p, headers=header, stream=True, verify=False, timeout=self.timeout)
|
|||
|
|
if not resp.status_code == 200:
|
|||
|
|
return "The response status code for downloading the file is incorrect. Please check if the node address and API are correct. The current status code is {}, and the return message is:{}".format(
|
|||
|
|
resp.status_code, resp.text)
|
|||
|
|
|
|||
|
|
now_size = 0
|
|||
|
|
for chunk in resp.iter_content(chunk_size=1024 * 1024 * 3):
|
|||
|
|
if chunk:
|
|||
|
|
now_size += len(chunk)
|
|||
|
|
fb.write(chunk)
|
|||
|
|
fb.flush()
|
|||
|
|
call_log(now_size * 100 // file_size,
|
|||
|
|
"Download file {}, Downloaded: {}".format(filename, public.to_size(now_size)))
|
|||
|
|
if fb.tell() != file_size:
|
|||
|
|
return "Download file {} failed, file size check error".format(filename)
|
|||
|
|
fb.close()
|
|||
|
|
shutil.move(tmp_file, os.path.join(target_path, download_name))
|
|||
|
|
return ""
|
|||
|
|
except Exception as e:
|
|||
|
|
return "Download file {} failed,The error message is:{}".format(filename, str(e))
|
|||
|
|
finally:
|
|||
|
|
if not fb.closed:
|
|||
|
|
fb.close()
|
|||
|
|
if os.path.exists(tmp_file):
|
|||
|
|
os.remove(tmp_file)
|
|||
|
|
|
|||
|
|
def dir_size(self, path: str) -> Tuple[Optional[int], str]:
|
|||
|
|
data, err = self._request("/files", "get_path_size", pdata={
|
|||
|
|
"path": path,
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return None, err
|
|||
|
|
if isinstance(data, dict) and "size" in data:
|
|||
|
|
return data["size"], ""
|
|||
|
|
return None, "Failed to retrieve directory size, response message is:{}".format(data)
|
|||
|
|
|
|||
|
|
def get_sshd_port(self) -> Optional[int]:
|
|||
|
|
data, err = self._request("/safe/ssh/GetSshInfo", "", pdata={})
|
|||
|
|
if err:
|
|||
|
|
return None
|
|||
|
|
if isinstance(data, dict) and "port" in data:
|
|||
|
|
return int(data["port"])
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def restart_bt_panel(self) -> Dict[str, Any]:
|
|||
|
|
res, err = self._request("/system", "ReWeb", pdata={})
|
|||
|
|
if err:
|
|||
|
|
return {"status": False, "msg": err}
|
|||
|
|
return {"status": True, "msg": "Restart successful"}
|
|||
|
|
|
|||
|
|
def server_reboot(self):
|
|||
|
|
res, err = self._request("/system", "ServiceAdmin", pdata={
|
|||
|
|
"name": "nginx",
|
|||
|
|
"type": "stop",
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return {"status": False, "msg": err}
|
|||
|
|
if res.get('result','')!='Executed successfully!':
|
|||
|
|
return {"status": False, "msg": "Nginx failed to stop, unable to continue executing server restart:" + res["msg"]}
|
|||
|
|
|
|||
|
|
res, err = self._request("/system", "ServiceAdmin", pdata={
|
|||
|
|
"name": "mysqld",
|
|||
|
|
"type": "stop",
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return {"status": False, "msg": err}
|
|||
|
|
if res.get('result','')!='Executed successfully!':
|
|||
|
|
return {"status": False, "msg": "MySQL service failed to stop, unable to continue executing server restart:" + res["msg"]}
|
|||
|
|
|
|||
|
|
res, _ = self._request("/system", "RestartServer", pdata={})
|
|||
|
|
if res.get('result','')!='Command sent successfully!':
|
|||
|
|
return {"status": False, "msg": "Server restart failed:" + res["msg"]}
|
|||
|
|
|
|||
|
|
from mod.project.node.dbutil import ServerMonitorRepo
|
|||
|
|
repo = ServerMonitorRepo()
|
|||
|
|
repo.set_wait_reboot(self.node_server_ip, True)
|
|||
|
|
|
|||
|
|
def wait_for_reboot():
|
|||
|
|
# wait 等待服务器重启成功, 超时时间默认为 10 分钟
|
|||
|
|
wait_for = time.time() + 600
|
|||
|
|
time.sleep(3)
|
|||
|
|
while time.time() < wait_for:
|
|||
|
|
if self.test_conn() == "": # 无错误时表示重启成功
|
|||
|
|
repo.set_wait_reboot(self.node_server_ip, False)
|
|||
|
|
from mod.project.node.dbutil import ServerNodeDB
|
|||
|
|
if self.app_key !=None:self.app_key=self.app_key.to_string()
|
|||
|
|
node_data = ServerNodeDB().find_node(api_key=self.api_key, app_key=self.app_key)
|
|||
|
|
if node_data:
|
|||
|
|
monitor_node_once(node_data)
|
|||
|
|
return {"status": True, "msg": "Server restart successful"}
|
|||
|
|
time.sleep(3)
|
|||
|
|
# public.print_log("Waiting for server restart... {}".format(wait_for - time.time()))
|
|||
|
|
repo.set_wait_reboot(self.node_server_ip, False)
|
|||
|
|
return {"status": False, "msg": "Restarting server failed, server information has not been detected for more than 10 minutes"}
|
|||
|
|
|
|||
|
|
t = threading.Thread(target=wait_for_reboot, daemon=True)
|
|||
|
|
t.start()
|
|||
|
|
|
|||
|
|
return {"status": True, "msg": "The server restart has started, please wait patiently for the successful restart"}
|
|||
|
|
|
|||
|
|
def read_ssh_key(self) -> Tuple[Optional[str], str]:
|
|||
|
|
data, err = self._request("/ssh_security", "get_key", pdata={})
|
|||
|
|
if err:
|
|||
|
|
return None, err
|
|||
|
|
if not isinstance(data, dict) or "status" not in data:
|
|||
|
|
return None, "Failed to obtain SSH key, please check if the node address and API are correct"
|
|||
|
|
if not data["status"]:
|
|||
|
|
return None, data["msg"]
|
|||
|
|
return data["msg"], ""
|
|||
|
|
|
|||
|
|
def get_dir(self, path: str, search: str, disk: str):
|
|||
|
|
data, err = self._request("/files", "GetDir", pdata={
|
|||
|
|
"path": path, "disk": disk, "search": search
|
|||
|
|
})
|
|||
|
|
if err:
|
|||
|
|
return {"status": False, "msg": err}
|
|||
|
|
if not isinstance(data, dict):
|
|||
|
|
return {"status": False, "msg": "Failed to retrieve directory, please check if the node address and API are correct"}
|
|||
|
|
return data
|
|||
|
|
|
|||
|
|
def upload_dir_check(self, target_file: str) -> str:
|
|||
|
|
data, err = self._request("/files", "upload_files_exists", pdata={"files": target_file})
|
|||
|
|
if err:
|
|||
|
|
return err
|
|||
|
|
if not isinstance(data, list):
|
|||
|
|
return "data in wrong format: %s" % str(data)
|
|||
|
|
for f in data:
|
|||
|
|
if f["filename"] == target_file and f["exists"]:
|
|||
|
|
if f["isfile"]:
|
|||
|
|
return "The name path is not a directory"
|
|||
|
|
else:
|
|||
|
|
return ""
|
|||
|
|
return ""
|
|||
|
|
|
|||
|
|
|
|||
|
|
def monitor_all_node_status():
|
|||
|
|
all_nodes = public.S('node', public.get_panel_path() + "/data/db/node.db").select()
|
|||
|
|
if not isinstance(all_nodes, list) or not all_nodes:
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
import threading
|
|||
|
|
|
|||
|
|
threads = []
|
|||
|
|
for tmp_node_data in all_nodes:
|
|||
|
|
t = threading.Thread(target=monitor_node_once, args=(tmp_node_data,))
|
|||
|
|
t.start()
|
|||
|
|
threads.append(t)
|
|||
|
|
|
|||
|
|
# 等待所有线程完成(可选)
|
|||
|
|
for t in threads:
|
|||
|
|
t.join()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def monitor_node_once(node_data: dict):
|
|||
|
|
from mod.project.node.dbutil import Node, ServerNodeDB, ServerMonitorRepo
|
|||
|
|
from mod.project.node.nodeutil.ssh_wrap import SSHApi
|
|||
|
|
from mod.base.ssh_executor import test_ssh_config
|
|||
|
|
try:
|
|||
|
|
if node_data["app_key"] == "local" and node_data["api_key"] == "local":
|
|||
|
|
return
|
|||
|
|
node_data["error"] = json.loads(node_data["error"])
|
|||
|
|
node_data["ssh_conf"] = json.loads(node_data["ssh_conf"])
|
|||
|
|
node, err = Node.from_dict(node_data)
|
|||
|
|
if err:
|
|||
|
|
# public.print_log("Node data parsing error:{}".format(err))
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
if node_data["lpver"]:
|
|||
|
|
srv = LPanelNode(node.address, node.api_key, node.lpver)
|
|||
|
|
elif node.api_key or node.app_key:
|
|||
|
|
srv = ServerNode(node.address, node.api_key, node.app_key)
|
|||
|
|
else:
|
|||
|
|
srv =SSHApi(**node_data["ssh_conf"])
|
|||
|
|
|
|||
|
|
data, err = srv.get_net_work()
|
|||
|
|
if err:
|
|||
|
|
node.error_num += 1
|
|||
|
|
node.error = {
|
|||
|
|
"msg": err,
|
|||
|
|
"time": int(time.time())
|
|||
|
|
}
|
|||
|
|
if node.error_num >= 2:
|
|||
|
|
node.status = 0
|
|||
|
|
ServerMonitorRepo().remove_cache(node.id)
|
|||
|
|
|
|||
|
|
ServerNodeDB().update_node(node)
|
|||
|
|
|
|||
|
|
else:
|
|||
|
|
if node.error_num > 0:
|
|||
|
|
node.error_num = 0
|
|||
|
|
node.error = {}
|
|||
|
|
node.status = 1
|
|||
|
|
ServerNodeDB().update_node(node)
|
|||
|
|
|
|||
|
|
if data:
|
|||
|
|
repo = ServerMonitorRepo()
|
|||
|
|
repo.save_server_status(node.id, data)
|
|||
|
|
if repo.is_reboot_wait(node.server_ip):
|
|||
|
|
repo.set_wait_reboot(node.server_ip, False)
|
|||
|
|
if not node_data["ssh_test"] and not node_data["ssh_conf"] and not isinstance(srv, SSHApi):
|
|||
|
|
ssh_key, err = srv.read_ssh_key()
|
|||
|
|
if err:
|
|||
|
|
# public.print_log("Failed to obtain SSH key:{}".format(err))
|
|||
|
|
return
|
|||
|
|
port = srv.get_sshd_port()
|
|||
|
|
if not port:
|
|||
|
|
# public.print_log("Failed to obtain SSH service port")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
conf = {
|
|||
|
|
"host": srv.node_server_ip,
|
|||
|
|
"pkey": ssh_key,
|
|||
|
|
"port": port,
|
|||
|
|
"username": "root",
|
|||
|
|
"password": "",
|
|||
|
|
"pkey_passwd": "",
|
|||
|
|
}
|
|||
|
|
err = test_ssh_config(**conf)
|
|||
|
|
if err:
|
|||
|
|
# public.print_log("SSH key test failed:{}".format(err))
|
|||
|
|
ServerNodeDB().set_node_ssh_conf(node.id, {}, ssh_test=1)
|
|||
|
|
return
|
|||
|
|
else:
|
|||
|
|
ServerNodeDB().set_node_ssh_conf(node.id, conf, ssh_test=1)
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
if public.is_debug():
|
|||
|
|
public.print_error()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def monitor_node_once_with_timeout(node_data: dict, timeout: int = 5):
|
|||
|
|
if node_data["app_key"] == "local" and node_data["api_key"] == "local":
|
|||
|
|
return
|
|||
|
|
from mod.project.node.dbutil import Node, ServerNodeDB, ServerMonitorRepo
|
|||
|
|
from mod.project.node.nodeutil.ssh_wrap import SSHApi
|
|||
|
|
try:
|
|||
|
|
node_data["error"] = json.loads(node_data["error"])
|
|||
|
|
node_data["ssh_conf"] = json.loads(node_data["ssh_conf"])
|
|||
|
|
node, err = Node.from_dict(node_data)
|
|||
|
|
if err:
|
|||
|
|
# public.print_log("Node data parsing error:{}".format(err))
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
if node_data["app_key"] or node_data["api_key"]:
|
|||
|
|
node_data["timeout"] = timeout
|
|||
|
|
srv = ServerNode.new_by_data(node_data)
|
|||
|
|
node_data.pop("timeout")
|
|||
|
|
elif node_data["ssh_conf"]:
|
|||
|
|
srv = SSHApi(**node_data["ssh_conf"], timeout=timeout)
|
|||
|
|
else:
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
data, err = srv.get_net_work()
|
|||
|
|
if err:
|
|||
|
|
node.error_num += 1
|
|||
|
|
node.error = {
|
|||
|
|
"msg": err,
|
|||
|
|
"time": int(time.time())
|
|||
|
|
}
|
|||
|
|
if node.error_num >= 2:
|
|||
|
|
node.status = 0
|
|||
|
|
ServerMonitorRepo().remove_cache(node.id)
|
|||
|
|
|
|||
|
|
ServerNodeDB().update_node(node)
|
|||
|
|
|
|||
|
|
elif node.error_num > 0:
|
|||
|
|
node.error_num = 0
|
|||
|
|
node.error = {}
|
|||
|
|
node.status = 1
|
|||
|
|
ServerNodeDB().update_node(node)
|
|||
|
|
|
|||
|
|
if data:
|
|||
|
|
repo = ServerMonitorRepo()
|
|||
|
|
repo.save_server_status(node.id, data)
|
|||
|
|
if repo.is_reboot_wait(node.server_ip):
|
|||
|
|
repo.set_wait_reboot(node.server_ip, False)
|
|||
|
|
except Exception as e:
|
|||
|
|
if public.is_debug():
|
|||
|
|
public.print_error()
|
|||
|
|
|
|||
|
|
|
|||
|
|
class LocalNode(ServerNode):
|
|||
|
|
is_local = True
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
super().__init__("", "", "")
|
|||
|
|
|
|||
|
|
def create_php_site(self, site_name: str, port: int = 80, **kwargs) -> Tuple[Optional[int], str]:
|
|||
|
|
path = "/www/wwwroot/{}".format(site_name)
|
|||
|
|
webname = {
|
|||
|
|
"domain": site_name if port in (80, 443) else "{}:{}".format(site_name, port),
|
|||
|
|
"domainlist": [],
|
|||
|
|
"count": 0
|
|||
|
|
}
|
|||
|
|
pdata = {
|
|||
|
|
"path": path,
|
|||
|
|
"ftp": "false",
|
|||
|
|
"type": "PHP",
|
|||
|
|
"type_id": "0",
|
|||
|
|
"ps": kwargs.get("ps") if kwargs.get("ps", None) else "{}【Load balancing site】".format(site_name),
|
|||
|
|
"port": str(port),
|
|||
|
|
"version": "00",
|
|||
|
|
"sql": "false",
|
|||
|
|
"webname": json.dumps(webname)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if "/www/server/panel/class" not in sys.path:
|
|||
|
|
sys.path.insert(0, "/www/server/panel/class")
|
|||
|
|
|
|||
|
|
from panelSite import panelSite
|
|||
|
|
|
|||
|
|
res = panelSite().AddSite(public.to_dict_obj(pdata))
|
|||
|
|
|
|||
|
|
if "siteId" in res:
|
|||
|
|
return res["siteId"], ""
|
|||
|
|
if "status" in res and not res["status"]:
|
|||
|
|
return 0, res.get("msg", "Unknown error")
|
|||
|
|
return 0, "Unknown error"
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
def site_proxy_list(site_name: str) -> List[dict]:
|
|||
|
|
from mod.base.web_conf.proxy import RealProxy
|
|||
|
|
data = RealProxy(config_prefix="").get_proxy_list(public.to_dict_obj({
|
|||
|
|
"sitename": site_name
|
|||
|
|
}))
|
|||
|
|
if isinstance(data, list) and data:
|
|||
|
|
return data
|
|||
|
|
|
|||
|
|
from panelSite import panelSite
|
|||
|
|
|
|||
|
|
data = panelSite().GetProxyList(public.to_dict_obj({
|
|||
|
|
"sitename": site_name
|
|||
|
|
}))
|
|||
|
|
if isinstance(data, list) and data:
|
|||
|
|
return data
|
|||
|
|
return []
|
|||
|
|
|
|||
|
|
def show_name(self) -> str:
|
|||
|
|
return "Local node"
|
|||
|
|
|
|||
|
|
def php_site_list(self) -> Tuple[List[dict], str]:
|
|||
|
|
from mod.base.web_conf.ssl import RealSSLManger
|
|||
|
|
ssl_m = RealSSLManger()
|
|||
|
|
|
|||
|
|
all_sites = public.M("sites").where("project_type=? and status = 1", "PHP").select()
|
|||
|
|
res = []
|
|||
|
|
for i in all_sites:
|
|||
|
|
domain = public.M("domain").where("pid=?", i["id"]).select()
|
|||
|
|
domains = list(set([x["name"] for x in domain]))
|
|||
|
|
port = list(set([int(x["port"]) for x in domain]))
|
|||
|
|
ssl = ssl_m.get_site_ssl_info(i["name"]) is not None
|
|||
|
|
if ssl and 443 not in port:
|
|||
|
|
port.append(443)
|
|||
|
|
res.append({
|
|||
|
|
"site_id": i["id"],
|
|||
|
|
"site_name": i["name"],
|
|||
|
|
"ports": port,
|
|||
|
|
"domains": domains,
|
|||
|
|
"ssl": ssl
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return res, ""
|
|||
|
|
|
|||
|
|
def add_domain(self, site_id: int, site_name: str, domain: str, port: int) -> Tuple[bool, str]:
|
|||
|
|
from panelSite import panelSite
|
|||
|
|
res = panelSite().AddDomain(public.to_dict_obj({
|
|||
|
|
"domain": "{}:{}".format(domain, port),
|
|||
|
|
"webname": site_name,
|
|||
|
|
"id": str(site_id)
|
|||
|
|
}))
|
|||
|
|
if isinstance(res, dict) and res.get("status", False):
|
|||
|
|
return True, ""
|
|||
|
|
return False, res.get("msg", "Unknown error")
|
|||
|
|
|
|||
|
|
def has_domain(self, site_id: int, domain: str):
|
|||
|
|
return public.M("domain").where("pid=? and name=?", site_id, domain).count() > 0
|
|||
|
|
|
|||
|
|
def dir_walk(self, path: str) -> Tuple[List[dict], str]:
|
|||
|
|
if not os.path.isdir(path):
|
|||
|
|
return [], "{} Not a directory".format(path)
|
|||
|
|
res_file = []
|
|||
|
|
count = 0
|
|||
|
|
empty_dir = []
|
|||
|
|
for root, dirs, files in os.walk(path):
|
|||
|
|
if not files:
|
|||
|
|
empty_dir.append(root)
|
|||
|
|
for f in files:
|
|||
|
|
if count > 1000:
|
|||
|
|
return [], "The number of directory files exceeds 1000, please compress before operating"
|
|||
|
|
count += 1
|
|||
|
|
try:
|
|||
|
|
res_file.append({
|
|||
|
|
"path": os.path.join(root, f),
|
|||
|
|
"size": os.path.getsize(os.path.join(root, f)),
|
|||
|
|
"is_dir": 0
|
|||
|
|
})
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
return [{"path": d, "size": 0, "is_dir": 1} for d in empty_dir] + res_file, ""
|
|||
|
|
|
|||
|
|
def remove_file(self, filename: str, is_dir: bool) -> dict:
|
|||
|
|
from files_v2 import files
|
|||
|
|
if is_dir:
|
|||
|
|
return files().DeleteDir(public.to_dict_obj({"path": filename}))
|
|||
|
|
else:
|
|||
|
|
return files().DeleteFile(public.to_dict_obj({"path": filename}))
|
|||
|
|
|
|||
|
|
def file_list(self, path: str, p: int, row: int, search: str) -> Tuple[Dict, str]:
|
|||
|
|
from files_v2 import files
|
|||
|
|
return files().GetDirNew(public.to_dict_obj({
|
|||
|
|
"path": path,
|
|||
|
|
"p": p,
|
|||
|
|
"showRow": row,
|
|||
|
|
"search": search
|
|||
|
|
})), ""
|
|||
|
|
|
|||
|
|
def upload_proxy(self):
|
|||
|
|
from files_v2 import files
|
|||
|
|
return files().upload(args=public.to_dict_obj({}))
|
|||
|
|
|
|||
|
|
def upload_check(self, target_file_list: List[str]) -> Tuple[List[dict], str]:
|
|||
|
|
from files_v2 import files
|
|||
|
|
return files().upload_files_exists(args=public.to_dict_obj({
|
|||
|
|
"files": "\n".join(target_file_list),
|
|||
|
|
})), ""
|
|||
|
|
|
|||
|
|
def dir_size(self, path: str) -> Tuple[Optional[int], str]:
|
|||
|
|
return public.get_path_size(path), ""
|
|||
|
|
|
|||
|
|
def get_sshd_port(self) -> Optional[int]:
|
|||
|
|
return public.get_sshd_port()
|
|||
|
|
|
|||
|
|
def create_dir(self, path: str) -> Tuple[Optional[Dict], str]:
|
|||
|
|
from files_v2 import files
|
|||
|
|
return files().CreateDir(public.to_dict_obj({"path": path})), ""
|
|||
|
|
|
|||
|
|
def get_file_body(self, path: str) -> Tuple[Optional[str], str]:
|
|||
|
|
res = public.readFile(path)
|
|||
|
|
if isinstance(res, str):
|
|||
|
|
return res, ""
|
|||
|
|
return None, "fail to read file"
|
|||
|
|
|
|||
|
|
def read_ssh_key(self) -> Tuple[Optional[str], str]:
|
|||
|
|
from ssh_security import ssh_security
|
|||
|
|
data = ssh_security().get_key(None)
|
|||
|
|
if data["status"]:
|
|||
|
|
return data["msg"], ""
|
|||
|
|
return None, data["msg"]
|
|||
|
|
|
|||
|
|
def get_dir(self, path: str, search: str, disk: str):
|
|||
|
|
from files_v2 import files
|
|||
|
|
return files().GetDir(public.to_dict_obj({"path": path, "search":search, "disk": disk}))
|
|||
|
|
|
|||
|
|
|
|||
|
|
class LPanelNode(ServerNode):
|
|||
|
|
_TIME_REGEXP = re.compile(r":\d{2}(?P<err>\.\d{6,})[+-]\d{2}:\d{2}")
|
|||
|
|
|
|||
|
|
def __init__(self, address: str, api_key: str, lpver: str = "v2", timeout: int = 20):
|
|||
|
|
super().__init__(address, api_key, "", timeout=timeout)
|
|||
|
|
self.ver = lpver
|
|||
|
|
self.lpc = OnePanelApiClient(address, api_key, lpver, self.timeout)
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def check_api_key(cls, node: Node) -> str:
|
|||
|
|
# public.print_log(node.address, node.api_key)
|
|||
|
|
lpc = OnePanelApiClient(node.address, node.api_key)
|
|||
|
|
if lpc.test_ver():
|
|||
|
|
node.lpver = lpc.ver
|
|||
|
|
return ""
|
|||
|
|
return "1 Panel node test connection failed"
|
|||
|
|
|
|||
|
|
def test_conn(self) -> str:
|
|||
|
|
if self.lpc.test_ver():
|
|||
|
|
return ""
|
|||
|
|
return "1 Panel node test connection failed"
|
|||
|
|
|
|||
|
|
def get_net_work(self) -> Tuple[Optional[dict], str]:
|
|||
|
|
"""{
|
|||
|
|
"code": 200,
|
|||
|
|
"message": "",
|
|||
|
|
"data": {
|
|||
|
|
"path": "/",
|
|||
|
|
"name": "/",
|
|||
|
|
"user": "root",
|
|||
|
|
"group": "root",
|
|||
|
|
"uid": "0",
|
|||
|
|
"gid": "0",
|
|||
|
|
"extension": "",
|
|||
|
|
"content": "",
|
|||
|
|
"size": 4096,
|
|||
|
|
"isDir": true,
|
|||
|
|
"isSymlink": false,
|
|||
|
|
"isHidden": false,
|
|||
|
|
"linkPath": "",
|
|||
|
|
"type": "",
|
|||
|
|
"mode": "0555",
|
|||
|
|
"mimeType": "",
|
|||
|
|
"updateTime": "0001-01-01T00:00:00Z",
|
|||
|
|
"modTime": "2023-12-16T18:21:10.369198018+08:00",
|
|||
|
|
"items": [],
|
|||
|
|
"itemTotal": 22,
|
|||
|
|
"favoriteID": 0,
|
|||
|
|
"isDetail": true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
API Response Status: 200
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"message": "",
|
|||
|
|
"data": {
|
|||
|
|
"uptime": 8060394,
|
|||
|
|
"timeSinceUptime": "2025-03-05 10:02:04",
|
|||
|
|
"procs": 173,
|
|||
|
|
"load1": 1.05,
|
|||
|
|
"load5": 1.05,
|
|||
|
|
"load15": 1.05,
|
|||
|
|
"loadUsagePercent": 17.5,
|
|||
|
|
"cpuPercent": [
|
|||
|
|
100,
|
|||
|
|
0,
|
|||
|
|
0,
|
|||
|
|
0
|
|||
|
|
],
|
|||
|
|
"cpuUsedPercent": 25.000000232830644,
|
|||
|
|
"cpuUsed": 1.0000000093132257,
|
|||
|
|
"cpuTotal": 4,
|
|||
|
|
"memoryTotal": 1038336000,
|
|||
|
|
"memoryAvailable": 298635264,
|
|||
|
|
"memoryUsed": 572526592,
|
|||
|
|
"memoryUsedPercent": 55.13885601577909,
|
|||
|
|
"swapMemoryTotal": 4160745472,
|
|||
|
|
"swapMemoryAvailable": 3816017920,
|
|||
|
|
"swapMemoryUsed": 344727552,
|
|||
|
|
"swapMemoryUsedPercent": 8.285235285836778,
|
|||
|
|
"ioReadBytes": 0,
|
|||
|
|
"ioWriteBytes": 0,
|
|||
|
|
"ioCount": 0,
|
|||
|
|
"ioReadTime": 0,
|
|||
|
|
"ioWriteTime": 0,
|
|||
|
|
"diskData": [
|
|||
|
|
{
|
|||
|
|
"path": "/",
|
|||
|
|
"type": "xfs",
|
|||
|
|
"device": "/dev/mapper/centos-root",
|
|||
|
|
"total": 37688381440,
|
|||
|
|
"free": 9805086720,
|
|||
|
|
"used": 27883294720,
|
|||
|
|
"usedPercent": 73.98379461954417,
|
|||
|
|
"inodesTotal": 18411520,
|
|||
|
|
"inodesUsed": 729745,
|
|||
|
|
"inodesFree": 17681775,
|
|||
|
|
"inodesUsedPercent": 3.963523924151836
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"path": "/www/monitor_data",
|
|||
|
|
"type": "ext4",
|
|||
|
|
"device": "/dev/vdb1",
|
|||
|
|
"total": 10432565248,
|
|||
|
|
"free": 8118497280,
|
|||
|
|
"used": 1760526336,
|
|||
|
|
"usedPercent": 17.82085360286682,
|
|||
|
|
"inodesTotal": 655360,
|
|||
|
|
"inodesUsed": 10029,
|
|||
|
|
"inodesFree": 645331,
|
|||
|
|
"inodesUsedPercent": 1.530303955078125
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"netBytesSent": 0,
|
|||
|
|
"netBytesRecv": 0,
|
|||
|
|
"gpuData": null,
|
|||
|
|
"xpuData": null,
|
|||
|
|
"shotTime": "2025-06-06T17:01:59.156659566+08:00"
|
|||
|
|
}
|
|||
|
|
}"""
|
|||
|
|
try:
|
|||
|
|
data = self.lpc.system_status()
|
|||
|
|
system_data = data.get("data", {})
|
|||
|
|
return {
|
|||
|
|
"cpu": [round(system_data["cpuUsedPercent"], 2), system_data["cpuTotal"]],
|
|||
|
|
"mem": {
|
|||
|
|
"memRealUsed": system_data["memoryUsed"] / 1024 / 1024,
|
|||
|
|
"memTotal": system_data["memoryTotal"] / 1024 / 1024,
|
|||
|
|
"memNewTotal": public.to_size(system_data["memoryTotal"])
|
|||
|
|
},
|
|||
|
|
"version": "1Panel"
|
|||
|
|
}, ""
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
public.print_error()
|
|||
|
|
return None, str(e)
|
|||
|
|
|
|||
|
|
def get_tmp_token(self) -> Tuple[Optional[str], str]:
|
|||
|
|
return None, "1Panel does not support temporary API access"
|
|||
|
|
|
|||
|
|
def php_site_list(self) -> Tuple[List[dict], str]:
|
|||
|
|
"""{
|
|||
|
|
"code": 200,
|
|||
|
|
"message": "",
|
|||
|
|
"data": [
|
|||
|
|
{
|
|||
|
|
"id": 2,
|
|||
|
|
"createdAt": "2025-06-06T14:49:00.687860227+08:00",
|
|||
|
|
"updatedAt": "2025-06-06T14:49:00.687860227+08:00",
|
|||
|
|
"protocol": "HTTP",
|
|||
|
|
"primaryDomain": "www.halotest.com",
|
|||
|
|
"type": "deployment",
|
|||
|
|
"alias": "www.halotest.com",
|
|||
|
|
"remark": "",
|
|||
|
|
"status": "Running",
|
|||
|
|
"httpConfig": "",
|
|||
|
|
"expireDate": "9999-12-31T00:00:00Z",
|
|||
|
|
"proxy": "127.0.0.1:8090",
|
|||
|
|
"proxyType": "",
|
|||
|
|
"errorLog": true,
|
|||
|
|
"accessLog": true,
|
|||
|
|
"defaultServer": false,
|
|||
|
|
"IPV6": false,
|
|||
|
|
"rewrite": "",
|
|||
|
|
"webSiteGroupId": 1,
|
|||
|
|
"webSiteSSLId": 0,
|
|||
|
|
"runtimeID": 0,
|
|||
|
|
"appInstallId": 5,
|
|||
|
|
"ftpId": 0,
|
|||
|
|
"parentWebsiteID": 0,
|
|||
|
|
"user": "",
|
|||
|
|
"group": "",
|
|||
|
|
"dbType": "",
|
|||
|
|
"dbID": 0,
|
|||
|
|
"favorite": false,
|
|||
|
|
"domains": [
|
|||
|
|
{
|
|||
|
|
"id": 2,
|
|||
|
|
"createdAt": "2025-06-06T14:49:00.69011631+08:00",
|
|||
|
|
"updatedAt": "2025-06-06T14:49:00.69011631+08:00",
|
|||
|
|
"websiteId": 2,
|
|||
|
|
"domain": "www.halotest.com",
|
|||
|
|
"ssl": false,
|
|||
|
|
"port": 7080
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"id": 3,
|
|||
|
|
"createdAt": "2025-06-06T14:51:47.932141706+08:00",
|
|||
|
|
"updatedAt": "2025-06-06T14:51:47.932141706+08:00",
|
|||
|
|
"websiteId": 2,
|
|||
|
|
"domain": "qwww.com",
|
|||
|
|
"ssl": false,
|
|||
|
|
"port": 7080
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"webSiteSSL": {
|
|||
|
|
"id": 0,
|
|||
|
|
"createdAt": "0001-01-01T00:00:00Z",
|
|||
|
|
"updatedAt": "0001-01-01T00:00:00Z",
|
|||
|
|
"primaryDomain": "",
|
|||
|
|
"privateKey": "",
|
|||
|
|
"pem": "",
|
|||
|
|
"domains": "",
|
|||
|
|
"certURL": "",
|
|||
|
|
"type": "",
|
|||
|
|
"provider": "",
|
|||
|
|
"organization": "",
|
|||
|
|
"dnsAccountId": 0,
|
|||
|
|
"acmeAccountId": 0,
|
|||
|
|
"caId": 0,
|
|||
|
|
"autoRenew": false,
|
|||
|
|
"expireDate": "0001-01-01T00:00:00Z",
|
|||
|
|
"startDate": "0001-01-01T00:00:00Z",
|
|||
|
|
"status": "",
|
|||
|
|
"message": "",
|
|||
|
|
"keyType": "",
|
|||
|
|
"pushDir": false,
|
|||
|
|
"dir": "",
|
|||
|
|
"description": "",
|
|||
|
|
"skipDNS": false,
|
|||
|
|
"nameserver1": "",
|
|||
|
|
"nameserver2": "",
|
|||
|
|
"disableCNAME": false,
|
|||
|
|
"execShell": false,
|
|||
|
|
"shell": "",
|
|||
|
|
"acmeAccount": {
|
|||
|
|
"id": 0,
|
|||
|
|
"createdAt": "0001-01-01T00:00:00Z",
|
|||
|
|
"updatedAt": "0001-01-01T00:00:00Z",
|
|||
|
|
"email": "",
|
|||
|
|
"url": "",
|
|||
|
|
"type": "",
|
|||
|
|
"eabKid": "",
|
|||
|
|
"eabHmacKey": "",
|
|||
|
|
"keyType": "",
|
|||
|
|
"useProxy": false,
|
|||
|
|
"caDirURL": ""
|
|||
|
|
},
|
|||
|
|
"dnsAccount": {
|
|||
|
|
"id": 0,
|
|||
|
|
"createdAt": "0001-01-01T00:00:00Z",
|
|||
|
|
"updatedAt": "0001-01-01T00:00:00Z",
|
|||
|
|
"name": "",
|
|||
|
|
"type": ""
|
|||
|
|
},
|
|||
|
|
"websites": null
|
|||
|
|
},
|
|||
|
|
"errorLogPath": "",
|
|||
|
|
"accessLogPath": "",
|
|||
|
|
"sitePath": "",
|
|||
|
|
"appName": "",
|
|||
|
|
"runtimeName": "",
|
|||
|
|
"runtimeType": "",
|
|||
|
|
"siteDir": ""
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}"""
|
|||
|
|
try:
|
|||
|
|
data = self.lpc.get_websites()
|
|||
|
|
res = []
|
|||
|
|
for i in data["data"]:
|
|||
|
|
res.append({
|
|||
|
|
"site_id": i["id"],
|
|||
|
|
"site_name": i["alias"],
|
|||
|
|
"ports": list(set([i["port"] for i in i["domains"]])),
|
|||
|
|
"domains": list(set([i["domain"] for i in i["domains"]])),
|
|||
|
|
"ssl": any(i["ssl"] for i in i["domains"])
|
|||
|
|
})
|
|||
|
|
return res, ""
|
|||
|
|
except Exception as e:
|
|||
|
|
return [], str(e)
|
|||
|
|
|
|||
|
|
def create_php_site(self, site_name: str, port: int, **kwargs) -> Tuple[Optional[int], str]:
|
|||
|
|
try:
|
|||
|
|
dat = self.lpc.add_website(site_name, port, **kwargs)
|
|||
|
|
# public.print_log(dat)
|
|||
|
|
if dat["code"] == 200:
|
|||
|
|
time.sleep(0.5)
|
|||
|
|
site_id = self.lpc.check_site_create(site_name)
|
|||
|
|
if isinstance(site_id, int):
|
|||
|
|
return site_id, ""
|
|||
|
|
return None, "Website creation failed"
|
|||
|
|
return None, dat["message"]
|
|||
|
|
except Exception as e:
|
|||
|
|
return None, str(e)
|
|||
|
|
|
|||
|
|
def set_firewall_open(self, port: int, protocol: str = "tcp") -> Tuple[bool, str]:
|
|||
|
|
try:
|
|||
|
|
dat = self.lpc.open_port(port, protocol)
|
|||
|
|
if dat["code"] == 200:
|
|||
|
|
return True, ""
|
|||
|
|
return False, dat["message"]
|
|||
|
|
except Exception as e:
|
|||
|
|
return False, str(e)
|
|||
|
|
|
|||
|
|
def add_domain(self, site_id: int, site_name: str, domain: str, port: int) -> Tuple[bool, str]:
|
|||
|
|
try:
|
|||
|
|
dat = self.lpc.add_website_domain(site_id, domain, port)
|
|||
|
|
if dat["code"] == 200:
|
|||
|
|
return True, ""
|
|||
|
|
return False, dat["message"]
|
|||
|
|
except Exception as e:
|
|||
|
|
return False, str(e)
|
|||
|
|
|
|||
|
|
def has_domain(self, site_id: int, domain: str) -> bool:
|
|||
|
|
try:
|
|||
|
|
dat = self.lpc.website_domains(site_id)
|
|||
|
|
if dat["code"] == 200:
|
|||
|
|
for i in dat["data"]:
|
|||
|
|
if i["domain"] == domain:
|
|||
|
|
return True
|
|||
|
|
return False
|
|||
|
|
return False
|
|||
|
|
except Exception as e:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def target_file_exits(self, target_file: str) -> Tuple[bool, str]:
|
|||
|
|
data = self.lpc.files_exits([target_file])
|
|||
|
|
if not isinstance(data, dict):
|
|||
|
|
return False, "Request file: {} status failed".format(target_file)
|
|||
|
|
for i in data["data"]:
|
|||
|
|
if i["path"] == target_file:
|
|||
|
|
return True, ""
|
|||
|
|
return False, ""
|
|||
|
|
|
|||
|
|
def upload_dir_check(self, target_dir: str) -> str:
|
|||
|
|
data = self.lpc.files_exits([target_dir])
|
|||
|
|
if not isinstance(data, dict):
|
|||
|
|
return "Request file: {} status failed".format(target_dir)
|
|||
|
|
for i in data["data"]:
|
|||
|
|
if i["path"] == target_dir:
|
|||
|
|
if not i.get("isDir", True):
|
|||
|
|
return "The name path is not a directory"
|
|||
|
|
return ""
|
|||
|
|
return ""
|
|||
|
|
|
|||
|
|
def _upload_big_file(self, filename: str, target_path: str, upload_name: str,
|
|||
|
|
call_log: Callable[[int, str], None] = None) -> str:
|
|||
|
|
try:
|
|||
|
|
fb = open(filename, 'rb')
|
|||
|
|
except Exception as e:
|
|||
|
|
public.print_error()
|
|||
|
|
return "File {} failed to open, please check file permissions, error message is:{}".format(filename, str(e))
|
|||
|
|
|
|||
|
|
file_size = os.path.getsize(filename)
|
|||
|
|
count = math.ceil(file_size / (1024 * 1024 * 5))
|
|||
|
|
idx = 0
|
|||
|
|
for i in range(0, file_size, 1024 * 1024 * 5):
|
|||
|
|
file_data = fb.read(1024 * 1024 * 5)
|
|||
|
|
err, data = self.lpc.chunkupload(upload_name, target_path, file_data, idx, count)
|
|||
|
|
idx += 1
|
|||
|
|
if err:
|
|||
|
|
return "Upload file {} failed with error message:{}".format(filename, str(err))
|
|||
|
|
if data:
|
|||
|
|
call_log(100, "File upload:{} -> {},Upload successful".format(filename, upload_name))
|
|||
|
|
else:
|
|||
|
|
up_d = (i + len(file_data)) // file_size
|
|||
|
|
call_log(up_d, "File upload:{} -> {},The uploaded size is:{}".format(
|
|||
|
|
filename, upload_name, public.to_size(i + len(file_data))))
|
|||
|
|
return ""
|
|||
|
|
|
|||
|
|
def _upload_little_file(self, filename: str, target_path: str, upload_name: str,
|
|||
|
|
call_log: Callable[[int, str], None] = None) -> str:
|
|||
|
|
return self.lpc.upload(filename, target_path, upload_name)
|
|||
|
|
|
|||
|
|
def download_file(self, filename: str, target_path: str, mode: str,
|
|||
|
|
call_log: Callable[[int, str], None] = None) -> str:
|
|||
|
|
target_file = os.path.join(target_path, os.path.basename(filename))
|
|||
|
|
exits = os.path.exists(target_file)
|
|||
|
|
if exits and mode == "ignore":
|
|||
|
|
call_log(0, "File Download:{} -> {},The target file already exists, skip download".format(filename, target_file))
|
|||
|
|
return ""
|
|||
|
|
if exits and mode == "rename":
|
|||
|
|
download_name = "{}_{}".format(os.path.basename(filename), public.md5(filename))
|
|||
|
|
call_log(0, "File Download:{} -> {},The target file already exists, it will be renamed to {}".format(filename, target_file, download_name))
|
|||
|
|
else:
|
|||
|
|
download_name = os.path.basename(filename)
|
|||
|
|
|
|||
|
|
return self.lpc.download_file(filename, target_path, download_name, call_log=call_log)
|
|||
|
|
|
|||
|
|
def dir_walk(self, path: str) -> Tuple[List[dict], str]:
|
|||
|
|
return self.lpc.dir_walk(path)
|
|||
|
|
|
|||
|
|
def upload_proxy(self):
|
|||
|
|
try:
|
|||
|
|
from YakPanel import request, cache
|
|||
|
|
f_name = request.form.get('f_name')
|
|||
|
|
f_path = request.form.get('f_path')
|
|||
|
|
f_size = request.form.get('f_size')
|
|||
|
|
f_start = request.form.get('f_start')
|
|||
|
|
cache_key = "upload_file_{}_{}_{}".format(f_name, f_path, f_size)
|
|||
|
|
num = cache.get(cache_key)
|
|||
|
|
if num is None:
|
|||
|
|
num = 0
|
|||
|
|
cache.set(cache_key, num, 86400)
|
|||
|
|
else:
|
|||
|
|
num = int(num)
|
|||
|
|
cache.set(cache_key, num + 1, 86400)
|
|||
|
|
blob_file: FileStorage = request.files.getlist('blob')[0]
|
|||
|
|
file_data = blob_file.read()
|
|||
|
|
next_size = int(f_start) + len(file_data)
|
|||
|
|
if next_size == int(f_size):
|
|||
|
|
chunk_num = num + 1
|
|||
|
|
else:
|
|||
|
|
chunk_num = num + 2
|
|||
|
|
err, data = self.lpc.chunkupload(f_name, f_path, file_data, num, chunk_num)
|
|||
|
|
if err:
|
|||
|
|
return {"status": False, "msg": "Upload file failed with error message:{}".format(str(err))}
|
|||
|
|
elif data is None:
|
|||
|
|
return str(next_size)
|
|||
|
|
elif isinstance(data, dict) and data["code"] == 200:
|
|||
|
|
return {"status": True, "msg": "Upload successful"}
|
|||
|
|
return {"status": False, "msg": "Upload file failed with error message:{}".format(data["message"])}
|
|||
|
|
except Exception as e:
|
|||
|
|
public.print_error()
|
|||
|
|
return {"status": False, "msg": "Upload file failed with error message:{}".format(str(e))}
|
|||
|
|
|
|||
|
|
def remove_file(self, filename: str, is_dir: bool) -> dict:
|
|||
|
|
try:
|
|||
|
|
res = self.lpc.remove_file(filename, is_dir)
|
|||
|
|
if isinstance(res, dict):
|
|||
|
|
return {"status": res["code"] == 200, "msg": res["message"]}
|
|||
|
|
return {"status": False, "msg": "File deletion failed, remote request failed"}
|
|||
|
|
except Exception as e:
|
|||
|
|
return {"status": False, "msg": "Failed to delete file, error message is:{}".format(str(e))}
|
|||
|
|
|
|||
|
|
def file_list(self, path: str, p: int, row: int, search: str) -> Tuple[Dict, str]:
|
|||
|
|
try:
|
|||
|
|
res, err = self.lpc.files_search(path, p, row, search)
|
|||
|
|
if err:
|
|||
|
|
return {}, "Failed to retrieve file list, error message is:{}".format(err)
|
|||
|
|
count = res["itemTotal"]
|
|||
|
|
path = res["path"]
|
|||
|
|
dirs = []
|
|||
|
|
files = []
|
|||
|
|
sub_items = [] if res["items"] is None else res["items"]
|
|||
|
|
for i in sub_items:
|
|||
|
|
# res = self._TIME_REGEXP.findall(i["modTime"])
|
|||
|
|
# public.print_log(res)
|
|||
|
|
mt_str = self._TIME_REGEXP.sub(lambda x: x.group().replace(x.group("err"), ""), i["modTime"])
|
|||
|
|
for f_str in ("%Y-%m-%dT%H:%M:%S%z", "%Y-%m-%dT%H:%M:%S.%f%z"):
|
|||
|
|
try:
|
|||
|
|
mt = datetime.strptime(mt_str, f_str)
|
|||
|
|
break
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
# public.print_log(i["modTime"], mt_str)
|
|||
|
|
# public.print_error()
|
|||
|
|
else:
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
if i["isDir"]:
|
|||
|
|
dirs.append({
|
|||
|
|
"nm": html.unescape(i["name"]),
|
|||
|
|
"sz": i["size"],
|
|||
|
|
"mt": int(mt.timestamp()),
|
|||
|
|
"acc": i["mode"][1:],
|
|||
|
|
"user": i["user"],
|
|||
|
|
"lnk": "" if not i["linkPath"] else " -> " + i["linkPath"],
|
|||
|
|
"durl": "",
|
|||
|
|
"cmp": 0,
|
|||
|
|
"fav": "0",
|
|||
|
|
"rmk": "",
|
|||
|
|
"top": 0,
|
|||
|
|
"sn": i["name"]
|
|||
|
|
})
|
|||
|
|
else:
|
|||
|
|
files.append({
|
|||
|
|
"nm": html.unescape(i["name"]),
|
|||
|
|
"sz": i["size"],
|
|||
|
|
"mt": int(mt.timestamp()),
|
|||
|
|
"acc": i["mode"][1:],
|
|||
|
|
"user": i["user"],
|
|||
|
|
"lnk": "" if not i["linkPath"] else " -> " + i["linkPath"],
|
|||
|
|
"durl": "",
|
|||
|
|
"cmp": 0,
|
|||
|
|
"fav": "0",
|
|||
|
|
"rmk": "",
|
|||
|
|
"top": 0,
|
|||
|
|
"sn": i["name"]
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"path": path,
|
|||
|
|
"page": public.get_page(count, p, row)["page"],
|
|||
|
|
"dir": dirs,
|
|||
|
|
"files": files
|
|||
|
|
}, ""
|
|||
|
|
except Exception as e:
|
|||
|
|
return {}, "Failed to retrieve file list, error message is:{}".format(str(e))
|
|||
|
|
|
|||
|
|
def download_proxy(self, filename: str):
|
|||
|
|
return self.lpc.download_proxy(filename)
|
|||
|
|
|
|||
|
|
def upload_check(self, target_file_list: List[str]) -> Tuple[List[dict], str]:
|
|||
|
|
try:
|
|||
|
|
res = self.lpc.files_exits(target_file_list)
|
|||
|
|
if res is None:
|
|||
|
|
return [], "Request for remote 1Panel failed"
|
|||
|
|
data = res.get("data", [])
|
|||
|
|
res_data = []
|
|||
|
|
for i in data:
|
|||
|
|
try:
|
|||
|
|
mt = datetime.strptime(i["modTime"], "%Y-%m-%dT%H:%M:%S%z")
|
|||
|
|
except:
|
|||
|
|
try:
|
|||
|
|
mt = datetime.strptime(i["modTime"], "%Y-%m-%dT%H:%M:%S.%f%z")
|
|||
|
|
except:
|
|||
|
|
continue
|
|||
|
|
res_data.append({
|
|||
|
|
'filename': i["path"],
|
|||
|
|
'exists': True,
|
|||
|
|
'size': i["size"],
|
|||
|
|
'mtime': int(mt.timestamp()),
|
|||
|
|
'isfile': False
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return res_data, ""
|
|||
|
|
except Exception as e:
|
|||
|
|
return [], "Request for remote 1Panel failed with error message:{}".format(str(e))
|
|||
|
|
|
|||
|
|
def dir_size(self, path: str) -> Tuple[Optional[int], str]:
|
|||
|
|
try:
|
|||
|
|
res = self.lpc.dir_size(path)
|
|||
|
|
if res is None:
|
|||
|
|
return None, "Request for remote 1Panel failed"
|
|||
|
|
if not isinstance(res, dict):
|
|||
|
|
return None, "Request for remote 1Panel failed, response data:%s" % str(res)
|
|||
|
|
return res["data"].get("size", 0), ""
|
|||
|
|
except Exception as e:
|
|||
|
|
return None, "Request for remote 1Panel failed: %s" % str(e)
|
|||
|
|
|
|||
|
|
def get_sshd_port(self) -> Optional[int]:
|
|||
|
|
try:
|
|||
|
|
data = self.lpc.get_sshd_config()
|
|||
|
|
return int(data["port"]) if data else None
|
|||
|
|
except:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def create_dir(self, path: str) -> Tuple[Optional[Dict], str]:
|
|||
|
|
try:
|
|||
|
|
data = self.lpc.create_dir(path)
|
|||
|
|
if not data:
|
|||
|
|
return None, "Failed to create directory, 1Panel node connection failed"
|
|||
|
|
status = data["code"] == 200
|
|||
|
|
return {"status": status, "msg": data["message"] if not status else "Created successfully"}, ""
|
|||
|
|
except Exception as e:
|
|||
|
|
return None, "Request for remote 1Panel node failed: %s" % str(e)
|
|||
|
|
|
|||
|
|
def restart_bt_panel(self) -> Dict[str, Any]:
|
|||
|
|
self.lpc.restart_panel()
|
|||
|
|
return {"status": True, "msg": "Restart successful"}
|
|||
|
|
|
|||
|
|
def server_reboot(self) -> Dict[str, Any]:
|
|||
|
|
self.lpc.server_reboot()
|
|||
|
|
|
|||
|
|
from mod.project.node.dbutil import ServerMonitorRepo
|
|||
|
|
repo = ServerMonitorRepo()
|
|||
|
|
repo.set_wait_reboot(self.node_server_ip, True)
|
|||
|
|
|
|||
|
|
def wait_for_reboot():
|
|||
|
|
# wait 等待服务器重启成功, 超时时间默认为 10 分钟
|
|||
|
|
wait_for = time.time() + 600
|
|||
|
|
time.sleep(3)
|
|||
|
|
while time.time() < wait_for:
|
|||
|
|
if self.test_conn() == "": # 无错误时表示重启成功
|
|||
|
|
repo.set_wait_reboot(self.node_server_ip, False)
|
|||
|
|
from mod.project.node.dbutil import ServerNodeDB
|
|||
|
|
node_data = ServerNodeDB().find_node(api_key=self.api_key, app_key=self.app_key.to_string())
|
|||
|
|
if node_data:
|
|||
|
|
monitor_node_once(node_data)
|
|||
|
|
return {"status": True, "msg": "Server restart successful"}
|
|||
|
|
time.sleep(3)
|
|||
|
|
# public.print_log("Waiting for server restart... {}".format(wait_for - time.time()))
|
|||
|
|
repo.set_wait_reboot(self.node_server_ip, False)
|
|||
|
|
return {"status": False, "msg": "Restarting server failed, server information has not been detected for more than 10 minutes"}
|
|||
|
|
|
|||
|
|
t = threading.Thread(target=wait_for_reboot, daemon=True)
|
|||
|
|
t.start()
|
|||
|
|
|
|||
|
|
return {"status": True, "msg": "The server restart has started, please wait patiently for the successful restart"}
|
|||
|
|
|
|||
|
|
def get_file_body(self, path: str) -> Tuple[Optional[str], str]:
|
|||
|
|
data_dict, err = self.lpc.get_file_body(path)
|
|||
|
|
if err != "":
|
|||
|
|
return None, err
|
|||
|
|
return data_dict["content"], ""
|
|||
|
|
|
|||
|
|
def read_ssh_key(self) -> Tuple[Optional[str], str]:
|
|||
|
|
key, err = self.lpc.get_file_body("/root/.ssh/id_ed25519_1panel")
|
|||
|
|
if err:
|
|||
|
|
return None, "Failed to read SSH key, error message is: %s" % err
|
|||
|
|
return key, ""
|