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
4 changes: 2 additions & 2 deletions api/dev/configs/api.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"version": "4.29.2",
"version": "4.35.0",
"extraOrigins": [],
"sandbox": false,
"ssoSubIds": [],
"plugins": [
"unraid-api-plugin-connect"
]
}
}
111 changes: 62 additions & 49 deletions api/generated-schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,16 @@ type ArrayMutations {
input ArrayStateInput {
"""Array state"""
desiredState: ArrayStateInputState!

"""
Optional password used to unlock encrypted array disks when starting the array
"""
decryptionPassword: String

"""
Optional keyfile contents used to unlock encrypted array disks when starting the array. Accepts a data URL or raw base64 payload.
"""
decryptionKeyfile: String
}

enum ArrayStateInputState {
Expand All @@ -1232,6 +1242,9 @@ type DockerMutations {
"""Stop a container"""
stop(id: PrefixedID!): DockerContainer!

"""Restart a container"""
restart(id: PrefixedID!): DockerContainer!

"""Pause (Suspend) a container"""
pause(id: PrefixedID!): DockerContainer!

Expand Down Expand Up @@ -2514,6 +2527,52 @@ type LogFileContent {
startLine: Int
}

type NetworkMetrics implements Node {
id: PrefixedID!

"""Interface identifier"""
name: String!

"""Operational state"""
operstate: String

"""Total received bytes"""
bytesReceived: BigInt!

"""Total transmitted bytes"""
bytesSent: BigInt!

"""Total received packets"""
packetsReceived: BigInt!

"""Total transmitted packets"""
packetsSent: BigInt!

"""Receive errors"""
receiveErrors: BigInt!

"""Transmit errors"""
transmitErrors: BigInt!

"""Dropped receive packets"""
receiveDropped: BigInt!

"""Dropped transmit packets"""
transmitDropped: BigInt!

"""Receive throughput in bytes per second"""
rxSec: Float!

"""Transmit throughput in bytes per second"""
txSec: Float!

"""Estimated link utilization percentage"""
utilizationPercent: Float

"""Metric collection timestamp"""
lastUpdated: DateTime!
}

type TemperatureReading {
"""Temperature value"""
value: Float!
Expand Down Expand Up @@ -2614,52 +2673,6 @@ type TemperatureMetrics implements Node {
summary: TemperatureSummary!
}

type NetworkMetrics implements Node {
id: PrefixedID!

"""Interface identifier"""
name: String!

"""Operational state"""
operstate: String

"""Total received bytes"""
bytesReceived: BigInt!

"""Total transmitted bytes"""
bytesSent: BigInt!

"""Total received packets"""
packetsReceived: BigInt!

"""Total transmitted packets"""
packetsSent: BigInt!

"""Receive errors"""
receiveErrors: BigInt!

"""Transmit errors"""
transmitErrors: BigInt!

"""Dropped receive packets"""
receiveDropped: BigInt!

"""Dropped transmit packets"""
transmitDropped: BigInt!

"""Receive throughput in bytes per second"""
rxSec: Float!

"""Transmit throughput in bytes per second"""
txSec: Float!

"""Estimated link utilization percentage"""
utilizationPercent: Float

"""Metric collection timestamp"""
lastUpdated: DateTime!
}

"""System metrics including CPU and memory utilization"""
type Metrics implements Node {
id: PrefixedID!
Expand Down Expand Up @@ -3315,6 +3328,9 @@ type Query {
isFreshInstall: Boolean!
publicTheme: Theme!
info: Info!

"""Network interfaces"""
networkInterfaces: [InfoNetworkInterface!]!
docker: Docker!
disks: [Disk!]!
assignableDisks: [Disk!]!
Expand All @@ -3339,9 +3355,6 @@ type Query {

"""Validate an OIDC session token (internal use for CLI validation)"""
validateOidcSession(token: String!): OidcSessionValidation!

"""Network interfaces"""
networkInterfaces: [InfoNetworkInterface!]!
metrics: Metrics!

"""Retrieve current system time configuration"""
Expand Down
96 changes: 96 additions & 0 deletions api/src/unraid-api/cli/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,10 @@ export enum ArrayState {
}

export type ArrayStateInput = {
/** Optional keyfile contents used to unlock encrypted array disks when starting the array. Accepts a data URL or raw base64 payload. */
decryptionKeyfile?: InputMaybe<Scalars['String']['input']>;
/** Optional password used to unlock encrypted array disks when starting the array */
decryptionPassword?: InputMaybe<Scalars['String']['input']>;
/** Array state */
desiredState: ArrayStateInputState;
};
Expand Down Expand Up @@ -948,6 +952,8 @@ export type DockerMutations = {
pause: DockerContainer;
/** Remove a container */
removeContainer: Scalars['Boolean']['output'];
/** Restart a container */
restart: DockerContainer;
/** Start a container */
start: DockerContainer;
/** Stop a container */
Expand Down Expand Up @@ -976,6 +982,11 @@ export type DockerMutationsRemoveContainerArgs = {
};


export type DockerMutationsRestartArgs = {
id: Scalars['PrefixedID']['input'];
};


export type DockerMutationsStartArgs = {
id: Scalars['PrefixedID']['input'];
};
Expand Down Expand Up @@ -1311,31 +1322,69 @@ export type InfoNetworkInterface = Node & {
__typename?: 'InfoNetworkInterface';
/** Interface description/label */
description?: Maybe<Scalars['String']['output']>;
/** Link duplex mode */
duplex?: Maybe<Scalars['String']['output']>;
/** IPv4 Gateway */
gateway?: Maybe<Scalars['String']['output']>;
id: Scalars['PrefixedID']['output'];
/** Whether this is an internal interface */
internal?: Maybe<Scalars['Boolean']['output']>;
/** IPv4 Address */
ipAddress?: Maybe<Scalars['String']['output']>;
/** IPv4 addresses assigned to this interface */
ipv4Addresses: Array<InfoNetworkIpv4Address>;
/** IPv6 Address */
ipv6Address?: Maybe<Scalars['String']['output']>;
/** IPv6 addresses assigned to this interface */
ipv6Addresses: Array<InfoNetworkIpv6Address>;
/** IPv6 Gateway */
ipv6Gateway?: Maybe<Scalars['String']['output']>;
/** IPv6 Netmask */
ipv6Netmask?: Maybe<Scalars['String']['output']>;
/** MAC Address */
macAddress?: Maybe<Scalars['String']['output']>;
/** Maximum transmission unit */
mtu?: Maybe<Scalars['Int']['output']>;
/** Interface name (e.g. eth0) */
name: Scalars['String']['output'];
/** IPv4 Netmask */
netmask?: Maybe<Scalars['String']['output']>;
/** Operational state */
operstate?: Maybe<Scalars['String']['output']>;
/** IPv4 Protocol mode */
protocol?: Maybe<Scalars['String']['output']>;
/** Link speed in Mbps */
speed?: Maybe<Scalars['Int']['output']>;
/** Connection status */
status?: Maybe<Scalars['String']['output']>;
/** Interface type */
type?: Maybe<Scalars['String']['output']>;
/** Using DHCP for IPv4 */
useDhcp?: Maybe<Scalars['Boolean']['output']>;
/** Using DHCP for IPv6 */
useDhcp6?: Maybe<Scalars['Boolean']['output']>;
/** Whether this is a virtual interface */
virtual?: Maybe<Scalars['Boolean']['output']>;
/** VLAN identifier parsed from the interface name */
vlanId?: Maybe<Scalars['Int']['output']>;
};

/** IPv4 address assigned to a network interface */
export type InfoNetworkIpv4Address = {
__typename?: 'InfoNetworkIpv4Address';
/** IPv4 address */
address: Scalars['String']['output'];
/** IPv4 netmask */
netmask: Scalars['String']['output'];
};

/** IPv6 address assigned to a network interface */
export type InfoNetworkIpv6Address = {
__typename?: 'InfoNetworkIpv6Address';
/** IPv6 address */
address: Scalars['String']['output'];
/** IPv6 prefix length */
prefixLength?: Maybe<Scalars['Int']['output']>;
};

export type InfoOs = Node & {
Expand Down Expand Up @@ -1576,6 +1625,8 @@ export type Metrics = Node & {
id: Scalars['PrefixedID']['output'];
/** Current memory utilization metrics */
memory?: Maybe<MemoryUtilization>;
/** Current network metrics for all interfaces */
network: Array<NetworkMetrics>;
/** Temperature metrics */
temperature?: Maybe<TemperatureMetrics>;
};
Expand Down Expand Up @@ -1827,6 +1878,39 @@ export type Network = Node & {
id: Scalars['PrefixedID']['output'];
};

export type NetworkMetrics = Node & {
__typename?: 'NetworkMetrics';
/** Total received bytes */
bytesReceived: Scalars['BigInt']['output'];
/** Total transmitted bytes */
bytesSent: Scalars['BigInt']['output'];
id: Scalars['PrefixedID']['output'];
/** Metric collection timestamp */
lastUpdated: Scalars['DateTime']['output'];
/** Interface identifier */
name: Scalars['String']['output'];
/** Operational state */
operstate?: Maybe<Scalars['String']['output']>;
/** Total received packets */
packetsReceived: Scalars['BigInt']['output'];
/** Total transmitted packets */
packetsSent: Scalars['BigInt']['output'];
/** Dropped receive packets */
receiveDropped: Scalars['BigInt']['output'];
/** Receive errors */
receiveErrors: Scalars['BigInt']['output'];
/** Receive throughput in bytes per second */
rxSec: Scalars['Float']['output'];
/** Dropped transmit packets */
transmitDropped: Scalars['BigInt']['output'];
/** Transmit errors */
transmitErrors: Scalars['BigInt']['output'];
/** Transmit throughput in bytes per second */
txSec: Scalars['Float']['output'];
/** Estimated link utilization percentage */
utilizationPercent?: Maybe<Scalars['Float']['output']>;
};

export type Node = {
id: Scalars['PrefixedID']['output'];
};
Expand Down Expand Up @@ -1985,12 +2069,21 @@ export type OnboardingInternalBootContext = {
assignableDisks: Array<Disk>;
bootEligible?: Maybe<Scalars['Boolean']['output']>;
bootedFromFlashWithInternalBootSetup: Scalars['Boolean']['output'];
driveWarnings: Array<OnboardingInternalBootDriveWarning>;
enableBootTransfer?: Maybe<Scalars['String']['output']>;
poolNames: Array<Scalars['String']['output']>;
reservedNames: Array<Scalars['String']['output']>;
shareNames: Array<Scalars['String']['output']>;
};

/** Warning metadata for an assignable internal boot drive */
export type OnboardingInternalBootDriveWarning = {
__typename?: 'OnboardingInternalBootDriveWarning';
device: Scalars['String']['output'];
diskId: Scalars['String']['output'];
warnings: Array<Scalars['String']['output']>;
};

/** Result of attempting internal boot pool setup */
export type OnboardingInternalBootResult = {
__typename?: 'OnboardingInternalBootResult';
Expand Down Expand Up @@ -2318,6 +2411,8 @@ export type Query = {
me: UserAccount;
metrics: Metrics;
network: Network;
/** Network interfaces */
networkInterfaces: Array<InfoNetworkInterface>;
/** Get all notifications */
notifications: Notifications;
/** Get the full OIDC configuration (admin only) */
Expand Down Expand Up @@ -2727,6 +2822,7 @@ export type Subscription = {
systemMetricsCpu: CpuUtilization;
systemMetricsCpuTelemetry: CpuPackages;
systemMetricsMemory: MemoryUtilization;
systemMetricsNetwork: Array<NetworkMetrics>;
systemMetricsTemperature?: Maybe<TemperatureMetrics>;
upsUpdates: UpsDevice;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('DockerMutationsResolver', () => {
useValue: {
start: vi.fn(),
stop: vi.fn(),
restart: vi.fn(),
},
},
],
Expand Down Expand Up @@ -54,6 +55,27 @@ describe('DockerMutationsResolver', () => {
expect(dockerService.start).toHaveBeenCalledWith('1');
});

it('should restart', async () => {
const mockContainer: DockerContainer = {
id: '1',
autoStart: false,
command: 'test',
created: 1234567890,
image: 'test-image',
imageId: 'test-image-id',
ports: [],
state: ContainerState.RUNNING,
status: 'Up 2 seconds',
names: ['test-container'],
isOrphaned: false,
};
vi.mocked(dockerService.restart).mockResolvedValue(mockContainer);

const result = await resolver.restart('1');
expect(result).toEqual(mockContainer);
expect(dockerService.restart).toHaveBeenCalledWith('1');
});

it('should stop', async () => {
const mockContainer: DockerContainer = {
id: '1',
Expand Down
Loading
Loading