-
Notifications
You must be signed in to change notification settings - Fork 16
Feat/mothership v2 #1551
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feat/mothership v2 #1551
Changes from all commits
ff94160
5959549
fcf74d3
67944eb
90ed837
81cc9ff
92e30e2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| { | ||
| "version": "4.25.3", | ||
| "version": "4.27.2", | ||
| "extraOrigins": [], | ||
| "sandbox": true, | ||
| "ssoSubIds": [], | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "connectionStatus": "PRE_INIT", | ||
| "error": null, | ||
| "lastPing": null, | ||
| "allowedOrigins": "", | ||
| "timestamp": 1764601989840 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -204,7 +204,7 @@ export class CloudService { | |
| } | ||
|
|
||
| private async hardCheckDns() { | ||
| const mothershipGqlUri = this.configService.getOrThrow<string>('MOTHERSHIP_GRAPHQL_LINK'); | ||
| const mothershipGqlUri = this.configService.getOrThrow<string>('MOTHERSHIP_BASE_URL'); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Search for all references to MOTHERSHIP_BASE_URL and MOTHERSHIP_GRAPHQL_LINK
echo "=== MOTHERSHIP_BASE_URL references ==="
rg -n "MOTHERSHIP_BASE_URL" --type ts --type tsx --type js --type jsx
echo -e "\n=== MOTHERSHIP_GRAPHQL_LINK references ==="
rg -n "MOTHERSHIP_GRAPHQL_LINK" --type ts --type tsx --type js --type jsx
echo -e "\n=== Environment configuration files ==="
fd -e env -e env.example --type fRepository: unraid/api Length of output: 250 🏁 Script executed: # Retry search without problematic file types
echo "=== MOTHERSHIP_BASE_URL references ==="
rg -n "MOTHERSHIP_BASE_URL"
echo -e "\n=== MOTHERSHIP_GRAPHQL_LINK references ==="
rg -n "MOTHERSHIP_GRAPHQL_LINK"
echo -e "\n=== Environment files ==="
find . -name ".env*" -type f | grep -v node_modules | head -20Repository: unraid/api Length of output: 1773 🏁 Script executed: # Read the cloud.service.ts file to understand the context
cat -n packages/unraid-api-plugin-connect/src/connection-status/cloud.service.ts | sed -n '90,110p'
echo -e "\n=== Context around line 207 ==="
cat -n packages/unraid-api-plugin-connect/src/connection-status/cloud.service.ts | sed -n '200,220p'
echo -e "\n=== Check environment.ts for MOTHERSHIP_BASE_URL ==="
rg -n "MOTHERSHIP_BASE_URL" api/src/environment.tsRepository: unraid/api Length of output: 2233 🏁 Script executed: # Check environment.ts for configuration definitions
cat -n api/src/environment.ts | sed -n '90,105p'
echo -e "\n=== Full search for MOTHERSHIP_BASE_URL in entire repo ==="
find . -type f \( -name "*.ts" -o -name "*.js" -o -name ".env*" \) ! -path "*/node_modules/*" -exec grep -l "MOTHERSHIP_BASE_URL" {} \;Repository: unraid/api Length of output: 1184 🏁 Script executed: # Check .env.development for MOTHERSHIP_BASE_URL
cat -n api/.env.development | grep -A2 -B2 "MOTHERSHIP"
echo -e "\n=== Full content of .env.development ==="
cat api/.env.developmentRepository: unraid/api Length of output: 2102 🏁 Script executed: # Check other .env files for MOTHERSHIP_BASE_URL
echo "=== .env.staging ==="
grep "MOTHERSHIP" api/.env.staging
echo -e "\n=== .env.production ==="
grep "MOTHERSHIP" api/.env.production
echo -e "\n=== .env.test ==="
grep "MOTHERSHIP" api/.env.test
echo -e "\n=== plugin/.env.example ==="
grep "MOTHERSHIP" plugin/.env.example
echo -e "\n=== Check if MOTHERSHIP_BASE_URL should be in environment.ts ==="
rg -n "export const MOTHERSHIP" api/src/environment.tsRepository: unraid/api Length of output: 417 🏁 Script executed: # Verify configService type and how it accesses config
rg -n "configService.getOrThrow" packages/unraid-api-plugin-connect/src/connection-status/cloud.service.ts | head -5
echo -e "\n=== Check ConfigService implementation ==="
fd -n "config.service" packages/unraid-api-plugin-connect/src --type f
echo -e "\n=== Check if there's a NestJS ConfigModule setup ==="
rg -n "ConfigModule" packages/unraid-api-plugin-connect/src --type ts | head -10Repository: unraid/api Length of output: 1415 Critical: Incomplete refactoring with missing environment configuration for Line 207 uses Furthermore, Required fixes:
🤖 Prompt for AI Agents |
||
| const hostname = new URL(mothershipGqlUri).host; | ||
| const lookup = promisify(lookupDNS); | ||
| const resolve = promisify(resolveDNS); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,162 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Inject, Injectable, Logger } from '@nestjs/common'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { gql } from '@apollo/client/core/index.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { parse, print, visit } from 'graphql'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CANONICAL_INTERNAL_CLIENT_TOKEN, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type CanonicalInternalClientService, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '@unraid/shared'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface GraphQLExecutor { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| execute(params: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| query: string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variables?: Record<string, any> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| operationName?: string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| operationType?: 'query' | 'mutation' | 'subscription' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }): Promise<any> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stopSubscription?(operationId: string): Promise<void> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+18
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Replace The interface uses +interface GraphQLResult<T = Record<string, unknown>> {
+ data?: T;
+ errors?: Array<{
+ message: string;
+ extensions?: Record<string, unknown>;
+ }>;
+}
+
interface GraphQLExecutor {
execute(params: {
query: string
- variables?: Record<string, any>
+ variables?: Record<string, unknown>
operationName?: string
operationType?: 'query' | 'mutation' | 'subscription'
- }): Promise<any>
+ }): Promise<GraphQLResult>
stopSubscription?(operationId: string): Promise<void>
}As per coding guidelines, avoid 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Local GraphQL executor that maps remote queries to local API calls | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Injectable() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class LocalGraphQLExecutor implements GraphQLExecutor { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private readonly logger = new Logger(LocalGraphQLExecutor.name); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Inject(CANONICAL_INTERNAL_CLIENT_TOKEN) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private readonly internalClient: CanonicalInternalClientService, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async execute(params: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| query: string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variables?: Record<string, any> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| operationName?: string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| operationType?: 'query' | 'mutation' | 'subscription' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }): Promise<any> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { query, variables, operationName, operationType } = params; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.logger.debug(`Executing ${operationType} operation: ${operationName || 'unnamed'}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.logger.verbose(`Query: ${query}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.logger.verbose(`Variables: ${JSON.stringify(variables)}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Transform remote query to local query by removing "remote" prefixes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const localQuery = this.transformRemoteQueryToLocal(query); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Execute the transformed query against local API | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const client = await this.internalClient.getClient(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = await client.query({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| query: gql`${localQuery}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variables, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+48
to
+53
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Execute method ignores The method accepts // Execute the transformed query against local API
const client = await this.internalClient.getClient();
- const result = await client.query({
- query: gql`${localQuery}`,
- variables,
- });
+ let result;
+ if (operationType === 'mutation') {
+ result = await client.mutate({
+ mutation: gql`${localQuery}`,
+ variables,
+ });
+ } else {
+ result = await client.query({
+ query: gql`${localQuery}`,
+ variables,
+ });
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: result.data, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error: any) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.logger.error(`GraphQL execution error: ${error?.message}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| errors: [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: error?.message || 'Unknown error', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| extensions: { code: 'EXECUTION_ERROR' }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Transform remote GraphQL query to local query by removing "remote" prefixes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private transformRemoteQueryToLocal(query: string): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Parse the GraphQL query | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const document = parse(query); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Transform the document by removing "remote" prefixes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const transformedDocument = visit(document, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Transform operation names (e.g., remoteGetDockerInfo -> getDockerInfo) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OperationDefinition: (node) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (node.name?.value.startsWith('remote')) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...node, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...node.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: this.removeRemotePrefix(node.name.value), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return node; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Transform field names (e.g., remoteGetDockerInfo -> docker, remoteGetVms -> vms) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Field: (node) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (node.name.value.startsWith('remote')) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...node, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...node.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: this.transformRemoteFieldName(node.name.value), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return node; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Convert back to string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return print(transformedDocument); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.logger.error(`Failed to parse/transform GraphQL query: ${error}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw error; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Remove "remote" prefix from operation names | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private removeRemotePrefix(name: string): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (name.startsWith('remote')) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // remoteGetDockerInfo -> getDockerInfo | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return name.slice(6); // Remove "remote" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return name; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Transform remote field names to local equivalents | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private transformRemoteFieldName(fieldName: string): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle common patterns | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (fieldName === 'remoteGetDockerInfo') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 'docker'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (fieldName === 'remoteGetVms') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 'vms'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (fieldName === 'remoteGetSystemInfo') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 'system'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Generic transformation: remove "remoteGet" and convert to camelCase | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (fieldName.startsWith('remoteGet')) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const baseName = fieldName.slice(9); // Remove "remoteGet" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return baseName.charAt(0).toLowerCase() + baseName.slice(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Remove "remote" prefix as fallback | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (fieldName.startsWith('remote')) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const baseName = fieldName.slice(6); // Remove "remote" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return baseName.charAt(0).toLowerCase() + baseName.slice(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fieldName; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async stopSubscription(operationId: string): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.logger.debug(`Stopping subscription: ${operationId}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Subscription cleanup logic would go here | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add quotes around the value on line 22.
The
.envfile format requires quoted values for consistent parsing by dotenv libraries.Apply this fix:
📝 Committable suggestion
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 22-22: [UnorderedKey] The PATHS_CONNECT_STATUS key should go before the PATHS_CONNECT_STATUS_FILE_PATH key
(UnorderedKey)
[warning] 22-22: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
🤖 Prompt for AI Agents