Skip to content

Commit ec8960b

Browse files
committed
feat: enable sandbox with developer command
1 parent 4302f31 commit ec8960b

File tree

10 files changed

+126
-34
lines changed

10 files changed

+126
-34
lines changed

api/dev/Unraid.net/myservers.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
version="3.11.0"
33
extraOrigins="https://google.com,https://test.com"
44
[local]
5+
sandbox=""
56
[remote]
67
wanaccess="yes"
78
wanport="8443"

api/dev/states/myservers.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
version="3.11.0"
33
extraOrigins="https://google.com,https://test.com"
44
[local]
5+
sandbox=""
56
[remote]
67
wanaccess="yes"
78
wanport="8443"
@@ -19,5 +20,5 @@ dynamicRemoteAccessType="DISABLED"
1920
ssoSubIds=""
2021
allowedOrigins="/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, http://localhost:8080, https://localhost:4443, https://tower.local:4443, https://192.168.1.150:4443, https://tower:4443, https://192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net:4443, https://85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net:8443, https://10-252-0-1.hash.myunraid.net:4443, https://10-252-1-1.hash.myunraid.net:4443, https://10-253-3-1.hash.myunraid.net:4443, https://10-253-4-1.hash.myunraid.net:4443, https://10-253-5-1.hash.myunraid.net:4443, https://10-100-0-1.hash.myunraid.net:4443, https://10-100-0-2.hash.myunraid.net:4443, https://10-123-1-2.hash.myunraid.net:4443, https://221-123-121-112.hash.myunraid.net:4443, https://google.com, https://test.com, https://connect.myunraid.net, https://connect-staging.myunraid.net, https://dev-my.myunraid.net:4000, https://studio.apollographql.com"
2122
[connectionStatus]
22-
minigraph="PRE_INIT"
23+
minigraph="ERROR_RETRYING"
2324
upnpStatus=""

api/src/core/utils/files/config-file-normalizer.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isEqual } from 'lodash-es';
2+
import merge from 'lodash/merge';
23

