393 lines
12 KiB
Swift
393 lines
12 KiB
Swift
//
|
|
// APIv2_12.swift
|
|
// iKeyMon
|
|
//
|
|
// Created by tracer on 13.11.25.
|
|
//
|
|
|
|
|
|
import Foundation
|
|
|
|
extension APIv2_12 {
|
|
struct Load: Codable {
|
|
let current: LoadMetrics
|
|
let historical: [LoadMetrics]
|
|
|
|
struct LoadMetrics: Codable {
|
|
let oneMinute: Double
|
|
let fiveMinute: Double
|
|
let fifteenMinute: Double
|
|
let timestamp: Date
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case oneMinute = "load_1"
|
|
case fiveMinute = "load_5"
|
|
case fifteenMinute = "load_15"
|
|
case timestamp
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Memory: Codable {
|
|
let system: SystemMemory
|
|
let swap: SwapMemory?
|
|
|
|
struct SystemMemory: Codable {
|
|
let total: Int64
|
|
let used: Int64
|
|
let free: Int64
|
|
let available: Int64
|
|
let buffers: Int64?
|
|
let cached: Int64?
|
|
}
|
|
|
|
struct SwapMemory: Codable {
|
|
let total: Int64
|
|
let used: Int64
|
|
let free: Int64
|
|
}
|
|
}
|
|
|
|
struct Utilization: Codable {
|
|
let cpu: CPUUtilization
|
|
let memory: MemoryUtilization
|
|
let disk: [DiskUtilization]
|
|
let network: [NetworkUtilization]?
|
|
|
|
struct CPUUtilization: Codable {
|
|
let overall: Double
|
|
let cores: [Double]
|
|
let processes: [ProcessInfo]?
|
|
|
|
struct ProcessInfo: Codable {
|
|
let pid: Int
|
|
let name: String
|
|
let cpuPercent: Double
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case pid
|
|
case name
|
|
case cpuPercent = "cpu_percent"
|
|
}
|
|
}
|
|
}
|
|
|
|
struct MemoryUtilization: Codable {
|
|
let percent: Double
|
|
let topProcesses: [ProcessMemoryInfo]?
|
|
|
|
struct ProcessMemoryInfo: Codable {
|
|
let pid: Int
|
|
let name: String
|
|
let memoryMB: Double
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case pid
|
|
case name
|
|
case memoryMB = "memory_mb"
|
|
}
|
|
}
|
|
}
|
|
|
|
struct DiskUtilization: Codable {
|
|
let device: String
|
|
let mountpoint: String
|
|
let usedPercent: Double
|
|
let totalBytes: Int64
|
|
let usedBytes: Int64
|
|
let freeBytes: Int64
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case device
|
|
case mountpoint
|
|
case usedPercent = "used_percent"
|
|
case totalBytes = "total_bytes"
|
|
case usedBytes = "used_bytes"
|
|
case freeBytes = "free_bytes"
|
|
}
|
|
}
|
|
|
|
struct NetworkUtilization: Codable {
|
|
let interface: String
|
|
let bytesIn: Int64
|
|
let bytesOut: Int64
|
|
let packetsIn: Int64
|
|
let packetsOut: Int64
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case interface
|
|
case bytesIn = "bytes_in"
|
|
case bytesOut = "bytes_out"
|
|
case packetsIn = "packets_in"
|
|
case packetsOut = "packets_out"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class APIv2_12: BaseAPIClient, ServerAPIProtocol {
|
|
typealias LoadType = APIv2_12.Load
|
|
typealias MemoryType = APIv2_12.Memory
|
|
typealias UtilizationType = APIv2_12.Utilization
|
|
|
|
private enum Endpoint: String {
|
|
case systemInfo = "/api/v2.12/system/info"
|
|
case load = "/api/v2.12/metrics/load"
|
|
case memory = "/api/v2.12/metrics/memory"
|
|
case utilization = "/api/v2.12/metrics/utilization"
|
|
|
|
func url(baseURL: URL) -> URL {
|
|
return baseURL.appendingPathComponent(self.rawValue)
|
|
}
|
|
}
|
|
|
|
func fetchSystemInfo() async throws -> SystemInfo {
|
|
let url = Endpoint.systemInfo.url(baseURL: baseURL)
|
|
let request = URLRequest(url: url)
|
|
return try await performRequest(request, responseType: SystemInfo.self)
|
|
}
|
|
|
|
func fetchLoad() async throws -> LoadType {
|
|
let url = Endpoint.load.url(baseURL: baseURL)
|
|
var request = URLRequest(url: url)
|
|
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
return try await performRequest(request, responseType: LoadType.self)
|
|
}
|
|
|
|
func fetchMemory() async throws -> MemoryType {
|
|
let url = Endpoint.memory.url(baseURL: baseURL)
|
|
var request = URLRequest(url: url)
|
|
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
return try await performRequest(request, responseType: MemoryType.self)
|
|
}
|
|
|
|
func fetchUtilization() async throws -> UtilizationType {
|
|
let url = Endpoint.utilization.url(baseURL: baseURL)
|
|
var request = URLRequest(url: url)
|
|
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
return try await performRequest(request, responseType: UtilizationType.self)
|
|
}
|
|
|
|
func fetchServerSummary(apiKey: String) async throws -> ServerInfo {
|
|
let summaryURL = baseURL.appendingPathComponent("api/v2/server")
|
|
var request = URLRequest(url: summaryURL)
|
|
request.httpMethod = "GET"
|
|
request.setValue(apiKey, forHTTPHeaderField: "X-API-KEY")
|
|
request.timeoutInterval = 30
|
|
|
|
let (data, response) = try await session.data(for: request)
|
|
guard let httpResponse = response as? HTTPURLResponse else {
|
|
throw APIError.invalidResponse
|
|
}
|
|
|
|
guard httpResponse.statusCode == 200 else {
|
|
throw APIError.httpError(httpResponse.statusCode)
|
|
}
|
|
|
|
let decoder = JSONDecoder()
|
|
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
|
let envelope = try decoder.decode(ServerSummaryEnvelope.self, from: data)
|
|
return envelope.toDomain()
|
|
}
|
|
}
|
|
|
|
// MARK: - Server Summary Mapping
|
|
|
|
private extension APIv2_12 {
|
|
struct ServerSummaryEnvelope: Decodable {
|
|
let meta: Meta
|
|
let operatingSystem: OperatingSystem?
|
|
let utilization: Utilization
|
|
let components: Components
|
|
let ports: [Port]?
|
|
let additionalPhpInterpreters: [AdditionalInterpreter]?
|
|
|
|
struct Meta: Decodable {
|
|
struct Uptime: Decodable {
|
|
let days: Int
|
|
let hours: Int
|
|
let minutes: Int
|
|
let seconds: Int
|
|
|
|
var formatted: String {
|
|
"\(days) Days / \(hours) Hours / \(minutes) Minutes / \(seconds) Seconds"
|
|
}
|
|
}
|
|
|
|
let hostname: String
|
|
let ipAddresses: [String]
|
|
let serverTime: String
|
|
let uptime: Uptime
|
|
let panelVersion: String
|
|
let panelBuild: Int
|
|
let apiVersion: String
|
|
}
|
|
|
|
struct Utilization: Decodable {
|
|
struct Load: Decodable {
|
|
let minute1: Double
|
|
let minute5: Double
|
|
let minute15: Double
|
|
let cpuCount: Int
|
|
let percent: Double
|
|
let level: String
|
|
}
|
|
|
|
struct Memory: Decodable {
|
|
let free: Int
|
|
let used: Int
|
|
let total: Int
|
|
let percent: Double
|
|
}
|
|
|
|
struct Disk: Decodable {
|
|
let free: Int
|
|
let used: Int
|
|
let total: Int
|
|
let percent: Double
|
|
}
|
|
|
|
let processCount: Int
|
|
let load: Load
|
|
let diskSpace: Disk
|
|
let memory: Memory
|
|
let swap: Memory
|
|
}
|
|
|
|
struct Components: Decodable {
|
|
let apache: String
|
|
let php: String
|
|
let mysql: String?
|
|
let mariadb: String?
|
|
}
|
|
|
|
struct Port: Decodable {
|
|
let service: String
|
|
let status: String
|
|
let port: Int
|
|
let proto: String
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case service
|
|
case status
|
|
case port
|
|
case proto = "protocol"
|
|
}
|
|
}
|
|
|
|
struct AdditionalInterpreter: Decodable {
|
|
let version: String
|
|
let path: String?
|
|
let configFile: String?
|
|
}
|
|
|
|
struct OperatingSystem: Decodable {
|
|
struct Updates: Decodable {
|
|
let updateCount: Int
|
|
let securityUpdateCount: Int
|
|
let rebootRequired: Bool
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case updateCount = "update_count"
|
|
case securityUpdateCount = "security_update_count"
|
|
case rebootRequired = "reboot_required"
|
|
}
|
|
}
|
|
|
|
let label: String
|
|
let distribution: String
|
|
let version: String
|
|
let architecture: String
|
|
let endOfLife: Bool
|
|
let updates: Updates?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case label
|
|
case distribution
|
|
case version
|
|
case architecture
|
|
case endOfLife = "end_of_life"
|
|
case updates
|
|
}
|
|
}
|
|
|
|
func toDomain() -> ServerInfo {
|
|
let load = utilization.load
|
|
let disk = utilization.diskSpace
|
|
let memory = utilization.memory
|
|
let swapMemory = utilization.swap
|
|
|
|
return ServerInfo(
|
|
hostname: meta.hostname,
|
|
ipAddresses: meta.ipAddresses,
|
|
cpuCores: load.cpuCount,
|
|
serverTime: meta.serverTime,
|
|
uptime: meta.uptime.formatted,
|
|
processCount: utilization.processCount,
|
|
apacheVersion: components.apache,
|
|
phpVersion: components.php,
|
|
mysqlVersion: components.mysql,
|
|
mariadbVersion: components.mariadb,
|
|
operatingSystem: operatingSystem.map {
|
|
ServerInfo.OperatingSystem(
|
|
label: $0.label,
|
|
distribution: $0.distribution,
|
|
version: $0.version,
|
|
architecture: $0.architecture,
|
|
endOfLife: $0.endOfLife,
|
|
updates: $0.updates.map {
|
|
ServerInfo.OperatingSystem.UpdateStatus(
|
|
updateCount: $0.updateCount,
|
|
securityUpdateCount: $0.securityUpdateCount,
|
|
rebootRequired: $0.rebootRequired
|
|
)
|
|
}
|
|
)
|
|
},
|
|
ports: ports?.map {
|
|
ServerInfo.ServicePort(service: $0.service, status: $0.status, port: $0.port, proto: $0.proto)
|
|
},
|
|
load: ServerInfo.Load(
|
|
minute1: load.minute1,
|
|
minute5: load.minute5,
|
|
minute15: load.minute15,
|
|
percent: load.percent,
|
|
cpuCount: load.cpuCount,
|
|
level: load.level
|
|
),
|
|
memory: ServerInfo.Memory(
|
|
free: memory.free,
|
|
used: memory.used,
|
|
total: memory.total,
|
|
percent: memory.percent
|
|
),
|
|
swap: ServerInfo.Memory(
|
|
free: swapMemory.free,
|
|
used: swapMemory.used,
|
|
total: swapMemory.total,
|
|
percent: swapMemory.percent
|
|
),
|
|
diskSpace: ServerInfo.DiskSpace(
|
|
free: disk.free,
|
|
used: disk.used,
|
|
total: disk.total,
|
|
percent: disk.percent
|
|
),
|
|
panelVersion: meta.panelVersion,
|
|
panelBuild: String(meta.panelBuild),
|
|
apiVersion: meta.apiVersion,
|
|
additionalPHPInterpreters: additionalPhpInterpreters?.map {
|
|
ServerInfo.PHPInterpreter(
|
|
version: $0.version,
|
|
path: $0.path,
|
|
configFile: $0.configFile,
|
|
extensions: [],
|
|
memoryLimit: nil,
|
|
maxExecutionTime: nil
|
|
)
|
|
}
|
|
)
|
|
}
|
|
}
|
|
}
|