Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/src/unraid-api/graph/resolvers/resolvers.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { RCloneModule } from '@app/unraid-api/graph/resolvers/rclone/rclone.modu
import { RegistrationResolver } from '@app/unraid-api/graph/resolvers/registration/registration.resolver.js';
import { ServerResolver } from '@app/unraid-api/graph/resolvers/servers/server.resolver.js';
import { SettingsModule } from '@app/unraid-api/graph/resolvers/settings/settings.module.js';
import { UPSModule } from '@app/unraid-api/graph/resolvers/ups/ups.module.js';
import { VarsResolver } from '@app/unraid-api/graph/resolvers/vars/vars.resolver.js';
import { VmMutationsResolver } from '@app/unraid-api/graph/resolvers/vms/vms.mutations.resolver.js';
import { VmsResolver } from '@app/unraid-api/graph/resolvers/vms/vms.resolver.js';
Expand All @@ -46,6 +47,7 @@ import { MeResolver } from '@app/unraid-api/graph/user/user.resolver.js';
FlashBackupModule,
RCloneModule,
SettingsModule,
UPSModule,
],
providers: [
ConfigResolver,
Expand Down
138 changes: 138 additions & 0 deletions api/src/unraid-api/graph/resolvers/ups/ups.inputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { Field, InputType, Int, registerEnumType } from '@nestjs/graphql';

import { Max, Min } from 'class-validator';

/**
* Service state for UPS daemon
*/
export enum UPSServiceState {
ENABLE = 'enable',
DISABLE = 'disable',
}

/**
* UPS cable types
*/
export enum UPSCableType {
USB = 'usb',
SIMPLE = 'simple',
SMART = 'smart',
ETHER = 'ether',
CUSTOM = 'custom',
}

/**
* UPS communication types
*/
export enum UPSType {
USB = 'usb',
APCSMART = 'apcsmart',
NET = 'net',
SNMP = 'snmp',
DUMB = 'dumb',
PCNET = 'pcnet',
MODBUS = 'modbus',
}

/**
* Kill UPS power after shutdown option
*/
export enum UPSKillPower {
YES = 'yes',
NO = 'no',
}

// Register enums with GraphQL
registerEnumType(UPSServiceState, {
name: 'UPSServiceState',
description: 'Service state for UPS daemon',
});

registerEnumType(UPSCableType, {
name: 'UPSCableType',
description: 'UPS cable connection types',
});

registerEnumType(UPSType, {
name: 'UPSType',
description: 'UPS communication protocols',
});

registerEnumType(UPSKillPower, {
name: 'UPSKillPower',
description: 'Kill UPS power after shutdown option',
});

@InputType()
export class UPSConfigInput {
@Field(() => UPSServiceState, {
nullable: true,
description: 'Enable or disable the UPS monitoring service',
})
service?: UPSServiceState;

@Field(() => UPSCableType, {
nullable: true,
description: 'Type of cable connecting the UPS to the server',
})
upsCable?: UPSCableType;

@Field({
nullable: true,
description:
'Custom cable configuration (only used when upsCable is CUSTOM). Format depends on specific UPS model',
})
customUpsCable?: string;

@Field(() => UPSType, {
nullable: true,
description: 'UPS communication protocol',
})
upsType?: UPSType;

@Field({
nullable: true,
description:
"Device path or network address for UPS connection. Examples: '/dev/ttyUSB0' for USB, '192.168.1.100:3551' for network",
})
device?: string;

@Field(() => Int, {
nullable: true,
description:
'Override UPS capacity for runtime calculations. Unit: watts (W). Leave unset to use UPS-reported capacity',
})
@Min(0, { message: 'Override UPS capacity must be a positive number' })
overrideUpsCapacity?: number;

@Field(() => Int, {
nullable: true,
description:
'Battery level percentage to initiate shutdown. Unit: percent (%) - Valid range: 0-100',
})
@Min(0, { message: 'Battery level must be between 0 and 100' })
@Max(100, { message: 'Battery level must be between 0 and 100' })
batteryLevel?: number;

@Field(() => Int, {
nullable: true,
description: 'Runtime left in minutes to initiate shutdown. Unit: minutes',
})
@Min(0, { message: 'Minutes must be 0 or greater' })
minutes?: number;

@Field(() => Int, {
nullable: true,
description:
'Time on battery before shutdown. Unit: seconds. Set to 0 to disable timeout-based shutdown',
})
@Min(0, { message: 'Timeout must be 0 or greater (0 disables timeout-based shutdown)' })
timeout?: number;

@Field(() => UPSKillPower, {
nullable: true,
description:
'Turn off UPS power after system shutdown. Useful for ensuring complete power cycle',
})
killUps?: UPSKillPower;
}
171 changes: 171 additions & 0 deletions api/src/unraid-api/graph/resolvers/ups/ups.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { Field, Float, ID, Int, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class UPSBattery {
@Field(() => Int, {
description:
'Battery charge level as a percentage (0-100). Unit: percent (%). Example: 100 means battery is fully charged',
})
chargeLevel!: number;

@Field(() => Int, {
description:
'Estimated runtime remaining on battery power. Unit: seconds. Example: 3600 means 1 hour of runtime remaining',
})
estimatedRuntime!: number;

@Field({
description:
"Battery health status. Possible values: 'Good', 'Replace', 'Unknown'. Indicates if the battery needs replacement",
})
health!: string;
}

@ObjectType()
export class UPSPower {
@Field(() => Float, {
description:
'Input voltage from the wall outlet/mains power. Unit: volts (V). Example: 120.5 for typical US household voltage',
})
inputVoltage!: number;

@Field(() => Float, {
description:
'Output voltage being delivered to connected devices. Unit: volts (V). Example: 120.5 - should match input voltage when on mains power',
})
outputVoltage!: number;

@Field(() => Int, {
description:
'Current load on the UPS as a percentage of its capacity. Unit: percent (%). Example: 25 means UPS is loaded at 25% of its maximum capacity',
})
loadPercentage!: number;
}

@ObjectType()
export class UPSDevice {
@Field(() => ID, {
description:
'Unique identifier for the UPS device. Usually based on the model name or a generated ID',
})
id!: string;

@Field({ description: 'Display name for the UPS device. Can be customized by the user' })
name!: string;

@Field({ description: "UPS model name/number. Example: 'APC Back-UPS Pro 1500'" })
model!: string;

@Field({
description:
"Current operational status of the UPS. Common values: 'Online', 'On Battery', 'Low Battery', 'Replace Battery', 'Overload', 'Offline'. 'Online' means running on mains power, 'On Battery' means running on battery backup",
})
status!: string;

@Field(() => UPSBattery, { description: 'Battery-related information' })
battery!: UPSBattery;

@Field(() => UPSPower, { description: 'Power-related information' })
power!: UPSPower;
}

@ObjectType()
export class UPSConfiguration {
@Field({
nullable: true,
description:
"UPS service state. Values: 'enable' or 'disable'. Controls whether the UPS monitoring service is running",
})
service?: string;

@Field({
nullable: true,
description:
"Type of cable connecting the UPS to the server. Common values: 'usb', 'smart', 'ether', 'custom'. Determines communication protocol",
})
upsCable?: string;

@Field({
nullable: true,
description:
"Custom cable configuration string. Only used when upsCable is set to 'custom'. Format depends on specific UPS model",
})
customUpsCable?: string;

@Field({
nullable: true,
description:
"UPS communication type. Common values: 'usb', 'net', 'snmp', 'dumb', 'pcnet', 'modbus'. Defines how the server communicates with the UPS",
})
upsType?: string;

@Field({
nullable: true,
description:
"Device path or network address for UPS connection. Examples: '/dev/ttyUSB0' for USB, '192.168.1.100:3551' for network. Depends on upsType setting",
})
device?: string;

@Field(() => Int, {
nullable: true,
description:
'Override UPS capacity for runtime calculations. Unit: volt-amperes (VA). Example: 1500 for a 1500VA UPS. Leave unset to use UPS-reported capacity',
})
overrideUpsCapacity?: number;

@Field(() => Int, {
nullable: true,
description:
'Battery level threshold for shutdown. Unit: percent (%). Example: 10 means shutdown when battery reaches 10%. System will shutdown when battery drops to this level',
})
batteryLevel?: number;

@Field(() => Int, {
nullable: true,
description:
'Runtime threshold for shutdown. Unit: minutes. Example: 5 means shutdown when 5 minutes runtime remaining. System will shutdown when estimated runtime drops below this',
})
minutes?: number;

@Field(() => Int, {
nullable: true,
description:
'Timeout for UPS communications. Unit: seconds. Example: 0 means no timeout. Time to wait for UPS response before considering it offline',
})
timeout?: number;

@Field({
nullable: true,
description:
"Kill UPS power after shutdown. Values: 'yes' or 'no'. If 'yes', tells UPS to cut power after system shutdown. Useful for ensuring complete power cycle",
})
killUps?: string;

@Field({
nullable: true,
description:
"Network Information Server (NIS) IP address. Default: '0.0.0.0' (listen on all interfaces). IP address for apcupsd network information server",
})
nisIp?: string;

@Field({
nullable: true,
description:
"Network server mode. Values: 'on' or 'off'. Enable to allow network clients to monitor this UPS",
})
netServer?: string;

@Field({
nullable: true,
description:
"UPS name for network monitoring. Used to identify this UPS on the network. Example: 'SERVER_UPS'",
})
upsName?: string;

@Field({
nullable: true,
description:
'Override UPS model name. Used for display purposes. Leave unset to use UPS-reported model',
})
modelName?: string;
}
11 changes: 11 additions & 0 deletions api/src/unraid-api/graph/resolvers/ups/ups.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';

import { PubSub } from 'graphql-subscriptions';

import { UPSResolver } from '@app/unraid-api/graph/resolvers/ups/ups.resolver.js';
import { UPSService } from '@app/unraid-api/graph/resolvers/ups/ups.service.js';

@Module({
providers: [UPSResolver, UPSService, { provide: PubSub, useValue: new PubSub() }],
})
export class UPSModule {}
Loading