34
import { getAllowedOrigins } from '@app/common/allowed-origins';
45
import { initialState } from '@app/store/modules/config';
@@ -23,14 +24,10 @@ export const getWriteableConfig = <T extends ConfigType>(
2324

2425
const defaultConfig = schema.parse(initialState);
2526
// Use a type assertion for the mergedConfig to include `connectionStatus` only if `mode === 'memory`
26-
const mergedConfig = {
27-
...defaultConfig,
28-
...config,
29-
remote: {
30-
...defaultConfig.remote,
31-
...config.remote,
32-
},
33-
} as T extends 'memory' ? MyServersConfigMemory : MyServersConfig;
27+
const mergedConfig = merge<
28+
MyServersConfig,
29+
T extends 'memory' ? MyServersConfigMemory : MyServersConfig
30+
>(defaultConfig, config);
3431

3532
if (mode === 'memory') {
3633
(mergedConfig as MyServersConfigMemory).remote.allowedOrigins = getAllowedOrigins().join(', ');

api/src/store/modules/config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ export const initialState: SliceState = {
4949
dynamicRemoteAccessType: DynamicRemoteAccessType.DISABLED,
5050
ssoSubIds: '',
5151
},
52-
local: {},
52+
local: {
53+
sandbox: 'no'
54+
},
5355
api: {
5456
extraOrigins: '',
5557
version: '',

api/src/types/my-servers-config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ const RemoteConfigSchema = z.object({
4141
),
4242
});
4343

44-
const LocalConfigSchema = z.object({});
44+
const LocalConfigSchema = z.object({
45+
sandbox: z.string()
46+
});
4547

4648
// Base config schema
4749
export const MyServersConfigSchema = z

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { RemoveSSOUserQuestionSet } from '@app/unraid-api/cli/sso/remove-sso-use
2020
import { ListSSOUserCommand } from '@app/unraid-api/cli/sso/list-sso-user.command';
2121
import { AddApiKeyQuestionSet } from '@app/unraid-api/cli/apikey/add-api-key.questions';
2222
import { ApiKeyService } from '@app/unraid-api/auth/api-key.service';
23+
import { DeveloperCommand } from '@app/unraid-api/cli/developer/developer.command';
24+
import { DeveloperQuestions } from '@app/unraid-api/cli/developer/developer.questions';
2325

2426
@Module({
2527
providers: [
@@ -43,6 +45,8 @@ import { ApiKeyService } from '@app/unraid-api/auth/api-key.service';
4345
ValidateTokenCommand,
4446
LogsCommand,
4547
ConfigCommand,
48+
DeveloperCommand,
49+
DeveloperQuestions
4650
],
4751
})
4852
export class CliModule {}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Injectable } from '@nestjs/common';
2+
3+
import { Command, CommandRunner, InquirerService } from 'nest-commander';
4+
5+
import { loadConfigFile, updateUserConfig } from '@app/store/modules/config';
6+
import { writeConfigSync } from '@app/store/sync/config-disk-sync';
7+
import { DeveloperQuestions } from '@app/unraid-api/cli/developer/developer.questions';
8+
import { LogService } from '@app/unraid-api/cli/log.service';
9+
10+
interface DeveloperOptions {
11+
disclaimer: boolean;
12+
sandbox: boolean;
13+
}
14+
@Injectable()
15+
@Command({
16+
name: 'developer',
17+
description: 'Configure Developer Features for the API',
18+
})
19+
export class DeveloperCommand extends CommandRunner {
20+
constructor(
21+
private logger: LogService,
22+
private readonly inquirerService: InquirerService
23+
) {
24+
super();
25+
}
26+
async run(_, options?: DeveloperOptions): Promise<void> {
27+
options = await this.inquirerService.prompt(DeveloperQuestions.name, options);
28+
if (!options.disclaimer) {
29+
this.logger.warn('No changes made, disclaimer not accepted.');
30+
process.exit(1);
31+
}
32+
const { store } = await import('@app/store');
33+
await store.dispatch(loadConfigFile());
34+
store.dispatch(updateUserConfig({ local: { sandbox: options.sandbox ? 'yes' : 'no' } }));
35+
console.log(store.getState().config.local.sandbox);
36+
writeConfigSync('flash');
37+
38+
this.logger.info('Updated Developer Configuration');
39+
}
40+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Question, QuestionSet } from 'nest-commander';
2+
3+
@QuestionSet({ name: 'developer' })
4+
export class DeveloperQuestions {
5+
static name = 'developer';
6+
7+
@Question({
8+
message: `Are you sure you wish to enable developer mode?
9+
Currently this allows enabling the GraphQL sandbox on SERVER_URL/graphql.
10+
`,
11+
type: 'confirm',
12+
name: 'disclaimer',
13+
})
14+
parseDisclaimer(val: boolean) {
15+
return val;
16+
}
17+
18+
@Question({
19+
message: 'Do you wish to enable the sandbox?',
20+
type: 'confirm',
21+
name: 'sandbox',
22+
})
23+
parseSandbox(val: boolean) {
24+
return val;
25+
}
26+
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
UUIDResolver,
1313
} from 'graphql-scalars';
1414

15-
import { GRAPHQL_INTROSPECTION } from '@app/environment';
1615
import { GraphQLLong } from '@app/graphql/resolvers/graphql-type-long';
1716
import { typeDefs } from '@app/graphql/schema/index';
1817
import { idPrefixPlugin } from '@app/unraid-api/graph/id-prefix-plugin';
@@ -24,20 +23,21 @@ import { ResolversModule } from './resolvers/resolvers.module';
2423
import { sandboxPlugin } from './sandbox-plugin';
2524
import { ServicesResolver } from './services/services.resolver';
2625
import { SharesResolver } from './shares/shares.resolver';
26+
import { getters } from '@app/store/index';
2727

2828
@Module({
2929
imports: [
3030
ResolversModule,
3131
GraphQLModule.forRoot<ApolloDriverConfig>({
3232
driver: ApolloDriver,
33-
introspection: GRAPHQL_INTROSPECTION ? true : false,
33+
introspection: getters.config().local?.sandbox === 'yes' ? true : false,
34+
playground: false,
3435
context: ({ req, connectionParams, extra }) => ({
3536
req,
3637
connectionParams,
3738
extra,
3839
}),
39-
playground: false,
40-
plugins: GRAPHQL_INTROSPECTION ? [sandboxPlugin, idPrefixPlugin] : [idPrefixPlugin],
40+
plugins: [sandboxPlugin, idPrefixPlugin],
4141
subscriptions: {
4242
'graphql-ws': {
4343
path: '/graphql',

api/src/unraid-api/graph/sandbox-plugin.ts

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,43 @@ const preconditionFailed = (preconditionName: string) => {
2828
throw new HttpException(`Precondition failed: ${preconditionName} `, HttpStatus.PRECONDITION_FAILED);
2929
};
3030

31+
export const getPluginBasedOnSandbox = async (sandbox: boolean, csrfToken: string) => {
32+
if (sandbox) {
33+
const { ApolloServerPluginLandingPageLocalDefault } = await import(
34+
'@apollo/server/plugin/landingPage/default'
35+
);
36+
const plugin = ApolloServerPluginLandingPageLocalDefault({
37+
footer: false,
38+
includeCookies: true,
39+
document: initialDocument,
40+
embed: {
41+
initialState: {
42+
sharedHeaders: {
43+
'x-csrf-token': csrfToken,
44+
},
45+
},
46+
},
47+
});
48+
return plugin;
49+
} else {
50+
const { ApolloServerPluginLandingPageProductionDefault } = await import(
51+
'@apollo/server/plugin/landingPage/default'
52+
);
53+
54+
const plugin = ApolloServerPluginLandingPageProductionDefault({
55+
footer: false
56+
});
57+
return plugin;
58+
}
59+
};
60+
3161
/**
3262
* Renders the sandbox page for the GraphQL server with Apollo Server landing page configuration.
33-
*
63+
*
3464
* @param service - The GraphQL server context object
3565
* @returns Promise that resolves to an Apollo `LandingPage`, or throws a precondition failed error
3666
* @throws {Error} When downstream plugin components from apollo are unavailable. This should never happen.
37-
*
67+
*
3868
* @remarks
3969
* This function configures and renders the Apollo Server landing page with:
4070
* - Disabled footer
@@ -44,33 +74,22 @@ const preconditionFailed = (preconditionName: string) => {
4474
*/
4575
async function renderSandboxPage(service: GraphQLServerContext) {
4676
const { getters } = await import('@app/store');
47-
const { ApolloServerPluginLandingPageLocalDefault } = await import(
48-
'@apollo/server/plugin/landingPage/default'
49-
);
50-
const plugin = ApolloServerPluginLandingPageLocalDefault({
51-
footer: false,
52-
includeCookies: true,
53-
document: initialDocument,
54-
embed: {
55-
initialState: {
56-
sharedHeaders: {
57-
'x-csrf-token': getters.emhttp().var.csrfToken,
58-
},
59-
},
60-
},
61-
});
77+
const sandbox = getters.config().local.sandbox === 'yes';
78+
const csrfToken = getters.emhttp().var.csrfToken;
79+
const plugin = await getPluginBasedOnSandbox(sandbox, csrfToken);
80+
6281
if (!plugin.serverWillStart) return preconditionFailed('serverWillStart');
6382
const serverListener = await plugin.serverWillStart(service);
6483

6584
if (!serverListener) return preconditionFailed('serverListener');
6685
if (!serverListener.renderLandingPage) return preconditionFailed('renderLandingPage');
67-
86+
6887
return serverListener.renderLandingPage();
6988
}
7089

7190
/**
7291
* Apollo plugin to render the GraphQL Sandbox page on-demand based on current server state.
73-
*
92+
*
7493
* Usually, the `ApolloServerPluginLandingPageLocalDefault` plugin configures its
7594
* parameters once, during server startup. This plugin defers the configuration
7695
* and rendering to request-time instead of server startup.

0 commit comments

Comments
 (0)