Skip to content

Conversation

@yomybaby
Copy link
Member

@yomybaby yomybaby commented Jan 7, 2026

Resolves #4842 (FR-1797)

Summary

Migrate all app launcher related code from Lit-Element (backend-ai-app-launcher.ts) to React. This is part of the NEO: Session - App launcher epic (FR-495).

New Components

  • AppLaunchConfirmationModal: Confirmation dialog for nniboard/mlflow-ui apps that require user acknowledgment before launching
  • TensorboardPathModal: Modal for entering tensorboard log directory path
  • VNCConnectionInfoModal: Connection info modal for VNC apps
  • XRDPConnectionInfoModal: Connection info modal for XRDP apps
  • VSCodeDesktopConnectionModal: Connection info modal for VS Code Desktop
  • TCPConnectionInfoModal: Generic TCP connection info modal for custom TCP apps

Modified Components

  • AppLauncherModal: Enhanced to handle all app types including special apps (tensorboard, nniboard, mlflow-ui, sshd, vnc, xrdp, vscode-desktop)
  • useBackendAIAppLauncher: Major refactoring to implement app launching logic with proper proxy handling (v1/v2), TCP extraction, and notification management
  • SFTPConnectionInfoModal: Minor updates for consistency
  • useBAINotification: Added key-based notification management for better control

Key Features Migrated

  • HTTP/HTTPS app launching with proper proxy version detection
  • TCP app connection (SFTP, VNC, XRDP, VS Code Desktop)
  • Tensorboard with custom log directory support and proxy restart logic
  • nniboard/mlflow-ui confirmation flow
  • Open to public feature with client IP restrictions
  • Preferred port configuration
  • Subdomain support (debug mode)
  • Electron app support with wsproxy

Checklist: (if applicable)

  • Documentation
  • Minium required manager version
  • Specific setting for review (eg., KB link, endpoint or how to setup)
  • Minimum requirements to check during review
  • Test case(s) to demonstrate the difference of before/after

Copilot AI review requested due to automatic review settings January 7, 2026 09:11
@github-actions github-actions bot added the size:XL 500~ LoC label Jan 7, 2026
Copy link
Member Author

yomybaby commented Jan 7, 2026


How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • flow:merge-queue - adds this PR to the back of the merge queue
  • flow:hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has required the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 7, 2026

Coverage report for ./react

St.
Category Percentage Covered / Total
🔴 Statements
4.78% (-0.13% 🔻)
589/12316
🔴 Branches
4.35% (-0.12% 🔻)
377/8662
🔴 Functions
2.71% (-0.04% 🔻)
101/3725
🔴 Lines
4.61% (-0.13% 🔻)
555/12030
Show new covered files 🐣
St.
File Statements Branches Functions Lines
🔴
... / AppLaunchConfirmationModal.tsx
0% 0% 0% 0%
🔴
... / TCPConnectionInfoModal.tsx
0% 100% 0% 0%
🔴
... / TensorboardPathModal.tsx
0% 0% 0% 0%
🔴
... / VNCConnectionInfoModal.tsx
0% 0% 0% 0%
🔴
... / XRDPConnectionInfoModal.tsx
0% 0% 0% 0%
🔴
... / VSCodeDesktopConnectionModal.tsx
0% 0% 0% 0%

Test suite run success

173 tests passing in 13 suites.

Report generated by 🧪jest coverage report action from 6c7e370

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the app launcher functionality from Lit-Element web components to React, replacing the legacy globalThis.appLauncher with a modern React hook-based implementation. The changes introduce a comprehensive useBackendAIAppLauncher hook that handles all app launching logic with full notification integration, progress tracking, and error handling. New modal components are added for various connection types (SFTP, VNC, XRDP, VSCode Desktop, TCP, Tensorboard), making the codebase more maintainable and type-safe.

