Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 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
3 changes: 2 additions & 1 deletion api/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ PATHS_LOGS_FILE=./dev/log/graphql-api.log
PATHS_CONNECT_STATUS_FILE_PATH=./dev/connectStatus.json # Connect plugin status file
PATHS_OIDC_JSON=./dev/configs/oidc.local.json
PATHS_LOCAL_SESSION_FILE=./dev/local-session
PATHS_CONNECT_STATUS=./dev/states/connectStatus.json # Connect status file for development
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add quotes around the value on line 22.

The .env file format requires quoted values for consistent parsing by dotenv libraries.

Apply this fix:

-PATHS_CONNECT_STATUS=./dev/states/connectStatus.json # Connect status file for development
+PATHS_CONNECT_STATUS="./dev/states/connectStatus.json" # Connect status file for development
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
PATHS_CONNECT_STATUS=./dev/states/connectStatus.json # Connect status file for development
PATHS_CONNECT_STATUS="./dev/states/connectStatus.json" # Connect status file for development
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 22-22: [UnorderedKey] The PATHS_CONNECT_STATUS key should go before the PATHS_CONNECT_STATUS_FILE_PATH key

(UnorderedKey)


[warning] 22-22: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)

🤖 Prompt for AI Agents
In api/.env.development around line 22, the PATHS_CONNECT_STATUS value is
unquoted; update the line to wrap the value in quotes so dotenv parsers handle
it consistently (e.g., change
PATHS_CONNECT_STATUS=./dev/states/connectStatus.json to
PATHS_CONNECT_STATUS="./dev/states/connectStatus.json").

ENVIRONMENT="development"
NODE_ENV="development"
PORT="3001"
PLAYGROUND=true
INTROSPECTION=true
MOTHERSHIP_GRAPHQL_LINK="http://authenticator:3000/graphql"
MOTHERSHIP_BASE_URL="http://localhost:8787"
NODE_TLS_REJECT_UNAUTHORIZED=0
BYPASS_PERMISSION_CHECKS=false
BYPASS_CORS_CHECKS=true
Expand Down
7 changes: 7 additions & 0 deletions api/dev/states/connectStatus.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"connectionStatus": "PRE_INIT",
"error": null,
"lastPing": null,
"allowedOrigins": "",
"timestamp": 1753974976746
}
133 changes: 133 additions & 0 deletions api/generated-schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2110,6 +2110,136 @@ type UPSConfiguration {
modelName: String
}

type UPSBattery {
"""
Battery charge level as a percentage (0-100). Unit: percent (%). Example: 100 means battery is fully charged
"""
chargeLevel: Int!

"""
Estimated runtime remaining on battery power. Unit: seconds. Example: 3600 means 1 hour of runtime remaining
"""
estimatedRuntime: Int!

"""
Battery health status. Possible values: 'Good', 'Replace', 'Unknown'. Indicates if the battery needs replacement
"""
health: String!
}

type UPSPower {
"""
Input voltage from the wall outlet/mains power. Unit: volts (V). Example: 120.5 for typical US household voltage
"""
inputVoltage: Float!

"""
Output voltage being delivered to connected devices. Unit: volts (V). Example: 120.5 - should match input voltage when on mains power
"""
outputVoltage: Float!

"""
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: Int!
}

type UPSDevice {
"""
Unique identifier for the UPS device. Usually based on the model name or a generated ID
"""
id: ID!

"""Display name for the UPS device. Can be customized by the user"""
name: String!

"""UPS model name/number. Example: 'APC Back-UPS Pro 1500'"""
model: String!

"""
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!

"""Battery-related information"""
battery: UPSBattery!

"""Power-related information"""
power: UPSPower!
}

type UPSConfiguration {
"""
UPS service state. Values: 'enable' or 'disable'. Controls whether the UPS monitoring service is running
"""
service: String

"""
Type of cable connecting the UPS to the server. Common values: 'usb', 'smart', 'ether', 'custom'. Determines communication protocol
"""
upsCable: String

"""
Custom cable configuration string. Only used when upsCable is set to 'custom'. Format depends on specific UPS model
"""
customUpsCable: String

"""
UPS communication type. Common values: 'usb', 'net', 'snmp', 'dumb', 'pcnet', 'modbus'. Defines how the server communicates with the UPS
"""
upsType: String

"""
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

"""
Override UPS capacity for runtime calculations. Unit: volt-amperes (VA). Example: 1500 for a 1500VA UPS. Leave unset to use UPS-reported capacity
"""
overrideUpsCapacity: Int

"""
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: Int

"""
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: Int

"""
Timeout for UPS communications. Unit: seconds. Example: 0 means no timeout. Time to wait for UPS response before considering it offline
"""
timeout: Int

"""
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

"""
Network Information Server (NIS) IP address. Default: '0.0.0.0' (listen on all interfaces). IP address for apcupsd network information server
"""
nisIp: String

"""
Network server mode. Values: 'on' or 'off'. Enable to allow network clients to monitor this UPS
"""
netServer: String

"""
UPS name for network monitoring. Used to identify this UPS on the network. Example: 'SERVER_UPS'
"""
upsName: String

"""
Override UPS model name. Used for display purposes. Leave unset to use UPS-reported model
"""
modelName: String
}

