chore: add a prefix scalar instead of prefix plugin (#1361)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a new `PrefixedID` scalar type for all GraphQL IDs,
ensuring unique identifiers across multiple servers by prefixing IDs
with a server identifier.
- Added detailed documentation for the `PrefixedID` scalar in the API
schema.
- Grouped VM and parity check mutations under dedicated fields for
better organization.
- Added new scalar `Port` and input type `AccessUrlInput` for improved
type safety.

- **Refactor**
- Replaced all usages of standard `ID` or `String` with `PrefixedID` for
IDs in queries, mutations, and models.
- Consolidated ID handling by extending a common `Node` base class
across models, removing redundant `id` declarations.
- Updated GraphQL argument types and resolver signatures to explicitly
use `PrefixedID`.
- Updated GraphQL code generation to map `PrefixedID` to TypeScript
`string`.

- **Bug Fixes**
- Enhanced test assertions to verify API key creation timestamps are
strings.
  - Fixed internal property naming for registration ID.

- **Chores**
- Removed legacy ID prefix Apollo Server plugin in favor of the new
scalar approach.
  - Cleaned up imports and unused fields related to ID handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
Eli Bosley
2025-04-29 12:22:18 -04:00
committed by GitHub
parent c5a394eddf
commit 586653ccc1
38 changed files with 617 additions and 568 deletions

View File

@@ -74,8 +74,7 @@ type ArrayCapacity {
}
type ArrayDisk implements Node {
"""Disk identifier, only set for present disks on the system"""
id: ID!
id: PrefixedID!
"""
Array slot number. Parity1 is always 0 and Parity2 is always 29. Array slots will be 1 - 28. Cache slots are 30 - 53. Flash is 54.
@@ -143,7 +142,7 @@ type ArrayDisk implements Node {
}
interface Node {
id: ID!
id: PrefixedID!
}
"""The `Long` scalar type represents 52-bit integers"""
@@ -181,7 +180,7 @@ enum ArrayDiskFsColor {
}
type UnraidArray implements Node {
id: ID!
id: PrefixedID!
"""Current array state"""
state: ArrayState!
@@ -217,7 +216,7 @@ enum ArrayState {
}
type Share implements Node {
id: ID!
id: PrefixedID!
"""Display name"""
name: String
@@ -348,8 +347,7 @@ type ConnectSettingsValues {
}
type ConnectSettings implements Node {
"""The unique identifier for the Connect settings"""
id: ID!
id: PrefixedID!
"""The data schema for the Connect settings"""
dataSchema: JSON!
@@ -367,8 +365,7 @@ The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://
scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
type Connect implements Node {
"""The unique identifier for the Connect instance"""
id: ID!
id: PrefixedID!
"""The status of dynamic remote access"""
dynamicRemoteAccess: DynamicRemoteAccessStatus!
@@ -378,18 +375,19 @@ type Connect implements Node {
}
type Network implements Node {
id: ID!
id: PrefixedID!
accessUrls: [AccessUrl!]
}
type ProfileModel {
userId: ID
type ProfileModel implements Node {
id: PrefixedID!
username: String!
url: String!
avatar: String!
}
type Server {
type Server implements Node {
id: PrefixedID!
owner: ProfileModel!
guid: String!
apikey: String!
@@ -428,9 +426,8 @@ enum DiskFsType {
NTFS
}
type Disk {
"""The unique identifier of the disk"""
id: String!
type Disk implements Node {
id: PrefixedID!
"""The device path of the disk (e.g. /dev/sdb)"""
device: String!
@@ -509,8 +506,8 @@ type KeyFile {
contents: String
}
type Registration {
guid: ID
type Registration implements Node {
id: PrefixedID!
type: registrationType
keyFile: KeyFile
state: RegistrationState
@@ -559,7 +556,7 @@ enum RegistrationState {
}
type Vars implements Node {
id: ID!
id: PrefixedID!
"""Unraid version"""
version: String
@@ -786,8 +783,8 @@ enum Resource {
WELCOME
}
type ApiKey {
id: ID!
type ApiKey implements Node {
id: PrefixedID!
name: String!
description: String
roles: [Role!]!
@@ -802,8 +799,8 @@ enum Role {
GUEST
}
type ApiKeyWithSecret {
id: ID!
type ApiKeyWithSecret implements Node {
id: PrefixedID!
name: String!
description: String
roles: [Role!]!
@@ -825,13 +822,13 @@ type ArrayMutations {
removeDiskFromArray(input: ArrayDiskInput!): UnraidArray!
"""Mount a disk in the array"""
mountArrayDisk(id: String!): ArrayDisk!
mountArrayDisk(id: PrefixedID!): ArrayDisk!
"""Unmount a disk from the array"""
unmountArrayDisk(id: String!): ArrayDisk!
unmountArrayDisk(id: PrefixedID!): ArrayDisk!
"""Clear statistics for a disk in the array"""
clearArrayDiskStatistics(id: String!): Boolean!
clearArrayDiskStatistics(id: PrefixedID!): Boolean!
}
input ArrayStateInput {
@@ -846,7 +843,7 @@ enum ArrayStateInputState {
input ArrayDiskInput {
"""Disk ID"""
id: ID!
id: PrefixedID!
"""The slot for the disk"""
slot: Int
@@ -854,33 +851,33 @@ input ArrayDiskInput {
type DockerMutations {
"""Start a container"""
start(id: String!): DockerContainer!
start(id: PrefixedID!): DockerContainer!
"""Stop a container"""
stop(id: String!): DockerContainer!
stop(id: PrefixedID!): DockerContainer!
}
type VmMutations {
"""Start a virtual machine"""
start(id: String!): Boolean!
start(id: PrefixedID!): Boolean!
"""Stop a virtual machine"""
stop(id: String!): Boolean!
stop(id: PrefixedID!): Boolean!
"""Pause a virtual machine"""
pause(id: String!): Boolean!
pause(id: PrefixedID!): Boolean!
"""Resume a virtual machine"""
resume(id: String!): Boolean!
resume(id: PrefixedID!): Boolean!
"""Force stop a virtual machine"""
forceStop(id: String!): Boolean!
forceStop(id: PrefixedID!): Boolean!
"""Reboot a virtual machine"""
reboot(id: String!): Boolean!
reboot(id: PrefixedID!): Boolean!
"""Reset a virtual machine"""
reset(id: String!): Boolean!
reset(id: PrefixedID!): Boolean!
}
"""
@@ -935,13 +932,13 @@ A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date
scalar DateTime
type Config implements Node {
id: ID!
id: PrefixedID!
valid: Boolean
error: String
}
type InfoApps implements Node {
id: ID!
id: PrefixedID!
"""How many docker containers are installed"""
installed: Int!
@@ -951,7 +948,7 @@ type InfoApps implements Node {
}
type Baseboard implements Node {
id: ID!
id: PrefixedID!
manufacturer: String!
model: String
version: String
@@ -960,7 +957,7 @@ type Baseboard implements Node {
}
type InfoCpu implements Node {
id: ID!
id: PrefixedID!
manufacturer: String!
brand: String!
vendor: String!
@@ -981,7 +978,7 @@ type InfoCpu implements Node {
}
type Gpu implements Node {
id: ID!
id: PrefixedID!
type: String!
typeid: String!
vendorname: String!
@@ -991,7 +988,7 @@ type Gpu implements Node {
}
type Pci implements Node {
id: ID!
id: PrefixedID!
type: String
typeid: String
vendorname: String
@@ -1002,20 +999,20 @@ type Pci implements Node {
class: String
}
type Usb {
id: ID!
type Usb implements Node {
id: PrefixedID!
name: String
}
type Devices implements Node {
id: ID!
id: PrefixedID!
gpu: [Gpu!]!
pci: [Pci!]!
usb: [Usb!]!
}
type Case implements Node {
id: ID!
id: PrefixedID!
icon: String
url: String
error: String
@@ -1023,7 +1020,7 @@ type Case implements Node {
}
type Display implements Node {
id: ID!
id: PrefixedID!
case: Case
date: String
number: String
@@ -1057,7 +1054,8 @@ enum Temperature {
F
}
type MemoryLayout {
type MemoryLayout implements Node {
id: PrefixedID!
size: Int!
bank: String
type: String
@@ -1072,7 +1070,7 @@ type MemoryLayout {
}
type InfoMemory implements Node {
id: ID!
id: PrefixedID!
max: Int!
total: Int!
free: Int!
@@ -1087,7 +1085,7 @@ type InfoMemory implements Node {
}
type Os implements Node {
id: ID!
id: PrefixedID!
platform: String
distro: String
release: String
@@ -1103,7 +1101,7 @@ type Os implements Node {
}
type System implements Node {
id: ID!
id: PrefixedID!
manufacturer: String
model: String
version: String
@@ -1113,7 +1111,7 @@ type System implements Node {
}
type Versions implements Node {
id: ID!
id: PrefixedID!
kernel: String
openssl: String
systemOpenssl: String
@@ -1143,7 +1141,7 @@ type Versions implements Node {
}
type Info implements Node {
id: ID!
id: PrefixedID!
"""Count of docker containers"""
apps: InfoApps!
@@ -1153,7 +1151,7 @@ type Info implements Node {
display: Display!
"""Machine ID"""
machineId: ID
machineId: PrefixedID
memory: InfoMemory!
os: Os!
system: System!
@@ -1182,8 +1180,8 @@ type ContainerHostConfig {
networkMode: String!
}
type DockerContainer {
id: ID!
type DockerContainer implements Node {
id: PrefixedID!
names: [String!]!
image: String!
imageId: String!
@@ -1212,9 +1210,9 @@ enum ContainerState {
EXITED
}
type DockerNetwork {
type DockerNetwork implements Node {
id: PrefixedID!
name: String!
id: ID!
created: String!
scope: String!
driver: String!
@@ -1231,13 +1229,13 @@ type DockerNetwork {
}
type Docker implements Node {
id: ID!
id: PrefixedID!
containers(skipCache: Boolean! = false): [DockerContainer!]!
networks(skipCache: Boolean! = false): [DockerNetwork!]!
}
type Flash implements Node {
id: ID!
id: PrefixedID!
guid: String!
vendor: String!
product: String!
@@ -1283,8 +1281,8 @@ type NotificationOverview {
archive: NotificationCounts!
}
type Notification {
id: ID!
type Notification implements Node {
id: PrefixedID!
"""Also known as 'event'"""
title: String!
@@ -1310,8 +1308,8 @@ enum NotificationType {
ARCHIVE
}
type Notifications {
id: ID!
type Notifications implements Node {
id: PrefixedID!
"""A cached overview of the notifications in the system & their severity."""
overview: NotificationOverview!
@@ -1331,14 +1329,18 @@ type Owner {
avatar: String!
}
type VmDomain {
uuid: ID!
type VmDomain implements Node {
"""The unique identifier for the vm (uuid)"""
id: PrefixedID!
"""A friendly name for the vm"""
name: String
"""Current domain vm state"""
state: VmState!
"""The UUID of the vm"""
uuid: String @deprecated(reason: "Use id instead")
}
"""The state of a virtual machine"""
@@ -1353,8 +1355,8 @@ enum VmState {
PMSUSPENDED
}
type Vms {
id: ID!
type Vms implements Node {
id: PrefixedID!
domains: [VmDomain!]
domain: [VmDomain!]
}
@@ -1364,16 +1366,15 @@ type Uptime {
}
type Service implements Node {
id: ID!
id: PrefixedID!
name: String
online: Boolean
uptime: Uptime
version: String
}
type UserAccount {
"""A unique identifier for the user"""
id: ID!
type UserAccount implements Node {
id: PrefixedID!
"""The name of the user"""
name: String!
@@ -1388,9 +1389,12 @@ type UserAccount {
permissions: [Permission!]
}
"\n### Description:\n\nID scalar type that prefixes the underlying ID with the server identifier on output and strips it on input.\n\nWe use this scalar type to ensure that the ID is unique across all servers, allowing the same underlying resource ID to be used across different server instances.\n\n#### Input Behavior:\n\nWhen providing an ID as input (e.g., in arguments or input objects), the server identifier prefix ('<serverId>:') is optional.\n\n- If the prefix is present (e.g., '123:456'), it will be automatically stripped, and only the underlying ID ('456') will be used internally.\n- If the prefix is absent (e.g., '456'), the ID will be used as-is.\n\nThis makes it flexible for clients, as they don't strictly need to know or provide the server ID.\n\n#### Output Behavior:\n\nWhen an ID is returned in the response (output), it will *always* be prefixed with the current server's unique identifier (e.g., '123:456').\n\n#### Example:\n\nNote: The server identifier is '123' in this example.\n\n##### Input (Prefix Optional):\n```graphql\n# Both of these are valid inputs resolving to internal ID '456'\n{\n someQuery(id: \"123:456\") { ... }\n anotherQuery(id: \"456\") { ... }\n}\n```\n\n##### Output (Prefix Always Added):\n```graphql\n# Assuming internal ID is '456'\n{\n \"data\": {\n \"someResource\": {\n \"id\": \"123:456\" \n }\n }\n}\n```\n "
scalar PrefixedID
type Query {
apiKeys: [ApiKey!]!
apiKey(id: String!): ApiKey
apiKey(id: PrefixedID!): ApiKey
cloud: Cloud!
config: Config!
display: Display!
@@ -1421,7 +1425,7 @@ type Query {
extraAllowedOrigins: [String!]!
docker: Docker!
disks: [Disk!]!
disk(id: String!): Disk!
disk(id: PrefixedID!): Disk!
health: String!
getDemo: String!
}
@@ -1433,19 +1437,19 @@ type Mutation {
"""Creates a new notification record"""
createNotification(input: NotificationData!): Notification!
deleteNotification(id: String!, type: NotificationType!): NotificationOverview!
deleteNotification(id: PrefixedID!, type: NotificationType!): NotificationOverview!
"""Deletes all archived notifications on server."""
deleteArchivedNotifications: NotificationOverview!
"""Marks a notification as archived."""
archiveNotification(id: String!): Notification!
archiveNotifications(ids: [String!]!): NotificationOverview!
archiveNotification(id: PrefixedID!): Notification!
archiveNotifications(ids: [PrefixedID!]!): NotificationOverview!
archiveAll(importance: NotificationImportance): NotificationOverview!
"""Marks a notification as unread."""
unreadNotification(id: String!): Notification!
unarchiveNotifications(ids: [String!]!): NotificationOverview!
unreadNotification(id: PrefixedID!): Notification!
unarchiveNotifications(ids: [PrefixedID!]!): NotificationOverview!
unarchiveAll(importance: NotificationImportance): NotificationOverview!
"""Reads each notification to recompute & update the overview."""
@@ -1481,12 +1485,12 @@ input AddPermissionInput {
}
input AddRoleForApiKeyInput {
apiKeyId: ID!
apiKeyId: PrefixedID!
role: Role!
}
input RemoveRoleFromApiKeyInput {
apiKeyId: ID!
apiKeyId: PrefixedID!
role: Role!
}

View File

@@ -83,7 +83,9 @@ export const getLocalServer = (getState = store.getState): Array<Server> => {
return [
{
id: 'local',
owner: {
id: 'local',
username: config.remote.username ?? 'root',
url: '',
avatar: '',

View File

@@ -520,11 +520,13 @@ describe('ApiKeyService', () => {
expect(result).toHaveLength(2);
expect(result[0]).toEqual({
...mockApiKey,
createdAt: expect.any(String),
id: 'test-api-id',
key: 'test-api-key',
});
expect(result[1]).toEqual({
...mockApiKey,
createdAt: expect.any(String),
id: 'unique-id',
key: 'unique-key',
});

View File

@@ -7,15 +7,15 @@ import { NoUnusedVariablesRule } from 'graphql';
import { JSONResolver, URLResolver } from 'graphql-scalars';
import { ENVIRONMENT } from '@app/environment.js';
import { GraphQLLong } from '@app/graphql/resolvers/graphql-type-long.js';
import { getters } from '@app/store/index.js';
import {
UsePermissionsDirective,
usePermissionsSchemaTransformer,
} from '@app/unraid-api/graph/directives/use-permissions.directive.js';
import { idPrefixPlugin } from '@app/unraid-api/graph/id-prefix-plugin.js';
import { ResolversModule } from '@app/unraid-api/graph/resolvers/resolvers.module.js';
import { sandboxPlugin } from '@app/unraid-api/graph/sandbox-plugin.js';
import { GraphQLLong } from '@app/unraid-api/graph/scalars/graphql-type-long.js';
import { PrefixedID as PrefixedIDScalar } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
import { PluginModule } from '@app/unraid-api/plugin/plugin.module.js';
@Module({
@@ -42,7 +42,7 @@ import { PluginModule } from '@app/unraid-api/plugin/plugin.module.js';
extra,
};
},
plugins: [sandboxPlugin, idPrefixPlugin] as any[],
plugins: [sandboxPlugin] as any[],
subscriptions: {
'graphql-ws': {
path: '/graphql',
@@ -63,7 +63,7 @@ import { PluginModule } from '@app/unraid-api/plugin/plugin.module.js';
},
}),
],
providers: [],
providers: [PrefixedIDScalar],
exports: [GraphQLModule],
})
export class GraphModule {}

View File

@@ -1,67 +0,0 @@
import { type ApolloServerPlugin } from '@apollo/server';
import { getServerIdentifier } from '@app/core/utils/server-identifier.js';
import { updateObject } from '@app/utils.js';
type ObjectModifier = (obj: object) => void;
/**
* Returns a function that takes an object and updates any 'id' properties to
* include the given serverId as a prefix.
*
* e.g. If the object is { id: '1234' }, the returned function will update it to
* { id: '<serverId>:1234' }.
*
* @param serverId - The server identifier to use as the prefix.
* @returns A function that takes an object and updates any 'id' properties with the given serverId.
*/
function prefixWithServerId(serverId: string): ObjectModifier {
return (currentObj) => {
if ('id' in currentObj && typeof currentObj.id === 'string') {
currentObj.id = `${serverId}:${currentObj.id}`;
}
};
}
/**
* Takes an object and removes any server prefix from the 'id' property.
*
* e.g. If the object is { id: '<serverId>:1234' }, the returned function will update it to
* { id: '1234' }.
*
* @param current - The object to update. If it has an 'id' property that is a string and
* has a server prefix, the prefix is removed.
*/
const stripServerPrefixFromIds: ObjectModifier = (current) => {
if ('id' in current && typeof current.id === 'string') {
const parts = current.id.split(':');
// if there are more or less than 2 parts to the split,
// assume there is no server prefix and don't touch it.
if (parts.length === 2) {
current.id = parts[1];
}
}
};
export const idPrefixPlugin: ApolloServerPlugin = {
async requestDidStart(requestContext) {
if (requestContext.request.operationName === 'IntrospectionQuery') {
// Don't modify the introspection query
return;
}
// If ID is requested, return an ID field with an extra prefix
return {
async didResolveOperation({ request }) {
if (request.variables) {
updateObject(request.variables, stripServerPrefixFromIds);
}
},
async willSendResponse({ response }) {
if (response.body.kind === 'single' && response.body.singleResult.data) {
const serverId = getServerIdentifier();
updateObject(response.body.singleResult.data, prefixWithServerId(serverId));
}
},
};
},
};

View File

@@ -1,20 +1,19 @@
import { Field, ID, InputType, ObjectType } from '@nestjs/graphql';
import { Field, InputType, ObjectType } from '@nestjs/graphql';
import { Transform, Type } from 'class-transformer';
import {
ArrayMinSize,
IsArray,
IsBoolean,
IsDate,
IsEnum,
IsNotEmpty,
IsOptional,
IsString,
ValidateIf,
ValidateNested,
} from 'class-validator';
import { Resource, Role } from '@app/unraid-api/graph/resolvers/base.model.js';
import { Node, Resource, Role } from '@app/unraid-api/graph/resolvers/base.model.js';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
@ObjectType()
export class Permission {
@@ -29,13 +28,8 @@ export class Permission {
actions!: string[];
}
@ObjectType()
export class ApiKey {
@Field(() => ID)
@IsString()
@IsNotEmpty()
id!: string;
@ObjectType({ implements: () => Node })
export class ApiKey extends Node {
@Field()
@IsString()
@IsNotEmpty()
@@ -119,7 +113,7 @@ export class CreateApiKeyInput {
@InputType()
export class AddRoleForApiKeyInput {
@Field(() => ID)
@Field(() => PrefixedID)
@IsString()
apiKeyId!: string;
@@ -130,7 +124,7 @@ export class AddRoleForApiKeyInput {
@InputType()
export class RemoveRoleFromApiKeyInput {
@Field(() => ID)
@Field(() => PrefixedID)
@IsString()
apiKeyId!: string;

View File

@@ -16,6 +16,7 @@ import {
} from '@app/unraid-api/graph/resolvers/api-key/api-key.model.js';
import { Resource, Role } from '@app/unraid-api/graph/resolvers/base.model.js';
import { validateObject } from '@app/unraid-api/graph/resolvers/validation.utils.js';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
@Resolver(() => ApiKey)
export class ApiKeyResolver {
@@ -40,7 +41,10 @@ export class ApiKeyResolver {
resource: Resource.API_KEY,
possession: AuthPossession.ANY,
})
async apiKey(@Args('id') id: string): Promise<ApiKey | null> {
async apiKey(
@Args('id', { type: () => PrefixedID })
id: string
): Promise<ApiKey | null> {
return this.apiKeyService.findById(id);
}

View File

@@ -1,9 +1,10 @@
import { Field, ID, InputType, Int, ObjectType, registerEnumType } from '@nestjs/graphql';
import { Field, InputType, Int, ObjectType, registerEnumType } from '@nestjs/graphql';
import { IsEnum } from 'class-validator';
import { GraphQLLong } from '@app/graphql/resolvers/graphql-type-long.js';
import { Node } from '@app/unraid-api/graph/resolvers/base.model.js';
import { GraphQLLong } from '@app/unraid-api/graph/scalars/graphql-type-long.js';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
@ObjectType()
export class Capacity {
@@ -29,10 +30,7 @@ export class ArrayCapacity {
@ObjectType({
implements: () => Node,
})
export class ArrayDisk implements Node {
@Field(() => ID, { description: 'Disk identifier, only set for present disks on the system' })
id!: string;
export class ArrayDisk extends Node {
@Field(() => Int, {
description:
'Array slot number. Parity1 is always 0 and Parity2 is always 29. Array slots will be 1 - 28. Cache slots are 30 - 53. Flash is 54.',
@@ -132,10 +130,7 @@ export class ArrayDisk implements Node {
@ObjectType({
implements: () => Node,
})
export class UnraidArray implements Node {
@Field(() => ID)
id!: string;
export class UnraidArray extends Node {
@Field(() => ArrayState, { description: 'Current array state' })
state!: ArrayState;
@@ -157,7 +152,7 @@ export class UnraidArray implements Node {
@InputType()
export class ArrayDiskInput {
@Field(() => ID, { description: 'Disk ID' })
@Field(() => PrefixedID, { description: 'Disk ID' })
id!: string;
@Field(() => Int, { nullable: true, description: 'The slot for the disk' })
@@ -244,10 +239,7 @@ registerEnumType(ArrayDiskFsColor, {
@ObjectType({
implements: () => Node,
})
export class Share implements Node {
@Field(() => ID)
id!: string;
export class Share extends Node {
@Field(() => String, { description: 'Display name', nullable: true })
name?: string | null;

View File

@@ -15,6 +15,7 @@ import {
import { ArrayService } from '@app/unraid-api/graph/resolvers/array/array.service.js';
import { Resource } from '@app/unraid-api/graph/resolvers/base.model.js';
import { ArrayMutations } from '@app/unraid-api/graph/resolvers/mutation/mutation.model.js';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
/**
* Nested Resolvers for Mutations MUST use @ResolveField() instead of @Mutation()
@@ -62,7 +63,7 @@ export class ArrayMutationsResolver {
resource: Resource.ARRAY,
possession: AuthPossession.ANY,
})
public async mountArrayDisk(@Args('id') id: string): Promise<ArrayDisk> {
public async mountArrayDisk(@Args('id', { type: () => PrefixedID }) id: string): Promise<ArrayDisk> {
const array = await this.arrayService.mountArrayDisk(id);
const disk =
array.disks.find((disk) => disk.id === id) ||
@@ -82,7 +83,9 @@ export class ArrayMutationsResolver {
resource: Resource.ARRAY,
possession: AuthPossession.ANY,
})
public async unmountArrayDisk(@Args('id') id: string): Promise<ArrayDisk> {
public async unmountArrayDisk(
@Args('id', { type: () => PrefixedID }) id: string
): Promise<ArrayDisk> {
const array = await this.arrayService.unmountArrayDisk(id);
const disk =
array.disks.find((disk) => disk.id === id) ||
@@ -102,7 +105,9 @@ export class ArrayMutationsResolver {
resource: Resource.ARRAY,
possession: AuthPossession.ANY,
})
public async clearArrayDiskStatistics(@Args('id') id: string): Promise<boolean> {
public async clearArrayDiskStatistics(
@Args('id', { type: () => PrefixedID }) id: string
): Promise<boolean> {
await this.arrayService.clearArrayDiskStatistics(id);
return true;
}

View File

@@ -1,4 +1,8 @@
import { Field, ID, InterfaceType, registerEnumType } from '@nestjs/graphql';
import { Field, InterfaceType, registerEnumType } from '@nestjs/graphql';
import { IsNotEmpty, IsString } from 'class-validator';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
// Register enums
export enum Resource {
@@ -40,7 +44,9 @@ export enum Role {
@InterfaceType()
export class Node {
@Field(() => ID)
@Field(() => PrefixedID)
@IsString()
@IsNotEmpty()
id!: string;
}

View File

@@ -1,14 +1,11 @@
import { Field, ID, ObjectType } from '@nestjs/graphql';
import { Field, ObjectType } from '@nestjs/graphql';
import { Node } from '@app/unraid-api/graph/resolvers/base.model.js';
@ObjectType({
implements: () => Node,
})
export class Config implements Node {
@Field(() => ID)
id!: string;
export class Config extends Node {
@Field(() => Boolean, { nullable: true })
valid?: boolean | null;

View File

@@ -1,5 +1,5 @@
import { Logger } from '@nestjs/common';
import { Args, ID, Mutation, Query, ResolveField, Resolver } from '@nestjs/graphql';
import { Args, Mutation, Query, ResolveField, Resolver } from '@nestjs/graphql';
import { Layout } from '@jsonforms/core';
import { GraphQLJSON, GraphQLJSONObject } from 'graphql-scalars';
@@ -24,6 +24,7 @@ import {
RemoteAccess,
SetupRemoteAccessInput,
} from '@app/unraid-api/graph/resolvers/connect/connect.model.js';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
import { DataSlice } from '@app/unraid-api/types/json-forms.js';
@Resolver(() => ConnectSettings)
@@ -31,7 +32,7 @@ export class ConnectSettingsResolver {
private readonly logger = new Logger(ConnectSettingsResolver.name);
constructor(private readonly connectSettingsService: ConnectSettingsService) {}
@ResolveField(() => ID)
@ResolveField(() => PrefixedID)
public async id(): Promise<string> {
return 'connectSettingsForm';
}

View File

@@ -17,6 +17,7 @@ import {
import { GraphQLJSON, GraphQLURL } from 'graphql-scalars';
import { Node } from '@app/unraid-api/graph/resolvers/base.model.js';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
export enum WAN_ACCESS_TYPE {
DYNAMIC = 'DYNAMIC',
@@ -326,12 +327,7 @@ export class ApiSettingsInput {
@ObjectType({
implements: () => Node,
})
export class ConnectSettings implements Node {
@Field(() => ID, { description: 'The unique identifier for the Connect settings' })
@IsString()
@IsNotEmpty()
id!: string;
export class ConnectSettings extends Node {
@Field(() => GraphQLJSON, { description: 'The data schema for the Connect settings' })
@IsObject()
dataSchema!: Record<string, any>;
@@ -348,12 +344,8 @@ export class ConnectSettings implements Node {
@ObjectType({
implements: () => Node,
})
export class Connect {
@Field(() => ID, { description: 'The unique identifier for the Connect instance' })
@IsString()
@IsNotEmpty()
id!: string;
export class Connect extends Node {
@Field(() => DynamicRemoteAccessStatus, { description: 'The status of dynamic remote access' })
@Field(() => DynamicRemoteAccessStatus, { description: 'The status of dynamic remote access' })
@ValidateNested()
dynamicRemoteAccess?: DynamicRemoteAccessStatus;
@@ -366,10 +358,7 @@ export class Connect {
@ObjectType({
implements: () => Node,
})
export class Network implements Node {
@Field(() => ID)
id!: string;
export class Network extends Node {
@Field(() => [AccessUrl], { nullable: true })
accessUrls?: AccessUrl[];
}

View File

@@ -3,6 +3,9 @@ import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { IsArray, IsEnum, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator';
import { Node } from '@app/unraid-api/graph/resolvers/base.model.js';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
export enum DiskFsType {
XFS = 'XFS',
BTRFS = 'BTRFS',
@@ -55,12 +58,8 @@ export class DiskPartition {
size!: number;
}
@ObjectType()
export class Disk {
@Field(() => String, { description: 'The unique identifier of the disk' })
@IsString()
id!: string;
@ObjectType({ implements: () => Node })
export class Disk extends Node {
@Field(() => String, { description: 'The device path of the disk (e.g. /dev/sdb)' })
@IsString()
device!: string;

View File

@@ -8,6 +8,7 @@ import {
import { Resource } from '@app/unraid-api/graph/resolvers/base.model.js';
import { Disk } from '@app/unraid-api/graph/resolvers/disks/disks.model.js';
import { DisksService } from '@app/unraid-api/graph/resolvers/disks/disks.service.js';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
@Resolver(() => Disk)
export class DisksResolver {
@@ -29,7 +30,7 @@ export class DisksResolver {
resource: Resource.DISK,
possession: AuthPossession.ANY,
})
public async disk(@Args('id') id: string) {
public async disk(@Args('id', { type: () => PrefixedID }) id: string) {
return this.disksService.getDisk(id);
}

View File

@@ -71,11 +71,8 @@ export class ContainerMount {
propagation!: string;
}
@ObjectType()
export class DockerContainer {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class DockerContainer extends Node {
@Field(() => [String])
names!: string[];
@@ -119,14 +116,11 @@ export class DockerContainer {
autoStart!: boolean;
}
@ObjectType()
export class DockerNetwork {
@ObjectType({ implements: () => Node })
export class DockerNetwork extends Node {
@Field(() => String)
name!: string;
@Field(() => ID)
id!: string;
@Field(() => String)
created!: string;
@@ -170,10 +164,7 @@ export class DockerNetwork {
@ObjectType({
implements: () => Node,
})
export class Docker implements Node {
@Field(() => ID)
id!: string;
export class Docker extends Node {
@Field(() => [DockerContainer])
containers!: DockerContainer[];

View File

@@ -9,6 +9,7 @@ import { Resource } from '@app/unraid-api/graph/resolvers/base.model.js';
import { DockerContainer } from '@app/unraid-api/graph/resolvers/docker/docker.model.js';
import { DockerService } from '@app/unraid-api/graph/resolvers/docker/docker.service.js';
import { DockerMutations } from '@app/unraid-api/graph/resolvers/mutation/mutation.model.js';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
/**
* Nested Resolvers for Mutations MUST use @ResolveField() instead of @Mutation()
@@ -23,7 +24,7 @@ export class DockerMutationsResolver {
resource: Resource.DOCKER,
possession: AuthPossession.ANY,
})
public async start(@Args('id') id: string) {
public async start(@Args('id', { type: () => PrefixedID }) id: string) {
return this.dockerService.start(id);
}
@@ -33,7 +34,7 @@ export class DockerMutationsResolver {
resource: Resource.DOCKER,
possession: AuthPossession.ANY,
})
public async stop(@Args('id') id: string) {
public async stop(@Args('id', { type: () => PrefixedID }) id: string) {
return this.dockerService.stop(id);
}
}

View File

@@ -1,14 +1,11 @@
import { Field, ID, ObjectType } from '@nestjs/graphql';
import { Field, ObjectType } from '@nestjs/graphql';
import { Node } from '@app/unraid-api/graph/resolvers/base.model.js';
@ObjectType({
implements: () => Node,
})
export class Flash implements Node {
@Field(() => ID)
id!: string;
export class Flash extends Node {
@Field(() => String)
guid!: string;

View File

@@ -22,7 +22,6 @@ export class FlashResolver {
return {
id: 'flash',
guid: emhttp.var.flashGuid,
vendor: emhttp.var.flashVendor,
product: emhttp.var.flashProduct,
};

View File

@@ -11,6 +11,7 @@ import {
import { GraphQLJSON } from 'graphql-scalars';
import { Node } from '@app/unraid-api/graph/resolvers/base.model.js';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
export enum Temperature {
C = 'C',
@@ -31,13 +32,8 @@ registerEnumType(Theme, {
description: 'Display theme',
});
@ObjectType({
implements: () => Node,
})
export class InfoApps implements Node {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class InfoApps extends Node {
@Field(() => Int, { description: 'How many docker containers are installed' })
installed!: number;
@@ -45,13 +41,8 @@ export class InfoApps implements Node {
started!: number;
}
@ObjectType({
implements: () => Node,
})
export class Baseboard implements Node {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class Baseboard extends Node {
@Field(() => String)
manufacturer!: string;
@@ -68,13 +59,8 @@ export class Baseboard implements Node {
assetTag?: string;
}
@ObjectType({
implements: () => Node,
})
export class InfoCpu implements Node {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class InfoCpu extends Node {
@Field(() => String)
manufacturer!: string;
@@ -127,13 +113,8 @@ export class InfoCpu implements Node {
flags!: string[];
}
@ObjectType({
implements: () => Node,
})
export class Gpu implements Node {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class Gpu extends Node {
@Field(() => String)
type!: string;
@@ -153,13 +134,8 @@ export class Gpu implements Node {
class!: string;
}
@ObjectType({
implements: () => Node,
})
export class Network implements Node {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class Network extends Node {
@Field(() => String, { nullable: true })
iface?: string;
@@ -197,13 +173,8 @@ export class Network implements Node {
carrierChanges?: string;
}
@ObjectType({
implements: () => Node,
})
export class Pci implements Node {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class Pci extends Node {
@Field(() => String, { nullable: true })
type?: string;
@@ -229,22 +200,14 @@ export class Pci implements Node {
class?: string;
}
@ObjectType()
export class Usb {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class Usb extends Node {
@Field(() => String, { nullable: true })
name?: string;
}
@ObjectType({
implements: () => Node,
})
export class Devices implements Node {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class Devices extends Node {
@Field(() => [Gpu])
gpu!: Gpu[];
@@ -255,9 +218,7 @@ export class Devices implements Node {
usb!: Usb[];
}
@ObjectType({
implements: () => Node,
})
@ObjectType({ implements: () => Node })
export class Case {
@Field(() => String, { nullable: true })
icon?: string;
@@ -272,13 +233,8 @@ export class Case {
base64?: string;
}
@ObjectType({
implements: () => Node,
})
export class Display implements Node {
@Field(() => ID, { nullable: false })
id!: string;
@ObjectType({ implements: () => Node })
export class Display extends Node {
@Field(() => Case, { nullable: true })
case?: Case;
@@ -340,8 +296,8 @@ export class Display implements Node {
locale?: string;
}
@ObjectType()
export class MemoryLayout {
@ObjectType({ implements: () => Node })
export class MemoryLayout extends Node {
@Field(() => Int)
size!: number;
@@ -376,13 +332,8 @@ export class MemoryLayout {
voltageMax?: number;
}
@ObjectType({
implements: () => Node,
})
export class InfoMemory implements Node {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class InfoMemory extends Node {
@Field(() => Int)
max!: number;
@@ -417,13 +368,8 @@ export class InfoMemory implements Node {
layout!: MemoryLayout[];
}
@ObjectType({
implements: () => Node,
})
export class Os implements Node {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class Os extends Node {
@Field(() => String, { nullable: true })
platform?: string;
@@ -461,13 +407,8 @@ export class Os implements Node {
uptime?: string;
}
@ObjectType({
implements: () => Node,
})
export class System implements Node {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class System extends Node {
@Field(() => String, { nullable: true })
manufacturer?: string;
@@ -487,13 +428,8 @@ export class System implements Node {
sku?: string;
}
@ObjectType({
implements: () => Node,
})
export class Versions implements Node {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class Versions extends Node {
@Field(() => String, { nullable: true })
kernel?: string;
@@ -573,13 +509,8 @@ export class Versions implements Node {
unraid?: string;
}
@ObjectType({
implements: () => Node,
})
export class Info implements Node {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class Info extends Node {
@Field(() => InfoApps, { description: 'Count of docker containers' })
apps!: InfoApps;
@@ -595,7 +526,7 @@ export class Info implements Node {
@Field(() => Display)
display!: Display;
@Field(() => ID, { description: 'Machine ID', nullable: true })
@Field(() => PrefixedID, { description: 'Machine ID', nullable: true })
machineId?: string;
@Field(() => InfoMemory)

View File

@@ -1,7 +1,9 @@
import { Field, ID, InputType, Int, ObjectType, registerEnumType } from '@nestjs/graphql';
import { Field, InputType, Int, ObjectType, registerEnumType } from '@nestjs/graphql';
import { IsEnum, IsInt, IsNotEmpty, IsOptional, IsString, Min } from 'class-validator';
import { Node } from '@app/unraid-api/graph/resolvers/base.model.js';
export enum NotificationType {
UNREAD = 'UNREAD',
ARCHIVE = 'ARCHIVE',
@@ -109,13 +111,9 @@ export class NotificationOverview {
archive!: NotificationCounts;
}
@ObjectType('Notification')
export class Notification {
@Field(() => ID)
@IsString()
@IsNotEmpty()
id!: string;
@ObjectType({ implements: () => Node })
export class Notification extends Node {
@Field({ description: "Also known as 'event'" })
@Field({ description: "Also known as 'event'" })
@IsString()
@IsNotEmpty()
@@ -157,13 +155,8 @@ export class Notification {
formattedTimestamp?: string;
}
@ObjectType('Notifications')
export class Notifications {
@Field(() => ID)
@IsString()
@IsNotEmpty()
id!: string;
@ObjectType({ implements: () => Node })
export class Notifications extends Node {
@Field(() => NotificationOverview, {
description: 'A cached overview of the notifications in the system & their severity.',
})

View File

@@ -18,6 +18,7 @@ import {
NotificationType,
} from '@app/unraid-api/graph/resolvers/notifications/notifications.model.js';
import { NotificationsService } from '@app/unraid-api/graph/resolvers/notifications/notifications.service.js';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
@Resolver(() => Notifications)
export class NotificationsResolver {
@@ -66,7 +67,7 @@ export class NotificationsResolver {
@Mutation(() => NotificationOverview)
public async deleteNotification(
@Args('id', { type: () => String })
@Args('id', { type: () => PrefixedID })
id: string,
@Args('type', { type: () => NotificationType })
type: NotificationType
@@ -84,7 +85,7 @@ export class NotificationsResolver {
@Mutation(() => Notification, { description: 'Marks a notification as archived.' })
public archiveNotification(
@Args('id', { type: () => String })
@Args('id', { type: () => PrefixedID })
id: string
): Promise<Notification> {
return this.notificationsService.archiveNotification({ id });
@@ -92,7 +93,7 @@ export class NotificationsResolver {
@Mutation(() => NotificationOverview)
public async archiveNotifications(
@Args('ids', { type: () => [String] })
@Args('ids', { type: () => [PrefixedID] })
ids: string[]
): Promise<NotificationOverview> {
await this.notificationsService.archiveIds(ids);
@@ -110,7 +111,7 @@ export class NotificationsResolver {
@Mutation(() => Notification, { description: 'Marks a notification as unread.' })
public unreadNotification(
@Args('id', { type: () => String })
@Args('id', { type: () => PrefixedID })
id: string
): Promise<Notification> {
return this.notificationsService.markAsUnread({ id });
@@ -118,7 +119,7 @@ export class NotificationsResolver {
@Mutation(() => NotificationOverview)
public async unarchiveNotifications(
@Args('ids', { type: () => [String] })
@Args('ids', { type: () => [PrefixedID] })
ids: string[]
): Promise<NotificationOverview> {
await this.notificationsService.unarchiveIds(ids);

View File

@@ -1,4 +1,6 @@
import { Field, ID, ObjectType, registerEnumType } from '@nestjs/graphql';
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
import { Node } from '@app/unraid-api/graph/resolvers/base.model.js';
export enum RegistrationType {
BASIC = 'BASIC',
@@ -83,11 +85,8 @@ export class KeyFile {
contents?: string;
}
@ObjectType()
export class Registration {
@Field(() => ID, { nullable: true })
guid?: string;
@ObjectType({ implements: () => Node })
export class Registration extends Node {
@Field(() => RegistrationType, { nullable: true })
type?: RegistrationType;

View File

@@ -33,7 +33,7 @@ export class RegistrationResolver {
const isExpired = emhttp.var.regTy.includes('expired');
const registration: Registration = {
guid: emhttp.var.regGuid,
id: emhttp.var.regGuid,
type: emhttp.var.regTy,
state: emhttp.var.regState,
// Based on https://github.com/unraid/dynamix.unraid.net/blob/c565217fa8b2acf23943dc5c22a12d526cdf70a1/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/state.php#L64

View File

@@ -1,10 +1,9 @@
import { Field, ID, ObjectType, registerEnumType } from '@nestjs/graphql';
@ObjectType()
export class ProfileModel {
@Field(() => ID, { nullable: true })
userId?: string;
import { Node } from '@app/unraid-api/graph/resolvers/base.model.js';
@ObjectType({ implements: () => Node })
export class ProfileModel extends Node {
@Field()
username!: string;
@@ -25,8 +24,8 @@ registerEnumType(ServerStatus, {
name: 'ServerStatus',
});
@ObjectType()
export class Server {
@ObjectType({ implements: () => Node })
export class Server extends Node {
@Field(() => ProfileModel)
owner!: ProfileModel;

View File

@@ -32,10 +32,7 @@ registerEnumType(MdState, {
@ObjectType({
implements: () => Node,
})
export class Vars implements Node {
@Field(() => ID)
id!: string;
export class Vars extends Node {
@Field({ nullable: true, description: 'Unraid version' })
version?: string;

View File

@@ -1,4 +1,9 @@
import { Field, ID, InputType, ObjectType, registerEnumType } from '@nestjs/graphql';
import { Field, InputType, ObjectType, registerEnumType } from '@nestjs/graphql';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { Node } from '@app/unraid-api/graph/resolvers/base.model.js';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
// Register the VmState enum
export enum VmState {
@@ -17,23 +22,31 @@ registerEnumType(VmState, {
description: 'The state of a virtual machine',
});
@ObjectType()
export class VmDomain {
@Field(() => ID)
uuid!: string;
@ObjectType({ implements: () => Node })
export class VmDomain implements Node {
@Field(() => PrefixedID, { description: 'The unique identifier for the vm (uuid)' })
@IsString()
@IsNotEmpty()
id!: string;
@Field({ nullable: true, description: 'A friendly name for the vm' })
@IsString()
name?: string;
@Field(() => VmState, { description: 'Current domain vm state' })
@IsEnum(VmState)
state!: VmState;
@Field(() => String, {
nullable: true,
description: 'The UUID of the vm',
deprecationReason: 'Use id instead',
})
uuid?: string;
}
@ObjectType()
export class Vms {
@Field(() => ID)
id!: string;
@ObjectType({ implements: () => Node })
export class Vms extends Node {
@Field(() => [VmDomain], { nullable: true })
domains?: VmDomain[];

View File

@@ -8,6 +8,7 @@ import {
import { Resource } from '@app/unraid-api/graph/resolvers/base.model.js';
import { VmMutations } from '@app/unraid-api/graph/resolvers/mutation/mutation.model.js';
import { VmsService } from '@app/unraid-api/graph/resolvers/vms/vms.service.js';
import { PrefixedID } from '@app/unraid-api/graph/scalars/graphql-type-prefixed-id.js';
/**
* Nested Resolvers for Mutations MUST use @ResolveField() instead of @Mutation()
@@ -22,7 +23,7 @@ export class VmMutationsResolver {
possession: AuthPossession.ANY,
})
@ResolveField(() => Boolean, { description: 'Start a virtual machine' })
async start(@Args('id') id: string): Promise<boolean> {
async start(@Args('id', { type: () => PrefixedID }) id: string): Promise<boolean> {
return this.vmsService.startVm(id);
}
@@ -32,7 +33,7 @@ export class VmMutationsResolver {
possession: AuthPossession.ANY,
})
@ResolveField(() => Boolean, { description: 'Stop a virtual machine' })
async stop(@Args('id') id: string): Promise<boolean> {
async stop(@Args('id', { type: () => PrefixedID }) id: string): Promise<boolean> {
return this.vmsService.stopVm(id);
}
@@ -42,7 +43,7 @@ export class VmMutationsResolver {
possession: AuthPossession.ANY,
})
@ResolveField(() => Boolean, { description: 'Pause a virtual machine' })
async pause(@Args('id') id: string): Promise<boolean> {
async pause(@Args('id', { type: () => PrefixedID }) id: string): Promise<boolean> {
return this.vmsService.pauseVm(id);
}
@@ -52,7 +53,7 @@ export class VmMutationsResolver {
possession: AuthPossession.ANY,
})
@ResolveField(() => Boolean, { description: 'Resume a virtual machine' })
async resume(@Args('id') id: string): Promise<boolean> {
async resume(@Args('id', { type: () => PrefixedID }) id: string): Promise<boolean> {
return this.vmsService.resumeVm(id);
}
@@ -62,7 +63,7 @@ export class VmMutationsResolver {
possession: AuthPossession.ANY,
})
@ResolveField(() => Boolean, { description: 'Force stop a virtual machine' })
async forceStop(@Args('id') id: string): Promise<boolean> {
async forceStop(@Args('id', { type: () => PrefixedID }) id: string): Promise<boolean> {
return this.vmsService.forceStopVm(id);
}
@@ -72,7 +73,7 @@ export class VmMutationsResolver {
possession: AuthPossession.ANY,
})
@ResolveField(() => Boolean, { description: 'Reboot a virtual machine' })
async reboot(@Args('id') id: string): Promise<boolean> {
async reboot(@Args('id', { type: () => PrefixedID }) id: string): Promise<boolean> {
return this.vmsService.rebootVm(id);
}
@@ -82,7 +83,7 @@ export class VmMutationsResolver {
possession: AuthPossession.ANY,
})
@ResolveField(() => Boolean, { description: 'Reset a virtual machine' })
async reset(@Args('id') id: string): Promise<boolean> {
async reset(@Args('id', { type: () => PrefixedID }) id: string): Promise<boolean> {
return this.vmsService.resetVm(id);
}
}

View File

@@ -261,10 +261,10 @@ describe('VmsService', () => {
it('should start and stop the test VM', async () => {
expect(testVm).toBeDefined();
expect(testVm?.uuid).toBeDefined();
expect(testVm?.id).toBeDefined();
// Start the VM
const startResult = await service.startVm(testVm!.uuid);
const startResult = await service.startVm(testVm!.id);
expect(startResult).toBe(true);
// Wait for VM to start with a more targeted approach
@@ -282,7 +282,7 @@ describe('VmsService', () => {
expect(isRunning).toBe(true);
// Stop the VM
const stopResult = await service.stopVm(testVm!.uuid);
const stopResult = await service.stopVm(testVm!.id);
expect(stopResult).toBe(true);
// Wait for VM to stop with a more targeted approach

View File

@@ -360,8 +360,9 @@ export class VmsService implements OnModuleInit, OnModuleDestroy {
const state = this.mapDomainStateToVmState(info.state);
return {
name,
id: uuid,
uuid,
name,
state,
};
})

View File

@@ -0,0 +1,107 @@
import { CustomScalar, Scalar } from '@nestjs/graphql';
import { Kind, ValueNode } from 'graphql';
import { getServerIdentifier } from '@app/core/utils/server-identifier.js';
@Scalar('PrefixedID', () => PrefixedID)
export class PrefixedID implements CustomScalar<string, string> {
description: string = `
### Description:
ID scalar type that prefixes the underlying ID with the server identifier on output and strips it on input.
We use this scalar type to ensure that the ID is unique across all servers, allowing the same underlying resource ID to be used across different server instances.
#### Input Behavior:
When providing an ID as input (e.g., in arguments or input objects), the server identifier prefix ('<serverId>:') is optional.
- If the prefix is present (e.g., '123:456'), it will be automatically stripped, and only the underlying ID ('456') will be used internally.
- If the prefix is absent (e.g., '456'), the ID will be used as-is.
This makes it flexible for clients, as they don't strictly need to know or provide the server ID.
#### Output Behavior:
When an ID is returned in the response (output), it will *always* be prefixed with the current server's unique identifier (e.g., '123:456').
#### Example:
Note: The server identifier is '123' in this example.
##### Input (Prefix Optional):
\`\`\`graphql
# Both of these are valid inputs resolving to internal ID '456'
{
someQuery(id: "123:456") { ... }
anotherQuery(id: "456") { ... }
}
\`\`\`
##### Output (Prefix Always Added):
\`\`\`graphql
# Assuming internal ID is '456'
{
"data": {
"someResource": {
"id": "123:456"
}
}
}
\`\`\`
`;
// For output: Add the prefix
serialize(value: unknown): string {
if (typeof value !== 'string') {
// Consider logging this error or handling it based on your specific needs
console.error(`PrefixedID cannot represent non-string value: ${value}`);
throw new Error(`PrefixedID cannot represent non-string value: ${value}`);
}
// Simple check to avoid double-prefixing if somehow already prefixed
// This might happen if data is fetched internally already prefixed
if (value.includes(':')) {
// Optionally log or verify if the prefix matches the current serverId
// const serverId = this.serverIdentifierService.getId();
// if (!value.startsWith(`${serverId}:`)) {
// console.warn(`PrefixedID serialize: Value '${value}' already has a different prefix.`);
// }
return value;
}
const serverId = getServerIdentifier();
return `${serverId}:${value}`;
}
// For input variables: Remove the prefix
parseValue(value: unknown): string {
if (typeof value !== 'string') {
throw new Error(`PrefixedID cannot represent non-string value: ${value}`);
}
// Expecting '<serverId>:<originalId>'
const parts = value.split(':');
if (parts.length === 2) {
return parts[1];
}
// If it doesn't have the prefix format, assume it's an internal ID already.
// console.debug(`PrefixedID parseValue: Value '${value}' does not contain expected prefix.`);
return value;
}
// For inline query arguments: Remove the prefix
parseLiteral(ast: ValueNode): string {
if (ast.kind !== Kind.STRING) {
// Handle or throw error for non-string literals if necessary
throw new Error(
`PrefixedID cannot represent non-string literal value: ${'value' in ast ? ast.value : null}`
);
}
// Same logic as parseValue
const parts = ast.value.split(':');
if (parts.length === 2) {
return parts[1];
}
// console.debug(`PrefixedID parseLiteral: Value '${ast.value}' does not contain expected prefix.`);
return ast.value;
}
}

View File

@@ -11,10 +11,7 @@ export class Uptime {
@ObjectType({
implements: () => Node,
})
export class Service implements Node {
@Field(() => ID)
id!: string;
export class Service extends Node {
@Field(() => String, { nullable: true })
name?: string;

View File

@@ -1,13 +1,10 @@
import { Field, ID, ObjectType } from '@nestjs/graphql';
import { Field, ObjectType } from '@nestjs/graphql';
import { Permission } from '@app/unraid-api/graph/resolvers/api-key/api-key.model.js';
import { Role } from '@app/unraid-api/graph/resolvers/base.model.js';
@ObjectType()
export class UserAccount {
@Field(() => ID, { description: 'A unique identifier for the user' })
id!: string;
import { Node, Role } from '@app/unraid-api/graph/resolvers/base.model.js';
@ObjectType({ implements: () => Node })
export class UserAccount extends Node {
@Field({ description: 'The name of the user' })
name!: string;

View File

@@ -20,6 +20,7 @@ const config: CodegenConfig = {
URL: 'URL',
Port: 'number',
UUID: 'string',
PrefixedID: 'string',
},
},
generates: {

View File

@@ -35,7 +35,7 @@ export const getNotifications = graphql(/* GraphQL */ `
`);
export const archiveNotification = graphql(/* GraphQL */ `
mutation ArchiveNotification($id: String!) {
mutation ArchiveNotification($id: PrefixedID!) {
archiveNotification(id: $id) {
...NotificationFragment
}
@@ -59,7 +59,7 @@ export const archiveAllNotifications = graphql(/* GraphQL */ `
`);
export const deleteNotification = graphql(/* GraphQL */ `
mutation DeleteNotification($id: String!, $type: NotificationType!) {
mutation DeleteNotification($id: PrefixedID!, $type: NotificationType!) {
deleteNotification(id: $id, type: $type) {
archive {
total

View File

@@ -22,9 +22,9 @@ type Documents = {
"\n fragment NotificationFragment on Notification {\n id\n title\n subject\n description\n importance\n link\n type\n timestamp\n formattedTimestamp\n }\n": typeof types.NotificationFragmentFragmentDoc,
"\n fragment NotificationCountFragment on NotificationCounts {\n total\n info\n warning\n alert\n }\n": typeof types.NotificationCountFragmentFragmentDoc,
"\n query Notifications($filter: NotificationFilter!) {\n notifications {\n id\n list(filter: $filter) {\n ...NotificationFragment\n }\n }\n }\n": typeof types.NotificationsDocument,
"\n mutation ArchiveNotification($id: String!) {\n archiveNotification(id: $id) {\n ...NotificationFragment\n }\n }\n": typeof types.ArchiveNotificationDocument,
"\n mutation ArchiveNotification($id: PrefixedID!) {\n archiveNotification(id: $id) {\n ...NotificationFragment\n }\n }\n": typeof types.ArchiveNotificationDocument,
"\n mutation ArchiveAllNotifications {\n archiveAll {\n unread {\n total\n }\n archive {\n info\n warning\n alert\n total\n }\n }\n }\n": typeof types.ArchiveAllNotificationsDocument,
"\n mutation DeleteNotification($id: String!, $type: NotificationType!) {\n deleteNotification(id: $id, type: $type) {\n archive {\n total\n }\n }\n }\n": typeof types.DeleteNotificationDocument,
"\n mutation DeleteNotification($id: PrefixedID!, $type: NotificationType!) {\n deleteNotification(id: $id, type: $type) {\n archive {\n total\n }\n }\n }\n": typeof types.DeleteNotificationDocument,
"\n mutation DeleteAllNotifications {\n deleteArchivedNotifications {\n archive {\n total\n }\n unread {\n total\n }\n }\n }\n": typeof types.DeleteAllNotificationsDocument,
"\n query Overview {\n notifications {\n id\n overview {\n unread {\n info\n warning\n alert\n total\n }\n archive {\n total\n }\n }\n }\n }\n": typeof types.OverviewDocument,
"\n mutation RecomputeOverview {\n recalculateOverview {\n archive {\n ...NotificationCountFragment\n }\n unread {\n ...NotificationCountFragment\n }\n }\n }\n": typeof types.RecomputeOverviewDocument,
@@ -48,9 +48,9 @@ const documents: Documents = {
"\n fragment NotificationFragment on Notification {\n id\n title\n subject\n description\n importance\n link\n type\n timestamp\n formattedTimestamp\n }\n": types.NotificationFragmentFragmentDoc,
"\n fragment NotificationCountFragment on NotificationCounts {\n total\n info\n warning\n alert\n }\n": types.NotificationCountFragmentFragmentDoc,
"\n query Notifications($filter: NotificationFilter!) {\n notifications {\n id\n list(filter: $filter) {\n ...NotificationFragment\n }\n }\n }\n": types.NotificationsDocument,
"\n mutation ArchiveNotification($id: String!) {\n archiveNotification(id: $id) {\n ...NotificationFragment\n }\n }\n": types.ArchiveNotificationDocument,
"\n mutation ArchiveNotification($id: PrefixedID!) {\n archiveNotification(id: $id) {\n ...NotificationFragment\n }\n }\n": types.ArchiveNotificationDocument,
"\n mutation ArchiveAllNotifications {\n archiveAll {\n unread {\n total\n }\n archive {\n info\n warning\n alert\n total\n }\n }\n }\n": types.ArchiveAllNotificationsDocument,
"\n mutation DeleteNotification($id: String!, $type: NotificationType!) {\n deleteNotification(id: $id, type: $type) {\n archive {\n total\n }\n }\n }\n": types.DeleteNotificationDocument,
"\n mutation DeleteNotification($id: PrefixedID!, $type: NotificationType!) {\n deleteNotification(id: $id, type: $type) {\n archive {\n total\n }\n }\n }\n": types.DeleteNotificationDocument,
"\n mutation DeleteAllNotifications {\n deleteArchivedNotifications {\n archive {\n total\n }\n unread {\n total\n }\n }\n }\n": types.DeleteAllNotificationsDocument,
"\n query Overview {\n notifications {\n id\n overview {\n unread {\n info\n warning\n alert\n total\n }\n archive {\n total\n }\n }\n }\n }\n": types.OverviewDocument,
"\n mutation RecomputeOverview {\n recalculateOverview {\n archive {\n ...NotificationCountFragment\n }\n unread {\n ...NotificationCountFragment\n }\n }\n }\n": types.RecomputeOverviewDocument,
@@ -115,7 +115,7 @@ export function graphql(source: "\n query Notifications($filter: NotificationFi
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation ArchiveNotification($id: String!) {\n archiveNotification(id: $id) {\n ...NotificationFragment\n }\n }\n"): (typeof documents)["\n mutation ArchiveNotification($id: String!) {\n archiveNotification(id: $id) {\n ...NotificationFragment\n }\n }\n"];
export function graphql(source: "\n mutation ArchiveNotification($id: PrefixedID!) {\n archiveNotification(id: $id) {\n ...NotificationFragment\n }\n }\n"): (typeof documents)["\n mutation ArchiveNotification($id: PrefixedID!) {\n archiveNotification(id: $id) {\n ...NotificationFragment\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -123,7 +123,7 @@ export function graphql(source: "\n mutation ArchiveAllNotifications {\n arc
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation DeleteNotification($id: String!, $type: NotificationType!) {\n deleteNotification(id: $id, type: $type) {\n archive {\n total\n }\n }\n }\n"): (typeof documents)["\n mutation DeleteNotification($id: String!, $type: NotificationType!) {\n deleteNotification(id: $id, type: $type) {\n archive {\n total\n }\n }\n }\n"];
export function graphql(source: "\n mutation DeleteNotification($id: PrefixedID!, $type: NotificationType!) {\n deleteNotification(id: $id, type: $type) {\n archive {\n total\n }\n }\n }\n"): (typeof documents)["\n mutation DeleteNotification($id: PrefixedID!, $type: NotificationType!) {\n deleteNotification(id: $id, type: $type) {\n archive {\n total\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/

View File

@@ -22,6 +22,56 @@ export type Scalars = {
JSONObject: { input: any; output: any; }
/** The `Long` scalar type represents 52-bit integers */
Long: { input: number; output: number; }
/** A field whose value is a valid TCP port within the range of 0 to 65535: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_ports */
Port: { input: number; output: number; }
/**
*
* ### Description:
*
* ID scalar type that prefixes the underlying ID with the server identifier on output and strips it on input.
*
* We use this scalar type to ensure that the ID is unique across all servers, allowing the same underlying resource ID to be used across different server instances.
*
* #### Input Behavior:
*
* When providing an ID as input (e.g., in arguments or input objects), the server identifier prefix ('<serverId>:') is optional.
*
* - If the prefix is present (e.g., '123:456'), it will be automatically stripped, and only the underlying ID ('456') will be used internally.
* - If the prefix is absent (e.g., '456'), the ID will be used as-is.
*
* This makes it flexible for clients, as they don't strictly need to know or provide the server ID.
*
* #### Output Behavior:
*
* When an ID is returned in the response (output), it will *always* be prefixed with the current server's unique identifier (e.g., '123:456').
*
* #### Example:
*
* Note: The server identifier is '123' in this example.
*
* ##### Input (Prefix Optional):
* ```graphql
* # Both of these are valid inputs resolving to internal ID '456'
* {
* someQuery(id: "123:456") { ... }
* anotherQuery(id: "456") { ... }
* }
* ```
*
* ##### Output (Prefix Always Added):
* ```graphql
* # Assuming internal ID is '456'
* {
* "data": {
* "someResource": {
* "id": "123:456"
* }
* }
* }
* ```
*
*/
PrefixedID: { input: string; output: string; }
/** A field whose value conforms to the standard URL format as specified in RFC3986: https://www.ietf.org/rfc/rfc3986.txt. */
URL: { input: URL; output: URL; }
};
@@ -34,13 +84,20 @@ export type AccessUrl = {
type: UrlType;
};
export type AccessUrlInput = {
ipv4?: InputMaybe<Scalars['URL']['input']>;
ipv6?: InputMaybe<Scalars['URL']['input']>;
name?: InputMaybe<Scalars['String']['input']>;
type: UrlType;
};
export type AddPermissionInput = {
actions: Array<Scalars['String']['input']>;
resource: Resource;
};
export type AddRoleForApiKeyInput = {
apiKeyId: Scalars['ID']['input'];
apiKeyId: Scalars['PrefixedID']['input'];
role: Role;
};
@@ -49,11 +106,11 @@ export type AllowedOriginInput = {
origins: Array<Scalars['String']['input']>;
};
export type ApiKey = {
export type ApiKey = Node & {
__typename?: 'ApiKey';
createdAt: Scalars['String']['output'];
description?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
name: Scalars['String']['output'];
permissions: Array<Permission>;
roles: Array<Role>;
@@ -65,11 +122,11 @@ export type ApiKeyResponse = {
valid: Scalars['Boolean']['output'];
};
export type ApiKeyWithSecret = {
export type ApiKeyWithSecret = Node & {
__typename?: 'ApiKeyWithSecret';
createdAt: Scalars['String']['output'];
description?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
key: Scalars['String']['output'];
name: Scalars['String']['output'];
permissions: Array<Permission>;
@@ -118,8 +175,7 @@ export type ArrayDisk = Node & {
fsType?: Maybe<Scalars['String']['output']>;
/** (KB) Used Size on the FS (Not present on Parity type drive) */
fsUsed?: Maybe<Scalars['Long']['output']>;
/** Disk identifier, only set for present disks on the system */
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
/** Array slot number. Parity1 is always 0 and Parity2 is always 29. Array slots will be 1 - 28. Cache slots are 30 - 53. Flash is 54. */
idx: Scalars['Int']['output'];
name?: Maybe<Scalars['String']['output']>;
@@ -158,7 +214,7 @@ export enum ArrayDiskFsColor {
export type ArrayDiskInput = {
/** Disk ID */
id: Scalars['ID']['input'];
id: Scalars['PrefixedID']['input'];
/** The slot for the disk */
slot?: InputMaybe<Scalars['Int']['input']>;
};
@@ -205,12 +261,12 @@ export type ArrayMutationsAddDiskToArrayArgs = {
export type ArrayMutationsClearArrayDiskStatisticsArgs = {
id: Scalars['String']['input'];
id: Scalars['PrefixedID']['input'];
};
export type ArrayMutationsMountArrayDiskArgs = {
id: Scalars['String']['input'];
id: Scalars['PrefixedID']['input'];
};
@@ -225,16 +281,9 @@ export type ArrayMutationsSetStateArgs = {
export type ArrayMutationsUnmountArrayDiskArgs = {
id: Scalars['String']['input'];
id: Scalars['PrefixedID']['input'];
};
export enum ArrayPendingState {
NO_DATA_DISKS = 'NO_DATA_DISKS',
STARTING = 'STARTING',
STOPPING = 'STOPPING',
TOO_MANY_MISSING_DISKS = 'TOO_MANY_MISSING_DISKS'
}
export enum ArrayState {
DISABLE_DISK = 'DISABLE_DISK',
INVALID_EXPANSION = 'INVALID_EXPANSION',
@@ -259,10 +308,25 @@ export enum ArrayStateInputState {
STOP = 'STOP'
}
/** Available authentication action verbs */
export enum AuthActionVerb {
CREATE = 'CREATE',
DELETE = 'DELETE',
READ = 'READ',
UPDATE = 'UPDATE'
}
/** Available authentication possession types */
export enum AuthPossession {
ANY = 'ANY',
OWN = 'OWN',
OWN_ANY = 'OWN_ANY'
}
export type Baseboard = Node & {
__typename?: 'Baseboard';
assetTag?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
manufacturer: Scalars['String']['output'];
model?: Maybe<Scalars['String']['output']>;
serial?: Maybe<Scalars['String']['output']>;
@@ -284,7 +348,7 @@ export type Case = Node & {
base64?: Maybe<Scalars['String']['output']>;
error?: Maybe<Scalars['String']['output']>;
icon?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
url?: Maybe<Scalars['String']['output']>;
};
@@ -308,7 +372,7 @@ export type CloudResponse = {
export type Config = Node & {
__typename?: 'Config';
error?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
valid?: Maybe<Scalars['Boolean']['output']>;
};
@@ -325,8 +389,7 @@ export type Connect = Node & {
__typename?: 'Connect';
/** The status of dynamic remote access */
dynamicRemoteAccess: DynamicRemoteAccessStatus;
/** The unique identifier for the Connect instance */
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
/** The settings for the Connect instance */
settings: ConnectSettings;
};
@@ -335,8 +398,7 @@ export type ConnectSettings = Node & {
__typename?: 'ConnectSettings';
/** The data schema for the Connect settings */
dataSchema: Scalars['JSON']['output'];
/** The unique identifier for the Connect settings */
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
/** The UI schema for the Connect settings */
uiSchema: Scalars['JSON']['output'];
/** The values for the Connect settings */
@@ -389,8 +451,8 @@ export type ContainerHostConfig = {
export type ContainerPort = {
__typename?: 'ContainerPort';
ip?: Maybe<Scalars['String']['output']>;
privatePort: Scalars['Int']['output'];
publicPort: Scalars['Int']['output'];
privatePort?: Maybe<Scalars['Port']['output']>;
publicPort?: Maybe<Scalars['Port']['output']>;
type: ContainerPortType;
};
@@ -416,12 +478,12 @@ export type CreateApiKeyInput = {
export type Devices = Node & {
__typename?: 'Devices';
gpu: Array<Gpu>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
pci: Array<Pci>;
usb: Array<Usb>;
};
export type Disk = {
export type Disk = Node & {
__typename?: 'Disk';
/** The number of bytes per sector */
bytesPerSector: Scalars['Float']['output'];
@@ -429,8 +491,7 @@ export type Disk = {
device: Scalars['String']['output'];
/** The firmware revision of the disk */
firmwareRevision: Scalars['String']['output'];
/** The unique identifier of the disk */
id: Scalars['String']['output'];
id: Scalars['PrefixedID']['output'];
/** The interface type of the disk */
interfaceType: DiskInterfaceType;
/** The model name of the disk */
@@ -506,7 +567,7 @@ export type Display = Node & {
dashapps?: Maybe<Scalars['String']['output']>;
date?: Maybe<Scalars['String']['output']>;
hot?: Maybe<Scalars['Int']['output']>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
locale?: Maybe<Scalars['String']['output']>;
max?: Maybe<Scalars['Int']['output']>;
number?: Maybe<Scalars['String']['output']>;
@@ -526,17 +587,27 @@ export type Display = Node & {
export type Docker = Node & {
__typename?: 'Docker';
containers: Array<DockerContainer>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
networks: Array<DockerNetwork>;
};
export type DockerContainer = {
export type DockerContainersArgs = {
skipCache?: Scalars['Boolean']['input'];
};
export type DockerNetworksArgs = {
skipCache?: Scalars['Boolean']['input'];
};
export type DockerContainer = Node & {
__typename?: 'DockerContainer';
autoStart: Scalars['Boolean']['output'];
command: Scalars['String']['output'];
created: Scalars['Int']['output'];
hostConfig?: Maybe<ContainerHostConfig>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
image: Scalars['String']['output'];
imageId: Scalars['String']['output'];
labels?: Maybe<Scalars['JSONObject']['output']>;
@@ -560,15 +631,15 @@ export type DockerMutations = {
export type DockerMutationsStartArgs = {
id: Scalars['String']['input'];
id: Scalars['PrefixedID']['input'];
};
export type DockerMutationsStopArgs = {
id: Scalars['String']['input'];
id: Scalars['PrefixedID']['input'];
};
export type DockerNetwork = {
export type DockerNetwork = Node & {
__typename?: 'DockerNetwork';
attachable: Scalars['Boolean']['output'];
configFrom: Scalars['JSONObject']['output'];
@@ -577,7 +648,7 @@ export type DockerNetwork = {
created: Scalars['String']['output'];
driver: Scalars['String']['output'];
enableIPv6: Scalars['Boolean']['output'];
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
ingress: Scalars['Boolean']['output'];
internal: Scalars['Boolean']['output'];
ipam: Scalars['JSONObject']['output'];
@@ -606,14 +677,14 @@ export enum DynamicRemoteAccessType {
export type EnableDynamicRemoteAccessInput = {
/** Whether to enable or disable dynamic remote access */
enabled: Scalars['Boolean']['input'];
/** The URL for dynamic remote access */
url: Scalars['URL']['input'];
/** The AccessURL Input for dynamic remote access */
url: AccessUrlInput;
};
export type Flash = Node & {
__typename?: 'Flash';
guid: Scalars['String']['output'];
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
product: Scalars['String']['output'];
vendor: Scalars['String']['output'];
};
@@ -622,7 +693,7 @@ export type Gpu = Node & {
__typename?: 'Gpu';
blacklisted: Scalars['Boolean']['output'];
class: Scalars['String']['output'];
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
productid: Scalars['String']['output'];
type: Scalars['String']['output'];
typeid: Scalars['String']['output'];
@@ -637,9 +708,9 @@ export type Info = Node & {
cpu: InfoCpu;
devices: Devices;
display: Display;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
/** Machine ID */
machineId?: Maybe<Scalars['ID']['output']>;
machineId?: Maybe<Scalars['PrefixedID']['output']>;
memory: InfoMemory;
os: Os;
system: System;
@@ -649,7 +720,7 @@ export type Info = Node & {
export type InfoApps = Node & {
__typename?: 'InfoApps';
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
/** How many docker containers are installed */
installed: Scalars['Int']['output'];
/** How many docker containers are running */
@@ -663,7 +734,7 @@ export type InfoCpu = Node & {
cores: Scalars['Int']['output'];
family: Scalars['String']['output'];
flags: Array<Scalars['String']['output']>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
manufacturer: Scalars['String']['output'];
model: Scalars['String']['output'];
processors: Scalars['Int']['output'];
@@ -684,7 +755,7 @@ export type InfoMemory = Node & {
available: Scalars['Int']['output'];
buffcache: Scalars['Int']['output'];
free: Scalars['Int']['output'];
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
layout: Array<MemoryLayout>;
max: Scalars['Int']['output'];
swapfree: Scalars['Int']['output'];
@@ -724,11 +795,12 @@ export type LogFileContent = {
totalLines: Scalars['Int']['output'];
};
export type MemoryLayout = {
export type MemoryLayout = Node & {
__typename?: 'MemoryLayout';
bank?: Maybe<Scalars['String']['output']>;
clockSpeed?: Maybe<Scalars['Int']['output']>;
formFactor?: Maybe<Scalars['String']['output']>;
id: Scalars['PrefixedID']['output'];
manufacturer?: Maybe<Scalars['String']['output']>;
partNum?: Maybe<Scalars['String']['output']>;
serialNum?: Maybe<Scalars['String']['output']>;
@@ -762,7 +834,6 @@ export type Mutation = {
archiveNotification: Notification;
archiveNotifications: NotificationOverview;
array: ArrayMutations;
cancelParityCheck: Scalars['JSON']['output'];
connectSignIn: Scalars['Boolean']['output'];
connectSignOut: Scalars['Boolean']['output'];
createApiKey: ApiKeyWithSecret;
@@ -773,33 +844,19 @@ export type Mutation = {
deleteNotification: NotificationOverview;
docker: DockerMutations;
enableDynamicRemoteAccess: Scalars['Boolean']['output'];
/** Force stop a virtual machine */
forceStopVm: Scalars['Boolean']['output'];
pauseParityCheck: Scalars['JSON']['output'];
/** Pause a virtual machine */
pauseVm: Scalars['Boolean']['output'];
/** Reboot a virtual machine */
rebootVm: Scalars['Boolean']['output'];
parityCheck: ParityCheckMutations;
/** Reads each notification to recompute & update the overview. */
recalculateOverview: NotificationOverview;
removeRoleFromApiKey: Scalars['Boolean']['output'];
/** Reset a virtual machine */
resetVm: Scalars['Boolean']['output'];
resumeParityCheck: Scalars['JSON']['output'];
/** Resume a virtual machine */
resumeVm: Scalars['Boolean']['output'];
setAdditionalAllowedOrigins: Array<Scalars['String']['output']>;
setDemo: Scalars['String']['output'];
setupRemoteAccess: Scalars['Boolean']['output'];
startParityCheck: Scalars['JSON']['output'];
/** Start a virtual machine */
startVm: Scalars['Boolean']['output'];
/** Stop a virtual machine */
stopVm: Scalars['Boolean']['output'];
unarchiveAll: NotificationOverview;
unarchiveNotifications: NotificationOverview;
/** Marks a notification as unread. */
unreadNotification: Notification;
updateApiSettings: ConnectSettingsValues;
vm: VmMutations;
};
@@ -814,12 +871,12 @@ export type MutationArchiveAllArgs = {
export type MutationArchiveNotificationArgs = {
id: Scalars['String']['input'];
id: Scalars['PrefixedID']['input'];
};
export type MutationArchiveNotificationsArgs = {
ids: Array<Scalars['String']['input']>;
ids: Array<Scalars['PrefixedID']['input']>;
};
@@ -839,7 +896,7 @@ export type MutationCreateNotificationArgs = {
export type MutationDeleteNotificationArgs = {
id: Scalars['String']['input'];
id: Scalars['PrefixedID']['input'];
type: NotificationType;
};
@@ -849,36 +906,11 @@ export type MutationEnableDynamicRemoteAccessArgs = {
};
export type MutationForceStopVmArgs = {
id: Scalars['String']['input'];
};
export type MutationPauseVmArgs = {
id: Scalars['String']['input'];
};
export type MutationRebootVmArgs = {
id: Scalars['String']['input'];
};
export type MutationRemoveRoleFromApiKeyArgs = {
input: RemoveRoleFromApiKeyInput;
};
export type MutationResetVmArgs = {
id: Scalars['String']['input'];
};
export type MutationResumeVmArgs = {
id: Scalars['String']['input'];
};
export type MutationSetAdditionalAllowedOriginsArgs = {
input: AllowedOriginInput;
};
@@ -889,33 +921,18 @@ export type MutationSetupRemoteAccessArgs = {
};
export type MutationStartParityCheckArgs = {
correct: Scalars['Boolean']['input'];
};
export type MutationStartVmArgs = {
id: Scalars['String']['input'];
};
export type MutationStopVmArgs = {
id: Scalars['String']['input'];
};
export type MutationUnarchiveAllArgs = {
importance?: InputMaybe<NotificationImportance>;
};
export type MutationUnarchiveNotificationsArgs = {
ids: Array<Scalars['String']['input']>;
ids: Array<Scalars['PrefixedID']['input']>;
};
export type MutationUnreadNotificationArgs = {
id: Scalars['String']['input'];
id: Scalars['PrefixedID']['input'];
};
@@ -926,18 +943,18 @@ export type MutationUpdateApiSettingsArgs = {
export type Network = Node & {
__typename?: 'Network';
accessUrls?: Maybe<Array<AccessUrl>>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
};
export type Node = {
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
};
export type Notification = {
export type Notification = Node & {
__typename?: 'Notification';
description: Scalars['String']['output'];
formattedTimestamp?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
importance: NotificationImportance;
link?: Maybe<Scalars['String']['output']>;
subject: Scalars['String']['output'];
@@ -988,9 +1005,9 @@ export enum NotificationType {
UNREAD = 'UNREAD'
}
export type Notifications = {
export type Notifications = Node & {
__typename?: 'Notifications';
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
list: Array<Notification>;
/** A cached overview of the notifications in the system & their severity. */
overview: NotificationOverview;
@@ -1009,7 +1026,7 @@ export type Os = Node & {
codepage?: Maybe<Scalars['String']['output']>;
distro?: Maybe<Scalars['String']['output']>;
hostname?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
kernel?: Maybe<Scalars['String']['output']>;
logofile?: Maybe<Scalars['String']['output']>;
platform?: Maybe<Scalars['String']['output']>;
@@ -1047,11 +1064,30 @@ export type ParityCheck = {
status?: Maybe<Scalars['String']['output']>;
};
/** Parity check related mutations, WIP, response types and functionaliy will change */
export type ParityCheckMutations = {
__typename?: 'ParityCheckMutations';
/** Cancel a parity check */
cancel: Scalars['JSON']['output'];
/** Pause a parity check */
pause: Scalars['JSON']['output'];
/** Resume a parity check */
resume: Scalars['JSON']['output'];
/** Start a parity check */
start: Scalars['JSON']['output'];
};
/** Parity check related mutations, WIP, response types and functionaliy will change */
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['ID']['output'];
id: Scalars['PrefixedID']['output'];
productid?: Maybe<Scalars['String']['output']>;
productname?: Maybe<Scalars['String']['output']>;
type?: Maybe<Scalars['String']['output']>;
@@ -1066,11 +1102,11 @@ export type Permission = {
resource: Resource;
};
export type ProfileModel = {
export type ProfileModel = Node & {
__typename?: 'ProfileModel';
avatar: Scalars['String']['output'];
id: Scalars['PrefixedID']['output'];
url: Scalars['String']['output'];
userId?: Maybe<Scalars['ID']['output']>;
username: Scalars['String']['output'];
};
@@ -1088,6 +1124,8 @@ export type Query = {
docker: Docker;
extraAllowedOrigins: Array<Scalars['String']['output']>;
flash: Flash;
getDemo: Scalars['String']['output'];
health: Scalars['String']['output'];
info: Info;
logFile: LogFileContent;
logFiles: Array<LogFile>;
@@ -1105,17 +1143,18 @@ export type Query = {
services: Array<Service>;
shares: Array<Share>;
vars: Vars;
/** Get information about all VMs on the system */
vms: Vms;
};
export type QueryApiKeyArgs = {
id: Scalars['String']['input'];
id: Scalars['PrefixedID']['input'];
};
export type QueryDiskArgs = {
id: Scalars['String']['input'];
id: Scalars['PrefixedID']['input'];
};
@@ -1125,10 +1164,10 @@ export type QueryLogFileArgs = {
startLine?: InputMaybe<Scalars['Int']['input']>;
};
export type Registration = {
export type Registration = Node & {
__typename?: 'Registration';
expiration?: Maybe<Scalars['String']['output']>;
guid?: Maybe<Scalars['ID']['output']>;
id: Scalars['PrefixedID']['output'];
keyFile?: Maybe<KeyFile>;
state?: Maybe<RegistrationState>;
type?: Maybe<RegistrationType>;
@@ -1182,7 +1221,7 @@ export type RemoteAccess = {
};
export type RemoveRoleFromApiKeyInput = {
apiKeyId: Scalars['ID']['input'];
apiKeyId: Scalars['PrefixedID']['input'];
role: Role;
};
@@ -1225,10 +1264,11 @@ export enum Role {
GUEST = 'GUEST'
}
export type Server = {
export type Server = Node & {
__typename?: 'Server';
apikey: Scalars['String']['output'];
guid: Scalars['String']['output'];
id: Scalars['PrefixedID']['output'];
lanip: Scalars['String']['output'];
localurl: Scalars['String']['output'];
name: Scalars['String']['output'];
@@ -1246,7 +1286,7 @@ export enum ServerStatus {
export type Service = Node & {
__typename?: 'Service';
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
name?: Maybe<Scalars['String']['output']>;
online?: Maybe<Scalars['Boolean']['output']>;
uptime?: Maybe<Uptime>;
@@ -1280,7 +1320,7 @@ export type Share = Node & {
floor?: Maybe<Scalars['String']['output']>;
/** (KB) Free space */
free?: Maybe<Scalars['Long']['output']>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
/** Disks that are included in this share */
include?: Maybe<Array<Scalars['String']['output']>>;
/** LUKS status */
@@ -1318,7 +1358,7 @@ export type SubscriptionLogFileArgs = {
export type System = Node & {
__typename?: 'System';
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
manufacturer?: Maybe<Scalars['String']['output']>;
model?: Maybe<Scalars['String']['output']>;
serial?: Maybe<Scalars['String']['output']>;
@@ -1357,13 +1397,9 @@ export type UnraidArray = Node & {
capacity: ArrayCapacity;
/** Data disks in the current array */
disks: Array<ArrayDisk>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
/** Parity disks in the current array */
parities: Array<ArrayDisk>;
/** Array state after this query/mutation */
pendingState?: Maybe<ArrayPendingState>;
/** Array state before this query/mutation */
previousState?: Maybe<ArrayState>;
/** Current array state */
state: ArrayState;
};
@@ -1373,18 +1409,17 @@ export type Uptime = {
timestamp?: Maybe<Scalars['String']['output']>;
};
export type Usb = {
export type Usb = Node & {
__typename?: 'Usb';
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
name?: Maybe<Scalars['String']['output']>;
};
export type UserAccount = {
export type UserAccount = Node & {
__typename?: 'UserAccount';
/** A description of the user */
description: Scalars['String']['output'];
/** A unique identifier for the user */
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
/** The name of the user */
name: Scalars['String']['output'];
/** The permissions of the user */
@@ -1427,7 +1462,7 @@ export type Vars = Node & {
fuseRememberDefault?: Maybe<Scalars['String']['output']>;
fuseRememberStatus?: Maybe<Scalars['String']['output']>;
hideDotFiles?: Maybe<Scalars['Boolean']['output']>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
joinStatus?: Maybe<Scalars['String']['output']>;
localMaster?: Maybe<Scalars['Boolean']['output']>;
localTld?: Maybe<Scalars['String']['output']>;
@@ -1565,7 +1600,7 @@ export type Versions = Node & {
git?: Maybe<Scalars['String']['output']>;
grunt?: Maybe<Scalars['String']['output']>;
gulp?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
kernel?: Maybe<Scalars['String']['output']>;
mongodb?: Maybe<Scalars['String']['output']>;
mysql?: Maybe<Scalars['String']['output']>;
@@ -1588,13 +1623,72 @@ export type Versions = Node & {
yarn?: Maybe<Scalars['String']['output']>;
};
export type VmDomain = {
export type VmDomain = Node & {
__typename?: 'VmDomain';
/** The unique identifier for the vm (uuid) */
id: Scalars['PrefixedID']['output'];
/** A friendly name for the vm */
name?: Maybe<Scalars['String']['output']>;
/** Current domain vm state */
state: VmState;
uuid: Scalars['ID']['output'];
/**
* The UUID of the vm
* @deprecated Use id instead
*/
uuid?: Maybe<Scalars['String']['output']>;
};
export type VmMutations = {
__typename?: 'VmMutations';
/** Force stop a virtual machine */
forceStop: Scalars['Boolean']['output'];
/** Pause a virtual machine */
pause: Scalars['Boolean']['output'];
/** Reboot a virtual machine */
reboot: Scalars['Boolean']['output'];
/** Reset a virtual machine */
reset: Scalars['Boolean']['output'];
/** Resume a virtual machine */
resume: Scalars['Boolean']['output'];
/** Start a virtual machine */
start: Scalars['Boolean']['output'];
/** Stop a virtual machine */
stop: Scalars['Boolean']['output'];
};
export type VmMutationsForceStopArgs = {
id: Scalars['PrefixedID']['input'];
};
export type VmMutationsPauseArgs = {
id: Scalars['PrefixedID']['input'];
};
export type VmMutationsRebootArgs = {
id: Scalars['PrefixedID']['input'];
};
export type VmMutationsResetArgs = {
id: Scalars['PrefixedID']['input'];
};
export type VmMutationsResumeArgs = {
id: Scalars['PrefixedID']['input'];
};
export type VmMutationsStartArgs = {
id: Scalars['PrefixedID']['input'];
};
export type VmMutationsStopArgs = {
id: Scalars['PrefixedID']['input'];
};
/** The state of a virtual machine */
@@ -1609,10 +1703,11 @@ export enum VmState {
SHUTOFF = 'SHUTOFF'
}
export type Vms = {
export type Vms = Node & {
__typename?: 'Vms';
domain?: Maybe<Array<VmDomain>>;
domains?: Maybe<Array<VmDomain>>;
id: Scalars['ID']['output'];
id: Scalars['PrefixedID']['output'];
};
export enum WanAccessType {
@@ -1685,7 +1780,7 @@ export type NotificationsQuery = { __typename?: 'Query', notifications: { __type
)> } };
export type ArchiveNotificationMutationVariables = Exact<{
id: Scalars['String']['input'];
id: Scalars['PrefixedID']['input'];
}>;
@@ -1700,7 +1795,7 @@ export type ArchiveAllNotificationsMutationVariables = Exact<{ [key: string]: ne
export type ArchiveAllNotificationsMutation = { __typename?: 'Mutation', archiveAll: { __typename?: 'NotificationOverview', unread: { __typename?: 'NotificationCounts', total: number }, archive: { __typename?: 'NotificationCounts', info: number, warning: number, alert: number, total: number } } };
export type DeleteNotificationMutationVariables = Exact<{
id: Scalars['String']['input'];
id: Scalars['PrefixedID']['input'];
type: NotificationType;
}>;
@@ -1802,9 +1897,9 @@ export const LogFilesDocument = {"kind":"Document","definitions":[{"kind":"Opera
export const LogFileContentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"LogFileContent"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"path"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"lines"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"startLine"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"logFile"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"path"},"value":{"kind":"Variable","name":{"kind":"Name","value":"path"}}},{"kind":"Argument","name":{"kind":"Name","value":"lines"},"value":{"kind":"Variable","name":{"kind":"Name","value":"lines"}}},{"kind":"Argument","name":{"kind":"Name","value":"startLine"},"value":{"kind":"Variable","name":{"kind":"Name","value":"startLine"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"path"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"totalLines"}},{"kind":"Field","name":{"kind":"Name","value":"startLine"}}]}}]}}]} as unknown as DocumentNode<LogFileContentQuery, LogFileContentQueryVariables>;
export const LogFileSubscriptionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"LogFileSubscription"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"path"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"logFile"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"path"},"value":{"kind":"Variable","name":{"kind":"Name","value":"path"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"path"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"totalLines"}}]}}]}}]} as unknown as DocumentNode<LogFileSubscriptionSubscription, LogFileSubscriptionSubscriptionVariables>;
export const NotificationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Notifications"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationFilter"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"list"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Notification"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"importance"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"timestamp"}},{"kind":"Field","name":{"kind":"Name","value":"formattedTimestamp"}}]}}]} as unknown as DocumentNode<NotificationsQuery, NotificationsQueryVariables>;
export const ArchiveNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ArchiveNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"archiveNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Notification"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"importance"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"timestamp"}},{"kind":"Field","name":{"kind":"Name","value":"formattedTimestamp"}}]}}]} as unknown as DocumentNode<ArchiveNotificationMutation, ArchiveNotificationMutationVariables>;
export const ArchiveNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ArchiveNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PrefixedID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"archiveNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Notification"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"importance"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"timestamp"}},{"kind":"Field","name":{"kind":"Name","value":"formattedTimestamp"}}]}}]} as unknown as DocumentNode<ArchiveNotificationMutation, ArchiveNotificationMutationVariables>;
export const ArchiveAllNotificationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ArchiveAllNotifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"archiveAll"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unread"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}}]}},{"kind":"Field","name":{"kind":"Name","value":"archive"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"info"}},{"kind":"Field","name":{"kind":"Name","value":"warning"}},{"kind":"Field","name":{"kind":"Name","value":"alert"}},{"kind":"Field","name":{"kind":"Name","value":"total"}}]}}]}}]}}]} as unknown as DocumentNode<ArchiveAllNotificationsMutation, ArchiveAllNotificationsMutationVariables>;
export const DeleteNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"type"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationType"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"Argument","name":{"kind":"Name","value":"type"},"value":{"kind":"Variable","name":{"kind":"Name","value":"type"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"archive"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}}]}}]}}]}}]} as unknown as DocumentNode<DeleteNotificationMutation, DeleteNotificationMutationVariables>;
export const DeleteNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PrefixedID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"type"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationType"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"Argument","name":{"kind":"Name","value":"type"},"value":{"kind":"Variable","name":{"kind":"Name","value":"type"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"archive"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}}]}}]}}]}}]} as unknown as DocumentNode<DeleteNotificationMutation, DeleteNotificationMutationVariables>;
export const DeleteAllNotificationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteAllNotifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteArchivedNotifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"archive"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}}]}},{"kind":"Field","name":{"kind":"Name","value":"unread"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}}]}}]}}]}}]} as unknown as DocumentNode<DeleteAllNotificationsMutation, DeleteAllNotificationsMutationVariables>;
export const OverviewDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Overview"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"overview"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unread"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"info"}},{"kind":"Field","name":{"kind":"Name","value":"warning"}},{"kind":"Field","name":{"kind":"Name","value":"alert"}},{"kind":"Field","name":{"kind":"Name","value":"total"}}]}},{"kind":"Field","name":{"kind":"Name","value":"archive"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}}]}}]}}]}}]}}]} as unknown as DocumentNode<OverviewQuery, OverviewQueryVariables>;
export const RecomputeOverviewDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RecomputeOverview"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"recalculateOverview"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"archive"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationCountFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"unread"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationCountFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationCountFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationCounts"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"info"}},{"kind":"Field","name":{"kind":"Name","value":"warning"}},{"kind":"Field","name":{"kind":"Name","value":"alert"}}]}}]} as unknown as DocumentNode<RecomputeOverviewMutation, RecomputeOverviewMutationVariables>;