Key Changes

  • New useBackendAIAppLauncher hook: Centralizes app launch logic with proxy detection, connection management, and notification integration
  • Modal components: Seven new/modified modal components for different app connection types, replacing scattered legacy logic
  • Notification system enhancement: Added generic type parameters for better type safety in promise-based background tasks

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
react/src/hooks/useBackendAIAppLauncher.tsx New comprehensive hook implementing app launch logic with proxy management, error handling, and notification integration
react/src/hooks/useBAINotification.tsx Enhanced with generic type parameters for type-safe background task handling
react/src/hooks/index.tsx Updated BackendAIConfig type with exposed secretKey and connectionMode properties
react/src/index.tsx Removed @ant-design/v5-patch-for-react-19 import (likely no longer needed)
react/src/components/SourceCodeViewer.tsx Added layout props (direction, align) for better flex container behavior
react/src/components/ComputeSessionNodeItems/XRDPConnectionInfoModal.tsx New modal for XRDP connection information display
react/src/components/ComputeSessionNodeItems/VSCodeDesktopConnectionModal.tsx New modal for VSCode Desktop SSH connection with password fetching
react/src/components/ComputeSessionNodeItems/VNCConnectionInfoModal.tsx New modal for VNC connection information display
react/src/components/ComputeSessionNodeItems/TensorboardPathModal.tsx New modal for Tensorboard path configuration before launch
react/src/components/ComputeSessionNodeItems/TCPConnectionInfoModal.tsx New modal for generic TCP app connection information
react/src/components/ComputeSessionNodeItems/SFTPConnectionInfoModal.tsx Enhanced to accept host/port overrides for direct TCP connections
react/src/components/ComputeSessionNodeItems/AppLaunchConfirmationModal.tsx New confirmation modal for apps requiring acknowledgment (nniboard, mlflow-ui)
react/src/components/ComputeSessionNodeItems/AppLauncherModal.tsx Major refactoring to use new hook and modal components instead of globalThis.appLauncher
react/src/components/ComputeSessionNodeItems/SessionActionButtons.tsx Updated runTerminal call signature to accept options object
Comments suppressed due to low confidence (1)

react/src/components/ComputeSessionNodeItems/SFTPConnectionInfoModal.tsx:106

  • The Base64 encoding logic for the SSH key download button appears to be missing from the visible diff. The okButtonProps.onClick handler shows it's handled elsewhere, but the implementation of the actual SSH key generation and download is not shown in the changes.

