diff --git a/mcsmapi/__init__.py b/mcsmapi/__init__.py index 75217d8..ca57776 100644 --- a/mcsmapi/__init__.py +++ b/mcsmapi/__init__.py @@ -1,5 +1,4 @@ import urllib.parse -from mcsmapi.models.overview import OverviewModel from mcsmapi.pool import ApiPool from mcsmapi.apis.file import File from mcsmapi.apis.user import User @@ -11,12 +10,12 @@ class MCSMAPI: + def __init__(self, url: str, timeout: int = 5) -> None: split_url = urllib.parse.urlsplit(url) Request.set_mcsm_url( urllib.parse.urljoin(f"{split_url.scheme}://{split_url.netloc}", "") ) - self.authentication = None Request.set_timeout(timeout) def login(self, username: str, password: str) -> "MCSMAPI": @@ -35,8 +34,8 @@ def login_with_apikey(self, apikey: str): self.authentication = "apikey" return self - def overview(self) -> OverviewModel: - return Overview.init() + def overview(self) -> Overview: + return Overview() def instance(self) -> Instance: return Instance() diff --git a/mcsmapi/apis/file.py b/mcsmapi/apis/file.py index da35278..7750df0 100644 --- a/mcsmapi/apis/file.py +++ b/mcsmapi/apis/file.py @@ -1,6 +1,6 @@ from mcsmapi.pool import ApiPool from mcsmapi.request import Request, send, upload -from mcsmapi.models.file import CommonConfig, FileList +from mcsmapi.models.file import FileDownloadConfig, FileList import urllib.parse import os @@ -103,7 +103,7 @@ def download(daemonId: str, uuid: str, file_name: str) -> str: f"{ApiPool.FILE}/download", params={"daemonId": daemonId, "uuid": uuid, "file_name": file_name}, ) - result = CommonConfig(**result) + result = FileDownloadConfig(**result) protocol = Request.mcsm_url.split("://")[0] base_url = urllib.parse.urljoin(f"{protocol}://{result.addr}", "download") return urllib.parse.urljoin(base_url, f"{result.password}/{file_name}") @@ -127,7 +127,7 @@ async def upload(daemonId: str, uuid: str, file: bytes, upload_dir: str) -> bool f"{ApiPool.FILE}/upload", params={"daemonId": daemonId, "uuid": uuid, "upload_dir": upload_dir}, ) - result = CommonConfig(**result) + result = FileDownloadConfig(**result) protocol = Request.mcsm_url.split("://")[0] base_url = urllib.parse.urljoin(f"{protocol}://{result.addr}", "upload") final_url = urllib.parse.urljoin(base_url, result.password) diff --git a/mcsmapi/apis/overview.py b/mcsmapi/apis/overview.py index 5999b3b..9f5f016 100644 --- a/mcsmapi/apis/overview.py +++ b/mcsmapi/apis/overview.py @@ -1,18 +1,21 @@ from mcsmapi.pool import ApiPool from mcsmapi.request import send -from mcsmapi.models.overview import OverviewModel +from mcsmapi.models.overview import OverviewModel, LogDetail class Overview: @staticmethod - def init(): + def overview(): """ - 初始化方法,用于获取API概览信息并构建概览模型。 - - 本方法通过发送GET请求获取API概览信息,确保返回的数据类型为字典, - 然后使用这些数据来构建一个OverviewModel实例。 - - :return: 返回一个OverviewModel实例,该实例使用获取的API概览信息进行初始化。 + 获取面板基本信息 """ result = send("GET", ApiPool.OVERVIEW) return OverviewModel(**result) + + @staticmethod + def logs(): + """ + 获取面板操作日志 + """ + result = send("GET", ApiPool.LOG) + return [LogDetail(**item) for item in result] diff --git a/mcsmapi/models/common.py b/mcsmapi/models/common.py index 205d724..8bc1969 100644 --- a/mcsmapi/models/common.py +++ b/mcsmapi/models/common.py @@ -4,27 +4,27 @@ class CpuMemChart(BaseModel): """节点资源使用率信息""" - cpu: float = 0 + cpu: float """cpu使用率""" - mem: float = 0 + mem: float """内存使用率""" class ProcessInfo(BaseModel): """节点进程详细信息""" - cpu: int = 0 + cpu: int """远程节点使用的cpu资源(单位: byte)""" - memory: int = 0 + memory: int """远程节点使用的内存资源(单位: byte)""" - cwd: str = "" + cwd: str """远程节点的工作路径""" class InstanceInfo(BaseModel): """实例统计信息""" - running: int = 0 + running: int """运行中实例数量""" - total: int = 0 + total: int """全部实例数量""" diff --git a/mcsmapi/models/daemon.py b/mcsmapi/models/daemon.py index 09f7bbd..0666148 100644 --- a/mcsmapi/models/daemon.py +++ b/mcsmapi/models/daemon.py @@ -7,59 +7,59 @@ class SystemInfo(BaseModel): """节点系统信息""" - type: str = "" + type: str """系统类型""" - hostname: str = "" + hostname: str """主机名""" - platform: str = "" + platform: str """平台架构""" - release: str = "" + release: str """系统版本""" - uptime: float = 0 + uptime: float """系统运行时间(单位: sec)""" - cwd: str = "" + cwd: str """远程节点运行路径""" - loadavg: list[float] = [] + loadavg: list[float] """系统负载平均值(仅适用于 Linux 和 macOS),表示过去 **1 分钟、5 分钟、15 分钟** 内的 CPU 负载情况""" - freemem: int = 0 + freemem: int """可用内存(单位: byte)""" - cpuUsage: float = 0 + cpuUsage: float """cpu使用率""" - memUsage: float = 0 + memUsage: float """内存使用率""" - totalmem: int = 0 + totalmem: int """内存总量(单位: byte)""" - processCpu: int = 0 + processCpu: int """未知""" - processMem: int = 0 + processMem: int """未知""" class DaemonModel(BaseModel): """节点详细信息""" - version: str = "" + version: str """远程节点版本""" - process: ProcessInfo = ProcessInfo() + process: ProcessInfo """远程节点的基本信息""" - instance: InstanceInfo = InstanceInfo() + instance: InstanceInfo """远程节点实例基本信息""" - system: SystemInfo = SystemInfo() + system: SystemInfo """远程节点系统信息""" - cpuMemChart: list[CpuMemChart] = [] + cpuMemChart: list[CpuMemChart] """cpu和内存使用趋势""" - uuid: str = "" + uuid: str """远程节点的uuid""" - ip: str = "" + ip: str """远程节点的ip""" - port: int = 24444 + port: int """远程节点的端口""" - prefix: str = "" + prefix: str """远程节点的路径前缀""" - available: bool = False + available: bool """远程节点的可用状态""" - remarks: str = "" - """远程节点的备注""" + remarks: str + """远程节点的名称""" def delete(self) -> bool: """ diff --git a/mcsmapi/models/file.py b/mcsmapi/models/file.py index 1da01c5..1c3e45f 100644 --- a/mcsmapi/models/file.py +++ b/mcsmapi/models/file.py @@ -11,15 +11,15 @@ class FileType(IntEnum): class FileItem(BaseModel): """文件信息""" - name: str = "" + name: str """文件名称""" - size: int = 0 + size: int """文件大小(单位: byte)""" - time: str = "" + time: str """文件修改时间""" - mode: int = 777 + mode: int """文件操作权限(仅适用于Linux)""" - type: FileType = FileType.FOLDER + type: FileType """文件类型""" daemonId: str = "" """远程节点uuid""" @@ -27,7 +27,7 @@ class FileItem(BaseModel): """实例的uiid""" target: str = "" """文件所在路径""" - file_name: str = "" + file_name: str """当前文件列表过滤条件""" def rename(self, newName: str) -> bool: @@ -160,19 +160,19 @@ class FileList(BaseModel): items: list[FileItem] """文件信息列表""" - page: int = 0 + page: int """当前页数""" - pageSize: int = 100 + pageSize: int """文件列表单页大小""" - total: int = 0 + total: int """总页数""" - absolutePath: str = "\\" + absolutePath: str """当前路径在远程节点的绝对路径""" - daemonId: str = "" + daemonId: str """远程节点uuid""" - uuid: str = "" + uuid: str """实例uuid""" - target: str = "" + target: str """文件(名称或目录)路径""" def __init__(self, **data: str): @@ -226,9 +226,9 @@ def createFolder(self, target: str) -> bool: return File.createFolder(self.daemonId, self.uuid, target) -class CommonConfig(BaseModel): +class FileDownloadConfig(BaseModel): - password: str = "" + password: str """文件下载密码""" - addr: str = "" + addr: str """文件下载地址""" diff --git a/mcsmapi/models/instance.py b/mcsmapi/models/instance.py index 539705e..ba43208 100644 --- a/mcsmapi/models/instance.py +++ b/mcsmapi/models/instance.py @@ -111,27 +111,27 @@ class InstanceConfig(BaseModel): class ProcessInfo(BaseModel): """进程信息""" - cpu: int = 0 + cpu: int """CPU 使用率 (单位: %)""" - memory: int = 0 + memory: int """进程占用内存 (单位: KB)""" - ppid: int = 0 + ppid: int """父进程 ID""" - pid: int = 0 + pid: int """进程 ID""" - ctime: int = 0 + ctime: int """进程创建时间 (Unix 时间戳)""" - elapsed: int = 0 + elapsed: int """进程运行时长 (单位: 秒)""" - timestamp: int = 0 + timestamp: int """时间戳""" class InstanceInfo(BaseModel): """实例运行状态信息(这些选项在新版中已不再支持设置,但仍在API中返回)""" - currentPlayers: int = -1 - """当前玩家数量 (-1 表示未知)""" + currentPlayers: int + """当前玩家数量""" fileLock: int = 0 """文件锁状态 (0: 无锁)""" maxPlayers: int = -1 @@ -147,21 +147,19 @@ class InstanceInfo(BaseModel): class InstanceDetail(BaseModel): """实例详细信息""" - config: InstanceConfig = InstanceConfig() + config: InstanceConfig """实例的配置信息""" - info: InstanceInfo = InstanceInfo() + info: InstanceInfo """实例的运行状态信息""" - daemonId: str = "" + daemonId: str """所属的守护进程 (Daemon) ID""" - instanceUuid: str = "" + instanceUuid: str """实例唯一标识符 (UUID)""" - processInfo: ProcessInfo = ProcessInfo() + processInfo: ProcessInfo """实例的进程信息""" - space: int = 0 - """实例的存储空间大小(已弃用)""" - started: int = 0 + started: int """实例的启动次数""" - status: Status = Status.STOP + status: Status """实例状态""" def start(self) -> str | bool: diff --git a/mcsmapi/models/overview.py b/mcsmapi/models/overview.py index 344e1f5..fc92357 100644 --- a/mcsmapi/models/overview.py +++ b/mcsmapi/models/overview.py @@ -1,3 +1,5 @@ +from enum import Enum +from typing import Literal from pydantic import BaseModel from mcsmapi.models.common import CpuMemChart, InstanceInfo, ProcessInfo from mcsmapi.models.daemon import DaemonModel @@ -62,41 +64,107 @@ class RecordInfo(BaseModel): """登录失败次数""" - class ChartInfo(BaseModel): """图表数据信息""" - system: list[CpuMemChart] = [] + system: list[CpuMemChart] """系统统计信息""" - request: list[InstanceInfo] = [] + request: list[InstanceInfo] """实例统计信息""" class RemoteCountInfo(BaseModel): """远程守护进程统计信息""" - total: int = 0 + total: int """远程守护进程总数""" - available: int = 0 + available: int """可用的远程守护进程数量""" class OverviewModel(BaseModel): """系统概览信息""" - version: str = "" + version: str """系统当前版本""" - specifiedDaemonVersion: str = "" + specifiedDaemonVersion: str """指定的守护进程 (Daemon) 版本""" - system: SystemInfo = SystemInfo() + system: SystemInfo """系统信息""" - record: RecordInfo = RecordInfo() + record: RecordInfo """安全访问记录""" - process: ProcessInfo = ProcessInfo() + process: ProcessInfo """进程状态信息""" - chart: ChartInfo = ChartInfo() + chart: ChartInfo """系统与请求统计图表数据""" - remoteCount: RemoteCountInfo = RemoteCountInfo() + remoteCount: RemoteCountInfo """远程守护进程统计信息""" - remote: list[DaemonModel] = [] + remote: list[DaemonModel] """远程守护进程详细信息""" + + +class LogType(Enum): + """操作类型""" + + # 系统相关 + SYSTEM_CONFIG_CHANGE = "system_config_change" + + # 用户相关 + USER_LOGIN = "user_login" + USER_CONFIG_CHANGE = "user_config_change" + USER_DELETE = "user_delete" + USER_CREATE = "user_create" + + # 守护进程相关 + DAEMON_CONFIG_CHANGE = "daemon_config_change" + DAEMON_REMOVE = "daemon_remove" + DAEMON_CREATE = "daemon_create" + + # 实例任务相关 + INSTANCE_TASK_DELETE = "instance_task_delete" + INSTANCE_TASK_CREATE = "instance_task_create" + + # 实例文件相关 + INSTANCE_FILE_DELETE = "instance_file_delete" + INSTANCE_FILE_DOWNLOAD = "instance_file_download" + INSTANCE_FILE_UPDATE = "instance_file_update" + INSTANCE_FILE_UPLOAD = "instance_file_upload" + + # 实例操作相关 + INSTANCE_DELETE = "instance_delete" + INSTANCE_CREATE = "instance_create" + INSTANCE_CONFIG_CHANGE = "instance_config_change" + INSTANCE_KILL = "instance_kill" + INSTANCE_UPDATE = "instance_update" + INSTANCE_RESTART = "instance_restart" + INSTANCE_STOP = "instance_stop" + INSTANCE_START = "instance_start" + + +class LogDetail(BaseModel): + """操作日志详情""" + + operation_id: str + """操作者uuid""" + operator_name: str | None = None + """操作者用户名""" + operation_time: str + """操作时间戳""" + operator_ip: str + """操作者ip""" + operation_level: Literal["info", "warning", "error", "unknown"] + """日志等级""" + type: LogType + """操作类型""" + instance_name: str | None = None + """实例名称(仅实例事件存在)""" + instance_id: str | None = None + """实例ID(仅实例事件存在)""" + daemon_id: str | None = None + """守护进程ID(仅实例事件和守护进程事件存在)""" + login_result: bool | None = None + """登录结果(仅登录事件存在)""" + file: str | None = None + """文件名(仅文件操作事件存在)""" + task_name: str | None = None + """任务名称(仅任务操作事件存在)""" diff --git a/mcsmapi/pool.py b/mcsmapi/pool.py index 44e69d4..5cba869 100644 --- a/mcsmapi/pool.py +++ b/mcsmapi/pool.py @@ -12,3 +12,4 @@ def __str__(self): SERVICE = "api/service" FILE = "api/files" IMAGE = "api/environment" + LOG = "api/overview/operation_logs" diff --git a/mcsmapi/request.py b/mcsmapi/request.py index fc92ca3..62dd5c8 100644 --- a/mcsmapi/request.py +++ b/mcsmapi/request.py @@ -1,59 +1,65 @@ from typing import Any import requests import urllib.parse + +from mcsmapi.pool import ApiPool from .exceptions import MCSMError class Request: - mcsm_url = "" - timeout = 5 + mcsm_url: str = "" + timeout: int = 5 session = requests.Session() - apikey = None - token = None + apikey: str | None = None + token: str | None = None @classmethod - def set_mcsm_url(cls, url): + def set_mcsm_url(cls, url: str): """设置类级别的 mcsm_url""" cls.mcsm_url = url @classmethod - def set_timeout(cls, timeout): + def set_timeout(cls, timeout: int): """设置类级别的 timeout""" cls.timeout = timeout @classmethod - def set_apikey(cls, apikey): + def set_apikey(cls, apikey: str): """设置类级别的 apikey""" cls.apikey = apikey @classmethod - def set_token(cls, token): + def set_token(cls, token: str): """设置类级别的 token""" cls.token = token @classmethod - def __init__(cls, mcsm_url=None, timeout=None): + def __init__(cls, mcsm_url: str | None = None, timeout: int | None = None): """初始化时使用类变量,或者使用传入的参数覆盖默认值""" cls.mcsm_url = mcsm_url or cls.mcsm_url cls.timeout = timeout or cls.timeout @classmethod - def send(cls, method: str, endpoint: Any, params=None, data=None) -> Any: + def send( + cls, + method: str, + endpoint: str | ApiPool, + params: dict | None = None, + data: Any | None = None, + ) -> Any: """发送 HTTP 请求""" if params is None: params = {} if data is None: data = {} - if not isinstance(endpoint, str): - endpoint = str(endpoint) + if isinstance(endpoint, ApiPool): + endpoint = endpoint.value url = urllib.parse.urljoin(cls.mcsm_url, endpoint) if cls.apikey is not None: params["apikey"] = cls.apikey - data["apikey"] = cls.apikey if cls.token is not None: params["token"] = cls.token - data["token"] = cls.token response = cls.session.request( method.upper(),