Skip to content

Commit bb4dd04

Browse files
committed
feat: add graphql resource for API plugins
1 parent 8c8a527 commit bb4dd04

File tree

5 files changed

+178
-227
lines changed

5 files changed

+178
-227
lines changed

api/generated-schema.graphql

Lines changed: 30 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -226,27 +226,6 @@ type Share implements Node {
226226
luksStatus: String
227227
}
228228

229-
type AccessUrl {
230-
type: URL_TYPE!
231-
name: String
232-
ipv4: URL
233-
ipv6: URL
234-
}
235-
236-
enum URL_TYPE {
237-
LAN
238-
WIREGUARD
239-
WAN
240-
MDNS
241-
OTHER
242-
DEFAULT
243-
}
244-
245-
"""
246-
A field whose value conforms to the standard URL format as specified in RFC3986: https://www.ietf.org/rfc/rfc3986.txt.
247-
"""
248-
scalar URL
249-
250229
type DiskPartition {
251230
"""The name of the partition"""
252231
name: String!
@@ -1371,6 +1350,7 @@ type ApiConfig {
13711350
extraOrigins: [String!]!
13721351
sandbox: Boolean
13731352
ssoSubIds: [String!]!
1353+
plugins: [String!]!
13741354
}
13751355

13761356
type UnifiedSettings implements Node {
@@ -1464,137 +1444,18 @@ type UserAccount implements Node {
14641444
permissions: [Permission!]
14651445
}
14661446

1467-
type AccessUrlObject {
1468-
ipv4: String
1469-
ipv6: String
1470-
type: URL_TYPE!
1471-
name: String
1472-
}
1473-
1474-
type RemoteAccess {
1475-
"""The type of WAN access used for Remote Access"""
1476-
accessType: WAN_ACCESS_TYPE!
1477-
1478-
"""The type of port forwarding used for Remote Access"""
1479-
forwardType: WAN_FORWARD_TYPE
1480-
1481-
"""The port used for Remote Access"""
1482-
port: Int
1483-
}
1484-
1485-
enum WAN_ACCESS_TYPE {
1486-
DYNAMIC
1487-
ALWAYS
1488-
DISABLED
1489-
}
1490-
1491-
enum WAN_FORWARD_TYPE {
1492-
UPNP
1493-
STATIC
1494-
}
1495-
1496-
type DynamicRemoteAccessStatus {
1497-
"""The type of dynamic remote access that is enabled"""
1498-
enabledType: DynamicRemoteAccessType!
1499-
1500-
"""The type of dynamic remote access that is currently running"""
1501-
runningType: DynamicRemoteAccessType!
1502-
1503-
"""Any error message associated with the dynamic remote access"""
1504-
error: String
1505-
}
1506-
1507-
enum DynamicRemoteAccessType {
1508-
STATIC
1509-
UPNP
1510-
DISABLED
1511-
}
1512-
1513-
type ConnectSettingsValues {
1514-
"""The type of WAN access used for Remote Access"""
1515-
accessType: WAN_ACCESS_TYPE!
1516-
1517-
"""The type of port forwarding used for Remote Access"""
1518-
forwardType: WAN_FORWARD_TYPE
1519-
1520-
"""The port used for Remote Access"""
1521-
port: Int
1522-
}
1523-
1524-
type ConnectSettings implements Node {
1525-
id: PrefixedID!
1526-
1527-
"""The data schema for the Connect settings"""
1528-
dataSchema: JSON!
1529-
1530-
"""The UI schema for the Connect settings"""
1531-
uiSchema: JSON!
1532-
1533-
"""The values for the Connect settings"""
1534-
values: ConnectSettingsValues!
1535-
}
1536-
1537-
type Connect implements Node {
1538-
id: PrefixedID!
1539-
1540-
"""The status of dynamic remote access"""
1541-
dynamicRemoteAccess: DynamicRemoteAccessStatus!
1542-
1543-
"""The settings for the Connect instance"""
1544-
settings: ConnectSettings!
1545-
}
1546-
1547-
type Network implements Node {
1548-
id: PrefixedID!
1549-
accessUrls: [AccessUrl!]
1550-
}
1551-
1552-
type ApiKeyResponse {
1553-
valid: Boolean!
1554-
error: String
1555-
}
1556-
1557-
type MinigraphqlResponse {
1558-
status: MinigraphStatus!
1559-
timeout: Int
1560-
error: String
1561-
}
1562-
1563-
"""The status of the minigraph"""
1564-
enum MinigraphStatus {
1565-
PRE_INIT
1566-
CONNECTING
1567-
CONNECTED
1568-
PING_FAILURE
1569-
ERROR_RETRYING
1570-
}
1571-
1572-
type CloudResponse {
1573-
status: String!
1574-
ip: String
1575-
error: String
1576-
}
1447+
type Plugin {
1448+
"""The name of the plugin package"""
1449+
name: String!
15771450

1578-
type RelayResponse {
1579-
status: String!
1580-
timeout: String
1581-
error: String
1582-
}
1451+
"""The version of the plugin package"""
1452+
version: String!
15831453

1584-
type Cloud {
1585-
error: String
1586-
apiKey: ApiKeyResponse!
1587-
relay: RelayResponse
1588-
minigraphql: MinigraphqlResponse!
1589-
cloud: CloudResponse!
1590-
allowedOrigins: [String!]!
1591-
}
1454+
"""Whether the plugin has an API module"""
1455+
hasApiModule: Boolean
15921456

1593-
input AccessUrlObjectInput {
1594-
ipv4: String
1595-
ipv6: String
1596-
type: URL_TYPE!
1597-
name: String
1457+
"""Whether the plugin has a CLI module"""
1458+
hasCliModule: Boolean
15981459
}
15991460

16001461
"\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 "
@@ -1640,10 +1501,9 @@ type Query {
16401501
disk(id: PrefixedID!): Disk!
16411502
rclone: RCloneBackupSettings!
16421503
settings: Settings!
1643-
remoteAccess: RemoteAccess!
1644-
connect: Connect!
1645-
network: Network!
1646-
cloud: Cloud!
1504+
1505+
"""List all installed plugins with their metadata"""
1506+
plugins: [Plugin!]!
16471507
}
16481508

16491509
type Mutation {
@@ -1676,11 +1536,16 @@ type Mutation {
16761536
"""Initiates a flash drive backup using a configured remote."""
16771537
initiateFlashBackup(input: InitiateFlashBackupInput!): FlashBackupStatus!
16781538
updateSettings(input: JSON!): UpdateSettingsResponse!
1679-
updateApiSettings(input: ConnectSettingsInput!): ConnectSettingsValues!
1680-
connectSignIn(input: ConnectSignInInput!): Boolean!
1681-
connectSignOut: Boolean!
1682-
setupRemoteAccess(input: SetupRemoteAccessInput!): Boolean!
1683-
enableDynamicRemoteAccess(input: EnableDynamicRemoteAccessInput!): Boolean!
1539+
1540+
"""
1541+
Add one or more plugins to the API. Returns false if restart was triggered automatically, true if manual restart is required.
1542+
"""
1543+
addPlugin(input: PluginManagementInput!): Boolean!
1544+
1545+
"""
1546+
Remove one or more plugins from the API. Returns false if restart was triggered automatically, true if manual restart is required.
1547+
"""
1548+
removePlugin(input: PluginManagementInput!): Boolean!
16841549
}
16851550

16861551
input NotificationData {
@@ -1707,73 +1572,19 @@ input InitiateFlashBackupInput {
17071572
options: JSON
17081573
}
17091574

1710-
input ConnectSettingsInput {
1711-
"""The type of WAN access to use for Remote Access"""
1712-
accessType: WAN_ACCESS_TYPE
1713-
1714-
"""The type of port forwarding to use for Remote Access"""
1715-
forwardType: WAN_FORWARD_TYPE
1575+
input PluginManagementInput {
1576+
"""Array of plugin package names to add or remove"""
1577+
names: [String!]!
17161578

17171579
"""
1718-
The port to use for Remote Access. Not required for UPNP forwardType. Required for STATIC forwardType. Ignored if accessType is DISABLED or forwardType is UPNP.
1580+
Whether to treat plugins as bundled plugins. Bundled plugins are installed to node_modules at build time and controlled via config only.
17191581
"""
1720-
port: Int
1721-
}
1722-
1723-
input ConnectSignInInput {
1724-
"""The API key for authentication"""
1725-
apiKey: String!
1726-
1727-
"""The ID token for authentication"""
1728-
idToken: String
1729-
1730-
"""User information for the sign-in"""
1731-
userInfo: ConnectUserInfoInput
1732-
1733-
"""The access token for authentication"""
1734-
accessToken: String
1735-
1736-
"""The refresh token for authentication"""
1737-
refreshToken: String
1738-
}
1739-
1740-
input ConnectUserInfoInput {
1741-
"""The preferred username of the user"""
1742-
preferred_username: String!
1743-
1744-
"""The email address of the user"""
1745-
email: String!
1746-
1747-
"""The avatar URL of the user"""
1748-
avatar: String
1749-
}
1750-
1751-
input SetupRemoteAccessInput {
1752-
"""The type of WAN access to use for Remote Access"""
1753-
accessType: WAN_ACCESS_TYPE!
1754-
1755-
"""The type of port forwarding to use for Remote Access"""
1756-
forwardType: WAN_FORWARD_TYPE
1582+
bundled: Boolean! = false
17571583

17581584
"""
1759-
The port to use for Remote Access. Not required for UPNP forwardType. Required for STATIC forwardType. Ignored if accessType is DISABLED or forwardType is UPNP.
1585+
Whether to restart the API after the operation. When false, a restart has already been queued.
17601586
"""
1761-
port: Int
1762-
}
1763-
1764-
input EnableDynamicRemoteAccessInput {
1765-
"""The AccessURL Input for dynamic remote access"""
1766-
url: AccessUrlInput!
1767-
1768-
"""Whether to enable or disable dynamic remote access"""
1769-
enabled: Boolean!
1770-
}
1771-
1772-
input AccessUrlInput {
1773-
type: URL_TYPE!
1774-
name: String
1775-
ipv4: URL
1776-
ipv6: URL
1587+
restart: Boolean! = true
17771588
}
17781589

17791590
type Subscription {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Field, InputType, ObjectType } from '@nestjs/graphql';
2+
3+
@ObjectType()
4+
export class Plugin {
5+
@Field({ description: 'The name of the plugin package' })
6+
name!: string;
7+
8+
@Field({ description: 'The version of the plugin package' })
9+
version!: string;
10+
11+
@Field({ nullable: true, description: 'Whether the plugin has an API module' })
12+
hasApiModule?: boolean;
13+
14+
@Field({ nullable: true, description: 'Whether the plugin has a CLI module' })
15+
hasCliModule?: boolean;
16+
}
17+
18+
@InputType()
19+
export class PluginManagementInput {
20+
@Field(() => [String], { description: 'Array of plugin package names to add or remove' })
21+
names!: string[];
22+
23+
@Field({
24+
defaultValue: false,
25+
description:
26+
'Whether to treat plugins as bundled plugins. Bundled plugins are installed to node_modules at build time and controlled via config only.',
27+
})
28+
bundled!: boolean;
29+
30+
@Field({
31+
defaultValue: true,
32+
description:
33+
'Whether to restart the API after the operation. When false, a restart has already been queued.',
34+
})
35+
restart!: boolean;
36+
}

api/src/unraid-api/plugin/plugin.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { DependencyService } from '@app/unraid-api/app/dependency.service.js';
44
import { ResolversModule } from '@app/unraid-api/graph/resolvers/resolvers.module.js';
55
import { GlobalDepsModule } from '@app/unraid-api/plugin/global-deps.module.js';
66
import { PluginManagementService } from '@app/unraid-api/plugin/plugin-management.service.js';
7+
import { PluginResolver } from '@app/unraid-api/plugin/plugin.resolver.js';
78
import { PluginService } from '@app/unraid-api/plugin/plugin.service.js';
89

910
@Module({})
@@ -22,7 +23,7 @@ export class PluginModule {
2223
return {
2324
module: PluginModule,
2425
imports: [GlobalDepsModule, ResolversModule, ...apiModules],
25-
providers: [PluginService, PluginManagementService, DependencyService],
26+
providers: [PluginService, PluginManagementService, DependencyService, PluginResolver],
2627
exports: [PluginService, PluginManagementService, DependencyService, GlobalDepsModule],
2728
};
2829
}

0 commit comments

Comments
 (0)