While this may be outside the scope of this PR, ensure that the SSH key generation and Base64 encoding is implemented correctly to avoid security issues or broken downloads.

      okText={t('session.appLauncher.DownloadSSHKey')}
      onOk={readAndDownloadSSHKey}
    >
      <BAIFlex className={styles.description} direction="column" gap="md">
        <Alert
          showIcon
          type="info"
          title={<Trans i18nKey="session.SFTPDescription" />}
        />
        <Alert
          showIcon
          type="warning"
          title={
            <>
              <Trans i18nKey="session.SFTPExtraNotification" />
              &nbsp;{t('session.SFTPSessionManagementDesc')}
            </>

Comment on lines +317 to +320
if (_.keys(envs).length > 0) {
searchParams.set('envs', JSON.stringify(envs));
}
if (_.keys(args).length > 0) {
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _.keys(envs) check will throw a runtime error if envs is null or undefined. The function signature allows these parameters to be null, but the code doesn't handle this case.

Should add a check: if (envs && _.keys(envs).length > 0) to prevent accessing properties on null/undefined values.

Suggested change
if (_.keys(envs).length > 0) {
searchParams.set('envs', JSON.stringify(envs));
}
if (_.keys(args).length > 0) {
if (envs && _.keys(envs).length > 0) {
searchParams.set('envs', JSON.stringify(envs));
}
if (args && _.keys(args).length > 0) {

Copilot uses AI. Check for mistakes.
size="small"
title={t('session.ConnectionInformation')}
>
<Descriptions.Item label="RDP URL">
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded English text "RDP URL" violates the i18n guideline. All user-facing text must use i18n functions.

This should be replaced with a translation key like t('session.RDPUrl') or similar.

Copilot generated this review using guidance from repository custom instructions.
size="small"
title={t('session.ConnectionInformation')}
>
<Descriptions.Item label="VNC URL">
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded English text "VNC URL" violates the i18n guideline. All user-facing text must use i18n functions.

This should be replaced with a translation key like t('session.VNCUrl') or similar.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +36 to +37
<Descriptions.Item label="Host">{host}</Descriptions.Item>
<Descriptions.Item label="Port">{port}</Descriptions.Item>
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded English text "Host" and "Port" violate the i18n guideline. All user-facing text must use i18n functions.

These should be replaced with translation keys like t('session.Host') and t('session.Port') (which appear to already exist based on usage in SFTPConnectionInfoModal).

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +211 to +216
throw new AppLaunchError(
err instanceof Error
? err.message
: 'Proxy configurator is not responding.',
'configuring',
err instanceof Error ? err : undefined,
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling approach here swallows specific error information. When rethrowing an error that's already an AppLaunchError, the original stack trace and error context is preserved, but when creating a new AppLaunchError, we're only extracting the message and losing other error properties.

Consider preserving more error context, such as:

  • Error name and type information
  • Original stack trace (currently only passed as third parameter)
  • Any custom error properties that might be useful for debugging
Suggested change
throw new AppLaunchError(
err instanceof Error
? err.message
: 'Proxy configurator is not responding.',
'configuring',
err instanceof Error ? err : undefined,
const fallbackMessage = 'Proxy configurator is not responding.';
let wrappedError: Error | undefined;
if (err instanceof Error) {
wrappedError = err;
} else if (err !== null && err !== undefined) {
// Preserve non-Error thrown values as metadata on a synthetic Error
wrappedError = new Error(fallbackMessage);
(wrappedError as any).originalError = err;
}
throw new AppLaunchError(
err instanceof Error ? err.message : fallbackMessage,
'configuring',
wrappedError,

Copilot uses AI. Check for mistakes.
Comment on lines +286 to +290
await handleAppLaunch(app).then(() => {
setOpenToPublic(false);
setTryPreferredPort(false);
// onRequestClose();
});
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment on line 289 says "onRequestClose();" will be removed when apps close properly, but the actual app launch flow shows that the modal should remain open so users can see the notification progress.

The commented-out onRequestClose() at line 289 suggests uncertainty about when to close the modal. Consider clarifying the intended behavior: should the modal close immediately after launching an app, or stay open to show progress?

Suggested change
await handleAppLaunch(app).then(() => {
setOpenToPublic(false);
setTryPreferredPort(false);
// onRequestClose();
});
await handleAppLaunch(app);
setOpenToPublic(false);
setTryPreferredPort(false);

Copilot uses AI. Check for mistakes.
Comment on lines +288 to +296
const servicePortInfo = JSON.parse(session?.service_ports || '{}').find(
({ name }: { name?: string }) => name === app,
);

if (_.isEmpty(servicePortInfo)) {
throw new AppLaunchError(
`Service port not found for app: ${app}`,
'configuring',
);
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the _open_wsproxy function, there's a potential null pointer issue. The function checks if session?.service_ports exists at the start, but then accesses it with JSON.parse(session?.service_ports || '{}').

If session is null/undefined, this will parse an empty object {} which will return an empty array from .find(), and then the subsequent _.isEmpty(servicePortInfo) check will catch it. However, this logic is convoluted and could be clearer.

Consider making the check more explicit: check if session exists first, then check service_ports separately.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +108
const handleSubmit = async () => {
// TODO: use action pattern
const values = await form.validateFields();
const path = values.tensorboardPath || '/home/work/logs';

setIsSubmitting(true);
try {
// 1. Shutdown existing tensorboard service
await baiClient.shutdown_service(session?.row_id, 'tensorboard');

// 2. Close existing wsproxy connection for tensorboard
// This matches legacy behavior: shutdown_service -> _close_wsproxy -> _open_wsproxy
await closeWsproxy('tensorboard');

// 3. Launch new tensorboard with --logdir argument
await launchAppWithNotification({
app: 'tensorboard',
args: { '--logdir': path },
onPrepared(workerInfo) {
// Open tensorboard in new window
if (workerInfo.appConnectUrl) {
window.open(workerInfo.appConnectUrl.href, '_blank');
}
},
});

onRequestClose();
} catch (error) {
logger.error('Failed to launch tensorboard:', error);
} finally {
setIsSubmitting(false);
}
};

return (
<BAIModal
title={t('session.TensorboardPath')}
onCancel={onRequestClose}
footer={null}
{...modalProps}
>
<BAIFlex direction="column" gap="md" align="stretch">
<Typography.Paragraph>
{t('session.InputTensorboardPath')}
</Typography.Paragraph>

<Form form={form} layout="vertical">
<Form.Item name="tensorboardPath">
<Input
placeholder={t('session.DefaultTensorboardPath')}
defaultValue="/home/work/logs"
/>
</Form.Item>
</Form>

<BAIButton
type="primary"
size="large"
loading={isSubmitting}
action={handleSubmit}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commented-out TODO line references "action pattern" but then doesn't use it. The code manually manages isSubmitting state and passes it to the loading prop, while also using the action prop on BAIButton.

According to the React Component Guidelines, BAIButton with the action prop automatically handles loading states during async operations, so the manual isSubmitting state management is redundant. Either use the action pattern exclusively (removing manual loading state), or clarify why both are needed.

Copilot uses AI. Check for mistakes.
@yomybaby yomybaby marked this pull request as ready for review January 7, 2026 09:18
@yomybaby yomybaby marked this pull request as draft January 7, 2026 09:18
@yomybaby yomybaby force-pushed the feat/FR-1797-migrate-app-launcher-to-react branch from aaa8ff2 to f6b48af Compare January 7, 2026 12:47
@yomybaby yomybaby changed the base branch from main to graphite-base/4975 January 8, 2026 06:59
Copilot AI review requested due to automatic review settings January 8, 2026 07:00
@yomybaby yomybaby force-pushed the feat/FR-1797-migrate-app-launcher-to-react branch from f6b48af to ee5735e Compare January 8, 2026 07:00
@yomybaby yomybaby changed the base branch from graphite-base/4975 to copilot/update-e2e-user-info-handling January 8, 2026 07:00
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 14 comments.


const { t } = useTranslation();
const { upsertNotification } = useSetBAINotification();
// const { relieve: painKillerRelieve } = usePainKiller();
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented-out code. If the PainKiller feature is not needed, delete this line. If it might be needed in the future, document why it's commented out or create a task to implement it.

Suggested change
// const { relieve: painKillerRelieve } = usePainKiller();

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +155
throw new AppLaunchError(
'Proxy configurator is not responding.',
'configuring',
);
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These error messages should use i18n translation instead of hard-coded English text. All user-facing strings must use the translation system to support multiple languages.

Copilot generated this review using guidance from repository custom instructions.
if (_.keys(envs).length > 0) {
searchParams.set('envs', JSON.stringify(envs));
}
if (_.keys(args).length > 0) {
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential runtime error: If args is undefined or null, calling _.keys(args) will throw an error. The function parameter should provide a default value (e.g., args = {}) or add a null check before using it.

Suggested change
if (_.keys(args).length > 0) {
if (args && _.keys(args).length > 0) {

Copilot uses AI. Check for mistakes.
import { useCurrentResourceGroupValue } from './hooks/useCurrentProject';
import { ThemeModeProvider } from './hooks/useThemeMode';
import '@ant-design/v5-patch-for-react-19';
import { ConfigProvider, Tag, theme } from 'antd';
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import for '@ant-design/v5-patch-for-react-19' has been removed from index.tsx, but the package is still listed as a dependency in package.json. If this patch is no longer needed, the dependency should also be removed from package.json to avoid unused dependencies. If it's still needed elsewhere, verify all usages are correct.

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +37
<Descriptions.Item label="Host">{host}</Descriptions.Item>
<Descriptions.Item label="Port">{port}</Descriptions.Item>
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The labels "Host" and "Port" should use i18n translation instead of hard-coded English text. All user-facing strings must use the translation system to support multiple languages.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +317 to +318
if (_.keys(envs).length > 0) {
searchParams.set('envs', JSON.stringify(envs));
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential runtime error: If envs is undefined or null, calling _.keys(envs) will throw an error. The function parameter should provide a default value (e.g., envs = {}) or add a null check before using it.

Suggested change
if (_.keys(envs).length > 0) {
searchParams.set('envs', JSON.stringify(envs));
const envsForQuery = envs ?? {};
if (_.keys(envsForQuery).length > 0) {
searchParams.set('envs', JSON.stringify(envsForQuery));

Copilot uses AI. Check for mistakes.
});

if (!response || typeof response === 'boolean') {
throw new AppLaunchError('Failed to configure proxy', 'configuring');
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message should use i18n translation instead of hard-coded English text. All user-facing strings must use the translation system to support multiple languages.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +76 to +80
} catch (error) {
logger.error('Failed to launch tensorboard:', error);
} finally {
setIsSubmitting(false);
}
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error is caught and logged but not shown to the user. Consider using a notification or error message to inform users when tensorboard launch fails, similar to how other operations in the codebase handle errors.

Copilot uses AI. Check for mistakes.
Comment on lines +293 to +296
throw new AppLaunchError(
`Service port not found for app: ${app}`,
'configuring',
);
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message should use i18n translation instead of hard-coded English text. All user-facing strings must use the translation system to support multiple languages.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +922 to +924
throw new Error(
`Request failed: ${e instanceof Error ? e.message : 'Unknown error'}`,
);
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message should use i18n translation instead of hard-coded English text. All user-facing strings must use the translation system to support multiple languages.

Copilot generated this review using guidance from repository custom instructions.
@graphite-app graphite-app bot changed the base branch from copilot/update-e2e-user-info-handling to graphite-base/4975 January 8, 2026 07:24
@graphite-app graphite-app bot force-pushed the feat/FR-1797-migrate-app-launcher-to-react branch from ee5735e to a7accd7 Compare January 8, 2026 07:27
@graphite-app graphite-app bot force-pushed the graphite-base/4975 branch from 51e0a55 to 7957ac8 Compare January 8, 2026 07:27
@graphite-app graphite-app bot changed the base branch from graphite-base/4975 to main January 8, 2026 07:28
@graphite-app graphite-app bot force-pushed the feat/FR-1797-migrate-app-launcher-to-react branch from a7accd7 to 89c8aad Compare January 8, 2026 07:28
@yomybaby yomybaby force-pushed the feat/FR-1797-migrate-app-launcher-to-react branch from 89c8aad to 10cfe9a Compare January 8, 2026 07:58
@cla-assistant
Copy link

cla-assistant bot commented Jan 8, 2026

CLA assistant check
All committers have signed the CLA.

@cla-assistant
Copy link

cla-assistant bot commented Jan 8, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ yomybaby
❌ Copilot
You have signed the CLA already but the status is still pending? Let us recheck it.

@yomybaby yomybaby force-pushed the feat/FR-1797-migrate-app-launcher-to-react branch from 10cfe9a to 6c7e370 Compare January 9, 2026 04:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500~ LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate all app launcher related code to React

2 participants