界面优化,监控逻辑优化
This commit is contained in:
@ -2,14 +2,14 @@ import os
|
||||
import os.path
|
||||
import threading
|
||||
import sqlite3
|
||||
import time
|
||||
|
||||
from PySide6.QtCore import Signal, QThread
|
||||
|
||||
|
||||
def traversal_files(dir_path, signal):
|
||||
# 功能:以列表的形式分别返回指定路径下的文件和文件夹,不包含子目录
|
||||
# 参数:路径/信号/游标/功能编号
|
||||
# 返回值:路径下的文件夹列表 路径下的文件列表
|
||||
def traversal_files(dir_path):
|
||||
if not os.path.exists(dir_path):
|
||||
logger("ERROR", "clibs", f"数据文件夹{dir_path}不存在,请确认后重试......", "red", signal=signal)
|
||||
logger("ERROR", "clibs", f"数据文件夹{dir_path}不存在,请确认后重试......", color="red")
|
||||
else:
|
||||
dirs, files = [], []
|
||||
for item in os.scandir(dir_path):
|
||||
@ -56,33 +56,42 @@ def db_lock(func):
|
||||
return wrapper
|
||||
|
||||
|
||||
@db_lock
|
||||
def logger(level, module, content, color="black", flag="both", signal=""):
|
||||
global cursor
|
||||
if "move.monitor" in content:
|
||||
return
|
||||
class LoggerHandler(QThread):
|
||||
signal = Signal(str, str)
|
||||
|
||||
if flag == "signal":
|
||||
signal.emit(content, color)
|
||||
elif flag == "cursor":
|
||||
cursor.execute(f"INSERT INTO logs (level, module, content) VALUES (?, ?, ?)", (level, module, content))
|
||||
elif flag == "both":
|
||||
signal.emit(content, color)
|
||||
cursor.execute(f"INSERT INTO logs (level, module, content) VALUES (?, ?, ?)", (level, module, content))
|
||||
def __init__(self, /):
|
||||
super().__init__()
|
||||
|
||||
@db_lock
|
||||
def logger(self, level, module, content, color="black", flag="both"):
|
||||
global cursor
|
||||
if "move.monitor" in content:
|
||||
return
|
||||
|
||||
if level.upper() == "DEBUG":
|
||||
flag = "cursor"
|
||||
|
||||
if flag == "signal":
|
||||
self.signal.emit(content, color)
|
||||
elif flag == "cursor":
|
||||
cursor.execute(f"INSERT INTO logs (level, module, content) VALUES (?, ?, ?)", (level, module, content))
|
||||
elif flag == "both":
|
||||
self.signal.emit(content, color)
|
||||
cursor.execute(f"INSERT INTO logs (level, module, content) VALUES (?, ?, ?)", (level, module, content))
|
||||
|
||||
if level.upper() == "ERROR":
|
||||
raise Exception()
|
||||
|
||||
|
||||
def handle_exception(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception as err:
|
||||
print(f"{func.__name__} err = {err}")
|
||||
logger("DEBUG", "clibs", f"{func.__name__} err = {err}", flag="cursor")
|
||||
return wrapper
|
||||
def running_detection(idx):
|
||||
while True:
|
||||
time.sleep(INTERVAL*2)
|
||||
if not running[idx]:
|
||||
raise Exception("")
|
||||
|
||||
|
||||
PREFIX = "resources/assets" # for pyinstaller
|
||||
# PREFIX = "assets" # for local testing
|
||||
# PREFIX = "resources/assets" # for pyinstaller
|
||||
PREFIX = "assets" # for local testing
|
||||
log_path = f"{PREFIX}/logs"
|
||||
lock = threading.Lock()
|
||||
running = [0, 0, 0, 0, 0, 0, 0] # 制动数据/转矩数据/激光数据/精度数据/制动自动化/转矩自动化/耐久数据采集
|
||||
@ -91,7 +100,7 @@ levels = ["DEBUG", "INFO", "WARNING", "ERROR"]
|
||||
ip_addr, ssh_port, socket_port, xService_port, external_port, modbus_port, upgrade_port = "", 22, 5050, 6666, 8080, 502, 4567
|
||||
username, password = "luoshi", "luoshi2019"
|
||||
INTERVAL, RADIAN, MAX_FRAME_SIZE, MAX_LOG_NUMBER, CYCLE = 1, 57.3, 1024, 10, 300
|
||||
c_md, c_hr, c_ec, c_pd, conn, cursor, search_records = None, None, None, None, None, None, None
|
||||
c_md, c_hr, c_ec, c_pd, conn, cursor, search_records, logger, count = None, None, None, None, None, None, None, None, 0
|
||||
status = {"mysql": 0, "hmi": 0, "md": 0, "ec": 0}
|
||||
c_joint_vel, c_servo_trq, c_sensor_trq, c_estimate_trans_trq, c_safety_estop = 1, 2, 3, 4, 3 # 各个指标所在列
|
||||
|
||||
|
@ -16,12 +16,12 @@ from codes.common import clibs
|
||||
|
||||
|
||||
class ModbusRequest(QThread):
|
||||
output = Signal(str, str)
|
||||
|
||||
def __init__(self, ip, port, /):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.c = None
|
||||
self.logger = clibs.logger
|
||||
|
||||
def net_conn(self):
|
||||
self.logger("INFO", "openapi", f"Modbus 正在连接中,需要配置设备,这可能需要一点时间......", "blue")
|
||||
@ -30,21 +30,11 @@ class ModbusRequest(QThread):
|
||||
if self.c.connect():
|
||||
self.logger("INFO", "openapi", f"Modbus connection({clibs.ip_addr}:{clibs.modbus_port}) success!", "green")
|
||||
else:
|
||||
self.logger("ERROR", "openapi", f"Modbus connection({clibs.ip_addr}:{clibs.modbus_port}) failed!", "red", "MdConnFailed")
|
||||
|
||||
def logger(self, level, module, content, color="black", error="", flag="both"):
|
||||
flag = "cursor" if level.upper() == "DEBUG" else "both"
|
||||
clibs.logger(level, module, content, color, flag, signal=self.output)
|
||||
if level.upper() == "ERROR":
|
||||
raise Exception(f"{error} | {content}")
|
||||
self.logger("ERROR", "openapi", f"Modbus connection({clibs.ip_addr}:{clibs.modbus_port}) failed!", "red")
|
||||
|
||||
def close(self):
|
||||
if self.c.connect():
|
||||
try:
|
||||
self.c.close()
|
||||
self.logger("INFO", "openapi", f"modbus: 关闭 Modbus 连接成功", "green")
|
||||
except Exception as err:
|
||||
self.logger("ERROR", "openapi", f"modbus: 关闭 Modbus 连接失败:{err}", "red", "MdCloseFailed")
|
||||
self.c.close()
|
||||
self.logger("INFO", "openapi", f"modbus: 关闭 Modbus 连接成功", "green")
|
||||
|
||||
def __reg_high_pulse(self, addr: int) -> None:
|
||||
self.c.write_register(addr, 0)
|
||||
@ -344,7 +334,6 @@ class HmiRequest(QThread):
|
||||
self.__port = port
|
||||
self.__port_xs = port_xs
|
||||
self.__close_hmi = False
|
||||
self.__is_connected = False
|
||||
self.__index = 0
|
||||
self.__previous_data = b""
|
||||
self.__valid_data_length = 0
|
||||
@ -357,12 +346,10 @@ class HmiRequest(QThread):
|
||||
self.__half_pkg_flag = False
|
||||
self.__is_first_frame = True
|
||||
self.__is_debug = True
|
||||
self.logger = clibs.logger
|
||||
|
||||
def net_conn(self):
|
||||
self.__socket_conn()
|
||||
self.__t_heartbeat = threading.Thread(target=self.__heartbeat)
|
||||
self.__t_heartbeat.daemon = True
|
||||
self.__t_heartbeat.start()
|
||||
self.__t_unpackage = threading.Thread(target=self.__unpackage, args=(self.c,))
|
||||
self.__t_unpackage.daemon = True
|
||||
self.__t_unpackage.start()
|
||||
@ -370,30 +357,13 @@ class HmiRequest(QThread):
|
||||
self.__t_unpackage_xs.daemon = True
|
||||
self.__t_unpackage_xs.start()
|
||||
|
||||
def logger(self, level, module, content, color="black", error="", flag="both"):
|
||||
flag = "cursor" if level.upper() == "DEBUG" else "both"
|
||||
clibs.logger(level, module, content, color, flag, signal=self.output)
|
||||
if level.upper() == "ERROR":
|
||||
raise Exception(f"{error} | {content}")
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self.__is_connected
|
||||
|
||||
def close(self):
|
||||
if self.__is_connected:
|
||||
try:
|
||||
self.__is_connected = False
|
||||
time.sleep(clibs.INTERVAL/2)
|
||||
self.c.close()
|
||||
self.c_xs.close()
|
||||
clibs.status["hmi"] = 0
|
||||
self.logger("INFO", "openapi", f"hmi: 关闭 Socket 连接成功", "green")
|
||||
except Exception as err:
|
||||
self.logger("ERROR", "openapi", f"hmi: 关闭 Socket 连接失败 {err}", "red", "HmiCloseFailed")
|
||||
self.c.close()
|
||||
self.c_xs.close()
|
||||
self.logger("INFO", "openapi", f"hmi: 关闭 Socket 连接成功", "green")
|
||||
|
||||
def __socket_conn(self):
|
||||
self.close()
|
||||
# self.close()
|
||||
try:
|
||||
self.c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.c.settimeout(clibs.INTERVAL*5)
|
||||
@ -405,16 +375,11 @@ class HmiRequest(QThread):
|
||||
state = None
|
||||
for i in range(3):
|
||||
_ = self.execution("controller.heart")
|
||||
time.sleep(clibs.INTERVAL/2)
|
||||
self.__is_connected = True
|
||||
time.sleep(clibs.INTERVAL/4)
|
||||
clibs.status["hmi"] = 1
|
||||
self.logger("INFO", "openapi", "hmi: HMI connection success...", "green")
|
||||
except Exception as err:
|
||||
self.logger("ERROR", "openapi", f"hmi: HMI connection timeout...", "red", "HmiConnTimeout")
|
||||
|
||||
def __heartbeat(self):
|
||||
while self.__is_connected:
|
||||
self.execution("controller.heart")
|
||||
time.sleep(clibs.INTERVAL*2)
|
||||
except Exception:
|
||||
self.logger("ERROR", "openapi", f"hmi: HMI connection failed...", "red")
|
||||
|
||||
@staticmethod
|
||||
def package(cmd):
|
||||
@ -439,13 +404,13 @@ class HmiRequest(QThread):
|
||||
try:
|
||||
sel = selectors.DefaultSelector()
|
||||
sel.register(sock, selectors.EVENT_READ, to_read)
|
||||
while self.__is_connected:
|
||||
while clibs.status["hmi"]:
|
||||
events = sel.select()
|
||||
for key, mask in events:
|
||||
callback = key.data
|
||||
callback(key.fileobj, mask)
|
||||
except Exception as Err:
|
||||
self.logger("DEBUG", "openapi", f"hmi: 老协议解包报错 {Err}", "red", "UnpackageFailed")
|
||||
except Exception as err:
|
||||
self.logger("DEBUG", "openapi", f"hmi: 老协议解包报错 err = {err}")
|
||||
|
||||
def __get_headers(self, index, data):
|
||||
if index + 8 < len(data):
|
||||
@ -458,7 +423,7 @@ class HmiRequest(QThread):
|
||||
else:
|
||||
# print("hr-get_headers: 解包数据有误,需要确认!")
|
||||
# print(data)
|
||||
self.logger("ERROR", "openapi", f"hmi: 解包数据有误,需要确认,最后一个数据包如下 {data}")
|
||||
self.logger("ERROR", "openapi", f"hmi: 解包数据有误,需要确认,最后一个数据包如下 {data}", "red")
|
||||
else:
|
||||
self.__half_pkg = data[index:]
|
||||
self.__half_pkg_flag = True
|
||||
@ -681,7 +646,7 @@ class HmiRequest(QThread):
|
||||
# if self.__valid_data_length < 0 or self.__leftovers > 1024:
|
||||
# print(f"data = {data}")
|
||||
# raise Exception("DataError")
|
||||
self.logger("ERROR", "openapi", "hmi: Will never be here", "red", "WillNeverBeHere")
|
||||
self.logger("ERROR", "openapi", "hmi: will never be here", "red")
|
||||
|
||||
@staticmethod
|
||||
def package_xs(cmd):
|
||||
@ -701,13 +666,13 @@ class HmiRequest(QThread):
|
||||
sel = selectors.DefaultSelector()
|
||||
sel.register(sock, selectors.EVENT_READ, to_read)
|
||||
|
||||
while self.__is_connected:
|
||||
while clibs.status["hmi"]:
|
||||
events = sel.select()
|
||||
for key, mask in events:
|
||||
callback = key.data
|
||||
callback(key.fileobj, mask)
|
||||
except Exception as err:
|
||||
self.logger("DEBUG", "openapi", f"hmi: xService解包报错 {err}", "red", "XsUnpackageFailed")
|
||||
self.logger("DEBUG", "openapi", f"hmi: xService解包报错 err = {err}")
|
||||
|
||||
def get_response_xs(self, data):
|
||||
char, response = "", self.__response_xs
|
||||
@ -721,7 +686,6 @@ class HmiRequest(QThread):
|
||||
else:
|
||||
self.__response_xs = response
|
||||
|
||||
@clibs.handle_exception
|
||||
def get_from_id(self, msg_id):
|
||||
f_text, flag, records, time_delay, ts = f"%{msg_id}%", False, "Null", clibs.INTERVAL*10, msg_id.split("@")[-1]
|
||||
t = ts.replace("T", " ")
|
||||
@ -740,7 +704,7 @@ class HmiRequest(QThread):
|
||||
if flag is True:
|
||||
return records
|
||||
else:
|
||||
self.logger("ERROR", "openapi", f"hmi: {time_delay}s内无法找到请求 {msg_id} 的响应!", "red", "ResponseNotFound")
|
||||
self.logger("ERROR", "openapi", f"hmi: {time_delay}s内无法找到请求 {msg_id} 的响应!", "red")
|
||||
|
||||
def execution(self, command, **kwargs):
|
||||
req = None
|
||||
@ -752,7 +716,7 @@ class HmiRequest(QThread):
|
||||
flag = req["p_type"]
|
||||
del req["p_type"]
|
||||
except Exception as err:
|
||||
self.logger("ERROR", "openapi", f"hmi: 暂不支持 {command} 功能,或确认该功能存在... {err}", "red", "CommandNotSupport")
|
||||
self.logger("ERROR", "openapi", f"hmi: 暂不支持 {command} 功能,或确认该功能存在...<br>err = {err}", "red")
|
||||
|
||||
match command:
|
||||
case "state.set_tp_mode" | "overview.set_autoload" | "overview.reload" | "rl_task.pp_to_main" | "rl_task.run" | "rl_task.stop" | "rl_task.set_run_params" | "diagnosis.set_params" | "diagnosis.open" | "drag.set_params" | "controller.set_params" | "collision.set_state" | "collision.set_params" | "move.set_quickstop_distance" | "move.set_params" | "move.set_monitor_cfg" | "modbus.get_values" | "modbus.set_params" | "system_io.update_configuration" | "diagnosis.get_params" | "jog.set_params" | "jog.start" | "move.stop" | "move.quick_turn" | "move.set_quickturn_pos" | "soft_limit.set_params" | "fieldbus_device.set_params" | "socket.set_params" | "diagnosis.save" | "register.set_value":
|
||||
@ -768,17 +732,19 @@ class HmiRequest(QThread):
|
||||
cmd = json.dumps(req, separators=(",", ":"))
|
||||
try:
|
||||
self.c.send(self.package(cmd))
|
||||
time.sleep(clibs.INTERVAL/4)
|
||||
time.sleep(clibs.INTERVAL/4) # 这里一定是要等几百毫秒的,避免多指令同一时间发送,导致xCore不响应
|
||||
self.logger("DEBUG", "openapi", f"hmi: 老协议请求发送成功 {cmd}")
|
||||
except Exception as err:
|
||||
self.logger("ERROR", "openapi", f"hmi: 老协议请求发送失败 {cmd},报错信息 {err}", "red", "CommandSendFailed")
|
||||
if "controller.heart" in cmd:
|
||||
raise Exception()
|
||||
self.logger("ERROR", "openapi", f"hmi: 老协议请求发送失败 {cmd},报错信息 {err}", "red")
|
||||
elif flag == 1:
|
||||
try:
|
||||
self.c_xs.send(self.package_xs(req))
|
||||
time.sleep(clibs.INTERVAL/4)
|
||||
self.logger("DEBUG", "openapi", f"hmi: xService请求发送成功 {req}")
|
||||
except Exception as Err:
|
||||
self.logger("ERROR", "openapi", f"hr: xService请求发送失败 {req} 报错信息 {Err}", "red", "CommandSendFailed")
|
||||
except Exception as err:
|
||||
self.logger("ERROR", "openapi", f"hr: xService请求发送失败 {req} 报错信息 {err}", "red")
|
||||
|
||||
return req["id"]
|
||||
|
||||
@ -796,7 +762,7 @@ class HmiRequest(QThread):
|
||||
case "off":
|
||||
self.execution("state.switch_motor_off")
|
||||
case _:
|
||||
self.logger("ERROR", "openapi", f"hmi: switch_motor_state 参数错误 {state}, 非法参数,只接受 on/off", "red", "ArgumentError")
|
||||
self.logger("ERROR", "openapi", f"hmi: switch_motor_state 参数错误 {state}, 非法参数,只接受 on/off", "red")
|
||||
|
||||
def switch_operation_mode(self, mode: str): # OK
|
||||
"""
|
||||
@ -810,7 +776,7 @@ class HmiRequest(QThread):
|
||||
case "manual":
|
||||
self.execution("state.switch_manual")
|
||||
case _:
|
||||
self.logger("ERROR", "openapi", f"hmi: switch_operation_mode 参数错误 {mode},非法参数,只接受 auto/manual", "red", "ArgumentError")
|
||||
self.logger("ERROR", "openapi", f"hmi: switch_operation_mode 参数错误 {mode},非法参数,只接受 auto/manual", "red")
|
||||
|
||||
def reload_project(self, prj_name: str, tasks: list): # OK
|
||||
"""
|
||||
@ -887,7 +853,7 @@ class HmiRequest(QThread):
|
||||
self.__sth_wrong("3min 内未能完成重新连接,需要查看后台控制器是否正常启动,或者 ip/port 是否正确")
|
||||
break
|
||||
for _ in range(3):
|
||||
if not self.__is_connected:
|
||||
if not clibs.status["hmi"]:
|
||||
break
|
||||
time.sleep(2)
|
||||
else:
|
||||
@ -1517,7 +1483,7 @@ class HmiRequest(QThread):
|
||||
case "without":
|
||||
self.execution("state.set_tp_mode", tp_mode="without")
|
||||
case _:
|
||||
self.logger("ERROR", "openapi", f"hmi: switch_tp_mode 参数错误{mode}, 非法参数,只接受 with/without", "red", "ArgumentError")
|
||||
self.logger("ERROR", "openapi", f"hmi: switch_tp_mode 参数错误{mode}, 非法参数,只接受 with/without", "red")
|
||||
|
||||
@property
|
||||
def get_tp_mode(self): # OK
|
||||
@ -1772,8 +1738,6 @@ class HmiRequest(QThread):
|
||||
|
||||
|
||||
class ExternalCommunication(QThread):
|
||||
output = Signal(str, str)
|
||||
|
||||
def __init__(self, ip, port, /):
|
||||
super().__init__()
|
||||
self.c = None
|
||||
@ -1781,34 +1745,32 @@ class ExternalCommunication(QThread):
|
||||
self.port = int(port)
|
||||
self.suffix = "\r"
|
||||
self.exec_desc = " :--: 返回 true 表示执行成功,false 失败"
|
||||
|
||||
def logger(self, level, module, content, color="black", error="", flag="both"):
|
||||
flag = "cursor" if level.upper() == "DEBUG" else "both"
|
||||
clibs.logger(level, module, content, color, flag, signal=self.output)
|
||||
if level.upper() == "ERROR":
|
||||
raise Exception(f"{error} | {content}")
|
||||
self.logger = clibs.logger
|
||||
|
||||
def net_conn(self):
|
||||
# clibs.c_hr.execution("socket.set_params", enable=False, ip="0.0.0.0", port=str(self.port), suffix="\r", type=1)
|
||||
# time.sleep(clibs.INTERVAL)
|
||||
clibs.c_hr.execution("socket.set_params", enable=False, ip="0.0.0.0", port=str(self.port), suffix="\r", type=1)
|
||||
time.sleep(clibs.INTERVAL)
|
||||
clibs.c_hr.execution("socket.set_params", enable=True, ip="0.0.0.0", port=str(self.port), suffix="\r", type=1)
|
||||
time.sleep(clibs.INTERVAL*2)
|
||||
# time.sleep(clibs.INTERVAL*2)
|
||||
self.c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.c.settimeout(clibs.INTERVAL*5)
|
||||
try:
|
||||
self.c.connect((self.ip, self.port))
|
||||
self.logger("INFO", "openapi", f"ec: 外部通信连接成功...", "green")
|
||||
return self.c
|
||||
# return self.c
|
||||
except Exception as err:
|
||||
self.logger("ERROR", "openapi", f"ec: 外部通信连接失败... {err}", "red", "EcConnFailed")
|
||||
self.logger("ERROR", "openapi", f"ec: 外部通信连接失败... {err}", "red")
|
||||
|
||||
def close(self):
|
||||
if clibs.status["ec"]:
|
||||
try:
|
||||
self.c.close()
|
||||
self.logger("INFO", "openapi", f"ec: 关闭外部通信连接成功", "green")
|
||||
except Exception as err:
|
||||
self.logger("ERROR", "openapi", f"ec: 关闭外部通信连接失败:{err}", "red", "EcCloseFailed")
|
||||
self.c.close()
|
||||
self.logger("INFO", "openapi", f"ec: 关闭外部通信连接成功", "green")
|
||||
|
||||
@clibs.db_lock
|
||||
def sr_string(self, directive, interval=clibs.INTERVAL/2):
|
||||
self.s_string(directive)
|
||||
time.sleep(interval)
|
||||
result = self.r_string(directive)
|
||||
return result
|
||||
|
||||
def s_string(self, directive):
|
||||
order = "".join([directive, self.suffix])
|
||||
@ -1820,7 +1782,7 @@ class ExternalCommunication(QThread):
|
||||
try:
|
||||
char = self.c.recv(1).decode(encoding="unicode_escape")
|
||||
except Exception as err:
|
||||
self.logger("ERROR", "openapi", f"ec: 获取请求指令 {directive} 的返回数据超时,需确认指令发送格式以及内容正确!具体报错信息如下 {err}", "red", "RecvMsgFailed")
|
||||
self.logger("ERROR", "openapi", f"ec: 获取请求指令 {directive} 的返回数据超时,需确认指令发送格式以及内容正确!具体报错信息如下 {err}", "red")
|
||||
result = "".join([result, char])
|
||||
return result
|
||||
|
||||
@ -2057,7 +2019,7 @@ class ExternalCommunication(QThread):
|
||||
|
||||
def __exec_cmd(self, directive, description, more_desc=""):
|
||||
self.s_string(directive)
|
||||
time.sleep(clibs.INTERVAL)
|
||||
time.sleep(clibs.INTERVAL/2)
|
||||
result = self.r_string(directive).strip()
|
||||
self.logger("DEBUG", "openapi", f"ec: 执行{description}指令是 {directive},返回值为 {result}{more_desc}")
|
||||
return result
|
||||
@ -2082,7 +2044,6 @@ class PreDos(object):
|
||||
print(f"predos: SSH 无法连接到 {self.ip}:{self.ssh_port},需检查网络连通性或者登录信息是否正确 {err}")
|
||||
raise Exception("SshConnFailed")
|
||||
|
||||
@clibs.handle_exception
|
||||
def push_prj_to_server(self, prj_file):
|
||||
# prj_file:本地工程完整路径
|
||||
self.__ssh2server()
|
||||
@ -2151,7 +2112,6 @@ class PreDos(object):
|
||||
|
||||
class RobotInit(object):
|
||||
@staticmethod
|
||||
@clibs.handle_exception
|
||||
def modbus_init():
|
||||
clibs.c_pd = PreDos(clibs.ip_addr, clibs.ssh_port, clibs.username, clibs.password)
|
||||
# 推送配置文件
|
||||
|
Reference in New Issue
Block a user