type VmDomain implements Node {
"""The unique identifier for the vm (uuid)"""
id: PrefixedID!
Expand Down Expand Up @@ -2396,6 +2526,9 @@ type Query {
logFile(path: String!, lines: Int, startLine: Int): LogFileContent!
settings: Settings!
isSSOEnabled: Boolean!
upsDevices: [UPSDevice!]!
upsDeviceById(id: String!): UPSDevice
upsConfiguration: UPSConfiguration!

"""Get public OIDC provider information for login buttons"""
publicOidcProviders: [PublicOidcProvider!]!
Expand Down
9 changes: 9 additions & 0 deletions api/src/unraid-api/unraid-file-modifier/file-modification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { applyPatch, createPatch, parsePatch, reversePatch } from 'diff';
import { coerce, compare, gte, lte } from 'semver';

import { getUnraidVersion } from '@app/common/dashboard/get-unraid-version.js';
import { NODE_ENV } from '@app/environment.js';

export type ModificationEffect = 'nginx:reload';

Expand Down Expand Up @@ -225,6 +226,14 @@ export abstract class FileModification {
throw new Error('Invalid file modification configuration');
}

// Skip file modifications in development mode
if (NODE_ENV === 'development') {
return {
shouldApply: false,
reason: 'File modifications are disabled in development mode',
};
}

const fileExists = await access(this.filePath, constants.R_OK | constants.W_OK)
.then(() => true)
.catch(() => false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { ConfigService } from '@nestjs/config';

import type { ModificationEffect } from '@app/unraid-api/unraid-file-modifier/file-modification.js';
import { NODE_ENV } from '@app/environment.js';
import { FileModificationEffectService } from '@app/unraid-api/unraid-file-modifier/file-modification-effect.service.js';
import { FileModification } from '@app/unraid-api/unraid-file-modifier/file-modification.js';

Expand All @@ -29,6 +30,11 @@ export class UnraidFileModificationService
*/
async onModuleInit() {
try {
if (NODE_ENV === 'development') {
this.logger.log('Skipping file modifications in development mode');
return;
}

this.logger.log('Loading file modifications...');
const mods = await this.loadModifications();
await this.applyModifications(mods);
Expand Down
38 changes: 38 additions & 0 deletions packages/unraid-api-plugin-connect-2/.prettierrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @see https://prettier.io/docs/en/configuration.html
* @type {import("prettier").Config}
*/
module.exports = {
trailingComma: 'es5',
tabWidth: 4,
semi: true,
singleQuote: true,
printWidth: 105,
plugins: ['@ianvs/prettier-plugin-sort-imports'],
// decorators-legacy lets the import sorter transform files with decorators
importOrderParserPlugins: ['typescript', 'decorators-legacy'],
importOrder: [
/**----------------------
* Nest.js & node.js imports
*------------------------**/
'<TYPES>^@nestjs(/.*)?$',
'^@nestjs(/.*)?$', // matches imports starting with @nestjs
'<TYPES>^(node:)',
'<BUILTIN_MODULES>', // Node.js built-in modules
'',
/**----------------------
* Third party packages
*------------------------**/
'<TYPES>',
'<THIRD_PARTY_MODULES>', // Imports not matched by other special words or groups.
'',
/**----------------------
* Application Code
*------------------------**/
'<TYPES>^@app(/.*)?$', // matches type imports starting with @app
'^@app(/.*)?$',
'',
'<TYPES>^[.]',
'^[.]', // relative imports
],
};
36 changes: 36 additions & 0 deletions packages/unraid-api-plugin-connect-2/codegen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
overwrite: true,
emitLegacyCommonJSImports: false,
verbose: true,
config: {
namingConvention: {
enumValues: 'change-case-all#upperCase',
transformUnderscore: true,
useTypeImports: true,
},
scalars: {
DateTime: 'string',
Long: 'number',
JSON: 'Record<string, any>',
URL: 'URL',
Port: 'number',
UUID: 'string',
BigInt: 'number',
},
scalarSchemas: {
URL: 'z.instanceof(URL)',
Long: 'z.number()',
JSON: 'z.record(z.string(), z.any())',
Port: 'z.number()',
UUID: 'z.string()',
BigInt: 'z.number()',
},
},
generates: {
// No longer generating mothership GraphQL types since we switched to WebSocket-based UnraidServerClient
},
};

export default config;
39 changes: 39 additions & 0 deletions packages/unraid-api-plugin-connect-2/justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Justfile for unraid-api-plugin-connect

# Default recipe to run when just is called without arguments
default:
@just --list

# Watch for changes in src files and run clean + build
watch:
watchexec -r -e ts,tsx -w src -- pnpm build

# Count TypeScript lines in src directory, excluding test and generated files
count-lines:
#!/usr/bin/env bash
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

echo -e "${BLUE}Counting TypeScript lines in src/ (excluding test/ and graphql/generated/)...${NC}"
echo
echo -e "${GREEN}Lines by directory:${NC}"
cd src
# First pass to get total lines
total=$(find . -type f -name "*.ts" -not -path "*/test/*" -not -path "*/graphql/generated/*" | xargs wc -l | tail -n 1 | awk '{print $1}')

# Second pass to show directory breakdown with percentages
for dir in $(find . -type d -not -path "*/test/*" -not -path "*/graphql/generated/*" -not -path "." -not -path "./test" | sort); do
lines=$(find "$dir" -type f -name "*.ts" -not -path "*/graphql/generated/*" | xargs wc -l 2>/dev/null | tail -n 1 | awk '{print $1}')
if [ ! -z "$lines" ]; then
percentage=$(echo "scale=1; $lines * 100 / $total" | bc)
printf "%-30s %6d lines (%5.1f%%)\n" "$dir" "$lines" "$percentage"
fi
done
echo
echo -e "${GREEN}Top 10 largest files:${NC}"
find . -type f -name "*.ts" -not -path "*/test/*" -not -path "*/graphql/generated/*" | xargs wc -l | sort -nr | head -n 11
echo
echo -e "${GREEN}Total TypeScript lines:${NC} $total"
Loading
Loading