Skip to content

Commit 36f48d4

Browse files
feat: add ups monitoring to graphql api
1 parent 6758778 commit 36f48d4

File tree

4 files changed

+88
-22
lines changed

4 files changed

+88
-22
lines changed
Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
1-
import { Field, InputType, Int, Float } from '@nestjs/graphql';
1+
import { Field, InputType, Int } from '@nestjs/graphql';
22

33
@InputType()
44
export class UPSConfigInput {
55
@Field()
6-
id: string;
6+
SERVICE: string;
77

88
@Field()
9-
name: string;
9+
UPSCABLE: string;
10+
11+
@Field({ nullable: true })
12+
CUSTOMUPSCABLE?: string;
13+
14+
@Field()
15+
UPSTYPE: string;
16+
17+
@Field({ nullable: true })
18+
DEVICE?: string;
19+
20+
@Field(() => Int, { nullable: true })
21+
OVERRIDE_UPS_CAPACITY?: number;
22+
23+
@Field(() => Int)
24+
BATTERYLEVEL: number;
25+
26+
@Field(() => Int)
27+
MINUTES: number;
28+
29+
@Field(() => Int)
30+
TIMEOUT: number;
1031

1132
@Field()
12-
model: string;
33+
KILLUPS: string;
1334
}

api/src/unraid-api/graph/resolvers/ups/ups.resolver.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
22
import { UPSResolver } from './ups.resolver';
33
import { UPSService, UPSData } from './ups.service';
44
import { describe, it, expect, beforeEach, vi } from 'vitest';
5+
import { UPSConfigInput } from './ups.inputs';
56

67
describe('UPSResolver', () => {
78
let resolver: UPSResolver;
@@ -25,6 +26,7 @@ describe('UPSResolver', () => {
2526
provide: UPSService,
2627
useValue: {
2728
getUPSData: vi.fn().mockResolvedValue(mockUPSData),
29+
configureUPS: vi.fn().mockResolvedValue(undefined),
2830
},
2931
},
3032
],
@@ -46,4 +48,21 @@ describe('UPSResolver', () => {
4648
expect(service.getUPSData).toHaveBeenCalled();
4749
});
4850
});
51+
52+
describe('configureUps', () => {
53+
it('should call the configureUPS service method and return true', async () => {
54+
const config: UPSConfigInput = {
55+
SERVICE: 'enable',
56+
UPSCABLE: 'usb',
57+
UPSTYPE: 'usb',
58+
BATTERYLEVEL: 10,
59+
MINUTES: 5,
60+
TIMEOUT: 0,
61+
KILLUPS: 'no',
62+
};
63+
const result = await resolver.configureUps(config);
64+
expect(result).toBe(true);
65+
expect(service.configureUPS).toHaveBeenCalledWith(config);
66+
});
67+
});
4968
});

api/src/unraid-api/graph/resolvers/ups/ups.resolver.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,27 +57,28 @@ export class UPSResolver {
5757
return null;
5858
}
5959

60-
@Mutation(() => UPSDevice)
61-
async configureUps(@Args('config') config: UPSConfigInput): Promise<UPSDevice> {
62-
// Mock data for now, will be replaced with actual UPS data
60+
@Mutation(() => Boolean)
61+
async configureUps(@Args('config') config: UPSConfigInput): Promise<boolean> {
62+
await this.upsService.configureUPS(config);
63+
const updatedData = await this.upsService.getUPSData();
6364
const newDevice = {
64-
id: config.id,
65-
name: config.name,
66-
model: config.model,
67-
status: 'Online',
68-
battery: {
69-
chargeLevel: 100,
70-
estimatedRuntime: 3600,
71-
health: 'Good',
72-
},
73-
power: {
74-
inputVoltage: 120.5,
75-
outputVoltage: 120.5,
76-
loadPercentage: 25,
77-
},
65+
id: 'ups1',
66+
name: updatedData.MODEL || 'My UPS',
67+
model: updatedData.MODEL || 'APC Back-UPS Pro 1500',
68+
status: updatedData.STATUS || 'Online',
69+
battery: {
70+
chargeLevel: parseInt(updatedData.BCHARGE, 10) || 100,
71+
estimatedRuntime: parseInt(updatedData.TIMELEFT, 10) || 3600,
72+
health: 'Good',
73+
},
74+
power: {
75+
inputVoltage: parseFloat(updatedData.LINEV) || 120.5,
76+
outputVoltage: parseFloat(updatedData.OUTPUTV) || 120.5,
77+
loadPercentage: parseInt(updatedData.LOADPCT, 10) || 25,
78+
},
7879
};
7980
pubSub.publish('upsUpdates', { upsUpdates: newDevice });
80-
return newDevice;
81+
return true;
8182
}
8283

8384
@Subscription(() => UPSDevice)

api/src/unraid-api/graph/resolvers/ups/ups.service.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
22
import { exec } from 'child_process';
33
import { promisify } from 'util';
44
import { z } from 'zod';
5+
import { UPSConfigInput } from './ups.inputs';
56

67
const execPromise = promisify(exec);
78

@@ -30,6 +31,30 @@ export class UPSService {
3031
}
3132
}
3233

34+
async configureUPS(config: UPSConfigInput): Promise<void> {
35+
const conf = '/etc/apcupsd/apcupsd.conf';
36+
const cable = config.UPSCABLE === 'custom' ? config.CUSTOMUPSCABLE : config.UPSCABLE;
37+
38+
await execPromise('/etc/rc.d/rc.apcupsd stop');
39+
await execPromise(`sed -i -e '/^NISIP/c\\NISIP 0.0.0.0' ${conf}`);
40+
await execPromise(`sed -i -e '/^UPSTYPE/c\\UPSTYPE "${config.UPSTYPE}"' ${conf}`);
41+
await execPromise(`sed -i -e '/^DEVICE/c\\DEVICE "${config.DEVICE}"' ${conf}`);
42+
await execPromise(`sed -i -e '/^BATTERYLEVEL/c\\BATTERYLEVEL "${config.BATTERYLEVEL}"' ${conf}`);
43+
await execPromise(`sed -i -e '/^MINUTES/c\\MINUTES "${config.MINUTES}"' ${conf}`);
44+
await execPromise(`sed -i -e '/^TIMEOUT/c\\TIMEOUT "${config.TIMEOUT}"' ${conf}`);
45+
await execPromise(`sed -i -e '/^UPSCABLE/c\\UPSCABLE "${cable}"' ${conf}`);
46+
47+
if (config.KILLUPS === 'yes' && config.SERVICE === 'enable') {
48+
await execPromise(`! grep -q apccontrol /etc/rc.d/rc.6 && sed -i -e 's:/sbin/poweroff:/etc/apcupsd/apccontrol killpower; /sbin/poweroff:' /etc/rc.d/rc.6`);
49+
} else {
50+
await execPromise(`grep -q apccontrol /etc/rc.d/rc.6 && sed -i -e 's:/etc/apcupsd/apccontrol killpower; /sbin/poweroff:/sbin/poweroff:' /etc/rc.d/rc.6`);
51+
}
52+
53+
if (config.SERVICE === 'enable') {
54+
await execPromise('/etc/rc.d/rc.apcupsd start');
55+
}
56+
}
57+
3358
private parseUPSData(data: string): any {
3459
const lines = data.split('\n');
3560
const upsData = {};

0 commit comments

Comments
 (0)