mirror of
https://github.com/unraid/api.git
synced 2026-05-19 23:48:42 -05:00
feat: extract CPU and memory metrics from info resolver and move to metrics resolver (#1594)
- also enables subscription resolution for CPU and memory usage
This commit is contained in:
+583
-255
@@ -940,255 +940,6 @@ enum ThemeName {
|
||||
white
|
||||
}
|
||||
|
||||
type InfoApps implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""How many docker containers are installed"""
|
||||
installed: Int!
|
||||
|
||||
"""How many docker containers are running"""
|
||||
started: Int!
|
||||
}
|
||||
|
||||
type Baseboard implements Node {
|
||||
id: PrefixedID!
|
||||
manufacturer: String!
|
||||
model: String
|
||||
version: String
|
||||
serial: String
|
||||
assetTag: String
|
||||
}
|
||||
|
||||
type InfoCpu implements Node {
|
||||
id: PrefixedID!
|
||||
manufacturer: String!
|
||||
brand: String!
|
||||
vendor: String!
|
||||
family: String!
|
||||
model: String!
|
||||
stepping: Int!
|
||||
revision: String!
|
||||
voltage: String
|
||||
speed: Float!
|
||||
speedmin: Float!
|
||||
speedmax: Float!
|
||||
threads: Int!
|
||||
cores: Int!
|
||||
processors: Int!
|
||||
socket: String!
|
||||
cache: JSON!
|
||||
flags: [String!]!
|
||||
|
||||
"""CPU utilization in percent"""
|
||||
utilization: Float
|
||||
}
|
||||
|
||||
"""CPU load for a single core"""
|
||||
type CpuLoad {
|
||||
"""The total CPU load on a single core, in percent."""
|
||||
load: Float!
|
||||
|
||||
"""The percentage of time the CPU spent in user space."""
|
||||
loadUser: Float!
|
||||
|
||||
"""The percentage of time the CPU spent in kernel space."""
|
||||
loadSystem: Float!
|
||||
|
||||
"""
|
||||
The percentage of time the CPU spent on low-priority (niced) user space processes.
|
||||
"""
|
||||
loadNice: Float!
|
||||
|
||||
"""The percentage of time the CPU was idle."""
|
||||
loadIdle: Float!
|
||||
|
||||
"""The percentage of time the CPU spent servicing hardware interrupts."""
|
||||
loadIrq: Float!
|
||||
}
|
||||
|
||||
type CpuUtilization implements Node {
|
||||
id: PrefixedID!
|
||||
load: Float!
|
||||
cpus: [CpuLoad!]!
|
||||
}
|
||||
|
||||
type Gpu implements Node {
|
||||
id: PrefixedID!
|
||||
type: String!
|
||||
typeid: String!
|
||||
vendorname: String!
|
||||
productid: String!
|
||||
blacklisted: Boolean!
|
||||
class: String!
|
||||
}
|
||||
|
||||
type Pci implements Node {
|
||||
id: PrefixedID!
|
||||
type: String
|
||||
typeid: String
|
||||
vendorname: String
|
||||
vendorid: String
|
||||
productname: String
|
||||
productid: String
|
||||
blacklisted: String
|
||||
class: String
|
||||
}
|
||||
|
||||
type Usb implements Node {
|
||||
id: PrefixedID!
|
||||
name: String
|
||||
}
|
||||
|
||||
type Devices implements Node {
|
||||
id: PrefixedID!
|
||||
gpu: [Gpu!]!
|
||||
pci: [Pci!]!
|
||||
usb: [Usb!]!
|
||||
}
|
||||
|
||||
type Case implements Node {
|
||||
id: PrefixedID!
|
||||
icon: String
|
||||
url: String
|
||||
error: String
|
||||
base64: String
|
||||
}
|
||||
|
||||
type Display implements Node {
|
||||
id: PrefixedID!
|
||||
case: Case
|
||||
date: String
|
||||
number: String
|
||||
scale: Boolean
|
||||
tabs: Boolean
|
||||
users: String
|
||||
resize: Boolean
|
||||
wwn: Boolean
|
||||
total: Boolean
|
||||
usage: Boolean
|
||||
banner: String
|
||||
dashapps: String
|
||||
theme: ThemeName
|
||||
text: Boolean
|
||||
unit: Temperature
|
||||
warning: Int
|
||||
critical: Int
|
||||
hot: Int
|
||||
max: Int
|
||||
locale: String
|
||||
}
|
||||
|
||||
"""Temperature unit (Celsius or Fahrenheit)"""
|
||||
enum Temperature {
|
||||
C
|
||||
F
|
||||
}
|
||||
|
||||
type MemoryLayout implements Node {
|
||||
id: PrefixedID!
|
||||
size: BigInt!
|
||||
bank: String
|
||||
type: String
|
||||
clockSpeed: Int
|
||||
formFactor: String
|
||||
manufacturer: String
|
||||
partNum: String
|
||||
serialNum: String
|
||||
voltageConfigured: Int
|
||||
voltageMin: Int
|
||||
voltageMax: Int
|
||||
}
|
||||
|
||||
type InfoMemory implements Node {
|
||||
id: PrefixedID!
|
||||
max: BigInt!
|
||||
total: BigInt!
|
||||
free: BigInt!
|
||||
used: BigInt!
|
||||
active: BigInt!
|
||||
available: BigInt!
|
||||
buffcache: BigInt!
|
||||
swaptotal: BigInt!
|
||||
swapused: BigInt!
|
||||
swapfree: BigInt!
|
||||
layout: [MemoryLayout!]!
|
||||
}
|
||||
|
||||
type Os implements Node {
|
||||
id: PrefixedID!
|
||||
platform: String
|
||||
distro: String
|
||||
release: String
|
||||
codename: String
|
||||
kernel: String
|
||||
arch: String
|
||||
hostname: String
|
||||
codepage: String
|
||||
logofile: String
|
||||
serial: String
|
||||
build: String
|
||||
uptime: String
|
||||
}
|
||||
|
||||
type System implements Node {
|
||||
id: PrefixedID!
|
||||
manufacturer: String
|
||||
model: String
|
||||
version: String
|
||||
serial: String
|
||||
uuid: String
|
||||
sku: String
|
||||
}
|
||||
|
||||
type Versions implements Node {
|
||||
id: PrefixedID!
|
||||
kernel: String
|
||||
openssl: String
|
||||
systemOpenssl: String
|
||||
systemOpensslLib: String
|
||||
node: String
|
||||
v8: String
|
||||
npm: String
|
||||
yarn: String
|
||||
pm2: String
|
||||
gulp: String
|
||||
grunt: String
|
||||
git: String
|
||||
tsc: String
|
||||
mysql: String
|
||||
redis: String
|
||||
mongodb: String
|
||||
apache: String
|
||||
nginx: String
|
||||
php: String
|
||||
docker: String
|
||||
postfix: String
|
||||
postgresql: String
|
||||
perl: String
|
||||
python: String
|
||||
gcc: String
|
||||
unraid: String
|
||||
}
|
||||
|
||||
type Info implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Count of docker containers"""
|
||||
apps: InfoApps!
|
||||
baseboard: Baseboard!
|
||||
cpu: InfoCpu!
|
||||
devices: Devices!
|
||||
display: Display!
|
||||
|
||||
"""Machine ID"""
|
||||
machineId: PrefixedID
|
||||
memory: InfoMemory!
|
||||
os: Os!
|
||||
system: System!
|
||||
time: DateTime!
|
||||
versions: Versions!
|
||||
}
|
||||
|
||||
type ContainerPort {
|
||||
ip: String
|
||||
privatePort: Port
|
||||
@@ -1310,6 +1061,574 @@ type Flash implements Node {
|
||||
product: String!
|
||||
}
|
||||
|
||||
type InfoGpu implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""GPU type/manufacturer"""
|
||||
type: String!
|
||||
|
||||
"""GPU type identifier"""
|
||||
typeid: String!
|
||||
|
||||
"""Whether GPU is blacklisted"""
|
||||
blacklisted: Boolean!
|
||||
|
||||
"""Device class"""
|
||||
class: String!
|
||||
|
||||
"""Product ID"""
|
||||
productid: String!
|
||||
|
||||
"""Vendor name"""
|
||||
vendorname: String
|
||||
}
|
||||
|
||||
type InfoNetwork implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Network interface name"""
|
||||
iface: String!
|
||||
|
||||
"""Network interface model"""
|
||||
model: String
|
||||
|
||||
"""Network vendor"""
|
||||
vendor: String
|
||||
|
||||
"""MAC address"""
|
||||
mac: String
|
||||
|
||||
"""Virtual interface flag"""
|
||||
virtual: Boolean
|
||||
|
||||
"""Network speed"""
|
||||
speed: String
|
||||
|
||||
"""DHCP enabled flag"""
|
||||
dhcp: Boolean
|
||||
}
|
||||
|
||||
type InfoPci implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Device type/manufacturer"""
|
||||
type: String!
|
||||
|
||||
"""Type identifier"""
|
||||
typeid: String!
|
||||
|
||||
"""Vendor name"""
|
||||
vendorname: String
|
||||
|
||||
"""Vendor ID"""
|
||||
vendorid: String!
|
||||
|
||||
"""Product name"""
|
||||
productname: String
|
||||
|
||||
"""Product ID"""
|
||||
productid: String!
|
||||
|
||||
"""Blacklisted status"""
|
||||
blacklisted: String!
|
||||
|
||||
"""Device class"""
|
||||
class: String!
|
||||
}
|
||||
|
||||
type InfoUsb implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""USB device name"""
|
||||
name: String!
|
||||
|
||||
"""USB bus number"""
|
||||
bus: String
|
||||
|
||||
"""USB device number"""
|
||||
device: String
|
||||
}
|
||||
|
||||
type InfoDevices implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""List of GPU devices"""
|
||||
gpu: [InfoGpu!]
|
||||
|
||||
"""List of network interfaces"""
|
||||
network: [InfoNetwork!]
|
||||
|
||||
"""List of PCI devices"""
|
||||
pci: [InfoPci!]
|
||||
|
||||
"""List of USB devices"""
|
||||
usb: [InfoUsb!]
|
||||
}
|
||||
|
||||
type InfoDisplayCase implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Case image URL"""
|
||||
url: String!
|
||||
|
||||
"""Case icon identifier"""
|
||||
icon: String!
|
||||
|
||||
"""Error message if any"""
|
||||
error: String!
|
||||
|
||||
"""Base64 encoded case image"""
|
||||
base64: String!
|
||||
}
|
||||
|
||||
type InfoDisplay implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Case display configuration"""
|
||||
case: InfoDisplayCase!
|
||||
|
||||
"""UI theme name"""
|
||||
theme: ThemeName!
|
||||
|
||||
"""Temperature unit (C or F)"""
|
||||
unit: Temperature!
|
||||
|
||||
"""Enable UI scaling"""
|
||||
scale: Boolean!
|
||||
|
||||
"""Show tabs in UI"""
|
||||
tabs: Boolean!
|
||||
|
||||
"""Enable UI resize"""
|
||||
resize: Boolean!
|
||||
|
||||
"""Show WWN identifiers"""
|
||||
wwn: Boolean!
|
||||
|
||||
"""Show totals"""
|
||||
total: Boolean!
|
||||
|
||||
"""Show usage statistics"""
|
||||
usage: Boolean!
|
||||
|
||||
"""Show text labels"""
|
||||
text: Boolean!
|
||||
|
||||
"""Warning temperature threshold"""
|
||||
warning: Int!
|
||||
|
||||
"""Critical temperature threshold"""
|
||||
critical: Int!
|
||||
|
||||
"""Hot temperature threshold"""
|
||||
hot: Int!
|
||||
|
||||
"""Maximum temperature threshold"""
|
||||
max: Int
|
||||
|
||||
"""Locale setting"""
|
||||
locale: String
|
||||
}
|
||||
|
||||
"""Temperature unit"""
|
||||
enum Temperature {
|
||||
CELSIUS
|
||||
FAHRENHEIT
|
||||
}
|
||||
|
||||
"""CPU load for a single core"""
|
||||
type CpuLoad {
|
||||
"""The total CPU load on a single core, in percent."""
|
||||
load: Float!
|
||||
|
||||
"""The percentage of time the CPU spent in user space."""
|
||||
loadUser: Float!
|
||||
|
||||
"""The percentage of time the CPU spent in kernel space."""
|
||||
loadSystem: Float!
|
||||
|
||||
"""
|
||||
The percentage of time the CPU spent on low-priority (niced) user space processes.
|
||||
"""
|
||||
loadNice: Float!
|
||||
|
||||
"""The percentage of time the CPU was idle."""
|
||||
loadIdle: Float!
|
||||
|
||||
"""The percentage of time the CPU spent servicing hardware interrupts."""
|
||||
loadIrq: Float!
|
||||
}
|
||||
|
||||
type CpuUtilization implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Total CPU load in percent"""
|
||||
load: Float!
|
||||
|
||||
"""CPU load for each core"""
|
||||
cpus: [CpuLoad!]!
|
||||
}
|
||||
|
||||
type InfoCpu implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""CPU manufacturer"""
|
||||
manufacturer: String
|
||||
|
||||
"""CPU brand name"""
|
||||
brand: String
|
||||
|
||||
"""CPU vendor"""
|
||||
vendor: String
|
||||
|
||||
"""CPU family"""
|
||||
family: String
|
||||
|
||||
"""CPU model"""
|
||||
model: String
|
||||
|
||||
"""CPU stepping"""
|
||||
stepping: Int
|
||||
|
||||
"""CPU revision"""
|
||||
revision: String
|
||||
|
||||
"""CPU voltage"""
|
||||
voltage: String
|
||||
|
||||
"""Current CPU speed in GHz"""
|
||||
speed: Float
|
||||
|
||||
"""Minimum CPU speed in GHz"""
|
||||
speedmin: Float
|
||||
|
||||
"""Maximum CPU speed in GHz"""
|
||||
speedmax: Float
|
||||
|
||||
"""Number of CPU threads"""
|
||||
threads: Int
|
||||
|
||||
"""Number of CPU cores"""
|
||||
cores: Int
|
||||
|
||||
"""Number of physical processors"""
|
||||
processors: Int
|
||||
|
||||
"""CPU socket type"""
|
||||
socket: String
|
||||
|
||||
"""CPU cache information"""
|
||||
cache: JSON
|
||||
|
||||
"""CPU feature flags"""
|
||||
flags: [String!]
|
||||
}
|
||||
|
||||
type MemoryLayout implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Memory module size in bytes"""
|
||||
size: BigInt!
|
||||
|
||||
"""Memory bank location (e.g., BANK 0)"""
|
||||
bank: String
|
||||
|
||||
"""Memory type (e.g., DDR4, DDR5)"""
|
||||
type: String
|
||||
|
||||
"""Memory clock speed in MHz"""
|
||||
clockSpeed: Int
|
||||
|
||||
"""Part number of the memory module"""
|
||||
partNum: String
|
||||
|
||||
"""Serial number of the memory module"""
|
||||
serialNum: String
|
||||
|
||||
"""Memory manufacturer"""
|
||||
manufacturer: String
|
||||
|
||||
"""Form factor (e.g., DIMM, SODIMM)"""
|
||||
formFactor: String
|
||||
|
||||
"""Configured voltage in millivolts"""
|
||||
voltageConfigured: Int
|
||||
|
||||
"""Minimum voltage in millivolts"""
|
||||
voltageMin: Int
|
||||
|
||||
"""Maximum voltage in millivolts"""
|
||||
voltageMax: Int
|
||||
}
|
||||
|
||||
type MemoryUtilization implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Total system memory in bytes"""
|
||||
total: BigInt!
|
||||
|
||||
"""Used memory in bytes"""
|
||||
used: BigInt!
|
||||
|
||||
"""Free memory in bytes"""
|
||||
free: BigInt!
|
||||
|
||||
"""Available memory in bytes"""
|
||||
available: BigInt!
|
||||
|
||||
"""Active memory in bytes"""
|
||||
active: BigInt!
|
||||
|
||||
"""Buffer/cache memory in bytes"""
|
||||
buffcache: BigInt!
|
||||
|
||||
"""Memory usage percentage"""
|
||||
usedPercent: Float!
|
||||
|
||||
"""Total swap memory in bytes"""
|
||||
swapTotal: BigInt!
|
||||
|
||||
"""Used swap memory in bytes"""
|
||||
swapUsed: BigInt!
|
||||
|
||||
"""Free swap memory in bytes"""
|
||||
swapFree: BigInt!
|
||||
|
||||
"""Swap usage percentage"""
|
||||
swapUsedPercent: Float!
|
||||
}
|
||||
|
||||
type InfoMemory implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Physical memory layout"""
|
||||
layout: [MemoryLayout!]!
|
||||
}
|
||||
|
||||
type InfoOs implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Operating system platform"""
|
||||
platform: String
|
||||
|
||||
"""Linux distribution name"""
|
||||
distro: String
|
||||
|
||||
"""OS release version"""
|
||||
release: String
|
||||
|
||||
"""OS codename"""
|
||||
codename: String
|
||||
|
||||
"""Kernel version"""
|
||||
kernel: String
|
||||
|
||||
"""OS architecture"""
|
||||
arch: String
|
||||
|
||||
"""Hostname"""
|
||||
hostname: String
|
||||
|
||||
"""Fully qualified domain name"""
|
||||
fqdn: String
|
||||
|
||||
"""OS build identifier"""
|
||||
build: String
|
||||
|
||||
"""Service pack version"""
|
||||
servicepack: String
|
||||
|
||||
"""Boot time ISO string"""
|
||||
uptime: String
|
||||
|
||||
"""OS logo name"""
|
||||
logofile: String
|
||||
|
||||
"""OS serial number"""
|
||||
serial: String
|
||||
|
||||
"""OS started via UEFI"""
|
||||
uefi: Boolean
|
||||
}
|
||||
|
||||
type InfoSystem implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""System manufacturer"""
|
||||
manufacturer: String
|
||||
|
||||
"""System model"""
|
||||
model: String
|
||||
|
||||
"""System version"""
|
||||
version: String
|
||||
|
||||
"""System serial number"""
|
||||
serial: String
|
||||
|
||||
"""System UUID"""
|
||||
uuid: String
|
||||
|
||||
"""System SKU"""
|
||||
sku: String
|
||||
|
||||
"""Virtual machine flag"""
|
||||
virtual: Boolean
|
||||
}
|
||||
|
||||
type InfoBaseboard implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Motherboard manufacturer"""
|
||||
manufacturer: String
|
||||
|
||||
"""Motherboard model"""
|
||||
model: String
|
||||
|
||||
"""Motherboard version"""
|
||||
version: String
|
||||
|
||||
"""Motherboard serial number"""
|
||||
serial: String
|
||||
|
||||
"""Motherboard asset tag"""
|
||||
assetTag: String
|
||||
|
||||
"""Maximum memory capacity in bytes"""
|
||||
memMax: Float
|
||||
|
||||
"""Number of memory slots"""
|
||||
memSlots: Float
|
||||
}
|
||||
|
||||
type InfoVersions implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Kernel version"""
|
||||
kernel: String
|
||||
|
||||
"""OpenSSL version"""
|
||||
openssl: String
|
||||
|
||||
"""System OpenSSL version"""
|
||||
systemOpenssl: String
|
||||
|
||||
"""Node.js version"""
|
||||
node: String
|
||||
|
||||
"""V8 engine version"""
|
||||
v8: String
|
||||
|
||||
"""npm version"""
|
||||
npm: String
|
||||
|
||||
"""Yarn version"""
|
||||
yarn: String
|
||||
|
||||
"""pm2 version"""
|
||||
pm2: String
|
||||
|
||||
"""Gulp version"""
|
||||
gulp: String
|
||||
|
||||
"""Grunt version"""
|
||||
grunt: String
|
||||
|
||||
"""Git version"""
|
||||
git: String
|
||||
|
||||
"""tsc version"""
|
||||
tsc: String
|
||||
|
||||
"""MySQL version"""
|
||||
mysql: String
|
||||
|
||||
"""Redis version"""
|
||||
redis: String
|
||||
|
||||
"""MongoDB version"""
|
||||
mongodb: String
|
||||
|
||||
"""Apache version"""
|
||||
apache: String
|
||||
|
||||
"""nginx version"""
|
||||
nginx: String
|
||||
|
||||
"""PHP version"""
|
||||
php: String
|
||||
|
||||
"""Postfix version"""
|
||||
postfix: String
|
||||
|
||||
"""PostgreSQL version"""
|
||||
postgresql: String
|
||||
|
||||
"""Perl version"""
|
||||
perl: String
|
||||
|
||||
"""Python version"""
|
||||
python: String
|
||||
|
||||
"""Python3 version"""
|
||||
python3: String
|
||||
|
||||
"""pip version"""
|
||||
pip: String
|
||||
|
||||
"""pip3 version"""
|
||||
pip3: String
|
||||
|
||||
"""Java version"""
|
||||
java: String
|
||||
|
||||
"""gcc version"""
|
||||
gcc: String
|
||||
|
||||
"""VirtualBox version"""
|
||||
virtualbox: String
|
||||
|
||||
"""Docker version"""
|
||||
docker: String
|
||||
|
||||
"""Unraid version"""
|
||||
unraid: String
|
||||
}
|
||||
|
||||
type Info implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Current server time"""
|
||||
time: DateTime!
|
||||
|
||||
"""Motherboard information"""
|
||||
baseboard: InfoBaseboard!
|
||||
|
||||
"""CPU information"""
|
||||
cpu: InfoCpu!
|
||||
|
||||
"""Device information"""
|
||||
devices: InfoDevices!
|
||||
|
||||
"""Display configuration"""
|
||||
display: InfoDisplay!
|
||||
|
||||
"""Machine ID"""
|
||||
machineId: ID
|
||||
|
||||
"""Memory information"""
|
||||
memory: InfoMemory!
|
||||
|
||||
"""Operating system information"""
|
||||
os: InfoOs!
|
||||
|
||||
"""System information"""
|
||||
system: InfoSystem!
|
||||
|
||||
"""Software versions"""
|
||||
versions: InfoVersions!
|
||||
}
|
||||
|
||||
type LogFile {
|
||||
"""Name of the log file"""
|
||||
name: String!
|
||||
@@ -1338,6 +1657,17 @@ type LogFileContent {
|
||||
startLine: Int
|
||||
}
|
||||
|
||||
"""System metrics including CPU and memory utilization"""
|
||||
type Metrics implements Node {
|
||||
id: PrefixedID!
|
||||
|
||||
"""Current CPU utilization metrics"""
|
||||
cpu: CpuUtilization
|
||||
|
||||
"""Current memory utilization metrics"""
|
||||
memory: MemoryUtilization
|
||||
}
|
||||
|
||||
type NotificationCounts {
|
||||
info: Int!
|
||||
warning: Int!
|
||||
@@ -1949,10 +2279,7 @@ type Query {
|
||||
"""All possible permissions for API keys"""
|
||||
apiKeyPossiblePermissions: [Permission!]!
|
||||
config: Config!
|
||||
display: Display!
|
||||
flash: Flash!
|
||||
info: Info!
|
||||
cpuUtilization: CpuUtilization!
|
||||
logFiles: [LogFile!]!
|
||||
logFile(path: String!, lines: Int, startLine: Int): LogFileContent!
|
||||
me: UserAccount!
|
||||
@@ -1980,6 +2307,7 @@ type Query {
|
||||
disks: [Disk!]!
|
||||
disk(id: PrefixedID!): Disk!
|
||||
rclone: RCloneBackupSettings!
|
||||
info: Info!
|
||||
settings: Settings!
|
||||
isSSOEnabled: Boolean!
|
||||
|
||||
@@ -1994,6 +2322,7 @@ type Query {
|
||||
|
||||
"""Validate an OIDC session token (internal use for CLI validation)"""
|
||||
validateOidcSession(token: String!): OidcSessionValidation!
|
||||
metrics: Metrics!
|
||||
upsDevices: [UPSDevice!]!
|
||||
upsDeviceById(id: String!): UPSDevice
|
||||
upsConfiguration: UPSConfiguration!
|
||||
@@ -2234,9 +2563,6 @@ input AccessUrlInput {
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
displaySubscription: Display!
|
||||
infoSubscription: Info!
|
||||
cpuUtilization: CpuUtilization!
|
||||
logFile(path: String!): LogFileContent!
|
||||
notificationAdded: Notification!
|
||||
notificationsOverview: NotificationOverview!
|
||||
@@ -2244,6 +2570,8 @@ type Subscription {
|
||||
serversSubscription: Server!
|
||||
parityHistorySubscription: ParityCheck!
|
||||
arraySubscription: UnraidArray!
|
||||
systemMetricsCpu: CpuUtilization!
|
||||
systemMetricsMemory: MemoryUtilization!
|
||||
upsUpdates: UPSDevice!
|
||||
}
|
||||
|
||||
|
||||
@@ -399,16 +399,6 @@ export enum AuthorizationRuleMode {
|
||||
OR = 'OR'
|
||||
}
|
||||
|
||||
export type Baseboard = Node & {
|
||||
__typename?: 'Baseboard';
|
||||
assetTag?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
manufacturer: Scalars['String']['output'];
|
||||
model?: Maybe<Scalars['String']['output']>;
|
||||
serial?: Maybe<Scalars['String']['output']>;
|
||||
version?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Capacity = {
|
||||
__typename?: 'Capacity';
|
||||
/** Free capacity */
|
||||
@@ -419,15 +409,6 @@ export type Capacity = {
|
||||
used: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Case = Node & {
|
||||
__typename?: 'Case';
|
||||
base64?: Maybe<Scalars['String']['output']>;
|
||||
error?: Maybe<Scalars['String']['output']>;
|
||||
icon?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
url?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Cloud = {
|
||||
__typename?: 'Cloud';
|
||||
allowedOrigins: Array<Scalars['String']['output']>;
|
||||
@@ -539,6 +520,32 @@ export enum ContainerState {
|
||||
RUNNING = 'RUNNING'
|
||||
}
|
||||
|
||||
/** CPU load for a single core */
|
||||
export type CpuLoad = {
|
||||
__typename?: 'CpuLoad';
|
||||
/** The total CPU load on a single core, in percent. */
|
||||
load: Scalars['Float']['output'];
|
||||
/** The percentage of time the CPU was idle. */
|
||||
loadIdle: Scalars['Float']['output'];
|
||||
/** The percentage of time the CPU spent servicing hardware interrupts. */
|
||||
loadIrq: Scalars['Float']['output'];
|
||||
/** The percentage of time the CPU spent on low-priority (niced) user space processes. */
|
||||
loadNice: Scalars['Float']['output'];
|
||||
/** The percentage of time the CPU spent in kernel space. */
|
||||
loadSystem: Scalars['Float']['output'];
|
||||
/** The percentage of time the CPU spent in user space. */
|
||||
loadUser: Scalars['Float']['output'];
|
||||
};
|
||||
|
||||
export type CpuUtilization = Node & {
|
||||
__typename?: 'CpuUtilization';
|
||||
/** CPU load for each core */
|
||||
cpus: Array<CpuLoad>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Total CPU load in percent */
|
||||
load: Scalars['Float']['output'];
|
||||
};
|
||||
|
||||
export type CreateApiKeyInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
name: Scalars['String']['input'];
|
||||
@@ -569,14 +576,6 @@ export type DeleteRCloneRemoteInput = {
|
||||
name: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type Devices = Node & {
|
||||
__typename?: 'Devices';
|
||||
gpu: Array<Gpu>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
pci: Array<Pci>;
|
||||
usb: Array<Usb>;
|
||||
};
|
||||
|
||||
export type Disk = Node & {
|
||||
__typename?: 'Disk';
|
||||
/** The number of bytes per sector */
|
||||
@@ -653,31 +652,6 @@ export enum DiskSmartStatus {
|
||||
UNKNOWN = 'UNKNOWN'
|
||||
}
|
||||
|
||||
export type Display = Node & {
|
||||
__typename?: 'Display';
|
||||
banner?: Maybe<Scalars['String']['output']>;
|
||||
case?: Maybe<Case>;
|
||||
critical?: Maybe<Scalars['Int']['output']>;
|
||||
dashapps?: Maybe<Scalars['String']['output']>;
|
||||
date?: Maybe<Scalars['String']['output']>;
|
||||
hot?: Maybe<Scalars['Int']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
locale?: Maybe<Scalars['String']['output']>;
|
||||
max?: Maybe<Scalars['Int']['output']>;
|
||||
number?: Maybe<Scalars['String']['output']>;
|
||||
resize?: Maybe<Scalars['Boolean']['output']>;
|
||||
scale?: Maybe<Scalars['Boolean']['output']>;
|
||||
tabs?: Maybe<Scalars['Boolean']['output']>;
|
||||
text?: Maybe<Scalars['Boolean']['output']>;
|
||||
theme?: Maybe<ThemeName>;
|
||||
total?: Maybe<Scalars['Boolean']['output']>;
|
||||
unit?: Maybe<Temperature>;
|
||||
usage?: Maybe<Scalars['Boolean']['output']>;
|
||||
users?: Maybe<Scalars['String']['output']>;
|
||||
warning?: Maybe<Scalars['Int']['output']>;
|
||||
wwn?: Maybe<Scalars['Boolean']['output']>;
|
||||
};
|
||||
|
||||
export type Docker = Node & {
|
||||
__typename?: 'Docker';
|
||||
containers: Array<DockerContainer>;
|
||||
@@ -792,80 +766,340 @@ export type FlashBackupStatus = {
|
||||
status: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Gpu = Node & {
|
||||
__typename?: 'Gpu';
|
||||
blacklisted: Scalars['Boolean']['output'];
|
||||
class: Scalars['String']['output'];
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
productid: Scalars['String']['output'];
|
||||
type: Scalars['String']['output'];
|
||||
typeid: Scalars['String']['output'];
|
||||
vendorname: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Info = Node & {
|
||||
__typename?: 'Info';
|
||||
/** Count of docker containers */
|
||||
apps: InfoApps;
|
||||
baseboard: Baseboard;
|
||||
/** Motherboard information */
|
||||
baseboard: InfoBaseboard;
|
||||
/** CPU information */
|
||||
cpu: InfoCpu;
|
||||
devices: Devices;
|
||||
display: Display;
|
||||
/** Device information */
|
||||
devices: InfoDevices;
|
||||
/** Display configuration */
|
||||
display: InfoDisplay;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Machine ID */
|
||||
machineId?: Maybe<Scalars['PrefixedID']['output']>;
|
||||
machineId?: Maybe<Scalars['ID']['output']>;
|
||||
/** Memory information */
|
||||
memory: InfoMemory;
|
||||
os: Os;
|
||||
system: System;
|
||||
/** Operating system information */
|
||||
os: InfoOs;
|
||||
/** System information */
|
||||
system: InfoSystem;
|
||||
/** Current server time */
|
||||
time: Scalars['DateTime']['output'];
|
||||
versions: Versions;
|
||||
/** Software versions */
|
||||
versions: InfoVersions;
|
||||
};
|
||||
|
||||
export type InfoApps = Node & {
|
||||
__typename?: 'InfoApps';
|
||||
export type InfoBaseboard = Node & {
|
||||
__typename?: 'InfoBaseboard';
|
||||
/** Motherboard asset tag */
|
||||
assetTag?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** How many docker containers are installed */
|
||||
installed: Scalars['Int']['output'];
|
||||
/** How many docker containers are running */
|
||||
started: Scalars['Int']['output'];
|
||||
/** Motherboard manufacturer */
|
||||
manufacturer?: Maybe<Scalars['String']['output']>;
|
||||
/** Maximum memory capacity in bytes */
|
||||
memMax?: Maybe<Scalars['Float']['output']>;
|
||||
/** Number of memory slots */
|
||||
memSlots?: Maybe<Scalars['Float']['output']>;
|
||||
/** Motherboard model */
|
||||
model?: Maybe<Scalars['String']['output']>;
|
||||
/** Motherboard serial number */
|
||||
serial?: Maybe<Scalars['String']['output']>;
|
||||
/** Motherboard version */
|
||||
version?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type InfoCpu = Node & {
|
||||
__typename?: 'InfoCpu';
|
||||
brand: Scalars['String']['output'];
|
||||
cache: Scalars['JSON']['output'];
|
||||
cores: Scalars['Int']['output'];
|
||||
family: Scalars['String']['output'];
|
||||
flags: Array<Scalars['String']['output']>;
|
||||
/** CPU brand name */
|
||||
brand?: Maybe<Scalars['String']['output']>;
|
||||
/** CPU cache information */
|
||||
cache?: Maybe<Scalars['JSON']['output']>;
|
||||
/** Number of CPU cores */
|
||||
cores?: Maybe<Scalars['Int']['output']>;
|
||||
/** CPU family */
|
||||
family?: Maybe<Scalars['String']['output']>;
|
||||
/** CPU feature flags */
|
||||
flags?: Maybe<Array<Scalars['String']['output']>>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
manufacturer: Scalars['String']['output'];
|
||||
model: Scalars['String']['output'];
|
||||
processors: Scalars['Int']['output'];
|
||||
revision: Scalars['String']['output'];
|
||||
socket: Scalars['String']['output'];
|
||||
speed: Scalars['Float']['output'];
|
||||
speedmax: Scalars['Float']['output'];
|
||||
speedmin: Scalars['Float']['output'];
|
||||
stepping: Scalars['Int']['output'];
|
||||
threads: Scalars['Int']['output'];
|
||||
vendor: Scalars['String']['output'];
|
||||
/** CPU manufacturer */
|
||||
manufacturer?: Maybe<Scalars['String']['output']>;
|
||||
/** CPU model */
|
||||
model?: Maybe<Scalars['String']['output']>;
|
||||
/** Number of physical processors */
|
||||
processors?: Maybe<Scalars['Int']['output']>;
|
||||
/** CPU revision */
|
||||
revision?: Maybe<Scalars['String']['output']>;
|
||||
/** CPU socket type */
|
||||
socket?: Maybe<Scalars['String']['output']>;
|
||||
/** Current CPU speed in GHz */
|
||||
speed?: Maybe<Scalars['Float']['output']>;
|
||||
/** Maximum CPU speed in GHz */
|
||||
speedmax?: Maybe<Scalars['Float']['output']>;
|
||||
/** Minimum CPU speed in GHz */
|
||||
speedmin?: Maybe<Scalars['Float']['output']>;
|
||||
/** CPU stepping */
|
||||
stepping?: Maybe<Scalars['Int']['output']>;
|
||||
/** Number of CPU threads */
|
||||
threads?: Maybe<Scalars['Int']['output']>;
|
||||
/** CPU vendor */
|
||||
vendor?: Maybe<Scalars['String']['output']>;
|
||||
/** CPU voltage */
|
||||
voltage?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type InfoDevices = Node & {
|
||||
__typename?: 'InfoDevices';
|
||||
/** List of GPU devices */
|
||||
gpu?: Maybe<Array<InfoGpu>>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** List of network interfaces */
|
||||
network?: Maybe<Array<InfoNetwork>>;
|
||||
/** List of PCI devices */
|
||||
pci?: Maybe<Array<InfoPci>>;
|
||||
/** List of USB devices */
|
||||
usb?: Maybe<Array<InfoUsb>>;
|
||||
};
|
||||
|
||||
export type InfoDisplay = Node & {
|
||||
__typename?: 'InfoDisplay';
|
||||
/** Case display configuration */
|
||||
case: InfoDisplayCase;
|
||||
/** Critical temperature threshold */
|
||||
critical: Scalars['Int']['output'];
|
||||
/** Hot temperature threshold */
|
||||
hot: Scalars['Int']['output'];
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Locale setting */
|
||||
locale?: Maybe<Scalars['String']['output']>;
|
||||
/** Maximum temperature threshold */
|
||||
max?: Maybe<Scalars['Int']['output']>;
|
||||
/** Enable UI resize */
|
||||
resize: Scalars['Boolean']['output'];
|
||||
/** Enable UI scaling */
|
||||
scale: Scalars['Boolean']['output'];
|
||||
/** Show tabs in UI */
|
||||
tabs: Scalars['Boolean']['output'];
|
||||
/** Show text labels */
|
||||
text: Scalars['Boolean']['output'];
|
||||
/** UI theme name */
|
||||
theme: ThemeName;
|
||||
/** Show totals */
|
||||
total: Scalars['Boolean']['output'];
|
||||
/** Temperature unit (C or F) */
|
||||
unit: Temperature;
|
||||
/** Show usage statistics */
|
||||
usage: Scalars['Boolean']['output'];
|
||||
/** Warning temperature threshold */
|
||||
warning: Scalars['Int']['output'];
|
||||
/** Show WWN identifiers */
|
||||
wwn: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
export type InfoDisplayCase = Node & {
|
||||
__typename?: 'InfoDisplayCase';
|
||||
/** Base64 encoded case image */
|
||||
base64: Scalars['String']['output'];
|
||||
/** Error message if any */
|
||||
error: Scalars['String']['output'];
|
||||
/** Case icon identifier */
|
||||
icon: Scalars['String']['output'];
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Case image URL */
|
||||
url: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type InfoGpu = Node & {
|
||||
__typename?: 'InfoGpu';
|
||||
/** Whether GPU is blacklisted */
|
||||
blacklisted: Scalars['Boolean']['output'];
|
||||
/** Device class */
|
||||
class: Scalars['String']['output'];
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Product ID */
|
||||
productid: Scalars['String']['output'];
|
||||
/** GPU type/manufacturer */
|
||||
type: Scalars['String']['output'];
|
||||
/** GPU type identifier */
|
||||
typeid: Scalars['String']['output'];
|
||||
/** Vendor name */
|
||||
vendorname?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type InfoMemory = Node & {
|
||||
__typename?: 'InfoMemory';
|
||||
active: Scalars['BigInt']['output'];
|
||||
available: Scalars['BigInt']['output'];
|
||||
buffcache: Scalars['BigInt']['output'];
|
||||
free: Scalars['BigInt']['output'];
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Physical memory layout */
|
||||
layout: Array<MemoryLayout>;
|
||||
max: Scalars['BigInt']['output'];
|
||||
swapfree: Scalars['BigInt']['output'];
|
||||
swaptotal: Scalars['BigInt']['output'];
|
||||
swapused: Scalars['BigInt']['output'];
|
||||
total: Scalars['BigInt']['output'];
|
||||
used: Scalars['BigInt']['output'];
|
||||
};
|
||||
|
||||
export type InfoNetwork = Node & {
|
||||
__typename?: 'InfoNetwork';
|
||||
/** DHCP enabled flag */
|
||||
dhcp?: Maybe<Scalars['Boolean']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Network interface name */
|
||||
iface: Scalars['String']['output'];
|
||||
/** MAC address */
|
||||
mac?: Maybe<Scalars['String']['output']>;
|
||||
/** Network interface model */
|
||||
model?: Maybe<Scalars['String']['output']>;
|
||||
/** Network speed */
|
||||
speed?: Maybe<Scalars['String']['output']>;
|
||||
/** Network vendor */
|
||||
vendor?: Maybe<Scalars['String']['output']>;
|
||||
/** Virtual interface flag */
|
||||
virtual?: Maybe<Scalars['Boolean']['output']>;
|
||||
};
|
||||
|
||||
export type InfoOs = Node & {
|
||||
__typename?: 'InfoOs';
|
||||
/** OS architecture */
|
||||
arch?: Maybe<Scalars['String']['output']>;
|
||||
/** OS build identifier */
|
||||
build?: Maybe<Scalars['String']['output']>;
|
||||
/** OS codename */
|
||||
codename?: Maybe<Scalars['String']['output']>;
|
||||
/** Linux distribution name */
|
||||
distro?: Maybe<Scalars['String']['output']>;
|
||||
/** Fully qualified domain name */
|
||||
fqdn?: Maybe<Scalars['String']['output']>;
|
||||
/** Hostname */
|
||||
hostname?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Kernel version */
|
||||
kernel?: Maybe<Scalars['String']['output']>;
|
||||
/** OS logo name */
|
||||
logofile?: Maybe<Scalars['String']['output']>;
|
||||
/** Operating system platform */
|
||||
platform?: Maybe<Scalars['String']['output']>;
|
||||
/** OS release version */
|
||||
release?: Maybe<Scalars['String']['output']>;
|
||||
/** OS serial number */
|
||||
serial?: Maybe<Scalars['String']['output']>;
|
||||
/** Service pack version */
|
||||
servicepack?: Maybe<Scalars['String']['output']>;
|
||||
/** OS started via UEFI */
|
||||
uefi?: Maybe<Scalars['Boolean']['output']>;
|
||||
/** Boot time ISO string */
|
||||
uptime?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type InfoPci = Node & {
|
||||
__typename?: 'InfoPci';
|
||||
/** Blacklisted status */
|
||||
blacklisted: Scalars['String']['output'];
|
||||
/** Device class */
|
||||
class: Scalars['String']['output'];
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Product ID */
|
||||
productid: Scalars['String']['output'];
|
||||
/** Product name */
|
||||
productname?: Maybe<Scalars['String']['output']>;
|
||||
/** Device type/manufacturer */
|
||||
type: Scalars['String']['output'];
|
||||
/** Type identifier */
|
||||
typeid: Scalars['String']['output'];
|
||||
/** Vendor ID */
|
||||
vendorid: Scalars['String']['output'];
|
||||
/** Vendor name */
|
||||
vendorname?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type InfoSystem = Node & {
|
||||
__typename?: 'InfoSystem';
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** System manufacturer */
|
||||
manufacturer?: Maybe<Scalars['String']['output']>;
|
||||
/** System model */
|
||||
model?: Maybe<Scalars['String']['output']>;
|
||||
/** System serial number */
|
||||
serial?: Maybe<Scalars['String']['output']>;
|
||||
/** System SKU */
|
||||
sku?: Maybe<Scalars['String']['output']>;
|
||||
/** System UUID */
|
||||
uuid?: Maybe<Scalars['String']['output']>;
|
||||
/** System version */
|
||||
version?: Maybe<Scalars['String']['output']>;
|
||||
/** Virtual machine flag */
|
||||
virtual?: Maybe<Scalars['Boolean']['output']>;
|
||||
};
|
||||
|
||||
export type InfoUsb = Node & {
|
||||
__typename?: 'InfoUsb';
|
||||
/** USB bus number */
|
||||
bus?: Maybe<Scalars['String']['output']>;
|
||||
/** USB device number */
|
||||
device?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** USB device name */
|
||||
name: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type InfoVersions = Node & {
|
||||
__typename?: 'InfoVersions';
|
||||
/** Apache version */
|
||||
apache?: Maybe<Scalars['String']['output']>;
|
||||
/** Docker version */
|
||||
docker?: Maybe<Scalars['String']['output']>;
|
||||
/** gcc version */
|
||||
gcc?: Maybe<Scalars['String']['output']>;
|
||||
/** Git version */
|
||||
git?: Maybe<Scalars['String']['output']>;
|
||||
/** Grunt version */
|
||||
grunt?: Maybe<Scalars['String']['output']>;
|
||||
/** Gulp version */
|
||||
gulp?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Java version */
|
||||
java?: Maybe<Scalars['String']['output']>;
|
||||
/** Kernel version */
|
||||
kernel?: Maybe<Scalars['String']['output']>;
|
||||
/** MongoDB version */
|
||||
mongodb?: Maybe<Scalars['String']['output']>;
|
||||
/** MySQL version */
|
||||
mysql?: Maybe<Scalars['String']['output']>;
|
||||
/** nginx version */
|
||||
nginx?: Maybe<Scalars['String']['output']>;
|
||||
/** Node.js version */
|
||||
node?: Maybe<Scalars['String']['output']>;
|
||||
/** npm version */
|
||||
npm?: Maybe<Scalars['String']['output']>;
|
||||
/** OpenSSL version */
|
||||
openssl?: Maybe<Scalars['String']['output']>;
|
||||
/** Perl version */
|
||||
perl?: Maybe<Scalars['String']['output']>;
|
||||
/** PHP version */
|
||||
php?: Maybe<Scalars['String']['output']>;
|
||||
/** pip version */
|
||||
pip?: Maybe<Scalars['String']['output']>;
|
||||
/** pip3 version */
|
||||
pip3?: Maybe<Scalars['String']['output']>;
|
||||
/** pm2 version */
|
||||
pm2?: Maybe<Scalars['String']['output']>;
|
||||
/** Postfix version */
|
||||
postfix?: Maybe<Scalars['String']['output']>;
|
||||
/** PostgreSQL version */
|
||||
postgresql?: Maybe<Scalars['String']['output']>;
|
||||
/** Python version */
|
||||
python?: Maybe<Scalars['String']['output']>;
|
||||
/** Python3 version */
|
||||
python3?: Maybe<Scalars['String']['output']>;
|
||||
/** Redis version */
|
||||
redis?: Maybe<Scalars['String']['output']>;
|
||||
/** System OpenSSL version */
|
||||
systemOpenssl?: Maybe<Scalars['String']['output']>;
|
||||
/** tsc version */
|
||||
tsc?: Maybe<Scalars['String']['output']>;
|
||||
/** Unraid version */
|
||||
unraid?: Maybe<Scalars['String']['output']>;
|
||||
/** V8 engine version */
|
||||
v8?: Maybe<Scalars['String']['output']>;
|
||||
/** VirtualBox version */
|
||||
virtualbox?: Maybe<Scalars['String']['output']>;
|
||||
/** Yarn version */
|
||||
yarn?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type InitiateFlashBackupInput = {
|
||||
@@ -911,20 +1145,68 @@ export type LogFileContent = {
|
||||
|
||||
export type MemoryLayout = Node & {
|
||||
__typename?: 'MemoryLayout';
|
||||
/** Memory bank location (e.g., BANK 0) */
|
||||
bank?: Maybe<Scalars['String']['output']>;
|
||||
/** Memory clock speed in MHz */
|
||||
clockSpeed?: Maybe<Scalars['Int']['output']>;
|
||||
/** Form factor (e.g., DIMM, SODIMM) */
|
||||
formFactor?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Memory manufacturer */
|
||||
manufacturer?: Maybe<Scalars['String']['output']>;
|
||||
/** Part number of the memory module */
|
||||
partNum?: Maybe<Scalars['String']['output']>;
|
||||
/** Serial number of the memory module */
|
||||
serialNum?: Maybe<Scalars['String']['output']>;
|
||||
/** Memory module size in bytes */
|
||||
size: Scalars['BigInt']['output'];
|
||||
/** Memory type (e.g., DDR4, DDR5) */
|
||||
type?: Maybe<Scalars['String']['output']>;
|
||||
/** Configured voltage in millivolts */
|
||||
voltageConfigured?: Maybe<Scalars['Int']['output']>;
|
||||
/** Maximum voltage in millivolts */
|
||||
voltageMax?: Maybe<Scalars['Int']['output']>;
|
||||
/** Minimum voltage in millivolts */
|
||||
voltageMin?: Maybe<Scalars['Int']['output']>;
|
||||
};
|
||||
|
||||
export type MemoryUtilization = Node & {
|
||||
__typename?: 'MemoryUtilization';
|
||||
/** Active memory in bytes */
|
||||
active: Scalars['BigInt']['output'];
|
||||
/** Available memory in bytes */
|
||||
available: Scalars['BigInt']['output'];
|
||||
/** Buffer/cache memory in bytes */
|
||||
buffcache: Scalars['BigInt']['output'];
|
||||
/** Free memory in bytes */
|
||||
free: Scalars['BigInt']['output'];
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Free swap memory in bytes */
|
||||
swapFree: Scalars['BigInt']['output'];
|
||||
/** Total swap memory in bytes */
|
||||
swapTotal: Scalars['BigInt']['output'];
|
||||
/** Used swap memory in bytes */
|
||||
swapUsed: Scalars['BigInt']['output'];
|
||||
/** Swap usage percentage */
|
||||
swapUsedPercent: Scalars['Float']['output'];
|
||||
/** Total system memory in bytes */
|
||||
total: Scalars['BigInt']['output'];
|
||||
/** Used memory in bytes */
|
||||
used: Scalars['BigInt']['output'];
|
||||
/** Memory usage percentage */
|
||||
usedPercent: Scalars['Float']['output'];
|
||||
};
|
||||
|
||||
/** System metrics including CPU and memory utilization */
|
||||
export type Metrics = Node & {
|
||||
__typename?: 'Metrics';
|
||||
/** Current CPU utilization metrics */
|
||||
cpu?: Maybe<CpuUtilization>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Current memory utilization metrics */
|
||||
memory?: Maybe<MemoryUtilization>;
|
||||
};
|
||||
|
||||
/** The status of the minigraph */
|
||||
export enum MinigraphStatus {
|
||||
CONNECTED = 'CONNECTED',
|
||||
@@ -1237,23 +1519,6 @@ export type OrganizerResource = {
|
||||
type: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Os = Node & {
|
||||
__typename?: 'Os';
|
||||
arch?: Maybe<Scalars['String']['output']>;
|
||||
build?: Maybe<Scalars['String']['output']>;
|
||||
codename?: Maybe<Scalars['String']['output']>;
|
||||
codepage?: Maybe<Scalars['String']['output']>;
|
||||
distro?: Maybe<Scalars['String']['output']>;
|
||||
hostname?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
kernel?: Maybe<Scalars['String']['output']>;
|
||||
logofile?: Maybe<Scalars['String']['output']>;
|
||||
platform?: Maybe<Scalars['String']['output']>;
|
||||
release?: Maybe<Scalars['String']['output']>;
|
||||
serial?: Maybe<Scalars['String']['output']>;
|
||||
uptime?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Owner = {
|
||||
__typename?: 'Owner';
|
||||
avatar: Scalars['String']['output'];
|
||||
@@ -1302,19 +1567,6 @@ export type ParityCheckMutationsStartArgs = {
|
||||
correct: Scalars['Boolean']['input'];
|
||||
};
|
||||
|
||||
export type Pci = Node & {
|
||||
__typename?: 'Pci';
|
||||
blacklisted?: Maybe<Scalars['String']['output']>;
|
||||
class?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
productid?: Maybe<Scalars['String']['output']>;
|
||||
productname?: Maybe<Scalars['String']['output']>;
|
||||
type?: Maybe<Scalars['String']['output']>;
|
||||
typeid?: Maybe<Scalars['String']['output']>;
|
||||
vendorid?: Maybe<Scalars['String']['output']>;
|
||||
vendorname?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Permission = {
|
||||
__typename?: 'Permission';
|
||||
actions: Array<Scalars['String']['output']>;
|
||||
@@ -1385,7 +1637,6 @@ export type Query = {
|
||||
customization?: Maybe<Customization>;
|
||||
disk: Disk;
|
||||
disks: Array<Disk>;
|
||||
display: Display;
|
||||
docker: Docker;
|
||||
flash: Flash;
|
||||
info: Info;
|
||||
@@ -1394,6 +1645,7 @@ export type Query = {
|
||||
logFile: LogFileContent;
|
||||
logFiles: Array<LogFile>;
|
||||
me: UserAccount;
|
||||
metrics: Metrics;
|
||||
network: Network;
|
||||
/** Get all notifications */
|
||||
notifications: Notifications;
|
||||
@@ -1743,14 +1995,14 @@ export type SsoSettings = Node & {
|
||||
export type Subscription = {
|
||||
__typename?: 'Subscription';
|
||||
arraySubscription: UnraidArray;
|
||||
displaySubscription: Display;
|
||||
infoSubscription: Info;
|
||||
logFile: LogFileContent;
|
||||
notificationAdded: Notification;
|
||||
notificationsOverview: NotificationOverview;
|
||||
ownerSubscription: Owner;
|
||||
parityHistorySubscription: ParityCheck;
|
||||
serversSubscription: Server;
|
||||
systemMetricsCpu: CpuUtilization;
|
||||
systemMetricsMemory: MemoryUtilization;
|
||||
upsUpdates: UpsDevice;
|
||||
};
|
||||
|
||||
@@ -1759,21 +2011,10 @@ export type SubscriptionLogFileArgs = {
|
||||
path: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type System = Node & {
|
||||
__typename?: 'System';
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
manufacturer?: Maybe<Scalars['String']['output']>;
|
||||
model?: Maybe<Scalars['String']['output']>;
|
||||
serial?: Maybe<Scalars['String']['output']>;
|
||||
sku?: Maybe<Scalars['String']['output']>;
|
||||
uuid?: Maybe<Scalars['String']['output']>;
|
||||
version?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
/** Temperature unit (Celsius or Fahrenheit) */
|
||||
/** Temperature unit */
|
||||
export enum Temperature {
|
||||
C = 'C',
|
||||
F = 'F'
|
||||
CELSIUS = 'CELSIUS',
|
||||
FAHRENHEIT = 'FAHRENHEIT'
|
||||
}
|
||||
|
||||
export type Theme = {
|
||||
@@ -1985,12 +2226,6 @@ export type Uptime = {
|
||||
timestamp?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Usb = Node & {
|
||||
__typename?: 'Usb';
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
name?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type UserAccount = Node & {
|
||||
__typename?: 'UserAccount';
|
||||
/** A description of the user */
|
||||
@@ -2168,37 +2403,6 @@ export type Vars = Node & {
|
||||
workgroup?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Versions = Node & {
|
||||
__typename?: 'Versions';
|
||||
apache?: Maybe<Scalars['String']['output']>;
|
||||
docker?: Maybe<Scalars['String']['output']>;
|
||||
gcc?: Maybe<Scalars['String']['output']>;
|
||||
git?: Maybe<Scalars['String']['output']>;
|
||||
grunt?: Maybe<Scalars['String']['output']>;
|
||||
gulp?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
kernel?: Maybe<Scalars['String']['output']>;
|
||||
mongodb?: Maybe<Scalars['String']['output']>;
|
||||
mysql?: Maybe<Scalars['String']['output']>;
|
||||
nginx?: Maybe<Scalars['String']['output']>;
|
||||
node?: Maybe<Scalars['String']['output']>;
|
||||
npm?: Maybe<Scalars['String']['output']>;
|
||||
openssl?: Maybe<Scalars['String']['output']>;
|
||||
perl?: Maybe<Scalars['String']['output']>;
|
||||
php?: Maybe<Scalars['String']['output']>;
|
||||
pm2?: Maybe<Scalars['String']['output']>;
|
||||
postfix?: Maybe<Scalars['String']['output']>;
|
||||
postgresql?: Maybe<Scalars['String']['output']>;
|
||||
python?: Maybe<Scalars['String']['output']>;
|
||||
redis?: Maybe<Scalars['String']['output']>;
|
||||
systemOpenssl?: Maybe<Scalars['String']['output']>;
|
||||
systemOpensslLib?: Maybe<Scalars['String']['output']>;
|
||||
tsc?: Maybe<Scalars['String']['output']>;
|
||||
unraid?: Maybe<Scalars['String']['output']>;
|
||||
v8?: Maybe<Scalars['String']['output']>;
|
||||
yarn?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type VmDomain = Node & {
|
||||
__typename?: 'VmDomain';
|
||||
/** The unique identifier for the vm (uuid) */
|
||||
@@ -2349,7 +2553,7 @@ export type GetSsoUsersQuery = { __typename?: 'Query', settings: { __typename?:
|
||||
export type SystemReportQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type SystemReportQuery = { __typename?: 'Query', info: { __typename?: 'Info', id: any, machineId?: any | null, system: { __typename?: 'System', manufacturer?: string | null, model?: string | null, version?: string | null, sku?: string | null, serial?: string | null, uuid?: string | null }, versions: { __typename?: 'Versions', unraid?: string | null, kernel?: string | null, openssl?: string | null } }, config: { __typename?: 'Config', id: any, valid?: boolean | null, error?: string | null }, server?: { __typename?: 'Server', id: any, name: string } | null };
|
||||
export type SystemReportQuery = { __typename?: 'Query', info: { __typename?: 'Info', id: any, machineId?: string | null, system: { __typename?: 'InfoSystem', manufacturer?: string | null, model?: string | null, version?: string | null, sku?: string | null, serial?: string | null, uuid?: string | null }, versions: { __typename?: 'InfoVersions', unraid?: string | null, kernel?: string | null, openssl?: string | null } }, config: { __typename?: 'Config', id: any, valid?: boolean | null, error?: string | null }, server?: { __typename?: 'Server', id: any, name: string } | null };
|
||||
|
||||
export type ConnectStatusQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Test } from '@nestjs/testing';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { DisplayResolver } from '@app/unraid-api/graph/resolvers/display/display.resolver.js';
|
||||
import { DisplayService } from '@app/unraid-api/graph/resolvers/display/display.service.js';
|
||||
import { DisplayService } from '@app/unraid-api/graph/resolvers/info/display/display.service.js';
|
||||
|
||||
// Mock the pubsub module
|
||||
vi.mock('@app/core/pubsub.js', () => ({
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
} from '@unraid/shared/use-permissions.directive.js';
|
||||
|
||||
import { createSubscription, PUBSUB_CHANNEL } from '@app/core/pubsub.js';
|
||||
import { DisplayService } from '@app/unraid-api/graph/resolvers/display/display.service.js';
|
||||
import { Display } from '@app/unraid-api/graph/resolvers/info/info.model.js';
|
||||
import { Display } from '@app/unraid-api/graph/resolvers/info/display/display.model.js';
|
||||
import { DisplayService } from '@app/unraid-api/graph/resolvers/info/display/display.service.js';
|
||||
|
||||
@Resolver(() => Display)
|
||||
export class DisplayResolver {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Injectable, Scope } from '@nestjs/common';
|
||||
|
||||
import { currentLoad, Systeminformation } from 'systeminformation';
|
||||
|
||||
@Injectable({ scope: Scope.REQUEST })
|
||||
export class CpuDataService {
|
||||
private cpuLoadData: Promise<Systeminformation.CurrentLoadData> | undefined;
|
||||
|
||||
public getCpuLoad(): Promise<Systeminformation.CurrentLoadData> {
|
||||
this.cpuLoadData ??= currentLoad();
|
||||
return this.cpuLoadData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { Field, Float, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { Node } from '@unraid/shared/graphql.model.js';
|
||||
import { GraphQLJSON } from 'graphql-scalars';
|
||||
|
||||
@ObjectType({ description: 'CPU load for a single core' })
|
||||
export class CpuLoad {
|
||||
@Field(() => Float, { description: 'The total CPU load on a single core, in percent.' })
|
||||
load!: number;
|
||||
|
||||
@Field(() => Float, { description: 'The percentage of time the CPU spent in user space.' })
|
||||
loadUser!: number;
|
||||
|
||||
@Field(() => Float, { description: 'The percentage of time the CPU spent in kernel space.' })
|
||||
loadSystem!: number;
|
||||
|
||||
@Field(() => Float, {
|
||||
description:
|
||||
'The percentage of time the CPU spent on low-priority (niced) user space processes.',
|
||||
})
|
||||
loadNice!: number;
|
||||
|
||||
@Field(() => Float, { description: 'The percentage of time the CPU was idle.' })
|
||||
loadIdle!: number;
|
||||
|
||||
@Field(() => Float, {
|
||||
description: 'The percentage of time the CPU spent servicing hardware interrupts.',
|
||||
})
|
||||
loadIrq!: number;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class CpuUtilization extends Node {
|
||||
@Field(() => Float, { description: 'Total CPU load in percent' })
|
||||
load!: number;
|
||||
|
||||
@Field(() => [CpuLoad], { description: 'CPU load for each core' })
|
||||
cpus!: CpuLoad[];
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoCpu extends Node {
|
||||
@Field(() => String, { nullable: true, description: 'CPU manufacturer' })
|
||||
manufacturer?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'CPU brand name' })
|
||||
brand?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'CPU vendor' })
|
||||
vendor?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'CPU family' })
|
||||
family?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'CPU model' })
|
||||
model?: string;
|
||||
|
||||
@Field(() => Int, { nullable: true, description: 'CPU stepping' })
|
||||
stepping?: number;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'CPU revision' })
|
||||
revision?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'CPU voltage' })
|
||||
voltage?: string;
|
||||
|
||||
@Field(() => Float, { nullable: true, description: 'Current CPU speed in GHz' })
|
||||
speed?: number;
|
||||
|
||||
@Field(() => Float, { nullable: true, description: 'Minimum CPU speed in GHz' })
|
||||
speedmin?: number;
|
||||
|
||||
@Field(() => Float, { nullable: true, description: 'Maximum CPU speed in GHz' })
|
||||
speedmax?: number;
|
||||
|
||||
@Field(() => Int, { nullable: true, description: 'Number of CPU threads' })
|
||||
threads?: number;
|
||||
|
||||
@Field(() => Int, { nullable: true, description: 'Number of CPU cores' })
|
||||
cores?: number;
|
||||
|
||||
@Field(() => Int, { nullable: true, description: 'Number of physical processors' })
|
||||
processors?: number;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'CPU socket type' })
|
||||
socket?: string;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true, description: 'CPU cache information' })
|
||||
cache?: Record<string, any>;
|
||||
|
||||
@Field(() => [String], { nullable: true, description: 'CPU feature flags' })
|
||||
flags?: string[];
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Injectable, Scope } from '@nestjs/common';
|
||||
|
||||
import { cpu, cpuFlags, currentLoad, Systeminformation } from 'systeminformation';
|
||||
|
||||
import { CpuUtilization, InfoCpu } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.model.js';
|
||||
|
||||
@Injectable({ scope: Scope.REQUEST })
|
||||
export class CpuDataService {
|
||||
private cpuLoadData: Promise<Systeminformation.CurrentLoadData> | undefined;
|
||||
|
||||
public getCpuLoad(): Promise<Systeminformation.CurrentLoadData> {
|
||||
this.cpuLoadData ??= currentLoad();
|
||||
return this.cpuLoadData;
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class CpuService {
|
||||
async generateCpu(): Promise<InfoCpu> {
|
||||
const { cores, physicalCores, speedMin, speedMax, stepping, ...rest } = await cpu();
|
||||
const flags = await cpuFlags()
|
||||
.then((flags) => flags.split(' '))
|
||||
.catch(() => []);
|
||||
|
||||
return {
|
||||
id: 'info/cpu',
|
||||
...rest,
|
||||
cores: physicalCores,
|
||||
threads: cores,
|
||||
flags,
|
||||
stepping: Number(stepping),
|
||||
speedmin: speedMin || -1,
|
||||
speedmax: speedMax || -1,
|
||||
};
|
||||
}
|
||||
|
||||
async generateCpuLoad(): Promise<CpuUtilization> {
|
||||
const { currentLoad: load, cpus } = await currentLoad();
|
||||
|
||||
return {
|
||||
id: 'info/cpu-load',
|
||||
load,
|
||||
cpus,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { ResolveField, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { DevicesService } from '@app/unraid-api/graph/resolvers/info/devices.service.js';
|
||||
import { Devices, Gpu, Pci, Usb } from '@app/unraid-api/graph/resolvers/info/info.model.js';
|
||||
|
||||
@Resolver(() => Devices)
|
||||
export class DevicesResolver {
|
||||
constructor(private readonly devicesService: DevicesService) {}
|
||||
|
||||
@ResolveField(() => [Gpu])
|
||||
public async gpu(): Promise<Gpu[]> {
|
||||
return this.devicesService.generateGpu();
|
||||
}
|
||||
|
||||
@ResolveField(() => [Pci])
|
||||
public async pci(): Promise<Pci[]> {
|
||||
return this.devicesService.generatePci();
|
||||
}
|
||||
|
||||
@ResolveField(() => [Usb])
|
||||
public async usb(): Promise<Usb[]> {
|
||||
return this.devicesService.generateUsb();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { Node } from '@unraid/shared/graphql.model.js';
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoGpu extends Node {
|
||||
@Field(() => String, { description: 'GPU type/manufacturer' })
|
||||
type!: string;
|
||||
|
||||
@Field(() => String, { description: 'GPU type identifier' })
|
||||
typeid!: string;
|
||||
|
||||
@Field(() => Boolean, { description: 'Whether GPU is blacklisted' })
|
||||
blacklisted!: boolean;
|
||||
|
||||
@Field(() => String, { description: 'Device class' })
|
||||
class!: string;
|
||||
|
||||
@Field(() => String, { description: 'Product ID' })
|
||||
productid!: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Vendor name' })
|
||||
vendorname?: string;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoNetwork extends Node {
|
||||
@Field(() => String, { description: 'Network interface name' })
|
||||
iface!: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Network interface model' })
|
||||
model?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Network vendor' })
|
||||
vendor?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'MAC address' })
|
||||
mac?: string;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, description: 'Virtual interface flag' })
|
||||
virtual?: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Network speed' })
|
||||
speed?: string;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, description: 'DHCP enabled flag' })
|
||||
dhcp?: boolean;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoPci extends Node {
|
||||
@Field(() => String, { description: 'Device type/manufacturer' })
|
||||
type!: string;
|
||||
|
||||
@Field(() => String, { description: 'Type identifier' })
|
||||
typeid!: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Vendor name' })
|
||||
vendorname?: string;
|
||||
|
||||
@Field(() => String, { description: 'Vendor ID' })
|
||||
vendorid!: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Product name' })
|
||||
productname?: string;
|
||||
|
||||
@Field(() => String, { description: 'Product ID' })
|
||||
productid!: string;
|
||||
|
||||
@Field(() => String, { description: 'Blacklisted status' })
|
||||
blacklisted!: string;
|
||||
|
||||
@Field(() => String, { description: 'Device class' })
|
||||
class!: string;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoUsb extends Node {
|
||||
@Field(() => String, { description: 'USB device name' })
|
||||
name!: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'USB bus number' })
|
||||
bus?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'USB device number' })
|
||||
device?: string;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoDevices extends Node {
|
||||
@Field(() => [InfoGpu], { nullable: true, description: 'List of GPU devices' })
|
||||
gpu?: InfoGpu[];
|
||||
|
||||
@Field(() => [InfoNetwork], { nullable: true, description: 'List of network interfaces' })
|
||||
network?: InfoNetwork[];
|
||||
|
||||
@Field(() => [InfoPci], { nullable: true, description: 'List of PCI devices' })
|
||||
pci?: InfoPci[];
|
||||
|
||||
@Field(() => [InfoUsb], { nullable: true, description: 'List of USB devices' })
|
||||
usb?: InfoUsb[];
|
||||
}
|
||||
+2
-2
@@ -3,8 +3,8 @@ import { Test } from '@nestjs/testing';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { DevicesResolver } from '@app/unraid-api/graph/resolvers/info/devices.resolver.js';
|
||||
import { DevicesService } from '@app/unraid-api/graph/resolvers/info/devices.service.js';
|
||||
import { DevicesResolver } from '@app/unraid-api/graph/resolvers/info/devices/devices.resolver.js';
|
||||
import { DevicesService } from '@app/unraid-api/graph/resolvers/info/devices/devices.service.js';
|
||||
|
||||
describe('DevicesResolver', () => {
|
||||
let resolver: DevicesResolver;
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ResolveField, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
InfoDevices,
|
||||
InfoGpu,
|
||||
InfoNetwork,
|
||||
InfoPci,
|
||||
InfoUsb,
|
||||
} from '@app/unraid-api/graph/resolvers/info/devices/devices.model.js';
|
||||
import { DevicesService } from '@app/unraid-api/graph/resolvers/info/devices/devices.service.js';
|
||||
|
||||
@Resolver(() => InfoDevices)
|
||||
export class DevicesResolver {
|
||||
constructor(private readonly devicesService: DevicesService) {}
|
||||
|
||||
@ResolveField(() => [InfoGpu])
|
||||
public async gpu(): Promise<InfoGpu[]> {
|
||||
return this.devicesService.generateGpu();
|
||||
}
|
||||
|
||||
@ResolveField(() => [InfoNetwork])
|
||||
public async network(): Promise<InfoNetwork[]> {
|
||||
return this.devicesService.generateNetwork();
|
||||
}
|
||||
|
||||
@ResolveField(() => [InfoPci])
|
||||
public async pci(): Promise<InfoPci[]> {
|
||||
return this.devicesService.generatePci();
|
||||
}
|
||||
|
||||
@ResolveField(() => [InfoUsb])
|
||||
public async usb(): Promise<InfoUsb[]> {
|
||||
return this.devicesService.generateUsb();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { Test } from '@nestjs/testing';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { DevicesService } from '@app/unraid-api/graph/resolvers/info/devices.service.js';
|
||||
import { DevicesService } from '@app/unraid-api/graph/resolvers/info/devices/devices.service.js';
|
||||
|
||||
// Mock external dependencies
|
||||
vi.mock('fs/promises', () => ({
|
||||
+35
-10
@@ -13,24 +13,35 @@ import { filterDevices } from '@app/core/utils/vms/filter-devices.js';
|
||||
import { getPciDevices } from '@app/core/utils/vms/get-pci-devices.js';
|
||||
import { getters } from '@app/store/index.js';
|
||||
import {
|
||||
Gpu,
|
||||
Pci,
|
||||
RawUsbDeviceData,
|
||||
Usb,
|
||||
UsbDevice,
|
||||
} from '@app/unraid-api/graph/resolvers/info/info.model.js';
|
||||
InfoGpu,
|
||||
InfoNetwork,
|
||||
InfoPci,
|
||||
InfoUsb,
|
||||
} from '@app/unraid-api/graph/resolvers/info/devices/devices.model.js';
|
||||
|
||||
interface RawUsbDeviceData {
|
||||
id: string;
|
||||
n?: string;
|
||||
}
|
||||
|
||||
interface UsbDevice {
|
||||
id: string;
|
||||
name: string;
|
||||
guid: string;
|
||||
vendorname?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DevicesService {
|
||||
private readonly logger = new Logger(DevicesService.name);
|
||||
|
||||
async generateGpu(): Promise<Gpu[]> {
|
||||
async generateGpu(): Promise<InfoGpu[]> {
|
||||
try {
|
||||
const systemPciDevices = await this.getSystemPciDevices();
|
||||
return systemPciDevices
|
||||
.filter((device) => device.class === 'vga' && !device.allowed)
|
||||
.map((entry) => {
|
||||
const gpu: Gpu = {
|
||||
const gpu: InfoGpu = {
|
||||
id: `gpu/${entry.id}`,
|
||||
blacklisted: entry.allowed,
|
||||
class: entry.class,
|
||||
@@ -50,7 +61,7 @@ export class DevicesService {
|
||||
}
|
||||
}
|
||||
|
||||
async generatePci(): Promise<Pci[]> {
|
||||
async generatePci(): Promise<InfoPci[]> {
|
||||
try {
|
||||
const devices = await this.getSystemPciDevices();
|
||||
return devices.map((device) => ({
|
||||
@@ -73,7 +84,21 @@ export class DevicesService {
|
||||
}
|
||||
}
|
||||
|
||||
async generateUsb(): Promise<Usb[]> {
|
||||
async generateNetwork(): Promise<InfoNetwork[]> {
|
||||
try {
|
||||
// For now, return empty array. This can be implemented later to fetch actual network interfaces
|
||||
// using systeminformation or similar libraries
|
||||
return [];
|
||||
} catch (error: unknown) {
|
||||
this.logger.error(
|
||||
`Failed to generate network devices: ${error instanceof Error ? error.message : String(error)}`,
|
||||
error instanceof Error ? error.stack : undefined
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async generateUsb(): Promise<InfoUsb[]> {
|
||||
try {
|
||||
const usbDevices = await this.getSystemUSBDevices();
|
||||
return usbDevices.map((device) => ({
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Field, Float, Int, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { Node } from '@unraid/shared/graphql.model.js';
|
||||
|
||||
import { ThemeName } from '@app/unraid-api/graph/resolvers/customization/theme.model.js';
|
||||
|
||||
export enum Temperature {
|
||||
CELSIUS = 'C',
|
||||
FAHRENHEIT = 'F',
|
||||
}
|
||||
|
||||
registerEnumType(Temperature, {
|
||||
name: 'Temperature',
|
||||
description: 'Temperature unit',
|
||||
});
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoDisplayCase extends Node {
|
||||
@Field(() => String, { description: 'Case image URL' })
|
||||
url!: string;
|
||||
|
||||
@Field(() => String, { description: 'Case icon identifier' })
|
||||
icon!: string;
|
||||
|
||||
@Field(() => String, { description: 'Error message if any' })
|
||||
error!: string;
|
||||
|
||||
@Field(() => String, { description: 'Base64 encoded case image' })
|
||||
base64!: string;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoDisplay extends Node {
|
||||
@Field(() => InfoDisplayCase, { description: 'Case display configuration' })
|
||||
case!: InfoDisplayCase;
|
||||
|
||||
@Field(() => ThemeName, { description: 'UI theme name' })
|
||||
theme!: ThemeName;
|
||||
|
||||
@Field(() => Temperature, { description: 'Temperature unit (C or F)' })
|
||||
unit!: Temperature;
|
||||
|
||||
@Field(() => Boolean, { description: 'Enable UI scaling' })
|
||||
scale!: boolean;
|
||||
|
||||
@Field(() => Boolean, { description: 'Show tabs in UI' })
|
||||
tabs!: boolean;
|
||||
|
||||
@Field(() => Boolean, { description: 'Enable UI resize' })
|
||||
resize!: boolean;
|
||||
|
||||
@Field(() => Boolean, { description: 'Show WWN identifiers' })
|
||||
wwn!: boolean;
|
||||
|
||||
@Field(() => Boolean, { description: 'Show totals' })
|
||||
total!: boolean;
|
||||
|
||||
@Field(() => Boolean, { description: 'Show usage statistics' })
|
||||
usage!: boolean;
|
||||
|
||||
@Field(() => Boolean, { description: 'Show text labels' })
|
||||
text!: boolean;
|
||||
|
||||
@Field(() => Int, { description: 'Warning temperature threshold' })
|
||||
warning!: number;
|
||||
|
||||
@Field(() => Int, { description: 'Critical temperature threshold' })
|
||||
critical!: number;
|
||||
|
||||
@Field(() => Int, { description: 'Hot temperature threshold' })
|
||||
hot!: number;
|
||||
|
||||
@Field(() => Int, { nullable: true, description: 'Maximum temperature threshold' })
|
||||
max?: number;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Locale setting' })
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
// Export aliases for backward compatibility with the main DisplayResolver
|
||||
export { InfoDisplay as Display };
|
||||
export { InfoDisplayCase as DisplayCase };
|
||||
+5
-8
@@ -3,7 +3,7 @@ import { Test } from '@nestjs/testing';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { DisplayService } from '@app/unraid-api/graph/resolvers/display/display.service.js';
|
||||
import { DisplayService } from '@app/unraid-api/graph/resolvers/info/display/display.service.js';
|
||||
|
||||
// Mock fs/promises at the module level only for specific test cases
|
||||
vi.mock('node:fs/promises', async () => {
|
||||
@@ -37,7 +37,7 @@ describe('DisplayService', () => {
|
||||
const result = await service.generateDisplay();
|
||||
|
||||
// Verify basic structure
|
||||
expect(result).toHaveProperty('id', 'display');
|
||||
expect(result).toHaveProperty('id', 'info/display');
|
||||
expect(result).toHaveProperty('case');
|
||||
expect(result.case).toHaveProperty('url');
|
||||
expect(result.case).toHaveProperty('icon');
|
||||
@@ -69,6 +69,7 @@ describe('DisplayService', () => {
|
||||
const result = await service.generateDisplay();
|
||||
|
||||
expect(result.case).toEqual({
|
||||
id: 'display/case',
|
||||
url: '',
|
||||
icon: 'custom',
|
||||
error: 'could-not-read-config-file',
|
||||
@@ -90,7 +91,7 @@ describe('DisplayService', () => {
|
||||
const result = await service.generateDisplay();
|
||||
|
||||
// Should still return basic structure even if some config is missing
|
||||
expect(result).toHaveProperty('id', 'display');
|
||||
expect(result).toHaveProperty('id', 'info/display');
|
||||
expect(result).toHaveProperty('case');
|
||||
// The actual config depends on what's in the dev files
|
||||
});
|
||||
@@ -114,11 +115,6 @@ describe('DisplayService', () => {
|
||||
expect(result.critical).toBe(90);
|
||||
expect(result.hot).toBe(45);
|
||||
expect(result.max).toBe(55);
|
||||
expect(result.date).toBe('%c');
|
||||
expect(result.number).toBe('.,');
|
||||
expect(result.users).toBe('Tasks:3');
|
||||
expect(result.banner).toBe('image');
|
||||
expect(result.dashapps).toBe('icons');
|
||||
expect(result.locale).toBe('en_US'); // default fallback when not specified
|
||||
});
|
||||
|
||||
@@ -140,6 +136,7 @@ describe('DisplayService', () => {
|
||||
const result = await service.generateDisplay();
|
||||
|
||||
expect(result.case).toEqual({
|
||||
id: 'display/case',
|
||||
url: '',
|
||||
icon: 'default',
|
||||
error: '',
|
||||
+28
-5
@@ -8,17 +8,19 @@ import { fileExists } from '@app/core/utils/files/file-exists.js';
|
||||
import { loadState } from '@app/core/utils/misc/load-state.js';
|
||||
import { getters } from '@app/store/index.js';
|
||||
import { ThemeName } from '@app/unraid-api/graph/resolvers/customization/theme.model.js';
|
||||
import { Display, Temperature } from '@app/unraid-api/graph/resolvers/info/info.model.js';
|
||||
import { Display, Temperature } from '@app/unraid-api/graph/resolvers/info/display/display.model.js';
|
||||
|
||||
const states = {
|
||||
// Success
|
||||
custom: {
|
||||
id: 'display/case',
|
||||
url: '',
|
||||
icon: 'custom',
|
||||
error: '',
|
||||
base64: '',
|
||||
},
|
||||
default: {
|
||||
id: 'display/case',
|
||||
url: '',
|
||||
icon: 'default',
|
||||
error: '',
|
||||
@@ -27,30 +29,35 @@ const states = {
|
||||
|
||||
// Errors
|
||||
couldNotReadConfigFile: {
|
||||
id: 'display/case',
|
||||
url: '',
|
||||
icon: 'custom',
|
||||
error: 'could-not-read-config-file',
|
||||
base64: '',
|
||||
},
|
||||
couldNotReadImage: {
|
||||
id: 'display/case',
|
||||
url: '',
|
||||
icon: 'custom',
|
||||
error: 'could-not-read-image',
|
||||
base64: '',
|
||||
},
|
||||
imageMissing: {
|
||||
id: 'display/case',
|
||||
url: '',
|
||||
icon: 'custom',
|
||||
error: 'image-missing',
|
||||
base64: '',
|
||||
},
|
||||
imageTooBig: {
|
||||
id: 'display/case',
|
||||
url: '',
|
||||
icon: 'custom',
|
||||
error: 'image-too-big',
|
||||
base64: '',
|
||||
},
|
||||
imageCorrupt: {
|
||||
id: 'display/case',
|
||||
url: '',
|
||||
icon: 'custom',
|
||||
error: 'image-corrupt',
|
||||
@@ -67,11 +74,26 @@ export class DisplayService {
|
||||
// Get display configuration
|
||||
const config = await this.getDisplayConfig();
|
||||
|
||||
return {
|
||||
id: 'display',
|
||||
const display: Display = {
|
||||
id: 'info/display',
|
||||
case: caseInfo,
|
||||
...config,
|
||||
theme: config.theme || ThemeName.white,
|
||||
unit: config.unit || Temperature.CELSIUS,
|
||||
scale: config.scale ?? false,
|
||||
tabs: config.tabs ?? true,
|
||||
resize: config.resize ?? true,
|
||||
wwn: config.wwn ?? false,
|
||||
total: config.total ?? true,
|
||||
usage: config.usage ?? true,
|
||||
text: config.text ?? true,
|
||||
warning: config.warning ?? 60,
|
||||
critical: config.critical ?? 80,
|
||||
hot: config.hot ?? 90,
|
||||
max: config.max,
|
||||
locale: config.locale,
|
||||
};
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
private async getCaseInfo() {
|
||||
@@ -102,11 +124,12 @@ export class DisplayService {
|
||||
// Non-custom icon
|
||||
return {
|
||||
...states.default,
|
||||
id: 'display/case',
|
||||
icon: serverCase,
|
||||
};
|
||||
}
|
||||
|
||||
private async getDisplayConfig() {
|
||||
private async getDisplayConfig(): Promise<Partial<Omit<Display, 'id' | 'case'>>> {
|
||||
const filePaths = getters.paths()['dynamix-config'];
|
||||
|
||||
const state = filePaths.reduce<Partial<DynamixConfig>>((acc, filePath) => {
|
||||
@@ -1,593 +1,44 @@
|
||||
import {
|
||||
Field,
|
||||
Float,
|
||||
GraphQLISODateTime,
|
||||
ID,
|
||||
Int,
|
||||
ObjectType,
|
||||
registerEnumType,
|
||||
} from '@nestjs/graphql';
|
||||
import { Field, GraphQLISODateTime, ID, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { Node } from '@unraid/shared/graphql.model.js';
|
||||
import { PrefixedID } from '@unraid/shared/prefixed-id-scalar.js';
|
||||
import { GraphQLBigInt, GraphQLJSON } from 'graphql-scalars';
|
||||
|
||||
import { ThemeName } from '@app/unraid-api/graph/resolvers/customization/theme.model.js';
|
||||
|
||||
// USB device interface for type safety
|
||||
export interface UsbDevice {
|
||||
id: string;
|
||||
name: string;
|
||||
guid: string;
|
||||
vendorname: string;
|
||||
}
|
||||
|
||||
// Raw USB device data from lsusb parsing
|
||||
export interface RawUsbDeviceData {
|
||||
id: string;
|
||||
n?: string;
|
||||
}
|
||||
|
||||
export enum Temperature {
|
||||
C = 'C',
|
||||
F = 'F',
|
||||
}
|
||||
|
||||
registerEnumType(Temperature, {
|
||||
name: 'Temperature',
|
||||
description: 'Temperature unit (Celsius or Fahrenheit)',
|
||||
});
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoApps extends Node {
|
||||
@Field(() => Int, { description: 'How many docker containers are installed' })
|
||||
installed!: number;
|
||||
|
||||
@Field(() => Int, { description: 'How many docker containers are running' })
|
||||
started!: number;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class Baseboard extends Node {
|
||||
@Field(() => String)
|
||||
manufacturer!: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
model?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
version?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
serial?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
assetTag?: string;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoCpu extends Node {
|
||||
@Field(() => String)
|
||||
manufacturer!: string;
|
||||
|
||||
@Field(() => String)
|
||||
brand!: string;
|
||||
|
||||
@Field(() => String)
|
||||
vendor!: string;
|
||||
|
||||
@Field(() => String)
|
||||
family!: string;
|
||||
|
||||
@Field(() => String)
|
||||
model!: string;
|
||||
|
||||
@Field(() => Int)
|
||||
stepping!: number;
|
||||
|
||||
@Field(() => String)
|
||||
revision!: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
voltage?: string;
|
||||
|
||||
@Field(() => Float)
|
||||
speed!: number;
|
||||
|
||||
@Field(() => Float)
|
||||
speedmin!: number;
|
||||
|
||||
@Field(() => Float)
|
||||
speedmax!: number;
|
||||
|
||||
@Field(() => Int)
|
||||
threads!: number;
|
||||
|
||||
@Field(() => Int)
|
||||
cores!: number;
|
||||
|
||||
@Field(() => Int)
|
||||
processors!: number;
|
||||
|
||||
@Field(() => String)
|
||||
socket!: string;
|
||||
|
||||
@Field(() => GraphQLJSON)
|
||||
cache!: Record<string, any>;
|
||||
|
||||
@Field(() => [String])
|
||||
flags!: string[];
|
||||
|
||||
@Field(() => Float, {
|
||||
description: 'CPU utilization in percent',
|
||||
nullable: true,
|
||||
})
|
||||
utilization?: number;
|
||||
}
|
||||
|
||||
@ObjectType({ description: 'CPU load for a single core' })
|
||||
export class CpuLoad {
|
||||
@Field(() => Float, { description: 'The total CPU load on a single core, in percent.' })
|
||||
load!: number;
|
||||
|
||||
@Field(() => Float, { description: 'The percentage of time the CPU spent in user space.' })
|
||||
loadUser!: number;
|
||||
|
||||
@Field(() => Float, { description: 'The percentage of time the CPU spent in kernel space.' })
|
||||
loadSystem!: number;
|
||||
|
||||
@Field(() => Float, {
|
||||
description:
|
||||
'The percentage of time the CPU spent on low-priority (niced) user space processes.',
|
||||
})
|
||||
loadNice!: number;
|
||||
|
||||
@Field(() => Float, { description: 'The percentage of time the CPU was idle.' })
|
||||
loadIdle!: number;
|
||||
|
||||
@Field(() => Float, {
|
||||
description: 'The percentage of time the CPU spent servicing hardware interrupts.',
|
||||
})
|
||||
loadIrq!: number;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class CpuUtilization extends Node {
|
||||
@Field(() => Float)
|
||||
load!: number;
|
||||
|
||||
@Field(() => [CpuLoad])
|
||||
cpus!: CpuLoad[];
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class Gpu extends Node {
|
||||
@Field(() => String)
|
||||
type!: string;
|
||||
|
||||
@Field(() => String)
|
||||
typeid!: string;
|
||||
|
||||
@Field(() => String)
|
||||
vendorname!: string;
|
||||
|
||||
@Field(() => String)
|
||||
productid!: string;
|
||||
|
||||
@Field(() => Boolean)
|
||||
blacklisted!: boolean;
|
||||
|
||||
@Field(() => String)
|
||||
class!: string;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class Network extends Node {
|
||||
@Field(() => String, { nullable: true })
|
||||
iface?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
ifaceName?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
ipv4?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
ipv6?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
mac?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
internal?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
operstate?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
type?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
duplex?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
mtu?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
speed?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
carrierChanges?: string;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class Pci extends Node {
|
||||
@Field(() => String, { nullable: true })
|
||||
type?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
typeid?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
vendorname?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
vendorid?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
productname?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
productid?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
blacklisted?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
class?: string;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class Usb extends Node {
|
||||
@Field(() => String, { nullable: true })
|
||||
name?: string;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class Devices extends Node {
|
||||
@Field(() => [Gpu])
|
||||
gpu!: Gpu[];
|
||||
|
||||
@Field(() => [Pci])
|
||||
pci!: Pci[];
|
||||
|
||||
@Field(() => [Usb])
|
||||
usb!: Usb[];
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class Case {
|
||||
@Field(() => String, { nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
url?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
error?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
base64?: string;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class Display extends Node {
|
||||
@Field(() => Case, { nullable: true })
|
||||
case?: Case;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
date?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
number?: string;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
scale?: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
tabs?: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
users?: string;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
resize?: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
wwn?: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
total?: boolean;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
usage?: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
banner?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
dashapps?: string;
|
||||
|
||||
@Field(() => ThemeName, { nullable: true })
|
||||
theme?: ThemeName;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
text?: boolean;
|
||||
|
||||
@Field(() => Temperature, { nullable: true })
|
||||
unit?: Temperature;
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
warning?: number;
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
critical?: number;
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
hot?: number;
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
max?: number;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class MemoryLayout extends Node {
|
||||
@Field(() => GraphQLBigInt)
|
||||
size!: number;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
bank?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
type?: string;
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
clockSpeed?: number;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
formFactor?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
manufacturer?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
partNum?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
serialNum?: string;
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
voltageConfigured?: number;
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
voltageMin?: number;
|
||||
|
||||
@Field(() => Int, { nullable: true })
|
||||
voltageMax?: number;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoMemory extends Node {
|
||||
@Field(() => GraphQLBigInt)
|
||||
max!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt)
|
||||
total!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt)
|
||||
free!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt)
|
||||
used!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt)
|
||||
active!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt)
|
||||
available!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt)
|
||||
buffcache!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt)
|
||||
swaptotal!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt)
|
||||
swapused!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt)
|
||||
swapfree!: number;
|
||||
|
||||
@Field(() => [MemoryLayout])
|
||||
layout!: MemoryLayout[];
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class Os extends Node {
|
||||
@Field(() => String, { nullable: true })
|
||||
platform?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
distro?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
release?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
codename?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
kernel?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
arch?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
hostname?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
codepage?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
logofile?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
serial?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
build?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
uptime?: string;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class System extends Node {
|
||||
@Field(() => String, { nullable: true })
|
||||
manufacturer?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
model?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
version?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
serial?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
uuid?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
sku?: string;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class Versions extends Node {
|
||||
@Field(() => String, { nullable: true })
|
||||
kernel?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
openssl?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
systemOpenssl?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
systemOpensslLib?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
node?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
v8?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
npm?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
yarn?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
pm2?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
gulp?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
grunt?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
git?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
tsc?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
mysql?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
redis?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
mongodb?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
apache?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
nginx?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
php?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
docker?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
postfix?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
postgresql?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
perl?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
python?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
gcc?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
unraid?: string;
|
||||
}
|
||||
import { InfoCpu } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.model.js';
|
||||
import { InfoDevices } from '@app/unraid-api/graph/resolvers/info/devices/devices.model.js';
|
||||
import { InfoDisplay } from '@app/unraid-api/graph/resolvers/info/display/display.model.js';
|
||||
import { InfoMemory } from '@app/unraid-api/graph/resolvers/info/memory/memory.model.js';
|
||||
import { InfoOs } from '@app/unraid-api/graph/resolvers/info/os/os.model.js';
|
||||
import { InfoBaseboard, InfoSystem } from '@app/unraid-api/graph/resolvers/info/system/system.model.js';
|
||||
import { InfoVersions } from '@app/unraid-api/graph/resolvers/info/versions/versions.model.js';
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class Info extends Node {
|
||||
@Field(() => InfoApps, { description: 'Count of docker containers' })
|
||||
apps!: InfoApps;
|
||||
|
||||
@Field(() => Baseboard)
|
||||
baseboard!: Baseboard;
|
||||
|
||||
@Field(() => InfoCpu)
|
||||
cpu!: InfoCpu;
|
||||
|
||||
@Field(() => Devices)
|
||||
devices!: Devices;
|
||||
|
||||
@Field(() => Display)
|
||||
display!: Display;
|
||||
|
||||
@Field(() => PrefixedID, { description: 'Machine ID', nullable: true })
|
||||
machineId?: string;
|
||||
|
||||
@Field(() => InfoMemory)
|
||||
memory!: InfoMemory;
|
||||
|
||||
@Field(() => Os)
|
||||
os!: Os;
|
||||
|
||||
@Field(() => System)
|
||||
system!: System;
|
||||
|
||||
@Field(() => GraphQLISODateTime)
|
||||
@Field(() => GraphQLISODateTime, { description: 'Current server time' })
|
||||
time!: Date;
|
||||
|
||||
@Field(() => Versions)
|
||||
versions!: Versions;
|
||||
@Field(() => InfoBaseboard, { description: 'Motherboard information' })
|
||||
baseboard!: InfoBaseboard;
|
||||
|
||||
@Field(() => InfoCpu, { description: 'CPU information' })
|
||||
cpu!: InfoCpu;
|
||||
|
||||
@Field(() => InfoDevices, { description: 'Device information' })
|
||||
devices!: InfoDevices;
|
||||
|
||||
@Field(() => InfoDisplay, { description: 'Display configuration' })
|
||||
display!: InfoDisplay;
|
||||
|
||||
@Field(() => ID, { nullable: true, description: 'Machine ID' })
|
||||
machineId?: string;
|
||||
|
||||
@Field(() => InfoMemory, { description: 'Memory information' })
|
||||
memory!: InfoMemory;
|
||||
|
||||
@Field(() => InfoOs, { description: 'Operating system information' })
|
||||
os!: InfoOs;
|
||||
|
||||
@Field(() => InfoSystem, { description: 'System information' })
|
||||
system!: InfoSystem;
|
||||
|
||||
@Field(() => InfoVersions, { description: 'Software versions' })
|
||||
versions!: InfoVersions;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
import { CpuDataService, CpuService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.service.js';
|
||||
import { DevicesResolver } from '@app/unraid-api/graph/resolvers/info/devices/devices.resolver.js';
|
||||
import { DevicesService } from '@app/unraid-api/graph/resolvers/info/devices/devices.service.js';
|
||||
import { DisplayService } from '@app/unraid-api/graph/resolvers/info/display/display.service.js';
|
||||
import { InfoResolver } from '@app/unraid-api/graph/resolvers/info/info.resolver.js';
|
||||
import { MemoryService } from '@app/unraid-api/graph/resolvers/info/memory/memory.service.js';
|
||||
import { OsService } from '@app/unraid-api/graph/resolvers/info/os/os.service.js';
|
||||
import { VersionsService } from '@app/unraid-api/graph/resolvers/info/versions/versions.service.js';
|
||||
import { ServicesModule } from '@app/unraid-api/graph/services/services.module.js';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, ServicesModule],
|
||||
providers: [
|
||||
// Main resolver
|
||||
InfoResolver,
|
||||
|
||||
// Sub-resolvers
|
||||
DevicesResolver,
|
||||
|
||||
// Services
|
||||
CpuService,
|
||||
CpuDataService,
|
||||
MemoryService,
|
||||
DevicesService,
|
||||
OsService,
|
||||
VersionsService,
|
||||
DisplayService,
|
||||
],
|
||||
exports: [InfoResolver, DevicesResolver, DisplayService],
|
||||
})
|
||||
export class InfoModule {}
|
||||
@@ -0,0 +1,185 @@
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { DockerService } from '@app/unraid-api/graph/resolvers/docker/docker.service.js';
|
||||
import { CpuDataService, CpuService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.service.js';
|
||||
import { DevicesResolver } from '@app/unraid-api/graph/resolvers/info/devices/devices.resolver.js';
|
||||
import { DevicesService } from '@app/unraid-api/graph/resolvers/info/devices/devices.service.js';
|
||||
import { DisplayService } from '@app/unraid-api/graph/resolvers/info/display/display.service.js';
|
||||
import { InfoResolver } from '@app/unraid-api/graph/resolvers/info/info.resolver.js';
|
||||
import { MemoryService } from '@app/unraid-api/graph/resolvers/info/memory/memory.service.js';
|
||||
import { OsService } from '@app/unraid-api/graph/resolvers/info/os/os.service.js';
|
||||
import { VersionsService } from '@app/unraid-api/graph/resolvers/info/versions/versions.service.js';
|
||||
import { SubscriptionHelperService } from '@app/unraid-api/graph/services/subscription-helper.service.js';
|
||||
import { SubscriptionTrackerService } from '@app/unraid-api/graph/services/subscription-tracker.service.js';
|
||||
|
||||
describe('InfoResolver Integration Tests', () => {
|
||||
let infoResolver: InfoResolver;
|
||||
let devicesResolver: DevicesResolver;
|
||||
let module: TestingModule;
|
||||
|
||||
beforeEach(async () => {
|
||||
module = await Test.createTestingModule({
|
||||
providers: [
|
||||
InfoResolver,
|
||||
DevicesResolver,
|
||||
CpuService,
|
||||
CpuDataService,
|
||||
MemoryService,
|
||||
DevicesService,
|
||||
OsService,
|
||||
VersionsService,
|
||||
DisplayService,
|
||||
SubscriptionTrackerService,
|
||||
SubscriptionHelperService,
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: {
|
||||
get: (key: string) => {
|
||||
if (key === 'store.emhttp.var.version') {
|
||||
return '6.12.0';
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DockerService,
|
||||
useValue: {
|
||||
getContainers: async () => [],
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CACHE_MANAGER,
|
||||
useValue: {
|
||||
get: async () => null,
|
||||
set: async () => {},
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
infoResolver = module.get<InfoResolver>(InfoResolver);
|
||||
devicesResolver = module.get<DevicesResolver>(DevicesResolver);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await module.close();
|
||||
});
|
||||
|
||||
describe('InfoResolver ResolveFields', () => {
|
||||
it('should return basic info object', async () => {
|
||||
const result = await infoResolver.info();
|
||||
expect(result).toEqual({
|
||||
id: 'info',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return current time', async () => {
|
||||
const before = new Date();
|
||||
const result = await infoResolver.time();
|
||||
const after = new Date();
|
||||
|
||||
expect(result).toBeInstanceOf(Date);
|
||||
expect(result.getTime()).toBeGreaterThanOrEqual(before.getTime());
|
||||
expect(result.getTime()).toBeLessThanOrEqual(after.getTime());
|
||||
});
|
||||
|
||||
it('should return full cpu object from service', async () => {
|
||||
const result = await infoResolver.cpu();
|
||||
|
||||
expect(result).toHaveProperty('id', 'info/cpu');
|
||||
expect(result).toHaveProperty('manufacturer');
|
||||
expect(result).toHaveProperty('brand');
|
||||
});
|
||||
|
||||
it('should return full memory object from service', async () => {
|
||||
const result = await infoResolver.memory();
|
||||
|
||||
expect(result).toHaveProperty('id', 'info/memory');
|
||||
expect(result).toHaveProperty('layout');
|
||||
expect(result.layout).toBeInstanceOf(Array);
|
||||
});
|
||||
|
||||
it('should return minimal devices stub for sub-resolver', () => {
|
||||
const result = infoResolver.devices();
|
||||
|
||||
expect(result).toHaveProperty('id', 'info/devices');
|
||||
expect(Object.keys(result)).toEqual(['id']);
|
||||
});
|
||||
|
||||
it('should return full display object from service', async () => {
|
||||
const result = await infoResolver.display();
|
||||
|
||||
expect(result).toHaveProperty('id', 'info/display');
|
||||
expect(result).toHaveProperty('theme');
|
||||
expect(result).toHaveProperty('unit');
|
||||
});
|
||||
|
||||
it('should return baseboard data', async () => {
|
||||
const result = await infoResolver.baseboard();
|
||||
|
||||
expect(result).toHaveProperty('id', 'info/baseboard');
|
||||
expect(result).toHaveProperty('manufacturer');
|
||||
expect(result).toHaveProperty('model');
|
||||
expect(result).toHaveProperty('version');
|
||||
// These are the actual properties from systeminformation
|
||||
expect(typeof result.manufacturer).toBe('string');
|
||||
});
|
||||
|
||||
it('should return system data', async () => {
|
||||
const result = await infoResolver.system();
|
||||
|
||||
expect(result).toHaveProperty('id', 'info/system');
|
||||
expect(result).toHaveProperty('manufacturer');
|
||||
expect(result).toHaveProperty('model');
|
||||
expect(result).toHaveProperty('version');
|
||||
expect(result).toHaveProperty('serial');
|
||||
expect(result).toHaveProperty('uuid');
|
||||
// Verify types
|
||||
expect(typeof result.manufacturer).toBe('string');
|
||||
});
|
||||
|
||||
it('should return os data from service', async () => {
|
||||
const result = await infoResolver.os();
|
||||
|
||||
expect(result).toHaveProperty('id', 'info/os');
|
||||
expect(result).toHaveProperty('platform');
|
||||
expect(result).toHaveProperty('distro');
|
||||
expect(result).toHaveProperty('release');
|
||||
expect(result).toHaveProperty('kernel');
|
||||
// Verify platform is a string (could be linux, darwin, win32, etc)
|
||||
expect(typeof result.platform).toBe('string');
|
||||
});
|
||||
|
||||
it.skipIf(process.env.CI)('should return versions data from service', async () => {
|
||||
const result = await infoResolver.versions();
|
||||
|
||||
expect(result).toHaveProperty('id', 'info/versions');
|
||||
expect(result).toHaveProperty('unraid');
|
||||
expect(result).toHaveProperty('kernel');
|
||||
expect(result).toHaveProperty('node');
|
||||
expect(result).toHaveProperty('npm');
|
||||
// Verify unraid version from mock
|
||||
expect(result.unraid).toBe('6.12.0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sub-Resolver Integration', () => {
|
||||
it('should resolve device fields through DevicesResolver', async () => {
|
||||
const gpu = await devicesResolver.gpu();
|
||||
const network = await devicesResolver.network();
|
||||
const pci = await devicesResolver.pci();
|
||||
const usb = await devicesResolver.usb();
|
||||
|
||||
expect(gpu).toBeInstanceOf(Array);
|
||||
expect(network).toBeInstanceOf(Array);
|
||||
expect(pci).toBeInstanceOf(Array);
|
||||
expect(usb).toBeInstanceOf(Array);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,248 +1,115 @@
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { DisplayService } from '@app/unraid-api/graph/resolvers/display/display.service.js';
|
||||
import { DockerService } from '@app/unraid-api/graph/resolvers/docker/docker.service.js';
|
||||
import { CpuDataService } from '@app/unraid-api/graph/resolvers/info/cpu-data.service.js';
|
||||
import { CpuService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.service.js';
|
||||
import { DisplayService } from '@app/unraid-api/graph/resolvers/info/display/display.service.js';
|
||||
import { InfoResolver } from '@app/unraid-api/graph/resolvers/info/info.resolver.js';
|
||||
import { InfoService } from '@app/unraid-api/graph/resolvers/info/info.service.js';
|
||||
import { SubscriptionTrackerService } from '@app/unraid-api/graph/services/subscription-tracker.service.js';
|
||||
import { MemoryService } from '@app/unraid-api/graph/resolvers/info/memory/memory.service.js';
|
||||
import { OsService } from '@app/unraid-api/graph/resolvers/info/os/os.service.js';
|
||||
import { VersionsService } from '@app/unraid-api/graph/resolvers/info/versions/versions.service.js';
|
||||
|
||||
// Mock necessary modules
|
||||
vi.mock('fs/promises', () => ({
|
||||
readFile: vi.fn().mockResolvedValue(''),
|
||||
}));
|
||||
|
||||
vi.mock('@app/core/pubsub.js', () => ({
|
||||
pubsub: {
|
||||
publish: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
PUBSUB_CHANNEL: {
|
||||
INFO: 'info',
|
||||
},
|
||||
createSubscription: vi.fn().mockReturnValue('mock-subscription'),
|
||||
}));
|
||||
|
||||
vi.mock('dockerode', () => {
|
||||
return {
|
||||
default: vi.fn().mockImplementation(() => ({
|
||||
listContainers: vi.fn(),
|
||||
listNetworks: vi.fn(),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@app/store/index.js', () => ({
|
||||
getters: {
|
||||
paths: () => ({
|
||||
'docker-autostart': '/path/to/docker-autostart',
|
||||
}),
|
||||
},
|
||||
vi.mock('@app/core/utils/misc/get-machine-id.js', () => ({
|
||||
getMachineId: vi.fn().mockResolvedValue('test-machine-id-123'),
|
||||
}));
|
||||
|
||||
vi.mock('systeminformation', () => ({
|
||||
baseboard: vi.fn().mockResolvedValue({
|
||||
manufacturer: 'ASUS',
|
||||
model: 'PRIME X570-P',
|
||||
version: 'Rev X.0x',
|
||||
serial: 'ABC123',
|
||||
assetTag: 'Default string',
|
||||
model: 'ROG STRIX',
|
||||
version: '1.0',
|
||||
}),
|
||||
system: vi.fn().mockResolvedValue({
|
||||
manufacturer: 'ASUS',
|
||||
model: 'System Product Name',
|
||||
version: 'System Version',
|
||||
serial: 'System Serial Number',
|
||||
uuid: '550e8400-e29b-41d4-a716-446655440000',
|
||||
sku: 'SKU',
|
||||
model: 'System Model',
|
||||
version: '1.0',
|
||||
serial: '123456',
|
||||
uuid: 'test-uuid',
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@app/core/utils/misc/get-machine-id.js', () => ({
|
||||
getMachineId: vi.fn().mockResolvedValue('test-machine-id-123'),
|
||||
}));
|
||||
|
||||
// Mock Cache Manager
|
||||
const mockCacheManager = {
|
||||
get: vi.fn(),
|
||||
set: vi.fn(),
|
||||
del: vi.fn(),
|
||||
};
|
||||
|
||||
describe('InfoResolver', () => {
|
||||
let resolver: InfoResolver;
|
||||
|
||||
// Mock data for testing
|
||||
const mockAppsData = {
|
||||
id: 'info/apps',
|
||||
installed: 5,
|
||||
started: 3,
|
||||
};
|
||||
|
||||
const mockCpuData = {
|
||||
id: 'info/cpu',
|
||||
manufacturer: 'AMD',
|
||||
brand: 'AMD Ryzen 9 5900X',
|
||||
vendor: 'AMD',
|
||||
family: '19',
|
||||
model: '33',
|
||||
stepping: 0,
|
||||
revision: '',
|
||||
voltage: '1.4V',
|
||||
speed: 3.7,
|
||||
speedmin: 2.2,
|
||||
speedmax: 4.8,
|
||||
threads: 24,
|
||||
cores: 12,
|
||||
processors: 1,
|
||||
socket: 'AM4',
|
||||
cache: { l1d: 32768, l1i: 32768, l2: 524288, l3: 33554432 },
|
||||
flags: ['fpu', 'vme', 'de', 'pse'],
|
||||
};
|
||||
|
||||
const mockDevicesData = {
|
||||
id: 'info/devices',
|
||||
gpu: [],
|
||||
pci: [],
|
||||
usb: [],
|
||||
};
|
||||
|
||||
const mockDisplayData = {
|
||||
id: 'display',
|
||||
case: {
|
||||
url: '',
|
||||
icon: 'default',
|
||||
error: '',
|
||||
base64: '',
|
||||
},
|
||||
theme: 'black',
|
||||
unit: 'C',
|
||||
scale: true,
|
||||
tabs: false,
|
||||
resize: true,
|
||||
wwn: false,
|
||||
total: true,
|
||||
usage: false,
|
||||
text: true,
|
||||
warning: 40,
|
||||
critical: 50,
|
||||
hot: 60,
|
||||
max: 80,
|
||||
locale: 'en_US',
|
||||
};
|
||||
|
||||
const mockMemoryData = {
|
||||
id: 'info/memory',
|
||||
max: 68719476736,
|
||||
total: 67108864000,
|
||||
free: 33554432000,
|
||||
used: 33554432000,
|
||||
active: 16777216000,
|
||||
available: 50331648000,
|
||||
buffcache: 8388608000,
|
||||
swaptotal: 4294967296,
|
||||
swapused: 0,
|
||||
swapfree: 4294967296,
|
||||
layout: [],
|
||||
};
|
||||
|
||||
const mockOsData = {
|
||||
id: 'info/os',
|
||||
platform: 'linux',
|
||||
distro: 'Unraid',
|
||||
release: '6.12.0',
|
||||
codename: '',
|
||||
kernel: '6.1.0-unraid',
|
||||
arch: 'x64',
|
||||
hostname: 'Tower',
|
||||
codepage: 'UTF-8',
|
||||
logofile: 'unraid',
|
||||
serial: '',
|
||||
build: '',
|
||||
uptime: '2024-01-01T00:00:00.000Z',
|
||||
};
|
||||
|
||||
const mockVersionsData = {
|
||||
id: 'info/versions',
|
||||
unraid: '6.12.0',
|
||||
kernel: '6.1.0',
|
||||
node: '20.10.0',
|
||||
npm: '10.2.3',
|
||||
docker: '24.0.7',
|
||||
};
|
||||
|
||||
// Mock InfoService
|
||||
const mockInfoService = {
|
||||
generateApps: vi.fn().mockResolvedValue(mockAppsData),
|
||||
generateCpu: vi.fn().mockResolvedValue(mockCpuData),
|
||||
generateDevices: vi.fn().mockResolvedValue(mockDevicesData),
|
||||
generateMemory: vi.fn().mockResolvedValue(mockMemoryData),
|
||||
generateOs: vi.fn().mockResolvedValue(mockOsData),
|
||||
generateVersions: vi.fn().mockResolvedValue(mockVersionsData),
|
||||
};
|
||||
|
||||
// Mock DisplayService
|
||||
const mockDisplayService = {
|
||||
generateDisplay: vi.fn().mockResolvedValue(mockDisplayData),
|
||||
};
|
||||
|
||||
const mockSubscriptionTrackerService = {
|
||||
registerTopic: vi.fn(),
|
||||
subscribe: vi.fn(),
|
||||
unsubscribe: vi.fn(),
|
||||
};
|
||||
|
||||
const mockCpuDataService = {
|
||||
getCpuLoad: vi.fn().mockResolvedValue({
|
||||
currentLoad: 10,
|
||||
cpus: [],
|
||||
}),
|
||||
};
|
||||
let cpuService: CpuService;
|
||||
let memoryService: MemoryService;
|
||||
let module: TestingModule;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
module = await Test.createTestingModule({
|
||||
providers: [
|
||||
InfoResolver,
|
||||
{
|
||||
provide: InfoService,
|
||||
useValue: mockInfoService,
|
||||
provide: CpuService,
|
||||
useValue: {
|
||||
generateCpu: vi.fn().mockResolvedValue({
|
||||
id: 'info/cpu',
|
||||
manufacturer: 'Intel',
|
||||
brand: 'Core i7',
|
||||
cores: 8,
|
||||
threads: 16,
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MemoryService,
|
||||
useValue: {
|
||||
generateMemory: vi.fn().mockResolvedValue({
|
||||
id: 'info/memory',
|
||||
layout: [
|
||||
{
|
||||
id: 'mem-1',
|
||||
size: 8589934592,
|
||||
bank: 'BANK 0',
|
||||
type: 'DDR4',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DisplayService,
|
||||
useValue: mockDisplayService,
|
||||
useValue: {
|
||||
generateDisplay: vi.fn().mockResolvedValue({
|
||||
id: 'info/display',
|
||||
theme: 'dark',
|
||||
unit: 'metric',
|
||||
scale: true,
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DockerService,
|
||||
useValue: {},
|
||||
provide: OsService,
|
||||
useValue: {
|
||||
generateOs: vi.fn().mockResolvedValue({
|
||||
id: 'info/os',
|
||||
platform: 'linux',
|
||||
distro: 'Unraid',
|
||||
release: '6.12.0',
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CACHE_MANAGER,
|
||||
useValue: mockCacheManager,
|
||||
},
|
||||
{
|
||||
provide: SubscriptionTrackerService,
|
||||
useValue: mockSubscriptionTrackerService,
|
||||
},
|
||||
{
|
||||
provide: CpuDataService,
|
||||
useValue: mockCpuDataService,
|
||||
provide: VersionsService,
|
||||
useValue: {
|
||||
generateVersions: vi.fn().mockResolvedValue({
|
||||
id: 'info/versions',
|
||||
unraid: '6.12.0',
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<InfoResolver>(InfoResolver);
|
||||
|
||||
// Reset mocks before each test
|
||||
vi.clearAllMocks();
|
||||
cpuService = module.get<CpuService>(CpuService);
|
||||
memoryService = module.get<MemoryService>(MemoryService);
|
||||
});
|
||||
|
||||
describe('info', () => {
|
||||
it('should return basic info object', async () => {
|
||||
const result = await resolver.info();
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'info',
|
||||
});
|
||||
@@ -251,155 +118,129 @@ describe('InfoResolver', () => {
|
||||
|
||||
describe('time', () => {
|
||||
it('should return current date', async () => {
|
||||
const beforeCall = new Date();
|
||||
const before = new Date();
|
||||
const result = await resolver.time();
|
||||
const afterCall = new Date();
|
||||
const after = new Date();
|
||||
|
||||
expect(result).toBeInstanceOf(Date);
|
||||
expect(result.getTime()).toBeGreaterThanOrEqual(beforeCall.getTime());
|
||||
expect(result.getTime()).toBeLessThanOrEqual(afterCall.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe('apps', () => {
|
||||
it('should return apps info from service', async () => {
|
||||
const result = await resolver.apps();
|
||||
|
||||
expect(mockInfoService.generateApps).toHaveBeenCalledOnce();
|
||||
expect(result).toEqual(mockAppsData);
|
||||
expect(result.getTime()).toBeGreaterThanOrEqual(before.getTime());
|
||||
expect(result.getTime()).toBeLessThanOrEqual(after.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe('baseboard', () => {
|
||||
it('should return baseboard info with id', async () => {
|
||||
it('should return baseboard data from systeminformation', async () => {
|
||||
const result = await resolver.baseboard();
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'baseboard',
|
||||
id: 'info/baseboard',
|
||||
manufacturer: 'ASUS',
|
||||
model: 'PRIME X570-P',
|
||||
version: 'Rev X.0x',
|
||||
serial: 'ABC123',
|
||||
assetTag: 'Default string',
|
||||
model: 'ROG STRIX',
|
||||
version: '1.0',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('cpu', () => {
|
||||
it('should return cpu info from service', async () => {
|
||||
it('should return full cpu data from service', async () => {
|
||||
const result = await resolver.cpu();
|
||||
|
||||
expect(mockInfoService.generateCpu).toHaveBeenCalledOnce();
|
||||
expect(result).toEqual(mockCpuData);
|
||||
expect(cpuService.generateCpu).toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
id: 'info/cpu',
|
||||
manufacturer: 'Intel',
|
||||
brand: 'Core i7',
|
||||
cores: 8,
|
||||
threads: 16,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('devices', () => {
|
||||
it('should return devices info from service', async () => {
|
||||
const result = await resolver.devices();
|
||||
|
||||
expect(mockInfoService.generateDevices).toHaveBeenCalledOnce();
|
||||
expect(result).toEqual(mockDevicesData);
|
||||
it('should return devices stub for sub-resolver', () => {
|
||||
const result = resolver.devices();
|
||||
expect(result).toEqual({
|
||||
id: 'info/devices',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('display', () => {
|
||||
it('should return display info from display service', async () => {
|
||||
it('should return display data from service', async () => {
|
||||
const displayService = module.get<DisplayService>(DisplayService);
|
||||
const result = await resolver.display();
|
||||
|
||||
expect(mockDisplayService.generateDisplay).toHaveBeenCalledOnce();
|
||||
expect(result).toEqual(mockDisplayData);
|
||||
expect(displayService.generateDisplay).toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
id: 'info/display',
|
||||
theme: 'dark',
|
||||
unit: 'metric',
|
||||
scale: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('machineId', () => {
|
||||
it('should return machine id', async () => {
|
||||
const result = await resolver.machineId();
|
||||
|
||||
expect(result).toBe('test-machine-id-123');
|
||||
});
|
||||
|
||||
it('should handle getMachineId errors gracefully', async () => {
|
||||
const { getMachineId } = await import('@app/core/utils/misc/get-machine-id.js');
|
||||
vi.mocked(getMachineId).mockRejectedValueOnce(new Error('Machine ID error'));
|
||||
|
||||
await expect(resolver.machineId()).rejects.toThrow('Machine ID error');
|
||||
const result = await resolver.machineId();
|
||||
expect(getMachineId).toHaveBeenCalled();
|
||||
expect(result).toBe('test-machine-id-123');
|
||||
});
|
||||
});
|
||||
|
||||
describe('memory', () => {
|
||||
it('should return memory info from service', async () => {
|
||||
it('should return full memory data from service', async () => {
|
||||
const result = await resolver.memory();
|
||||
|
||||
expect(mockInfoService.generateMemory).toHaveBeenCalledOnce();
|
||||
expect(result).toEqual(mockMemoryData);
|
||||
expect(memoryService.generateMemory).toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
id: 'info/memory',
|
||||
layout: [
|
||||
{
|
||||
id: 'mem-1',
|
||||
size: 8589934592,
|
||||
bank: 'BANK 0',
|
||||
type: 'DDR4',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('os', () => {
|
||||
it('should return os info from service', async () => {
|
||||
it('should return os data from service', async () => {
|
||||
const osService = module.get<OsService>(OsService);
|
||||
const result = await resolver.os();
|
||||
|
||||
expect(mockInfoService.generateOs).toHaveBeenCalledOnce();
|
||||
expect(result).toEqual(mockOsData);
|
||||
expect(osService.generateOs).toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
id: 'info/os',
|
||||
platform: 'linux',
|
||||
distro: 'Unraid',
|
||||
release: '6.12.0',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('system', () => {
|
||||
it('should return system info with id', async () => {
|
||||
it('should return system data from systeminformation', async () => {
|
||||
const result = await resolver.system();
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'system',
|
||||
id: 'info/system',
|
||||
manufacturer: 'ASUS',
|
||||
model: 'System Product Name',
|
||||
version: 'System Version',
|
||||
serial: 'System Serial Number',
|
||||
uuid: '550e8400-e29b-41d4-a716-446655440000',
|
||||
sku: 'SKU',
|
||||
model: 'System Model',
|
||||
version: '1.0',
|
||||
serial: '123456',
|
||||
uuid: 'test-uuid',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('versions', () => {
|
||||
it('should return versions info from service', async () => {
|
||||
it('should return versions data from service', async () => {
|
||||
const versionsService = module.get<VersionsService>(VersionsService);
|
||||
const result = await resolver.versions();
|
||||
|
||||
expect(mockInfoService.generateVersions).toHaveBeenCalledOnce();
|
||||
expect(result).toEqual(mockVersionsData);
|
||||
});
|
||||
});
|
||||
|
||||
describe('infoSubscription', () => {
|
||||
it('should create and return subscription', async () => {
|
||||
const { createSubscription, PUBSUB_CHANNEL } = await import('@app/core/pubsub.js');
|
||||
|
||||
const result = await resolver.infoSubscription();
|
||||
|
||||
expect(createSubscription).toHaveBeenCalledWith(PUBSUB_CHANNEL.INFO);
|
||||
expect(result).toBe('mock-subscription');
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('should handle baseboard errors gracefully', async () => {
|
||||
const { baseboard } = await import('systeminformation');
|
||||
vi.mocked(baseboard).mockRejectedValueOnce(new Error('Baseboard error'));
|
||||
|
||||
await expect(resolver.baseboard()).rejects.toThrow('Baseboard error');
|
||||
});
|
||||
|
||||
it('should handle system errors gracefully', async () => {
|
||||
const { system } = await import('systeminformation');
|
||||
vi.mocked(system).mockRejectedValueOnce(new Error('System error'));
|
||||
|
||||
await expect(resolver.system()).rejects.toThrow('System error');
|
||||
});
|
||||
|
||||
it('should handle service errors gracefully', async () => {
|
||||
mockInfoService.generateApps.mockRejectedValueOnce(new Error('Service error'));
|
||||
|
||||
await expect(resolver.apps()).rejects.toThrow('Service error');
|
||||
expect(versionsService.generateVersions).toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
id: 'info/versions',
|
||||
unraid: '6.12.0',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { OnModuleInit } from '@nestjs/common';
|
||||
import { Query, ResolveField, Resolver, Subscription } from '@nestjs/graphql';
|
||||
import { GraphQLISODateTime, Query, ResolveField, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { Resource } from '@unraid/shared/graphql.model.js';
|
||||
import {
|
||||
@@ -9,53 +8,31 @@ import {
|
||||
} from '@unraid/shared/use-permissions.directive.js';
|
||||
import { baseboard as getBaseboard, system as getSystem } from 'systeminformation';
|
||||
|
||||
import { createSubscription, pubsub, PUBSUB_CHANNEL } from '@app/core/pubsub.js';
|
||||
import { getMachineId } from '@app/core/utils/misc/get-machine-id.js';
|
||||
import { DisplayService } from '@app/unraid-api/graph/resolvers/display/display.service.js';
|
||||
import { CpuDataService } from '@app/unraid-api/graph/resolvers/info/cpu-data.service.js';
|
||||
import {
|
||||
Baseboard,
|
||||
CpuUtilization,
|
||||
Devices,
|
||||
Display,
|
||||
Info,
|
||||
InfoApps,
|
||||
InfoCpu,
|
||||
InfoMemory,
|
||||
Os,
|
||||
System,
|
||||
Versions,
|
||||
} from '@app/unraid-api/graph/resolvers/info/info.model.js';
|
||||
import { InfoService } from '@app/unraid-api/graph/resolvers/info/info.service.js';
|
||||
import { SubscriptionHelperService } from '@app/unraid-api/graph/services/subscription-helper.service.js';
|
||||
import { SubscriptionTrackerService } from '@app/unraid-api/graph/services/subscription-tracker.service.js';
|
||||
import { InfoCpu } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.model.js';
|
||||
import { CpuService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.service.js';
|
||||
import { InfoDevices } from '@app/unraid-api/graph/resolvers/info/devices/devices.model.js';
|
||||
import { InfoDisplay } from '@app/unraid-api/graph/resolvers/info/display/display.model.js';
|
||||
import { DisplayService } from '@app/unraid-api/graph/resolvers/info/display/display.service.js';
|
||||
import { Info } from '@app/unraid-api/graph/resolvers/info/info.model.js';
|
||||
import { InfoMemory } from '@app/unraid-api/graph/resolvers/info/memory/memory.model.js';
|
||||
import { MemoryService } from '@app/unraid-api/graph/resolvers/info/memory/memory.service.js';
|
||||
import { InfoOs } from '@app/unraid-api/graph/resolvers/info/os/os.model.js';
|
||||
import { OsService } from '@app/unraid-api/graph/resolvers/info/os/os.service.js';
|
||||
import { InfoBaseboard, InfoSystem } from '@app/unraid-api/graph/resolvers/info/system/system.model.js';
|
||||
import { InfoVersions } from '@app/unraid-api/graph/resolvers/info/versions/versions.model.js';
|
||||
import { VersionsService } from '@app/unraid-api/graph/resolvers/info/versions/versions.service.js';
|
||||
|
||||
@Resolver(() => Info)
|
||||
export class InfoResolver implements OnModuleInit {
|
||||
private cpuPollingTimer: NodeJS.Timeout | undefined;
|
||||
|
||||
export class InfoResolver {
|
||||
constructor(
|
||||
private readonly infoService: InfoService,
|
||||
private readonly cpuService: CpuService,
|
||||
private readonly memoryService: MemoryService,
|
||||
private readonly displayService: DisplayService,
|
||||
private readonly subscriptionTracker: SubscriptionTrackerService,
|
||||
private readonly subscriptionHelper: SubscriptionHelperService
|
||||
private readonly osService: OsService,
|
||||
private readonly versionsService: VersionsService
|
||||
) {}
|
||||
|
||||
onModuleInit() {
|
||||
this.subscriptionTracker.registerTopic(
|
||||
PUBSUB_CHANNEL.CPU_UTILIZATION,
|
||||
() => {
|
||||
this.cpuPollingTimer = setInterval(async () => {
|
||||
const payload = await this.infoService.generateCpuLoad();
|
||||
pubsub.publish(PUBSUB_CHANNEL.CPU_UTILIZATION, { cpuUtilization: payload });
|
||||
}, 1000);
|
||||
},
|
||||
() => {
|
||||
clearInterval(this.cpuPollingTimer);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Query(() => Info)
|
||||
@UsePermissions({
|
||||
action: AuthActionVerb.READ,
|
||||
@@ -68,37 +45,30 @@ export class InfoResolver implements OnModuleInit {
|
||||
};
|
||||
}
|
||||
|
||||
@ResolveField(() => Date)
|
||||
@ResolveField(() => GraphQLISODateTime)
|
||||
public async time(): Promise<Date> {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
@ResolveField(() => InfoApps)
|
||||
public async apps(): Promise<InfoApps> {
|
||||
return this.infoService.generateApps();
|
||||
}
|
||||
|
||||
@ResolveField(() => Baseboard)
|
||||
public async baseboard(): Promise<Baseboard> {
|
||||
@ResolveField(() => InfoBaseboard)
|
||||
public async baseboard(): Promise<InfoBaseboard> {
|
||||
const baseboard = await getBaseboard();
|
||||
return {
|
||||
id: 'baseboard',
|
||||
...baseboard,
|
||||
};
|
||||
return { id: 'info/baseboard', ...baseboard } as InfoBaseboard;
|
||||
}
|
||||
|
||||
@ResolveField(() => InfoCpu)
|
||||
public async cpu(): Promise<InfoCpu> {
|
||||
return this.infoService.generateCpu();
|
||||
return this.cpuService.generateCpu();
|
||||
}
|
||||
|
||||
@ResolveField(() => Devices)
|
||||
public async devices(): Promise<Devices> {
|
||||
return this.infoService.generateDevices();
|
||||
@ResolveField(() => InfoDevices)
|
||||
public devices(): Partial<InfoDevices> {
|
||||
// Return minimal stub, let InfoDevicesResolver handle all fields
|
||||
return { id: 'info/devices' };
|
||||
}
|
||||
|
||||
@ResolveField(() => Display)
|
||||
public async display(): Promise<Display> {
|
||||
@ResolveField(() => InfoDisplay)
|
||||
public async display(): Promise<InfoDisplay> {
|
||||
return this.displayService.generateDisplay();
|
||||
}
|
||||
|
||||
@@ -109,72 +79,22 @@ export class InfoResolver implements OnModuleInit {
|
||||
|
||||
@ResolveField(() => InfoMemory)
|
||||
public async memory(): Promise<InfoMemory> {
|
||||
return this.infoService.generateMemory();
|
||||
return this.memoryService.generateMemory();
|
||||
}
|
||||
|
||||
@ResolveField(() => Os)
|
||||
public async os(): Promise<Os> {
|
||||
return this.infoService.generateOs();
|
||||
@ResolveField(() => InfoOs)
|
||||
public async os(): Promise<InfoOs> {
|
||||
return this.osService.generateOs();
|
||||
}
|
||||
|
||||
@ResolveField(() => System)
|
||||
public async system(): Promise<System> {
|
||||
@ResolveField(() => InfoSystem)
|
||||
public async system(): Promise<InfoSystem> {
|
||||
const system = await getSystem();
|
||||
return {
|
||||
id: 'system',
|
||||
...system,
|
||||
};
|
||||
return { id: 'info/system', ...system } as InfoSystem;
|
||||
}
|
||||
|
||||
@ResolveField(() => Versions)
|
||||
public async versions(): Promise<Versions> {
|
||||
return this.infoService.generateVersions();
|
||||
}
|
||||
|
||||
@Subscription(() => Info)
|
||||
@UsePermissions({
|
||||
action: AuthActionVerb.READ,
|
||||
resource: Resource.INFO,
|
||||
possession: AuthPossession.ANY,
|
||||
})
|
||||
public async infoSubscription() {
|
||||
return createSubscription(PUBSUB_CHANNEL.INFO);
|
||||
}
|
||||
|
||||
@Query(() => CpuUtilization)
|
||||
@UsePermissions({
|
||||
action: AuthActionVerb.READ,
|
||||
resource: Resource.INFO,
|
||||
possession: AuthPossession.ANY,
|
||||
})
|
||||
public async cpuUtilization(): Promise<CpuUtilization> {
|
||||
return this.infoService.generateCpuLoad();
|
||||
}
|
||||
|
||||
@Subscription(() => CpuUtilization, {
|
||||
name: 'cpuUtilization',
|
||||
resolve: (value) => value.cpuUtilization,
|
||||
})
|
||||
@UsePermissions({
|
||||
action: AuthActionVerb.READ,
|
||||
resource: Resource.INFO,
|
||||
possession: AuthPossession.ANY,
|
||||
})
|
||||
public async cpuUtilizationSubscription() {
|
||||
return this.subscriptionHelper.createTrackedSubscription(PUBSUB_CHANNEL.CPU_UTILIZATION);
|
||||
}
|
||||
}
|
||||
|
||||
@Resolver(() => InfoCpu)
|
||||
export class InfoCpuResolver {
|
||||
constructor(private readonly cpuDataService: CpuDataService) {}
|
||||
|
||||
@ResolveField(() => Number, {
|
||||
description: 'CPU utilization in percent',
|
||||
nullable: true,
|
||||
})
|
||||
public async utilization(): Promise<number> {
|
||||
const { currentLoad } = await this.cpuDataService.getCpuLoad();
|
||||
return currentLoad;
|
||||
@ResolveField(() => InfoVersions)
|
||||
public async versions(): Promise<InfoVersions> {
|
||||
return this.versionsService.generateVersions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,346 +0,0 @@
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { ContainerState } from '@app/unraid-api/graph/resolvers/docker/docker.model.js';
|
||||
import { DockerService } from '@app/unraid-api/graph/resolvers/docker/docker.service.js';
|
||||
import { InfoService } from '@app/unraid-api/graph/resolvers/info/info.service.js';
|
||||
|
||||
// Mock external dependencies
|
||||
vi.mock('fs/promises', () => ({
|
||||
access: vi.fn().mockResolvedValue(undefined),
|
||||
readFile: vi.fn().mockResolvedValue(''),
|
||||
}));
|
||||
|
||||
vi.mock('execa', () => ({
|
||||
execa: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('path-type', () => ({
|
||||
isSymlink: vi.fn().mockResolvedValue(false),
|
||||
}));
|
||||
|
||||
vi.mock('systeminformation', () => ({
|
||||
cpu: vi.fn(),
|
||||
cpuFlags: vi.fn(),
|
||||
mem: vi.fn(),
|
||||
memLayout: vi.fn(),
|
||||
osInfo: vi.fn(),
|
||||
versions: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@app/common/dashboard/boot-timestamp.js', () => ({
|
||||
bootTimestamp: new Date('2024-01-01T00:00:00.000Z'),
|
||||
}));
|
||||
|
||||
vi.mock('@app/common/dashboard/get-unraid-version.js', () => ({
|
||||
getUnraidVersion: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@app/core/pubsub.js', () => ({
|
||||
pubsub: {
|
||||
publish: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
PUBSUB_CHANNEL: {
|
||||
INFO: 'info',
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('dockerode', () => {
|
||||
return {
|
||||
default: vi.fn().mockImplementation(() => ({
|
||||
listContainers: vi.fn(),
|
||||
listNetworks: vi.fn(),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@app/core/utils/misc/clean-stdout.js', () => ({
|
||||
cleanStdout: vi.fn((input) => input),
|
||||
}));
|
||||
|
||||
vi.mock('bytes', () => ({
|
||||
default: vi.fn((value) => {
|
||||
if (value === '32 GB') return 34359738368;
|
||||
if (value === '16 GB') return 17179869184;
|
||||
if (value === '4 GB') return 4294967296;
|
||||
return 0;
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@app/core/utils/misc/load-state.js', () => ({
|
||||
loadState: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@app/store/index.js', () => ({
|
||||
getters: {
|
||||
emhttp: () => ({
|
||||
var: {
|
||||
name: 'test-hostname',
|
||||
flashGuid: 'test-flash-guid',
|
||||
},
|
||||
}),
|
||||
paths: () => ({
|
||||
'dynamix-config': ['/test/config/path'],
|
||||
'docker-autostart': '/path/to/docker-autostart',
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock Cache Manager
|
||||
const mockCacheManager = {
|
||||
get: vi.fn(),
|
||||
set: vi.fn(),
|
||||
del: vi.fn(),
|
||||
};
|
||||
|
||||
describe('InfoService', () => {
|
||||
let service: InfoService;
|
||||
let dockerService: DockerService;
|
||||
let mockSystemInfo: any;
|
||||
let mockExeca: any;
|
||||
let mockGetUnraidVersion: any;
|
||||
let mockLoadState: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Reset all mocks
|
||||
vi.clearAllMocks();
|
||||
mockCacheManager.get.mockReset();
|
||||
mockCacheManager.set.mockReset();
|
||||
mockCacheManager.del.mockReset();
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
InfoService,
|
||||
DockerService,
|
||||
{
|
||||
provide: CACHE_MANAGER,
|
||||
useValue: mockCacheManager,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<InfoService>(InfoService);
|
||||
dockerService = module.get<DockerService>(DockerService);
|
||||
|
||||
// Get mock references
|
||||
mockSystemInfo = await import('systeminformation');
|
||||
mockExeca = await import('execa');
|
||||
mockGetUnraidVersion = await import('@app/common/dashboard/get-unraid-version.js');
|
||||
mockLoadState = await import('@app/core/utils/misc/load-state.js');
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('generateApps', () => {
|
||||
it('should return docker container statistics', async () => {
|
||||
const mockContainers = [
|
||||
{ id: '1', state: ContainerState.RUNNING },
|
||||
{ id: '2', state: ContainerState.EXITED },
|
||||
{ id: '3', state: ContainerState.RUNNING },
|
||||
];
|
||||
|
||||
mockCacheManager.get.mockResolvedValue(mockContainers);
|
||||
|
||||
const result = await service.generateApps();
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'info/apps',
|
||||
installed: 3,
|
||||
started: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle docker errors gracefully', async () => {
|
||||
mockCacheManager.get.mockResolvedValue([]);
|
||||
|
||||
const result = await service.generateApps();
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'info/apps',
|
||||
installed: 0,
|
||||
started: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateOs', () => {
|
||||
it('should return OS information with hostname and uptime', async () => {
|
||||
const mockOsInfo = {
|
||||
platform: 'linux',
|
||||
distro: 'Unraid',
|
||||
release: '6.12.0',
|
||||
kernel: '6.1.0-unraid',
|
||||
};
|
||||
|
||||
mockSystemInfo.osInfo.mockResolvedValue(mockOsInfo);
|
||||
|
||||
const result = await service.generateOs();
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'info/os',
|
||||
...mockOsInfo,
|
||||
hostname: 'test-hostname',
|
||||
uptime: '2024-01-01T00:00:00.000Z',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateCpu', () => {
|
||||
it('should return CPU information with proper mapping', async () => {
|
||||
const mockCpuInfo = {
|
||||
manufacturer: 'Intel',
|
||||
brand: 'Intel(R) Core(TM) i7-9700K',
|
||||
family: '6',
|
||||
model: '158',
|
||||
cores: 16,
|
||||
physicalCores: 8,
|
||||
speedMin: 800,
|
||||
speedMax: 4900,
|
||||
stepping: '10',
|
||||
cache: { l1d: 32768 },
|
||||
};
|
||||
|
||||
const mockFlags = 'fpu vme de pse tsc msr pae mce';
|
||||
|
||||
mockSystemInfo.cpu.mockResolvedValue(mockCpuInfo);
|
||||
mockSystemInfo.cpuFlags.mockResolvedValue(mockFlags);
|
||||
|
||||
const result = await service.generateCpu();
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'info/cpu',
|
||||
manufacturer: 'Intel',
|
||||
brand: 'Intel(R) Core(TM) i7-9700K',
|
||||
family: '6',
|
||||
model: '158',
|
||||
cores: 8, // physicalCores
|
||||
threads: 16, // cores
|
||||
flags: ['fpu', 'vme', 'de', 'pse', 'tsc', 'msr', 'pae', 'mce'],
|
||||
stepping: 10,
|
||||
speedmin: 800,
|
||||
speedmax: 4900,
|
||||
cache: { l1d: 32768 },
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle missing speed values', async () => {
|
||||
const mockCpuInfo = {
|
||||
manufacturer: 'AMD',
|
||||
cores: 12,
|
||||
physicalCores: 6,
|
||||
stepping: '2',
|
||||
};
|
||||
|
||||
mockSystemInfo.cpu.mockResolvedValue(mockCpuInfo);
|
||||
mockSystemInfo.cpuFlags.mockResolvedValue('sse sse2');
|
||||
|
||||
const result = await service.generateCpu();
|
||||
|
||||
expect(result.speedmin).toBe(-1);
|
||||
expect(result.speedmax).toBe(-1);
|
||||
});
|
||||
|
||||
it('should handle cpuFlags error gracefully', async () => {
|
||||
mockSystemInfo.cpu.mockResolvedValue({ cores: 8, physicalCores: 4, stepping: '1' });
|
||||
mockSystemInfo.cpuFlags.mockRejectedValue(new Error('CPU flags error'));
|
||||
|
||||
const result = await service.generateCpu();
|
||||
|
||||
expect(result.flags).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateVersions', () => {
|
||||
it('should return version information', async () => {
|
||||
const mockUnraidVersion = '6.12.0';
|
||||
const mockSoftwareVersions = {
|
||||
node: '18.17.0',
|
||||
npm: '9.6.7',
|
||||
docker: '24.0.0',
|
||||
};
|
||||
|
||||
mockGetUnraidVersion.getUnraidVersion.mockResolvedValue(mockUnraidVersion);
|
||||
mockSystemInfo.versions.mockResolvedValue(mockSoftwareVersions);
|
||||
|
||||
const result = await service.generateVersions();
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'info/versions',
|
||||
unraid: '6.12.0',
|
||||
node: '18.17.0',
|
||||
npm: '9.6.7',
|
||||
docker: '24.0.0',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateMemory', () => {
|
||||
it('should return memory information with layout', async () => {
|
||||
const mockMemLayout = [
|
||||
{
|
||||
size: 8589934592,
|
||||
bank: 'BANK 0',
|
||||
type: 'DDR4',
|
||||
clockSpeed: 3200,
|
||||
},
|
||||
];
|
||||
|
||||
const mockMemInfo = {
|
||||
total: 17179869184,
|
||||
free: 8589934592,
|
||||
used: 8589934592,
|
||||
active: 4294967296,
|
||||
available: 12884901888,
|
||||
};
|
||||
|
||||
mockSystemInfo.memLayout.mockResolvedValue(mockMemLayout);
|
||||
mockSystemInfo.mem.mockResolvedValue(mockMemInfo);
|
||||
|
||||
const result = await service.generateMemory();
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'info/memory',
|
||||
layout: mockMemLayout,
|
||||
max: mockMemInfo.total, // No dmidecode output, so max = total
|
||||
...mockMemInfo,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle memLayout error gracefully', async () => {
|
||||
mockSystemInfo.memLayout.mockRejectedValue(new Error('Memory layout error'));
|
||||
mockSystemInfo.mem.mockResolvedValue({ total: 1000 });
|
||||
|
||||
const result = await service.generateMemory();
|
||||
|
||||
expect(result.layout).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle dmidecode parsing for maximum capacity', async () => {
|
||||
mockSystemInfo.memLayout.mockResolvedValue([]);
|
||||
mockSystemInfo.mem.mockResolvedValue({ total: 16000000000 });
|
||||
// Mock dmidecode command to throw error (simulating no dmidecode available)
|
||||
mockExeca.execa.mockRejectedValue(new Error('dmidecode not found'));
|
||||
|
||||
const result = await service.generateMemory();
|
||||
|
||||
// Should fallback to using mem.total when dmidecode fails
|
||||
expect(result.max).toBe(16000000000);
|
||||
expect(result.id).toBe('info/memory');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateDevices', () => {
|
||||
it('should return basic devices object with empty arrays', async () => {
|
||||
const result = await service.generateDevices();
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'info/devices',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,105 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { cpu, cpuFlags, currentLoad, mem, memLayout, osInfo, versions } from 'systeminformation';
|
||||
|
||||
import { bootTimestamp } from '@app/common/dashboard/boot-timestamp.js';
|
||||
import { getUnraidVersion } from '@app/common/dashboard/get-unraid-version.js';
|
||||
import { getters } from '@app/store/index.js';
|
||||
import { ContainerState } from '@app/unraid-api/graph/resolvers/docker/docker.model.js';
|
||||
import { DockerService } from '@app/unraid-api/graph/resolvers/docker/docker.service.js';
|
||||
import {
|
||||
CpuUtilization,
|
||||
Devices,
|
||||
InfoApps,
|
||||
InfoCpu,
|
||||
InfoMemory,
|
||||
Os as InfoOs,
|
||||
MemoryLayout,
|
||||
Versions,
|
||||
} from '@app/unraid-api/graph/resolvers/info/info.model.js';
|
||||
|
||||
@Injectable()
|
||||
export class InfoService {
|
||||
constructor(private readonly dockerService: DockerService) {}
|
||||
|
||||
async generateApps(): Promise<InfoApps> {
|
||||
const containers = await this.dockerService.getContainers({ skipCache: false });
|
||||
const installed = containers.length;
|
||||
const started = containers.filter(
|
||||
(container) => container.state === ContainerState.RUNNING
|
||||
).length;
|
||||
|
||||
return { id: 'info/apps', installed, started };
|
||||
}
|
||||
|
||||
async generateOs(): Promise<InfoOs> {
|
||||
const os = await osInfo();
|
||||
|
||||
return {
|
||||
id: 'info/os',
|
||||
...os,
|
||||
hostname: getters.emhttp().var.name,
|
||||
uptime: bootTimestamp.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
async generateCpu(): Promise<InfoCpu> {
|
||||
const { cores, physicalCores, speedMin, speedMax, stepping, ...rest } = await cpu();
|
||||
const flags = await cpuFlags()
|
||||
.then((flags) => flags.split(' '))
|
||||
.catch(() => []);
|
||||
|
||||
return {
|
||||
id: 'info/cpu',
|
||||
...rest,
|
||||
cores: physicalCores,
|
||||
threads: cores,
|
||||
flags,
|
||||
stepping: Number(stepping),
|
||||
speedmin: speedMin || -1,
|
||||
speedmax: speedMax || -1,
|
||||
};
|
||||
}
|
||||
|
||||
async generateVersions(): Promise<Versions> {
|
||||
const unraid = await getUnraidVersion();
|
||||
const softwareVersions = await versions();
|
||||
|
||||
return {
|
||||
id: 'info/versions',
|
||||
unraid,
|
||||
...softwareVersions,
|
||||
};
|
||||
}
|
||||
|
||||
async generateMemory(): Promise<InfoMemory> {
|
||||
const layout = await memLayout()
|
||||
.then((dims) => dims.map((dim) => dim as MemoryLayout))
|
||||
.catch(() => []);
|
||||
const info = await mem();
|
||||
|
||||
return {
|
||||
id: 'info/memory',
|
||||
layout,
|
||||
max: info.total,
|
||||
...info,
|
||||
};
|
||||
}
|
||||
|
||||
async generateDevices(): Promise<Devices> {
|
||||
return {
|
||||
id: 'info/devices',
|
||||
// These fields will be resolved by DevicesResolver
|
||||
} as Devices;
|
||||
}
|
||||
|
||||
async generateCpuLoad(): Promise<CpuUtilization> {
|
||||
const { currentLoad: load, cpus } = await currentLoad();
|
||||
|
||||
return {
|
||||
id: 'info/cpu-load',
|
||||
load,
|
||||
cpus,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Field, Float, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { Node } from '@unraid/shared/graphql.model.js';
|
||||
import { GraphQLBigInt } from 'graphql-scalars';
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class MemoryLayout extends Node {
|
||||
@Field(() => GraphQLBigInt, { description: 'Memory module size in bytes' })
|
||||
size!: number;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Memory bank location (e.g., BANK 0)' })
|
||||
bank?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Memory type (e.g., DDR4, DDR5)' })
|
||||
type?: string;
|
||||
|
||||
@Field(() => Int, { nullable: true, description: 'Memory clock speed in MHz' })
|
||||
clockSpeed?: number;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Part number of the memory module' })
|
||||
partNum?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Serial number of the memory module' })
|
||||
serialNum?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Memory manufacturer' })
|
||||
manufacturer?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Form factor (e.g., DIMM, SODIMM)' })
|
||||
formFactor?: string;
|
||||
|
||||
@Field(() => Int, { nullable: true, description: 'Configured voltage in millivolts' })
|
||||
voltageConfigured?: number;
|
||||
|
||||
@Field(() => Int, { nullable: true, description: 'Minimum voltage in millivolts' })
|
||||
voltageMin?: number;
|
||||
|
||||
@Field(() => Int, { nullable: true, description: 'Maximum voltage in millivolts' })
|
||||
voltageMax?: number;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class MemoryUtilization extends Node {
|
||||
@Field(() => GraphQLBigInt, { description: 'Total system memory in bytes' })
|
||||
total!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt, { description: 'Used memory in bytes' })
|
||||
used!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt, { description: 'Free memory in bytes' })
|
||||
free!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt, { description: 'Available memory in bytes' })
|
||||
available!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt, { description: 'Active memory in bytes' })
|
||||
active!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt, { description: 'Buffer/cache memory in bytes' })
|
||||
buffcache!: number;
|
||||
|
||||
@Field(() => Float, { description: 'Memory usage percentage' })
|
||||
usedPercent!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt, { description: 'Total swap memory in bytes' })
|
||||
swapTotal!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt, { description: 'Used swap memory in bytes' })
|
||||
swapUsed!: number;
|
||||
|
||||
@Field(() => GraphQLBigInt, { description: 'Free swap memory in bytes' })
|
||||
swapFree!: number;
|
||||
|
||||
@Field(() => Float, { description: 'Swap usage percentage' })
|
||||
swapUsedPercent!: number;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoMemory extends Node {
|
||||
@Field(() => [MemoryLayout], { description: 'Physical memory layout' })
|
||||
layout!: MemoryLayout[];
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { mem, memLayout } from 'systeminformation';
|
||||
|
||||
import {
|
||||
InfoMemory,
|
||||
MemoryLayout,
|
||||
MemoryUtilization,
|
||||
} from '@app/unraid-api/graph/resolvers/info/memory/memory.model.js';
|
||||
|
||||
@Injectable()
|
||||
export class MemoryService {
|
||||
async generateMemory(): Promise<InfoMemory> {
|
||||
const layout = await memLayout()
|
||||
.then((dims) =>
|
||||
dims.map(
|
||||
(dim, index) =>
|
||||
({
|
||||
...dim,
|
||||
id: `memory-layout-${index}`,
|
||||
}) as MemoryLayout
|
||||
)
|
||||
)
|
||||
.catch(() => []);
|
||||
|
||||
return {
|
||||
id: 'info/memory',
|
||||
layout,
|
||||
};
|
||||
}
|
||||
|
||||
async generateMemoryLoad(): Promise<MemoryUtilization> {
|
||||
const memInfo = await mem();
|
||||
|
||||
return {
|
||||
id: 'memory-utilization',
|
||||
total: Math.floor(memInfo.total),
|
||||
used: Math.floor(memInfo.used),
|
||||
free: Math.floor(memInfo.free),
|
||||
available: Math.floor(memInfo.available),
|
||||
active: Math.floor(memInfo.active),
|
||||
buffcache: Math.floor(memInfo.buffcache),
|
||||
usedPercent:
|
||||
memInfo.total > 0 ? ((memInfo.total - memInfo.available) / memInfo.total) * 100 : 0,
|
||||
swapTotal: Math.floor(memInfo.swaptotal),
|
||||
swapUsed: Math.floor(memInfo.swapused),
|
||||
swapFree: Math.floor(memInfo.swapfree),
|
||||
swapUsedPercent: memInfo.swaptotal > 0 ? (memInfo.swapused / memInfo.swaptotal) * 100 : 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { Node } from '@unraid/shared/graphql.model.js';
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoOs extends Node {
|
||||
@Field(() => String, { nullable: true, description: 'Operating system platform' })
|
||||
platform?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Linux distribution name' })
|
||||
distro?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'OS release version' })
|
||||
release?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'OS codename' })
|
||||
codename?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Kernel version' })
|
||||
kernel?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'OS architecture' })
|
||||
arch?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Hostname' })
|
||||
hostname?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Fully qualified domain name' })
|
||||
fqdn?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'OS build identifier' })
|
||||
build?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Service pack version' })
|
||||
servicepack?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Boot time ISO string' })
|
||||
uptime?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'OS logo name' })
|
||||
logofile?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'OS serial number' })
|
||||
serial?: string;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, description: 'OS started via UEFI' })
|
||||
uefi?: boolean | null;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { osInfo } from 'systeminformation';
|
||||
|
||||
import { bootTimestamp } from '@app/common/dashboard/boot-timestamp.js';
|
||||
import { getters } from '@app/store/index.js';
|
||||
import { InfoOs } from '@app/unraid-api/graph/resolvers/info/os/os.model.js';
|
||||
|
||||
@Injectable()
|
||||
export class OsService {
|
||||
async generateOs(): Promise<InfoOs> {
|
||||
const os = await osInfo();
|
||||
|
||||
return {
|
||||
id: 'info/os',
|
||||
...os,
|
||||
hostname: getters.emhttp().var.name,
|
||||
uptime: bootTimestamp.toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { Node } from '@unraid/shared/graphql.model.js';
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoSystem extends Node {
|
||||
@Field(() => String, { nullable: true, description: 'System manufacturer' })
|
||||
manufacturer?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'System model' })
|
||||
model?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'System version' })
|
||||
version?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'System serial number' })
|
||||
serial?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'System UUID' })
|
||||
uuid?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'System SKU' })
|
||||
sku?: string;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, description: 'Virtual machine flag' })
|
||||
virtual?: boolean;
|
||||
}
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoBaseboard extends Node {
|
||||
@Field(() => String, { nullable: true, description: 'Motherboard manufacturer' })
|
||||
manufacturer?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Motherboard model' })
|
||||
model?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Motherboard version' })
|
||||
version?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Motherboard serial number' })
|
||||
serial?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Motherboard asset tag' })
|
||||
assetTag?: string;
|
||||
|
||||
@Field(() => Number, { nullable: true, description: 'Maximum memory capacity in bytes' })
|
||||
memMax?: number | null;
|
||||
|
||||
@Field(() => Number, { nullable: true, description: 'Number of memory slots' })
|
||||
memSlots?: number;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { Node } from '@unraid/shared/graphql.model.js';
|
||||
|
||||
@ObjectType({ implements: () => Node })
|
||||
export class InfoVersions extends Node {
|
||||
@Field(() => String, { nullable: true, description: 'Kernel version' })
|
||||
kernel?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'OpenSSL version' })
|
||||
openssl?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'System OpenSSL version' })
|
||||
systemOpenssl?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Node.js version' })
|
||||
node?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'V8 engine version' })
|
||||
v8?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'npm version' })
|
||||
npm?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Yarn version' })
|
||||
yarn?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'pm2 version' })
|
||||
pm2?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Gulp version' })
|
||||
gulp?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Grunt version' })
|
||||
grunt?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Git version' })
|
||||
git?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'tsc version' })
|
||||
tsc?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'MySQL version' })
|
||||
mysql?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Redis version' })
|
||||
redis?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'MongoDB version' })
|
||||
mongodb?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Apache version' })
|
||||
apache?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'nginx version' })
|
||||
nginx?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'PHP version' })
|
||||
php?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Postfix version' })
|
||||
postfix?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'PostgreSQL version' })
|
||||
postgresql?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Perl version' })
|
||||
perl?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Python version' })
|
||||
python?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Python3 version' })
|
||||
python3?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'pip version' })
|
||||
pip?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'pip3 version' })
|
||||
pip3?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Java version' })
|
||||
java?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'gcc version' })
|
||||
gcc?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'VirtualBox version' })
|
||||
virtualbox?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Docker version' })
|
||||
docker?: string;
|
||||
|
||||
@Field(() => String, { nullable: true, description: 'Unraid version' })
|
||||
unraid?: string;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { versions } from 'systeminformation';
|
||||
|
||||
import { InfoVersions } from '@app/unraid-api/graph/resolvers/info/versions/versions.model.js';
|
||||
|
||||
@Injectable()
|
||||
export class VersionsService {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
async generateVersions(): Promise<InfoVersions> {
|
||||
const unraid = this.configService.get<string>('store.emhttp.var.version') || 'unknown';
|
||||
const softwareVersions = await versions();
|
||||
|
||||
return {
|
||||
id: 'info/versions',
|
||||
unraid,
|
||||
...softwareVersions,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { Node } from '@unraid/shared/graphql.model.js';
|
||||
|
||||
import { CpuUtilization } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.model.js';
|
||||
import { MemoryUtilization } from '@app/unraid-api/graph/resolvers/info/memory/memory.model.js';
|
||||
|
||||
@ObjectType({
|
||||
implements: () => Node,
|
||||
description: 'System metrics including CPU and memory utilization',
|
||||
})
|
||||
export class Metrics extends Node {
|
||||
@Field(() => CpuUtilization, { description: 'Current CPU utilization metrics', nullable: true })
|
||||
cpu?: CpuUtilization;
|
||||
|
||||
@Field(() => MemoryUtilization, {
|
||||
description: 'Current memory utilization metrics',
|
||||
nullable: true,
|
||||
})
|
||||
memory?: MemoryUtilization;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { CpuDataService, CpuService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.service.js';
|
||||
import { MemoryService } from '@app/unraid-api/graph/resolvers/info/memory/memory.service.js';
|
||||
import { MetricsResolver } from '@app/unraid-api/graph/resolvers/metrics/metrics.resolver.js';
|
||||
import { ServicesModule } from '@app/unraid-api/graph/services/services.module.js';
|
||||
|
||||
@Module({
|
||||
imports: [ServicesModule],
|
||||
providers: [MetricsResolver, CpuService, CpuDataService, MemoryService],
|
||||
exports: [MetricsResolver],
|
||||
})
|
||||
export class MetricsModule {}
|
||||
@@ -0,0 +1,201 @@
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { pubsub, PUBSUB_CHANNEL } from '@app/core/pubsub.js';
|
||||
import { CpuDataService, CpuService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.service.js';
|
||||
import { MemoryService } from '@app/unraid-api/graph/resolvers/info/memory/memory.service.js';
|
||||
import { MetricsResolver } from '@app/unraid-api/graph/resolvers/metrics/metrics.resolver.js';
|
||||
import { SubscriptionHelperService } from '@app/unraid-api/graph/services/subscription-helper.service.js';
|
||||
import { SubscriptionTrackerService } from '@app/unraid-api/graph/services/subscription-tracker.service.js';
|
||||
|
||||
describe('MetricsResolver Integration Tests', () => {
|
||||
let metricsResolver: MetricsResolver;
|
||||
let module: TestingModule;
|
||||
|
||||
beforeEach(async () => {
|
||||
module = await Test.createTestingModule({
|
||||
providers: [
|
||||
MetricsResolver,
|
||||
CpuService,
|
||||
CpuDataService,
|
||||
MemoryService,
|
||||
SubscriptionTrackerService,
|
||||
SubscriptionHelperService,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
metricsResolver = module.get<MetricsResolver>(MetricsResolver);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean up any active timers
|
||||
if (metricsResolver['cpuPollingTimer']) {
|
||||
clearInterval(metricsResolver['cpuPollingTimer']);
|
||||
}
|
||||
if (metricsResolver['memoryPollingTimer']) {
|
||||
clearInterval(metricsResolver['memoryPollingTimer']);
|
||||
}
|
||||
await module.close();
|
||||
});
|
||||
|
||||
describe('Metrics Query', () => {
|
||||
it('should return metrics root object', async () => {
|
||||
const result = await metricsResolver.metrics();
|
||||
expect(result).toEqual({
|
||||
id: 'metrics',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return CPU utilization metrics', async () => {
|
||||
const result = await metricsResolver.cpu();
|
||||
|
||||
expect(result).toHaveProperty('id', 'info/cpu-load');
|
||||
expect(result).toHaveProperty('load');
|
||||
expect(result).toHaveProperty('cpus');
|
||||
expect(result.cpus).toBeInstanceOf(Array);
|
||||
expect(result.load).toBeGreaterThanOrEqual(0);
|
||||
expect(result.load).toBeLessThanOrEqual(100);
|
||||
|
||||
if (result.cpus.length > 0) {
|
||||
const firstCpu = result.cpus[0];
|
||||
expect(firstCpu).toHaveProperty('load');
|
||||
expect(firstCpu).toHaveProperty('loadUser');
|
||||
expect(firstCpu).toHaveProperty('loadSystem');
|
||||
expect(firstCpu).toHaveProperty('loadIdle');
|
||||
}
|
||||
});
|
||||
|
||||
it('should return memory utilization metrics', async () => {
|
||||
const result = await metricsResolver.memory();
|
||||
|
||||
expect(result).toHaveProperty('id', 'memory-utilization');
|
||||
expect(result).toHaveProperty('total');
|
||||
expect(result).toHaveProperty('used');
|
||||
expect(result).toHaveProperty('free');
|
||||
expect(result).toHaveProperty('available');
|
||||
expect(result).toHaveProperty('usedPercent');
|
||||
expect(result).toHaveProperty('swapTotal');
|
||||
expect(result).toHaveProperty('swapUsed');
|
||||
expect(result).toHaveProperty('swapFree');
|
||||
expect(result).toHaveProperty('swapUsedPercent');
|
||||
|
||||
expect(result.total).toBeGreaterThan(0);
|
||||
expect(result.usedPercent).toBeGreaterThanOrEqual(0);
|
||||
expect(result.usedPercent).toBeLessThanOrEqual(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Polling Mechanism', () => {
|
||||
it('should prevent concurrent CPU polling executions', async () => {
|
||||
// Start multiple polling attempts simultaneously
|
||||
const promises = Array(5)
|
||||
.fill(null)
|
||||
.map(() => metricsResolver['pollCpuUtilization']());
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
// Only one execution should have occurred
|
||||
expect(metricsResolver['isCpuPollingInProgress']).toBe(false);
|
||||
});
|
||||
|
||||
it('should prevent concurrent memory polling executions', async () => {
|
||||
// Start multiple polling attempts simultaneously
|
||||
const promises = Array(5)
|
||||
.fill(null)
|
||||
.map(() => metricsResolver['pollMemoryUtilization']());
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
// Only one execution should have occurred
|
||||
expect(metricsResolver['isMemoryPollingInProgress']).toBe(false);
|
||||
});
|
||||
|
||||
it('should publish CPU metrics to pubsub', async () => {
|
||||
const publishSpy = vi.spyOn(pubsub, 'publish');
|
||||
|
||||
await metricsResolver['pollCpuUtilization']();
|
||||
|
||||
expect(publishSpy).toHaveBeenCalledWith(
|
||||
PUBSUB_CHANNEL.CPU_UTILIZATION,
|
||||
expect.objectContaining({
|
||||
systemMetricsCpu: expect.objectContaining({
|
||||
id: 'info/cpu-load',
|
||||
load: expect.any(Number),
|
||||
cpus: expect.any(Array),
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
publishSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should publish memory metrics to pubsub', async () => {
|
||||
const publishSpy = vi.spyOn(pubsub, 'publish');
|
||||
|
||||
await metricsResolver['pollMemoryUtilization']();
|
||||
|
||||
expect(publishSpy).toHaveBeenCalledWith(
|
||||
PUBSUB_CHANNEL.MEMORY_UTILIZATION,
|
||||
expect.objectContaining({
|
||||
systemMetricsMemory: expect.objectContaining({
|
||||
id: 'memory-utilization',
|
||||
used: expect.any(Number),
|
||||
free: expect.any(Number),
|
||||
usedPercent: expect.any(Number),
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
publishSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle errors in CPU polling gracefully', async () => {
|
||||
const service = module.get<CpuService>(CpuService);
|
||||
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
vi.spyOn(service, 'generateCpuLoad').mockRejectedValueOnce(new Error('CPU error'));
|
||||
|
||||
await metricsResolver['pollCpuUtilization']();
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith('Error polling CPU utilization:', expect.any(Error));
|
||||
expect(metricsResolver['isCpuPollingInProgress']).toBe(false);
|
||||
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle errors in memory polling gracefully', async () => {
|
||||
const service = module.get<MemoryService>(MemoryService);
|
||||
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
vi.spyOn(service, 'generateMemoryLoad').mockRejectedValueOnce(new Error('Memory error'));
|
||||
|
||||
await metricsResolver['pollMemoryUtilization']();
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
'Error polling memory utilization:',
|
||||
expect.any(Error)
|
||||
);
|
||||
expect(metricsResolver['isMemoryPollingInProgress']).toBe(false);
|
||||
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Polling cleanup on module destroy', () => {
|
||||
it('should clean up timers when module is destroyed', async () => {
|
||||
// Force-start polling
|
||||
await metricsResolver['pollCpuUtilization']();
|
||||
expect(metricsResolver['isCpuPollingInProgress']).toBe(false);
|
||||
|
||||
await metricsResolver['pollMemoryUtilization']();
|
||||
expect(metricsResolver['isMemoryPollingInProgress']).toBe(false);
|
||||
|
||||
// Clean up the module
|
||||
await module.close();
|
||||
|
||||
// Timers should be cleaned up
|
||||
expect(metricsResolver['cpuPollingTimer']).toBeUndefined();
|
||||
expect(metricsResolver['memoryPollingTimer']).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,186 @@
|
||||
import type { TestingModule } from '@nestjs/testing';
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { CpuDataService, CpuService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.service.js';
|
||||
import { MemoryService } from '@app/unraid-api/graph/resolvers/info/memory/memory.service.js';
|
||||
import { MetricsResolver } from '@app/unraid-api/graph/resolvers/metrics/metrics.resolver.js';
|
||||
import { SubscriptionHelperService } from '@app/unraid-api/graph/services/subscription-helper.service.js';
|
||||
import { SubscriptionTrackerService } from '@app/unraid-api/graph/services/subscription-tracker.service.js';
|
||||
|
||||
describe('MetricsResolver', () => {
|
||||
let resolver: MetricsResolver;
|
||||
let cpuService: CpuService;
|
||||
let memoryService: MemoryService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
MetricsResolver,
|
||||
{
|
||||
provide: CpuService,
|
||||
useValue: {
|
||||
generateCpuLoad: vi.fn().mockResolvedValue({
|
||||
id: 'info/cpu-load',
|
||||
load: 25.5,
|
||||
cpus: [
|
||||
{
|
||||
load: 30.0,
|
||||
loadUser: 20.0,
|
||||
loadSystem: 10.0,
|
||||
loadNice: 0,
|
||||
loadIdle: 70.0,
|
||||
loadIrq: 0,
|
||||
},
|
||||
{
|
||||
load: 21.0,
|
||||
loadUser: 15.0,
|
||||
loadSystem: 6.0,
|
||||
loadNice: 0,
|
||||
loadIdle: 79.0,
|
||||
loadIrq: 0,
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CpuDataService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: MemoryService,
|
||||
useValue: {
|
||||
generateMemoryLoad: vi.fn().mockResolvedValue({
|
||||
id: 'memory-utilization',
|
||||
total: 16777216000,
|
||||
used: 8388608000,
|
||||
free: 8388608000,
|
||||
available: 10000000000,
|
||||
active: 5000000000,
|
||||
buffcache: 2000000000,
|
||||
usedPercent: 50.0,
|
||||
swapTotal: 4294967296,
|
||||
swapUsed: 0,
|
||||
swapFree: 4294967296,
|
||||
swapUsedPercent: 0,
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: SubscriptionTrackerService,
|
||||
useValue: {
|
||||
registerTopic: vi.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: SubscriptionHelperService,
|
||||
useValue: {
|
||||
createTrackedSubscription: vi.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<MetricsResolver>(MetricsResolver);
|
||||
cpuService = module.get<CpuService>(CpuService);
|
||||
memoryService = module.get<MemoryService>(MemoryService);
|
||||
});
|
||||
|
||||
describe('metrics', () => {
|
||||
it('should return basic metrics object', async () => {
|
||||
const result = await resolver.metrics();
|
||||
expect(result).toEqual({
|
||||
id: 'metrics',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('cpu', () => {
|
||||
it('should return CPU utilization data', async () => {
|
||||
const result = await resolver.cpu();
|
||||
|
||||
expect(cpuService.generateCpuLoad).toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
id: 'info/cpu-load',
|
||||
load: 25.5,
|
||||
cpus: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
load: 30.0,
|
||||
loadUser: 20.0,
|
||||
loadSystem: 10.0,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
load: 21.0,
|
||||
loadUser: 15.0,
|
||||
loadSystem: 6.0,
|
||||
}),
|
||||
]),
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle CPU service errors gracefully', async () => {
|
||||
vi.mocked(cpuService.generateCpuLoad).mockRejectedValueOnce(new Error('CPU error'));
|
||||
|
||||
await expect(resolver.cpu()).rejects.toThrow('CPU error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('memory', () => {
|
||||
it('should return memory utilization data', async () => {
|
||||
const result = await resolver.memory();
|
||||
|
||||
expect(memoryService.generateMemoryLoad).toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
id: 'memory-utilization',
|
||||
total: 16777216000,
|
||||
used: 8388608000,
|
||||
free: 8388608000,
|
||||
available: 10000000000,
|
||||
active: 5000000000,
|
||||
buffcache: 2000000000,
|
||||
usedPercent: 50.0,
|
||||
swapTotal: 4294967296,
|
||||
swapUsed: 0,
|
||||
swapFree: 4294967296,
|
||||
swapUsedPercent: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle memory service errors gracefully', async () => {
|
||||
vi.mocked(memoryService.generateMemoryLoad).mockRejectedValueOnce(new Error('Memory error'));
|
||||
|
||||
await expect(resolver.memory()).rejects.toThrow('Memory error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('onModuleInit', () => {
|
||||
it('should register CPU and memory polling topics', () => {
|
||||
const subscriptionTracker = {
|
||||
registerTopic: vi.fn(),
|
||||
};
|
||||
|
||||
const testModule = new MetricsResolver(
|
||||
cpuService,
|
||||
memoryService,
|
||||
subscriptionTracker as any,
|
||||
{} as any
|
||||
);
|
||||
|
||||
testModule.onModuleInit();
|
||||
|
||||
expect(subscriptionTracker.registerTopic).toHaveBeenCalledTimes(2);
|
||||
expect(subscriptionTracker.registerTopic).toHaveBeenCalledWith(
|
||||
'CPU_UTILIZATION',
|
||||
expect.any(Function),
|
||||
expect.any(Function)
|
||||
);
|
||||
expect(subscriptionTracker.registerTopic).toHaveBeenCalledWith(
|
||||
'MEMORY_UTILIZATION',
|
||||
expect.any(Function),
|
||||
expect.any(Function)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,135 @@
|
||||
import { OnModuleInit } from '@nestjs/common';
|
||||
import { Query, ResolveField, Resolver, Subscription } from '@nestjs/graphql';
|
||||
|
||||
import { Resource } from '@unraid/shared/graphql.model.js';
|
||||
import {
|
||||
AuthActionVerb,
|
||||
AuthPossession,
|
||||
UsePermissions,
|
||||
} from '@unraid/shared/use-permissions.directive.js';
|
||||
|
||||
import { pubsub, PUBSUB_CHANNEL } from '@app/core/pubsub.js';
|
||||
import { CpuUtilization } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.model.js';
|
||||
import { CpuService } from '@app/unraid-api/graph/resolvers/info/cpu/cpu.service.js';
|
||||
import { MemoryUtilization } from '@app/unraid-api/graph/resolvers/info/memory/memory.model.js';
|
||||
import { MemoryService } from '@app/unraid-api/graph/resolvers/info/memory/memory.service.js';
|
||||
import { Metrics } from '@app/unraid-api/graph/resolvers/metrics/metrics.model.js';
|
||||
import { SubscriptionHelperService } from '@app/unraid-api/graph/services/subscription-helper.service.js';
|
||||
import { SubscriptionTrackerService } from '@app/unraid-api/graph/services/subscription-tracker.service.js';
|
||||
|
||||
@Resolver(() => Metrics)
|
||||
export class MetricsResolver implements OnModuleInit {
|
||||
private cpuPollingTimer: NodeJS.Timeout | undefined;
|
||||
private memoryPollingTimer: NodeJS.Timeout | undefined;
|
||||
private isCpuPollingInProgress = false;
|
||||
private isMemoryPollingInProgress = false;
|
||||
|
||||
constructor(
|
||||
private readonly cpuService: CpuService,
|
||||
private readonly memoryService: MemoryService,
|
||||
private readonly subscriptionTracker: SubscriptionTrackerService,
|
||||
private readonly subscriptionHelper: SubscriptionHelperService
|
||||
) {}
|
||||
|
||||
onModuleInit() {
|
||||
this.subscriptionTracker.registerTopic(
|
||||
PUBSUB_CHANNEL.CPU_UTILIZATION,
|
||||
() => {
|
||||
this.pollCpuUtilization();
|
||||
this.cpuPollingTimer = setInterval(() => this.pollCpuUtilization(), 1000);
|
||||
},
|
||||
() => {
|
||||
clearInterval(this.cpuPollingTimer);
|
||||
this.isCpuPollingInProgress = false;
|
||||
}
|
||||
);
|
||||
|
||||
this.subscriptionTracker.registerTopic(
|
||||
PUBSUB_CHANNEL.MEMORY_UTILIZATION,
|
||||
() => {
|
||||
this.pollMemoryUtilization();
|
||||
this.memoryPollingTimer = setInterval(() => this.pollMemoryUtilization(), 2000);
|
||||
},
|
||||
() => {
|
||||
clearInterval(this.memoryPollingTimer);
|
||||
this.isMemoryPollingInProgress = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async pollCpuUtilization(): Promise<void> {
|
||||
if (this.isCpuPollingInProgress) return;
|
||||
|
||||
this.isCpuPollingInProgress = true;
|
||||
try {
|
||||
const payload = await this.cpuService.generateCpuLoad();
|
||||
pubsub.publish(PUBSUB_CHANNEL.CPU_UTILIZATION, { systemMetricsCpu: payload });
|
||||
} catch (error) {
|
||||
console.error('Error polling CPU utilization:', error);
|
||||
} finally {
|
||||
this.isCpuPollingInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async pollMemoryUtilization(): Promise<void> {
|
||||
if (this.isMemoryPollingInProgress) return;
|
||||
|
||||
this.isMemoryPollingInProgress = true;
|
||||
try {
|
||||
const payload = await this.memoryService.generateMemoryLoad();
|
||||
pubsub.publish(PUBSUB_CHANNEL.MEMORY_UTILIZATION, { systemMetricsMemory: payload });
|
||||
} catch (error) {
|
||||
console.error('Error polling memory utilization:', error);
|
||||
} finally {
|
||||
this.isMemoryPollingInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => Metrics)
|
||||
@UsePermissions({
|
||||
action: AuthActionVerb.READ,
|
||||
resource: Resource.INFO,
|
||||
possession: AuthPossession.ANY,
|
||||
})
|
||||
public async metrics(): Promise<Partial<Metrics>> {
|
||||
return {
|
||||
id: 'metrics',
|
||||
};
|
||||
}
|
||||
|
||||
@ResolveField(() => CpuUtilization, { nullable: true })
|
||||
public async cpu(): Promise<CpuUtilization> {
|
||||
return this.cpuService.generateCpuLoad();
|
||||
}
|
||||
|
||||
@ResolveField(() => MemoryUtilization, { nullable: true })
|
||||
public async memory(): Promise<MemoryUtilization> {
|
||||
return this.memoryService.generateMemoryLoad();
|
||||
}
|
||||
|
||||
@Subscription(() => CpuUtilization, {
|
||||
name: 'systemMetricsCpu',
|
||||
resolve: (value) => value.systemMetricsCpu,
|
||||
})
|
||||
@UsePermissions({
|
||||
action: AuthActionVerb.READ,
|
||||
resource: Resource.INFO,
|
||||
possession: AuthPossession.ANY,
|
||||
})
|
||||
public async systemMetricsCpuSubscription() {
|
||||
return this.subscriptionHelper.createTrackedSubscription(PUBSUB_CHANNEL.CPU_UTILIZATION);
|
||||
}
|
||||
|
||||
@Subscription(() => MemoryUtilization, {
|
||||
name: 'systemMetricsMemory',
|
||||
resolve: (value) => value.systemMetricsMemory,
|
||||
})
|
||||
@UsePermissions({
|
||||
action: AuthActionVerb.READ,
|
||||
resource: Resource.INFO,
|
||||
possession: AuthPossession.ANY,
|
||||
})
|
||||
public async systemMetricsMemorySubscription() {
|
||||
return this.subscriptionHelper.createTrackedSubscription(PUBSUB_CHANNEL.MEMORY_UTILIZATION);
|
||||
}
|
||||
}
|
||||
@@ -7,18 +7,13 @@ import { ArrayModule } from '@app/unraid-api/graph/resolvers/array/array.module.
|
||||
import { ConfigResolver } from '@app/unraid-api/graph/resolvers/config/config.resolver.js';
|
||||
import { CustomizationModule } from '@app/unraid-api/graph/resolvers/customization/customization.module.js';
|
||||
import { DisksModule } from '@app/unraid-api/graph/resolvers/disks/disks.module.js';
|
||||
import { DisplayResolver } from '@app/unraid-api/graph/resolvers/display/display.resolver.js';
|
||||
import { DisplayService } from '@app/unraid-api/graph/resolvers/display/display.service.js';
|
||||
import { DockerModule } from '@app/unraid-api/graph/resolvers/docker/docker.module.js';
|
||||
import { FlashBackupModule } from '@app/unraid-api/graph/resolvers/flash-backup/flash-backup.module.js';
|
||||
import { FlashResolver } from '@app/unraid-api/graph/resolvers/flash/flash.resolver.js';
|
||||
import { CpuDataService } from '@app/unraid-api/graph/resolvers/info/cpu-data.service.js';
|
||||
import { DevicesResolver } from '@app/unraid-api/graph/resolvers/info/devices.resolver.js';
|
||||
import { DevicesService } from '@app/unraid-api/graph/resolvers/info/devices.service.js';
|
||||
import { InfoCpuResolver, InfoResolver } from '@app/unraid-api/graph/resolvers/info/info.resolver.js';
|
||||
import { InfoService } from '@app/unraid-api/graph/resolvers/info/info.service.js';
|
||||
import { InfoModule } from '@app/unraid-api/graph/resolvers/info/info.module.js';
|
||||
import { LogsResolver } from '@app/unraid-api/graph/resolvers/logs/logs.resolver.js';
|
||||
import { LogsService } from '@app/unraid-api/graph/resolvers/logs/logs.service.js';
|
||||
import { MetricsModule } from '@app/unraid-api/graph/resolvers/metrics/metrics.module.js';
|
||||
import { RootMutationsResolver } from '@app/unraid-api/graph/resolvers/mutation/mutation.resolver.js';
|
||||
import { NotificationsResolver } from '@app/unraid-api/graph/resolvers/notifications/notifications.resolver.js';
|
||||
import { NotificationsService } from '@app/unraid-api/graph/resolvers/notifications/notifications.service.js';
|
||||
@@ -49,20 +44,16 @@ import { MeResolver } from '@app/unraid-api/graph/user/user.resolver.js';
|
||||
DockerModule,
|
||||
DisksModule,
|
||||
FlashBackupModule,
|
||||
InfoModule,
|
||||
RCloneModule,
|
||||
SettingsModule,
|
||||
SsoModule,
|
||||
MetricsModule,
|
||||
UPSModule,
|
||||
],
|
||||
providers: [
|
||||
ConfigResolver,
|
||||
DevicesResolver,
|
||||
DevicesService,
|
||||
DisplayResolver,
|
||||
DisplayService,
|
||||
FlashResolver,
|
||||
InfoResolver,
|
||||
InfoService,
|
||||
LogsResolver,
|
||||
LogsService,
|
||||
MeResolver,
|
||||
@@ -79,8 +70,6 @@ import { MeResolver } from '@app/unraid-api/graph/user/user.resolver.js';
|
||||
VmMutationsResolver,
|
||||
VmsResolver,
|
||||
VmsService,
|
||||
InfoCpuResolver,
|
||||
CpuDataService,
|
||||
],
|
||||
exports: [ApiKeyModule],
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ export enum GRAPHQL_PUBSUB_CHANNEL {
|
||||
DASHBOARD = "DASHBOARD",
|
||||
DISPLAY = "DISPLAY",
|
||||
INFO = "INFO",
|
||||
MEMORY_UTILIZATION = "MEMORY_UTILIZATION",
|
||||
NOTIFICATION = "NOTIFICATION",
|
||||
NOTIFICATION_ADDED = "NOTIFICATION_ADDED",
|
||||
NOTIFICATION_OVERVIEW = "NOTIFICATION_OVERVIEW",
|
||||
|
||||
+394
-190
@@ -399,16 +399,6 @@ export enum AuthorizationRuleMode {
|
||||
OR = 'OR'
|
||||
}
|
||||
|
||||
export type Baseboard = Node & {
|
||||
__typename?: 'Baseboard';
|
||||
assetTag?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
manufacturer: Scalars['String']['output'];
|
||||
model?: Maybe<Scalars['String']['output']>;
|
||||
serial?: Maybe<Scalars['String']['output']>;
|
||||
version?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Capacity = {
|
||||
__typename?: 'Capacity';
|
||||
/** Free capacity */
|
||||
@@ -419,15 +409,6 @@ export type Capacity = {
|
||||
used: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Case = Node & {
|
||||
__typename?: 'Case';
|
||||
base64?: Maybe<Scalars['String']['output']>;
|
||||
error?: Maybe<Scalars['String']['output']>;
|
||||
icon?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
url?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Cloud = {
|
||||
__typename?: 'Cloud';
|
||||
allowedOrigins: Array<Scalars['String']['output']>;
|
||||
@@ -539,6 +520,32 @@ export enum ContainerState {
|
||||
RUNNING = 'RUNNING'
|
||||
}
|
||||
|
||||
/** CPU load for a single core */
|
||||
export type CpuLoad = {
|
||||
__typename?: 'CpuLoad';
|
||||
/** The total CPU load on a single core, in percent. */
|
||||
load: Scalars['Float']['output'];
|
||||
/** The percentage of time the CPU was idle. */
|
||||
loadIdle: Scalars['Float']['output'];
|
||||
/** The percentage of time the CPU spent servicing hardware interrupts. */
|
||||
loadIrq: Scalars['Float']['output'];
|
||||
/** The percentage of time the CPU spent on low-priority (niced) user space processes. */
|
||||
loadNice: Scalars['Float']['output'];
|
||||
/** The percentage of time the CPU spent in kernel space. */
|
||||
loadSystem: Scalars['Float']['output'];
|
||||
/** The percentage of time the CPU spent in user space. */
|
||||
loadUser: Scalars['Float']['output'];
|
||||
};
|
||||
|
||||
export type CpuUtilization = Node & {
|
||||
__typename?: 'CpuUtilization';
|
||||
/** CPU load for each core */
|
||||
cpus: Array<CpuLoad>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Total CPU load in percent */
|
||||
load: Scalars['Float']['output'];
|
||||
};
|
||||
|
||||
export type CreateApiKeyInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
name: Scalars['String']['input'];
|
||||
@@ -569,14 +576,6 @@ export type DeleteRCloneRemoteInput = {
|
||||
name: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type Devices = Node & {
|
||||
__typename?: 'Devices';
|
||||
gpu: Array<Gpu>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
pci: Array<Pci>;
|
||||
usb: Array<Usb>;
|
||||
};
|
||||
|
||||
export type Disk = Node & {
|
||||
__typename?: 'Disk';
|
||||
/** The number of bytes per sector */
|
||||
@@ -653,31 +652,6 @@ export enum DiskSmartStatus {
|
||||
UNKNOWN = 'UNKNOWN'
|
||||
}
|
||||
|
||||
export type Display = Node & {
|
||||
__typename?: 'Display';
|
||||
banner?: Maybe<Scalars['String']['output']>;
|
||||
case?: Maybe<Case>;
|
||||
critical?: Maybe<Scalars['Int']['output']>;
|
||||
dashapps?: Maybe<Scalars['String']['output']>;
|
||||
date?: Maybe<Scalars['String']['output']>;
|
||||
hot?: Maybe<Scalars['Int']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
locale?: Maybe<Scalars['String']['output']>;
|
||||
max?: Maybe<Scalars['Int']['output']>;
|
||||
number?: Maybe<Scalars['String']['output']>;
|
||||
resize?: Maybe<Scalars['Boolean']['output']>;
|
||||
scale?: Maybe<Scalars['Boolean']['output']>;
|
||||
tabs?: Maybe<Scalars['Boolean']['output']>;
|
||||
text?: Maybe<Scalars['Boolean']['output']>;
|
||||
theme?: Maybe<ThemeName>;
|
||||
total?: Maybe<Scalars['Boolean']['output']>;
|
||||
unit?: Maybe<Temperature>;
|
||||
usage?: Maybe<Scalars['Boolean']['output']>;
|
||||
users?: Maybe<Scalars['String']['output']>;
|
||||
warning?: Maybe<Scalars['Int']['output']>;
|
||||
wwn?: Maybe<Scalars['Boolean']['output']>;
|
||||
};
|
||||
|
||||
export type Docker = Node & {
|
||||
__typename?: 'Docker';
|
||||
containers: Array<DockerContainer>;
|
||||
@@ -792,80 +766,340 @@ export type FlashBackupStatus = {
|
||||
status: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Gpu = Node & {
|
||||
__typename?: 'Gpu';
|
||||
blacklisted: Scalars['Boolean']['output'];
|
||||
class: Scalars['String']['output'];
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
productid: Scalars['String']['output'];
|
||||
type: Scalars['String']['output'];
|
||||
typeid: Scalars['String']['output'];
|
||||
vendorname: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Info = Node & {
|
||||
__typename?: 'Info';
|
||||
/** Count of docker containers */
|
||||
apps: InfoApps;
|
||||
baseboard: Baseboard;
|
||||
/** Motherboard information */
|
||||
baseboard: InfoBaseboard;
|
||||
/** CPU information */
|
||||
cpu: InfoCpu;
|
||||
devices: Devices;
|
||||
display: Display;
|
||||
/** Device information */
|
||||
devices: InfoDevices;
|
||||
/** Display configuration */
|
||||
display: InfoDisplay;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Machine ID */
|
||||
machineId?: Maybe<Scalars['PrefixedID']['output']>;
|
||||
machineId?: Maybe<Scalars['ID']['output']>;
|
||||
/** Memory information */
|
||||
memory: InfoMemory;
|
||||
os: Os;
|
||||
system: System;
|
||||
/** Operating system information */
|
||||
os: InfoOs;
|
||||
/** System information */
|
||||
system: InfoSystem;
|
||||
/** Current server time */
|
||||
time: Scalars['DateTime']['output'];
|
||||
versions: Versions;
|
||||
/** Software versions */
|
||||
versions: InfoVersions;
|
||||
};
|
||||
|
||||
export type InfoApps = Node & {
|
||||
__typename?: 'InfoApps';
|
||||
export type InfoBaseboard = Node & {
|
||||
__typename?: 'InfoBaseboard';
|
||||
/** Motherboard asset tag */
|
||||
assetTag?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** How many docker containers are installed */
|
||||
installed: Scalars['Int']['output'];
|
||||
/** How many docker containers are running */
|
||||
started: Scalars['Int']['output'];
|
||||
/** Motherboard manufacturer */
|
||||
manufacturer?: Maybe<Scalars['String']['output']>;
|
||||
/** Maximum memory capacity in bytes */
|
||||
memMax?: Maybe<Scalars['Float']['output']>;
|
||||
/** Number of memory slots */
|
||||
memSlots?: Maybe<Scalars['Float']['output']>;
|
||||
/** Motherboard model */
|
||||
model?: Maybe<Scalars['String']['output']>;
|
||||
/** Motherboard serial number */
|
||||
serial?: Maybe<Scalars['String']['output']>;
|
||||
/** Motherboard version */
|
||||
version?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type InfoCpu = Node & {
|
||||
__typename?: 'InfoCpu';
|
||||
brand: Scalars['String']['output'];
|
||||
cache: Scalars['JSON']['output'];
|
||||
cores: Scalars['Int']['output'];
|
||||
family: Scalars['String']['output'];
|
||||
flags: Array<Scalars['String']['output']>;
|
||||
/** CPU brand name */
|
||||
brand?: Maybe<Scalars['String']['output']>;
|
||||
/** CPU cache information */
|
||||
cache?: Maybe<Scalars['JSON']['output']>;
|
||||
/** Number of CPU cores */
|
||||
cores?: Maybe<Scalars['Int']['output']>;
|
||||
/** CPU family */
|
||||
family?: Maybe<Scalars['String']['output']>;
|
||||
/** CPU feature flags */
|
||||
flags?: Maybe<Array<Scalars['String']['output']>>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
manufacturer: Scalars['String']['output'];
|
||||
model: Scalars['String']['output'];
|
||||
processors: Scalars['Int']['output'];
|
||||
revision: Scalars['String']['output'];
|
||||
socket: Scalars['String']['output'];
|
||||
speed: Scalars['Float']['output'];
|
||||
speedmax: Scalars['Float']['output'];
|
||||
speedmin: Scalars['Float']['output'];
|
||||
stepping: Scalars['Int']['output'];
|
||||
threads: Scalars['Int']['output'];
|
||||
vendor: Scalars['String']['output'];
|
||||
/** CPU manufacturer */
|
||||
manufacturer?: Maybe<Scalars['String']['output']>;
|
||||
/** CPU model */
|
||||
model?: Maybe<Scalars['String']['output']>;
|
||||
/** Number of physical processors */
|
||||
processors?: Maybe<Scalars['Int']['output']>;
|
||||
/** CPU revision */
|
||||
revision?: Maybe<Scalars['String']['output']>;
|
||||
/** CPU socket type */
|
||||
socket?: Maybe<Scalars['String']['output']>;
|
||||
/** Current CPU speed in GHz */
|
||||
speed?: Maybe<Scalars['Float']['output']>;
|
||||
/** Maximum CPU speed in GHz */
|
||||
speedmax?: Maybe<Scalars['Float']['output']>;
|
||||
/** Minimum CPU speed in GHz */
|
||||
speedmin?: Maybe<Scalars['Float']['output']>;
|
||||
/** CPU stepping */
|
||||
stepping?: Maybe<Scalars['Int']['output']>;
|
||||
/** Number of CPU threads */
|
||||
threads?: Maybe<Scalars['Int']['output']>;
|
||||
/** CPU vendor */
|
||||
vendor?: Maybe<Scalars['String']['output']>;
|
||||
/** CPU voltage */
|
||||
voltage?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type InfoDevices = Node & {
|
||||
__typename?: 'InfoDevices';
|
||||
/** List of GPU devices */
|
||||
gpu?: Maybe<Array<InfoGpu>>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** List of network interfaces */
|
||||
network?: Maybe<Array<InfoNetwork>>;
|
||||
/** List of PCI devices */
|
||||
pci?: Maybe<Array<InfoPci>>;
|
||||
/** List of USB devices */
|
||||
usb?: Maybe<Array<InfoUsb>>;
|
||||
};
|
||||
|
||||
export type InfoDisplay = Node & {
|
||||
__typename?: 'InfoDisplay';
|
||||
/** Case display configuration */
|
||||
case: InfoDisplayCase;
|
||||
/** Critical temperature threshold */
|
||||
critical: Scalars['Int']['output'];
|
||||
/** Hot temperature threshold */
|
||||
hot: Scalars['Int']['output'];
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Locale setting */
|
||||
locale?: Maybe<Scalars['String']['output']>;
|
||||
/** Maximum temperature threshold */
|
||||
max?: Maybe<Scalars['Int']['output']>;
|
||||
/** Enable UI resize */
|
||||
resize: Scalars['Boolean']['output'];
|
||||
/** Enable UI scaling */
|
||||
scale: Scalars['Boolean']['output'];
|
||||
/** Show tabs in UI */
|
||||
tabs: Scalars['Boolean']['output'];
|
||||
/** Show text labels */
|
||||
text: Scalars['Boolean']['output'];
|
||||
/** UI theme name */
|
||||
theme: ThemeName;
|
||||
/** Show totals */
|
||||
total: Scalars['Boolean']['output'];
|
||||
/** Temperature unit (C or F) */
|
||||
unit: Temperature;
|
||||
/** Show usage statistics */
|
||||
usage: Scalars['Boolean']['output'];
|
||||
/** Warning temperature threshold */
|
||||
warning: Scalars['Int']['output'];
|
||||
/** Show WWN identifiers */
|
||||
wwn: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
export type InfoDisplayCase = Node & {
|
||||
__typename?: 'InfoDisplayCase';
|
||||
/** Base64 encoded case image */
|
||||
base64: Scalars['String']['output'];
|
||||
/** Error message if any */
|
||||
error: Scalars['String']['output'];
|
||||
/** Case icon identifier */
|
||||
icon: Scalars['String']['output'];
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Case image URL */
|
||||
url: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type InfoGpu = Node & {
|
||||
__typename?: 'InfoGpu';
|
||||
/** Whether GPU is blacklisted */
|
||||
blacklisted: Scalars['Boolean']['output'];
|
||||
/** Device class */
|
||||
class: Scalars['String']['output'];
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Product ID */
|
||||
productid: Scalars['String']['output'];
|
||||
/** GPU type/manufacturer */
|
||||
type: Scalars['String']['output'];
|
||||
/** GPU type identifier */
|
||||
typeid: Scalars['String']['output'];
|
||||
/** Vendor name */
|
||||
vendorname?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type InfoMemory = Node & {
|
||||
__typename?: 'InfoMemory';
|
||||
active: Scalars['BigInt']['output'];
|
||||
available: Scalars['BigInt']['output'];
|
||||
buffcache: Scalars['BigInt']['output'];
|
||||
free: Scalars['BigInt']['output'];
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Physical memory layout */
|
||||
layout: Array<MemoryLayout>;
|
||||
max: Scalars['BigInt']['output'];
|
||||
swapfree: Scalars['BigInt']['output'];
|
||||
swaptotal: Scalars['BigInt']['output'];
|
||||
swapused: Scalars['BigInt']['output'];
|
||||
total: Scalars['BigInt']['output'];
|
||||
used: Scalars['BigInt']['output'];
|
||||
};
|
||||
|
||||
export type InfoNetwork = Node & {
|
||||
__typename?: 'InfoNetwork';
|
||||
/** DHCP enabled flag */
|
||||
dhcp?: Maybe<Scalars['Boolean']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Network interface name */
|
||||
iface: Scalars['String']['output'];
|
||||
/** MAC address */
|
||||
mac?: Maybe<Scalars['String']['output']>;
|
||||
/** Network interface model */
|
||||
model?: Maybe<Scalars['String']['output']>;
|
||||
/** Network speed */
|
||||
speed?: Maybe<Scalars['String']['output']>;
|
||||
/** Network vendor */
|
||||
vendor?: Maybe<Scalars['String']['output']>;
|
||||
/** Virtual interface flag */
|
||||
virtual?: Maybe<Scalars['Boolean']['output']>;
|
||||
};
|
||||
|
||||
export type InfoOs = Node & {
|
||||
__typename?: 'InfoOs';
|
||||
/** OS architecture */
|
||||
arch?: Maybe<Scalars['String']['output']>;
|
||||
/** OS build identifier */
|
||||
build?: Maybe<Scalars['String']['output']>;
|
||||
/** OS codename */
|
||||
codename?: Maybe<Scalars['String']['output']>;
|
||||
/** Linux distribution name */
|
||||
distro?: Maybe<Scalars['String']['output']>;
|
||||
/** Fully qualified domain name */
|
||||
fqdn?: Maybe<Scalars['String']['output']>;
|
||||
/** Hostname */
|
||||
hostname?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Kernel version */
|
||||
kernel?: Maybe<Scalars['String']['output']>;
|
||||
/** OS logo name */
|
||||
logofile?: Maybe<Scalars['String']['output']>;
|
||||
/** Operating system platform */
|
||||
platform?: Maybe<Scalars['String']['output']>;
|
||||
/** OS release version */
|
||||
release?: Maybe<Scalars['String']['output']>;
|
||||
/** OS serial number */
|
||||
serial?: Maybe<Scalars['String']['output']>;
|
||||
/** Service pack version */
|
||||
servicepack?: Maybe<Scalars['String']['output']>;
|
||||
/** OS started via UEFI */
|
||||
uefi?: Maybe<Scalars['Boolean']['output']>;
|
||||
/** Boot time ISO string */
|
||||
uptime?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type InfoPci = Node & {
|
||||
__typename?: 'InfoPci';
|
||||
/** Blacklisted status */
|
||||
blacklisted: Scalars['String']['output'];
|
||||
/** Device class */
|
||||
class: Scalars['String']['output'];
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Product ID */
|
||||
productid: Scalars['String']['output'];
|
||||
/** Product name */
|
||||
productname?: Maybe<Scalars['String']['output']>;
|
||||
/** Device type/manufacturer */
|
||||
type: Scalars['String']['output'];
|
||||
/** Type identifier */
|
||||
typeid: Scalars['String']['output'];
|
||||
/** Vendor ID */
|
||||
vendorid: Scalars['String']['output'];
|
||||
/** Vendor name */
|
||||
vendorname?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type InfoSystem = Node & {
|
||||
__typename?: 'InfoSystem';
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** System manufacturer */
|
||||
manufacturer?: Maybe<Scalars['String']['output']>;
|
||||
/** System model */
|
||||
model?: Maybe<Scalars['String']['output']>;
|
||||
/** System serial number */
|
||||
serial?: Maybe<Scalars['String']['output']>;
|
||||
/** System SKU */
|
||||
sku?: Maybe<Scalars['String']['output']>;
|
||||
/** System UUID */
|
||||
uuid?: Maybe<Scalars['String']['output']>;
|
||||
/** System version */
|
||||
version?: Maybe<Scalars['String']['output']>;
|
||||
/** Virtual machine flag */
|
||||
virtual?: Maybe<Scalars['Boolean']['output']>;
|
||||
};
|
||||
|
||||
export type InfoUsb = Node & {
|
||||
__typename?: 'InfoUsb';
|
||||
/** USB bus number */
|
||||
bus?: Maybe<Scalars['String']['output']>;
|
||||
/** USB device number */
|
||||
device?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** USB device name */
|
||||
name: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type InfoVersions = Node & {
|
||||
__typename?: 'InfoVersions';
|
||||
/** Apache version */
|
||||
apache?: Maybe<Scalars['String']['output']>;
|
||||
/** Docker version */
|
||||
docker?: Maybe<Scalars['String']['output']>;
|
||||
/** gcc version */
|
||||
gcc?: Maybe<Scalars['String']['output']>;
|
||||
/** Git version */
|
||||
git?: Maybe<Scalars['String']['output']>;
|
||||
/** Grunt version */
|
||||
grunt?: Maybe<Scalars['String']['output']>;
|
||||
/** Gulp version */
|
||||
gulp?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Java version */
|
||||
java?: Maybe<Scalars['String']['output']>;
|
||||
/** Kernel version */
|
||||
kernel?: Maybe<Scalars['String']['output']>;
|
||||
/** MongoDB version */
|
||||
mongodb?: Maybe<Scalars['String']['output']>;
|
||||
/** MySQL version */
|
||||
mysql?: Maybe<Scalars['String']['output']>;
|
||||
/** nginx version */
|
||||
nginx?: Maybe<Scalars['String']['output']>;
|
||||
/** Node.js version */
|
||||
node?: Maybe<Scalars['String']['output']>;
|
||||
/** npm version */
|
||||
npm?: Maybe<Scalars['String']['output']>;
|
||||
/** OpenSSL version */
|
||||
openssl?: Maybe<Scalars['String']['output']>;
|
||||
/** Perl version */
|
||||
perl?: Maybe<Scalars['String']['output']>;
|
||||
/** PHP version */
|
||||
php?: Maybe<Scalars['String']['output']>;
|
||||
/** pip version */
|
||||
pip?: Maybe<Scalars['String']['output']>;
|
||||
/** pip3 version */
|
||||
pip3?: Maybe<Scalars['String']['output']>;
|
||||
/** pm2 version */
|
||||
pm2?: Maybe<Scalars['String']['output']>;
|
||||
/** Postfix version */
|
||||
postfix?: Maybe<Scalars['String']['output']>;
|
||||
/** PostgreSQL version */
|
||||
postgresql?: Maybe<Scalars['String']['output']>;
|
||||
/** Python version */
|
||||
python?: Maybe<Scalars['String']['output']>;
|
||||
/** Python3 version */
|
||||
python3?: Maybe<Scalars['String']['output']>;
|
||||
/** Redis version */
|
||||
redis?: Maybe<Scalars['String']['output']>;
|
||||
/** System OpenSSL version */
|
||||
systemOpenssl?: Maybe<Scalars['String']['output']>;
|
||||
/** tsc version */
|
||||
tsc?: Maybe<Scalars['String']['output']>;
|
||||
/** Unraid version */
|
||||
unraid?: Maybe<Scalars['String']['output']>;
|
||||
/** V8 engine version */
|
||||
v8?: Maybe<Scalars['String']['output']>;
|
||||
/** VirtualBox version */
|
||||
virtualbox?: Maybe<Scalars['String']['output']>;
|
||||
/** Yarn version */
|
||||
yarn?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type InitiateFlashBackupInput = {
|
||||
@@ -911,20 +1145,68 @@ export type LogFileContent = {
|
||||
|
||||
export type MemoryLayout = Node & {
|
||||
__typename?: 'MemoryLayout';
|
||||
/** Memory bank location (e.g., BANK 0) */
|
||||
bank?: Maybe<Scalars['String']['output']>;
|
||||
/** Memory clock speed in MHz */
|
||||
clockSpeed?: Maybe<Scalars['Int']['output']>;
|
||||
/** Form factor (e.g., DIMM, SODIMM) */
|
||||
formFactor?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Memory manufacturer */
|
||||
manufacturer?: Maybe<Scalars['String']['output']>;
|
||||
/** Part number of the memory module */
|
||||
partNum?: Maybe<Scalars['String']['output']>;
|
||||
/** Serial number of the memory module */
|
||||
serialNum?: Maybe<Scalars['String']['output']>;
|
||||
/** Memory module size in bytes */
|
||||
size: Scalars['BigInt']['output'];
|
||||
/** Memory type (e.g., DDR4, DDR5) */
|
||||
type?: Maybe<Scalars['String']['output']>;
|
||||
/** Configured voltage in millivolts */
|
||||
voltageConfigured?: Maybe<Scalars['Int']['output']>;
|
||||
/** Maximum voltage in millivolts */
|
||||
voltageMax?: Maybe<Scalars['Int']['output']>;
|
||||
/** Minimum voltage in millivolts */
|
||||
voltageMin?: Maybe<Scalars['Int']['output']>;
|
||||
};
|
||||
|
||||
export type MemoryUtilization = Node & {
|
||||
__typename?: 'MemoryUtilization';
|
||||
/** Active memory in bytes */
|
||||
active: Scalars['BigInt']['output'];
|
||||
/** Available memory in bytes */
|
||||
available: Scalars['BigInt']['output'];
|
||||
/** Buffer/cache memory in bytes */
|
||||
buffcache: Scalars['BigInt']['output'];
|
||||
/** Free memory in bytes */
|
||||
free: Scalars['BigInt']['output'];
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Free swap memory in bytes */
|
||||
swapFree: Scalars['BigInt']['output'];
|
||||
/** Total swap memory in bytes */
|
||||
swapTotal: Scalars['BigInt']['output'];
|
||||
/** Used swap memory in bytes */
|
||||
swapUsed: Scalars['BigInt']['output'];
|
||||
/** Swap usage percentage */
|
||||
swapUsedPercent: Scalars['Float']['output'];
|
||||
/** Total system memory in bytes */
|
||||
total: Scalars['BigInt']['output'];
|
||||
/** Used memory in bytes */
|
||||
used: Scalars['BigInt']['output'];
|
||||
/** Memory usage percentage */
|
||||
usedPercent: Scalars['Float']['output'];
|
||||
};
|
||||
|
||||
/** System metrics including CPU and memory utilization */
|
||||
export type Metrics = Node & {
|
||||
__typename?: 'Metrics';
|
||||
/** Current CPU utilization metrics */
|
||||
cpu?: Maybe<CpuUtilization>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
/** Current memory utilization metrics */
|
||||
memory?: Maybe<MemoryUtilization>;
|
||||
};
|
||||
|
||||
/** The status of the minigraph */
|
||||
export enum MinigraphStatus {
|
||||
CONNECTED = 'CONNECTED',
|
||||
@@ -1237,23 +1519,6 @@ export type OrganizerResource = {
|
||||
type: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Os = Node & {
|
||||
__typename?: 'Os';
|
||||
arch?: Maybe<Scalars['String']['output']>;
|
||||
build?: Maybe<Scalars['String']['output']>;
|
||||
codename?: Maybe<Scalars['String']['output']>;
|
||||
codepage?: Maybe<Scalars['String']['output']>;
|
||||
distro?: Maybe<Scalars['String']['output']>;
|
||||
hostname?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
kernel?: Maybe<Scalars['String']['output']>;
|
||||
logofile?: Maybe<Scalars['String']['output']>;
|
||||
platform?: Maybe<Scalars['String']['output']>;
|
||||
release?: Maybe<Scalars['String']['output']>;
|
||||
serial?: Maybe<Scalars['String']['output']>;
|
||||
uptime?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Owner = {
|
||||
__typename?: 'Owner';
|
||||
avatar: Scalars['String']['output'];
|
||||
@@ -1302,19 +1567,6 @@ export type ParityCheckMutationsStartArgs = {
|
||||
correct: Scalars['Boolean']['input'];
|
||||
};
|
||||
|
||||
export type Pci = Node & {
|
||||
__typename?: 'Pci';
|
||||
blacklisted?: Maybe<Scalars['String']['output']>;
|
||||
class?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
productid?: Maybe<Scalars['String']['output']>;
|
||||
productname?: Maybe<Scalars['String']['output']>;
|
||||
type?: Maybe<Scalars['String']['output']>;
|
||||
typeid?: Maybe<Scalars['String']['output']>;
|
||||
vendorid?: Maybe<Scalars['String']['output']>;
|
||||
vendorname?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Permission = {
|
||||
__typename?: 'Permission';
|
||||
actions: Array<Scalars['String']['output']>;
|
||||
@@ -1385,7 +1637,6 @@ export type Query = {
|
||||
customization?: Maybe<Customization>;
|
||||
disk: Disk;
|
||||
disks: Array<Disk>;
|
||||
display: Display;
|
||||
docker: Docker;
|
||||
flash: Flash;
|
||||
info: Info;
|
||||
@@ -1394,6 +1645,7 @@ export type Query = {
|
||||
logFile: LogFileContent;
|
||||
logFiles: Array<LogFile>;
|
||||
me: UserAccount;
|
||||
metrics: Metrics;
|
||||
network: Network;
|
||||
/** Get all notifications */
|
||||
notifications: Notifications;
|
||||
@@ -1743,14 +1995,14 @@ export type SsoSettings = Node & {
|
||||
export type Subscription = {
|
||||
__typename?: 'Subscription';
|
||||
arraySubscription: UnraidArray;
|
||||
displaySubscription: Display;
|
||||
infoSubscription: Info;
|
||||
logFile: LogFileContent;
|
||||
notificationAdded: Notification;
|
||||
notificationsOverview: NotificationOverview;
|
||||
ownerSubscription: Owner;
|
||||
parityHistorySubscription: ParityCheck;
|
||||
serversSubscription: Server;
|
||||
systemMetricsCpu: CpuUtilization;
|
||||
systemMetricsMemory: MemoryUtilization;
|
||||
upsUpdates: UpsDevice;
|
||||
};
|
||||
|
||||
@@ -1759,21 +2011,10 @@ export type SubscriptionLogFileArgs = {
|
||||
path: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type System = Node & {
|
||||
__typename?: 'System';
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
manufacturer?: Maybe<Scalars['String']['output']>;
|
||||
model?: Maybe<Scalars['String']['output']>;
|
||||
serial?: Maybe<Scalars['String']['output']>;
|
||||
sku?: Maybe<Scalars['String']['output']>;
|
||||
uuid?: Maybe<Scalars['String']['output']>;
|
||||
version?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
/** Temperature unit (Celsius or Fahrenheit) */
|
||||
/** Temperature unit */
|
||||
export enum Temperature {
|
||||
C = 'C',
|
||||
F = 'F'
|
||||
CELSIUS = 'CELSIUS',
|
||||
FAHRENHEIT = 'FAHRENHEIT'
|
||||
}
|
||||
|
||||
export type Theme = {
|
||||
@@ -1985,12 +2226,6 @@ export type Uptime = {
|
||||
timestamp?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Usb = Node & {
|
||||
__typename?: 'Usb';
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
name?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type UserAccount = Node & {
|
||||
__typename?: 'UserAccount';
|
||||
/** A description of the user */
|
||||
@@ -2168,37 +2403,6 @@ export type Vars = Node & {
|
||||
workgroup?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Versions = Node & {
|
||||
__typename?: 'Versions';
|
||||
apache?: Maybe<Scalars['String']['output']>;
|
||||
docker?: Maybe<Scalars['String']['output']>;
|
||||
gcc?: Maybe<Scalars['String']['output']>;
|
||||
git?: Maybe<Scalars['String']['output']>;
|
||||
grunt?: Maybe<Scalars['String']['output']>;
|
||||
gulp?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['PrefixedID']['output'];
|
||||
kernel?: Maybe<Scalars['String']['output']>;
|
||||
mongodb?: Maybe<Scalars['String']['output']>;
|
||||
mysql?: Maybe<Scalars['String']['output']>;
|
||||
nginx?: Maybe<Scalars['String']['output']>;
|
||||
node?: Maybe<Scalars['String']['output']>;
|
||||
npm?: Maybe<Scalars['String']['output']>;
|
||||
openssl?: Maybe<Scalars['String']['output']>;
|
||||
perl?: Maybe<Scalars['String']['output']>;
|
||||
php?: Maybe<Scalars['String']['output']>;
|
||||
pm2?: Maybe<Scalars['String']['output']>;
|
||||
postfix?: Maybe<Scalars['String']['output']>;
|
||||
postgresql?: Maybe<Scalars['String']['output']>;
|
||||
python?: Maybe<Scalars['String']['output']>;
|
||||
redis?: Maybe<Scalars['String']['output']>;
|
||||
systemOpenssl?: Maybe<Scalars['String']['output']>;
|
||||
systemOpensslLib?: Maybe<Scalars['String']['output']>;
|
||||
tsc?: Maybe<Scalars['String']['output']>;
|
||||
unraid?: Maybe<Scalars['String']['output']>;
|
||||
v8?: Maybe<Scalars['String']['output']>;
|
||||
yarn?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type VmDomain = Node & {
|
||||
__typename?: 'VmDomain';
|
||||
/** The unique identifier for the vm (uuid) */
|
||||
@@ -2516,7 +2720,7 @@ export type PublicOidcProvidersQuery = { __typename?: 'Query', publicOidcProvide
|
||||
export type ServerInfoQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ServerInfoQuery = { __typename?: 'Query', info: { __typename?: 'Info', os: { __typename?: 'Os', hostname?: string | null } }, vars: { __typename?: 'Vars', comment?: string | null } };
|
||||
export type ServerInfoQuery = { __typename?: 'Query', info: { __typename?: 'Info', os: { __typename?: 'InfoOs', hostname?: string | null } }, vars: { __typename?: 'Vars', comment?: string | null } };
|
||||
|
||||
export type ConnectSignInMutationVariables = Exact<{
|
||||
input: ConnectSignInInput;
|
||||
@@ -2548,7 +2752,7 @@ export type CloudStateQuery = { __typename?: 'Query', cloud: (
|
||||
export type ServerStateQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type ServerStateQuery = { __typename?: 'Query', config: { __typename?: 'Config', error?: string | null, valid?: boolean | null }, info: { __typename?: 'Info', os: { __typename?: 'Os', hostname?: string | null } }, owner: { __typename?: 'Owner', avatar: string, username: string }, registration?: { __typename?: 'Registration', state?: RegistrationState | null, expiration?: string | null, updateExpiration?: string | null, keyFile?: { __typename?: 'KeyFile', contents?: string | null } | null } | null, vars: { __typename?: 'Vars', regGen?: string | null, regState?: RegistrationState | null, configError?: ConfigErrorState | null, configValid?: boolean | null } };
|
||||
export type ServerStateQuery = { __typename?: 'Query', config: { __typename?: 'Config', error?: string | null, valid?: boolean | null }, info: { __typename?: 'Info', os: { __typename?: 'InfoOs', hostname?: string | null } }, owner: { __typename?: 'Owner', avatar: string, username: string }, registration?: { __typename?: 'Registration', state?: RegistrationState | null, expiration?: string | null, updateExpiration?: string | null, keyFile?: { __typename?: 'KeyFile', contents?: string | null } | null } | null, vars: { __typename?: 'Vars', regGen?: string | null, regState?: RegistrationState | null, configError?: ConfigErrorState | null, configValid?: boolean | null } };
|
||||
|
||||
export type GetThemeQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user