From a4f69dc539ae9140f3525d44efeb8ee7824cd65d Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Tue, 18 Mar 2025 10:33:09 -0400 Subject: [PATCH] restart when developer sandbox is toggled (#1232) When the sandbox is toggled via api, the api now restarts after a 3 second delay. The Connect settings UI also informs users, when applicable, that the api will restart before and after they apply their settings. ## Summary by CodeRabbit - **New Features** - Improved deployment commands now allow specifying a target server, streamlining the deployment process. - Enhanced settings synchronization provides clear feedback on when a system restart is required after updates. - Automatic service restart is now triggered after applying connection settings changes. - User interface enhancements include added contextual descriptions for toggle controls. - New functionality to refetch connection settings after updates, providing users with the latest information. - **Bug Fixes** - Improved user feedback regarding API restart status after settings updates. --- api/justfile | 8 +++--- .../graph/connect/connect-settings.service.ts | 28 +++++++++++++++---- .../graph/connect/connect.resolver.ts | 18 ++++++++++-- .../graph/connect/connect.service.ts | 15 ++++++++-- unraid-ui/src/forms/Switch.vue | 4 +++ .../ConnectSettings/ConnectSettings.ce.vue | 18 +++++++----- 6 files changed, 69 insertions(+), 22 deletions(-) diff --git a/api/justfile b/api/justfile index e9bc9ef09..6df60e90b 100644 --- a/api/justfile +++ b/api/justfile @@ -9,8 +9,8 @@ default: pnpm run build # deploys to an unraid server -@deploy: - ./scripts/deploy-dev.sh +@deploy remote: + ./scripts/deploy-dev.sh {{remote}} -# build & deploy -bd: build deploy +alias b := build +alias d := deploy diff --git a/api/src/unraid-api/graph/connect/connect-settings.service.ts b/api/src/unraid-api/graph/connect/connect-settings.service.ts index 65a2c191c..16a97edf9 100644 --- a/api/src/unraid-api/graph/connect/connect-settings.service.ts +++ b/api/src/unraid-api/graph/connect/connect-settings.service.ts @@ -61,8 +61,10 @@ export class ConnectSettingsService { /** * Syncs the settings to the store and writes the config to disk * @param settings - The settings to sync + * @returns true if a restart is required, false otherwise */ async syncSettings(settings: Partial) { + let restartRequired = false; const { getters } = await import('@app/store/index.js'); const { nginx } = getters.emhttp(); if (settings.accessType === WAN_ACCESS_TYPE.DISABLED) { @@ -89,10 +91,11 @@ export class ConnectSettingsService { await this.updateAllowedOrigins(settings.extraOrigins); } if (typeof settings.sandbox === 'boolean') { - await this.setSandboxMode(settings.sandbox); + restartRequired ||= await this.setSandboxMode(settings.sandbox); } const { writeConfigSync } = await import('@app/store/sync/config-disk-sync.js'); writeConfigSync('flash'); + return restartRequired; } private async updateAllowedOrigins(origins: string[]) { @@ -100,9 +103,18 @@ export class ConnectSettingsService { store.dispatch(updateAllowedOrigins(origins)); } - private async setSandboxMode(sandbox: boolean) { - const { store } = await import('@app/store/index.js'); - store.dispatch(updateUserConfig({ local: { sandbox: sandbox ? 'yes' : 'no' } })); + /** + * Sets the sandbox mode and returns true if the mode was changed + * @param sandboxEnabled - Whether to enable sandbox mode + * @returns true if the mode was changed, false otherwise + */ + private async setSandboxMode(sandboxEnabled: boolean): Promise { + const { store, getters } = await import('@app/store/index.js'); + const currentSandbox = getters.config().local.sandbox; + const sandbox = sandboxEnabled ? 'yes' : 'no'; + if (currentSandbox === sandbox) return false; + store.dispatch(updateUserConfig({ local: { sandbox } })); + return true; } private async updateRemoteAccess(input: SetupRemoteAccessInput): Promise { @@ -137,7 +149,7 @@ export class ConnectSettingsService { async buildSettingsSchema(): Promise { const slices = [ await this.remoteAccessSlice(), - this.sandboxSlice(), + await this.sandboxSlice(), this.flashBackupSlice(), // Because CORS is effectively disabled, this setting is no longer necessary // keeping it here for in case it needs to be re-enabled @@ -257,7 +269,10 @@ export class ConnectSettingsService { /** * Developer sandbox settings slice */ - sandboxSlice(): SettingSlice { + async sandboxSlice(): Promise { + const { sandbox } = await this.getCurrentSettings(); + const description = + 'The developer sandbox is available at /graphql.'; return { properties: { sandbox: { @@ -273,6 +288,7 @@ export class ConnectSettingsService { label: 'Enable Developer Sandbox:', options: { toggle: true, + description: sandbox ? description : undefined, }, }, ], diff --git a/api/src/unraid-api/graph/connect/connect.resolver.ts b/api/src/unraid-api/graph/connect/connect.resolver.ts index ade8d57ee..76a55b897 100644 --- a/api/src/unraid-api/graph/connect/connect.resolver.ts +++ b/api/src/unraid-api/graph/connect/connect.resolver.ts @@ -16,11 +16,15 @@ import { RemoteAccessController } from '@app/remoteAccess/remote-access-controll import { store } from '@app/store/index.js'; import { setAllowedRemoteAccessUrl } from '@app/store/modules/dynamic-remote-access.js'; import { ConnectSettingsService } from '@app/unraid-api/graph/connect/connect-settings.service.js'; +import { ConnectService } from '@app/unraid-api/graph/connect/connect.service.js'; @Resolver('Connect') export class ConnectResolver implements ConnectResolvers { protected logger = new Logger(ConnectResolver.name); - constructor(private readonly connectSettingsService: ConnectSettingsService) {} + constructor( + private readonly connectSettingsService: ConnectSettingsService, + private readonly connectService: ConnectService + ) {} @Query('connect') @UsePermissions({ @@ -62,8 +66,16 @@ export class ConnectResolver implements ConnectResolvers { }) public async updateApiSettings(@Args('input') settings: ApiSettingsInput) { this.logger.verbose(`Attempting to update API settings: ${JSON.stringify(settings, null, 2)}`); - await this.connectSettingsService.syncSettings(settings); - return this.connectSettingsService.getCurrentSettings(); + const restartRequired = await this.connectSettingsService.syncSettings(settings); + const currentSettings = await this.connectSettingsService.getCurrentSettings(); + if (restartRequired) { + const restartDelayMs = 3_000; + setTimeout(async () => { + this.logger.log('Restarting API'); + await this.connectService.restartApi(); + }, restartDelayMs); + } + return currentSettings; } @ResolveField() diff --git a/api/src/unraid-api/graph/connect/connect.service.ts b/api/src/unraid-api/graph/connect/connect.service.ts index 27c146690..36546d255 100644 --- a/api/src/unraid-api/graph/connect/connect.service.ts +++ b/api/src/unraid-api/graph/connect/connect.service.ts @@ -1,4 +1,15 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; + +import { execa } from 'execa'; @Injectable() -export class ConnectService {} +export class ConnectService { + private logger = new Logger(ConnectService.name); + async restartApi() { + try { + await execa('unraid-api', ['restart'], { shell: 'bash', stdio: 'ignore' }); + } catch (error) { + this.logger.error(error); + } + } +} diff --git a/unraid-ui/src/forms/Switch.vue b/unraid-ui/src/forms/Switch.vue index 44184a688..ab0a47c0e 100644 --- a/unraid-ui/src/forms/Switch.vue +++ b/unraid-ui/src/forms/Switch.vue @@ -1,4 +1,6 @@