mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(projects): stop offering a project on a paired server's SSH host, and say where to add it (#29919)
Since #27609 a server serves no SSH target and refuses repo.addRemote, yet the add-project dialog still offered "Project on this server's SSH host" and the refusal read "SSH connection not found or not connected". The row is now disabled with a message pointing to adding the SSH host on this computer, and the server's refusal says the same for older clients that still offer it.
This commit is contained in:
@@ -635,7 +635,7 @@ describe('OrcaRuntimeService', () => {
|
||||
.catch((error: unknown) => error)
|
||||
|
||||
expect(() => runtime.addSshRepo({ connectionId: 'openclaw' })).toThrow(
|
||||
'SSH connection "openclaw" not found or not connected'
|
||||
"This Orca server can't add projects on its SSH hosts."
|
||||
)
|
||||
|
||||
// Why: the defect was a silent local clone/probe recorded as remote, not a bad message,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { RuntimeProjectHostSetupController } from './runtime-project-host-setup-controller'
|
||||
import {
|
||||
RuntimeProjectHostSetupController,
|
||||
SERVER_SSH_PROJECT_ADD_UNAVAILABLE_MESSAGE
|
||||
} from './runtime-project-host-setup-controller'
|
||||
import { getProjectHostSetupForRepo } from '../../shared/project-host-setup-lookup'
|
||||
import { projectHostSetupProjectionFromRepos } from '../../shared/project-host-setup-projection'
|
||||
import type { Repo } from '../../shared/repo-types'
|
||||
@@ -78,11 +81,11 @@ describe('RuntimeProjectHostSetupController host routing', () => {
|
||||
expect(addRepo).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps repo.addRemote failing with the not-connected text', () => {
|
||||
it('refuses repo.addRemote by saying where the project can be added instead', () => {
|
||||
const { controller, addRepo } = makeController()
|
||||
|
||||
expect(() => controller.addSshRepo({ connectionId: TARGET_ID })).toThrow(
|
||||
`SSH connection "${TARGET_ID}" not found or not connected`
|
||||
SERVER_SSH_PROJECT_ADD_UNAVAILABLE_MESSAGE
|
||||
)
|
||||
expect(addRepo).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -63,6 +63,9 @@ function assertCloneHostIsSupported(hostId: ExecutionHostId | null | undefined):
|
||||
)
|
||||
}
|
||||
|
||||
export const SERVER_SSH_PROJECT_ADD_UNAVAILABLE_MESSAGE =
|
||||
"This Orca server can't add projects on its SSH hosts. Add the SSH host on your computer to open its projects."
|
||||
|
||||
function sshTargetNotServedError(connectionId: string): SshHostNotServedError {
|
||||
return new SshHostNotServedError(connectionId, sshTargetNotConnectedMessage(connectionId))
|
||||
}
|
||||
@@ -124,12 +127,15 @@ export class RuntimeProjectHostSetupController {
|
||||
return this.completeSetup(args, repo, !knownRepoIds.has(repo.id))
|
||||
}
|
||||
|
||||
/** `repo.addRemote` stays registered for older clients; this process serves no SSH target. */
|
||||
/**
|
||||
* `repo.addRemote` stays registered for older clients, which still offer it; this process serves
|
||||
* no SSH target, so the refusal says where such a project can be added instead.
|
||||
*/
|
||||
addSshRepo(args: { connectionId: string }): Promise<Repo> {
|
||||
if (!this.deps.getStore()) {
|
||||
throw new Error('runtime_unavailable')
|
||||
}
|
||||
throw sshTargetNotServedError(args.connectionId)
|
||||
throw new SshHostNotServedError(args.connectionId, SERVER_SSH_PROJECT_ADD_UNAVAILABLE_MESSAGE)
|
||||
}
|
||||
|
||||
async setupClone(args: ProjectHostSetupCloneArgs): Promise<ProjectHostSetupResult> {
|
||||
|
||||
@@ -156,7 +156,7 @@ export function AddRepoDialogStepContent({
|
||||
hostSelector={hostSelector}
|
||||
actionsDisabled={actionsDisabled}
|
||||
browseHostKind={browseHostKind}
|
||||
serverSshProjects={serverSsh?.state}
|
||||
offersServerSshHosts={serverSsh !== null}
|
||||
onBrowse={onBrowse}
|
||||
onOpenCloneStep={onOpenCloneStep}
|
||||
onOpenRemoteStep={onOpenRemoteStep}
|
||||
|
||||
@@ -140,31 +140,26 @@ describe('AddRepoLocalStartStep', () => {
|
||||
expect(model.description).toBe('Existing Git repository or folder on this host')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['supported', false, 'Open a project folder from an SSH host this server connects to'],
|
||||
['unsupported', true, 'Update this server to add projects on its SSH hosts'],
|
||||
['unknown', true, 'Open a project folder from an SSH host this server connects to']
|
||||
] as const)(
|
||||
"offers a server's own SSH hosts only when the server supports it (%s)",
|
||||
(serverSshProjects, disabled, description) => {
|
||||
const { secondaryActions } = getAddRepoLocalStartActions({
|
||||
browseHostKind: 'runtime',
|
||||
serverSshProjects,
|
||||
onBrowse: vi.fn(),
|
||||
onOpenCloneStep: vi.fn(),
|
||||
onOpenRemoteStep: vi.fn(),
|
||||
onOpenCreateStep: vi.fn()
|
||||
})
|
||||
const remote = secondaryActions.find((action) => action.kind === 'remote')
|
||||
it("explains instead of offering a project on a paired server's SSH host", () => {
|
||||
const { secondaryActions } = getAddRepoLocalStartActions({
|
||||
browseHostKind: 'runtime',
|
||||
offersServerSshHosts: true,
|
||||
onBrowse: vi.fn(),
|
||||
onOpenCloneStep: vi.fn(),
|
||||
onOpenRemoteStep: vi.fn(),
|
||||
onOpenCreateStep: vi.fn()
|
||||
})
|
||||
const remote = secondaryActions.find((action) => action.kind === 'remote')
|
||||
|
||||
expect(secondaryActions.map((action) => action.kind)).toEqual(['clone', 'remote', 'create'])
|
||||
expect(remote).toMatchObject({
|
||||
title: "Project on this server's SSH host",
|
||||
description,
|
||||
disabled
|
||||
})
|
||||
}
|
||||
)
|
||||
expect(secondaryActions.map((action) => action.kind)).toEqual(['clone', 'remote', 'create'])
|
||||
// Why: the server refuses repo.addRemote, so the row must never open the step that calls it.
|
||||
expect(remote).toMatchObject({
|
||||
title: "Project on this server's SSH host",
|
||||
description:
|
||||
'Not available from a server. Add the SSH host on this computer to open its projects.',
|
||||
disabled: true
|
||||
})
|
||||
})
|
||||
|
||||
it('focuses Browse folder when the default Add Project step opens', async () => {
|
||||
const { container, root } = await renderLocalStartStepDom()
|
||||
|
||||
@@ -68,7 +68,7 @@ type AddRepoLocalStartStepProps = {
|
||||
hostSelector?: ReactNode
|
||||
actionsDisabled?: boolean
|
||||
browseHostKind?: 'local' | 'runtime'
|
||||
serverSshProjects?: 'supported' | 'unsupported' | 'unknown'
|
||||
offersServerSshHosts?: boolean
|
||||
onBrowse: () => void
|
||||
onOpenCloneStep: () => void
|
||||
onOpenRemoteStep: () => void
|
||||
@@ -85,7 +85,7 @@ export function AddRepoLocalStartStep({
|
||||
hostSelector,
|
||||
actionsDisabled = false,
|
||||
browseHostKind = 'local',
|
||||
serverSshProjects,
|
||||
offersServerSshHosts,
|
||||
onBrowse,
|
||||
onOpenCloneStep,
|
||||
onOpenRemoteStep,
|
||||
@@ -101,7 +101,7 @@ export function AddRepoLocalStartStep({
|
||||
onOpenRemoteStep,
|
||||
onOpenCreateStep,
|
||||
browseHostKind,
|
||||
serverSshProjects
|
||||
offersServerSshHosts
|
||||
})
|
||||
|
||||
// The white fill + ⏎ chip is a roving selection indicator, not a fixed "primary" badge:
|
||||
|
||||
@@ -8,8 +8,8 @@ export type AddRepoLocalStartActionHandlers = {
|
||||
onOpenRemoteStep: () => void
|
||||
onOpenCreateStep: () => void
|
||||
browseHostKind?: 'local' | 'runtime'
|
||||
/** A paired server's own SSH hosts; `unsupported` when it predates managing them. */
|
||||
serverSshProjects?: 'supported' | 'unsupported' | 'unknown'
|
||||
/** A paired server is selected, whose own SSH hosts the dialog explains instead of offering. */
|
||||
offersServerSshHosts?: boolean
|
||||
}
|
||||
|
||||
export type AddRepoLocalStartAction = {
|
||||
@@ -27,7 +27,7 @@ export function getAddRepoLocalStartActions({
|
||||
onOpenRemoteStep,
|
||||
onOpenCreateStep,
|
||||
browseHostKind = 'local',
|
||||
serverSshProjects
|
||||
offersServerSshHosts = false
|
||||
}: AddRepoLocalStartActionHandlers): {
|
||||
primaryAction: AddRepoLocalStartAction
|
||||
secondaryActions: AddRepoLocalStartAction[]
|
||||
@@ -52,7 +52,9 @@ export function getAddRepoLocalStartActions({
|
||||
onClick: onBrowse
|
||||
}
|
||||
|
||||
const remote = serverSshProjects
|
||||
// Why disabled: a server registers no project on its SSH hosts (each runs its own managed
|
||||
// server), so the row only says where that project can be added instead.
|
||||
const remote = offersServerSshHosts
|
||||
? {
|
||||
kind: 'remote' as const,
|
||||
icon: Monitor,
|
||||
@@ -60,18 +62,11 @@ export function getAddRepoLocalStartActions({
|
||||
'auto.components.sidebar.add.repo.local.start.actions.serverSshTitle',
|
||||
"Project on this server's SSH host"
|
||||
),
|
||||
description:
|
||||
serverSshProjects === 'unsupported'
|
||||
? translate(
|
||||
'auto.components.sidebar.add.repo.local.start.actions.serverSshUpdate',
|
||||
'Update this server to add projects on its SSH hosts'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.sidebar.add.repo.local.start.actions.serverSshDescription',
|
||||
'Open a project folder from an SSH host this server connects to'
|
||||
),
|
||||
// Why: an old server has no way to do this, and this client must not do it locally.
|
||||
disabled: serverSshProjects !== 'supported',
|
||||
description: translate(
|
||||
'auto.components.sidebar.add.repo.local.start.actions.serverSshAddOnThisComputer',
|
||||
'Not available from a server. Add the SSH host on this computer to open its projects.'
|
||||
),
|
||||
disabled: true,
|
||||
onClick: onOpenRemoteStep
|
||||
}
|
||||
: null
|
||||
|
||||
@@ -6227,8 +6227,7 @@
|
||||
"7edb8ebe24": "Clone from URL",
|
||||
"a6c20dca96": "Open a project from an SSH target",
|
||||
"serverSshTitle": "Project on this server's SSH host",
|
||||
"serverSshDescription": "Open a project folder from an SSH host this server connects to",
|
||||
"serverSshUpdate": "Update this server to add projects on its SSH hosts",
|
||||
"serverSshAddOnThisComputer": "Not available from a server. Add the SSH host on this computer to open its projects.",
|
||||
"fb4fc5380e": "Local project, Git repo, or folder with many repos",
|
||||
"2281fdc8c7": "Browse folder",
|
||||
"runtimeBrowseDescription": "Existing Git repository or folder on this host"
|
||||
|
||||
@@ -5990,8 +5990,7 @@
|
||||
"7edb8ebe24": "Clonar desde URL",
|
||||
"a6c20dca96": "Abrir un proyecto desde un destino SSH",
|
||||
"serverSshTitle": "Proyecto en un host SSH de este servidor",
|
||||
"serverSshDescription": "Abre una carpeta de proyecto de un host SSH al que se conecta este servidor",
|
||||
"serverSshUpdate": "Actualiza este servidor para añadir proyectos en sus hosts SSH",
|
||||
"serverSshAddOnThisComputer": "No disponible desde un servidor. Añade el host SSH en este ordenador para abrir sus proyectos.",
|
||||
"fb4fc5380e": "Proyecto local, repositorio de Git o carpeta con muchos repositorios",
|
||||
"2281fdc8c7": "Explorar carpeta",
|
||||
"runtimeBrowseDescription": "Repositorio Git o carpeta existente en este host"
|
||||
|
||||
@@ -6103,8 +6103,7 @@
|
||||
"7edb8ebe24": "Cloner depuis une URL",
|
||||
"a6c20dca96": "Ouvrir un projet depuis une cible SSH",
|
||||
"serverSshTitle": "Projet sur un hôte SSH de ce serveur",
|
||||
"serverSshDescription": "Ouvrir un dossier de projet depuis un hôte SSH auquel ce serveur se connecte",
|
||||
"serverSshUpdate": "Mettez ce serveur à jour pour ajouter des projets sur ses hôtes SSH",
|
||||
"serverSshAddOnThisComputer": "Indisponible depuis un serveur. Ajoutez l’hôte SSH sur cet ordinateur pour ouvrir ses projets.",
|
||||
"fb4fc5380e": "Projet local, dépôt Git ou dossier contenant plusieurs dépôts",
|
||||
"2281fdc8c7": "Parcourir le dossier",
|
||||
"runtimeBrowseDescription": "Dépôt Git existant ou dossier sur cette machine"
|
||||
|
||||
@@ -5975,8 +5975,7 @@
|
||||
"7edb8ebe24": "URL からクローン",
|
||||
"a6c20dca96": "SSH ターゲットからプロジェクトを開く",
|
||||
"serverSshTitle": "このサーバーの SSH ホスト上のプロジェクト",
|
||||
"serverSshDescription": "このサーバーが接続する SSH ホストのプロジェクトフォルダーを開きます",
|
||||
"serverSshUpdate": "SSH ホストにプロジェクトを追加するには、このサーバーを更新してください",
|
||||
"serverSshAddOnThisComputer": "サーバーからは利用できません。プロジェクトを開くには、このコンピューターに SSH ホストを追加してください。",
|
||||
"fb4fc5380e": "ローカルプロジェクト、Git リポジトリ、または多数のリポジトリを含むフォルダー",
|
||||
"2281fdc8c7": "フォルダーを参照",
|
||||
"runtimeBrowseDescription": "このホスト上の既存の Git リポジトリまたはフォルダー"
|
||||
|
||||
@@ -5975,8 +5975,7 @@
|
||||
"7edb8ebe24": "URL에서 복제",
|
||||
"a6c20dca96": "SSH 대상에서 프로젝트 열기",
|
||||
"serverSshTitle": "이 서버의 SSH 호스트에 있는 프로젝트",
|
||||
"serverSshDescription": "이 서버가 연결하는 SSH 호스트의 프로젝트 폴더를 엽니다",
|
||||
"serverSshUpdate": "SSH 호스트에 프로젝트를 추가하려면 이 서버를 업데이트하세요",
|
||||
"serverSshAddOnThisComputer": "서버에서는 사용할 수 없습니다. 프로젝트를 열려면 이 컴퓨터에 SSH 호스트를 추가하세요.",
|
||||
"fb4fc5380e": "로컬 프로젝트, Git repo 또는 repos가 많은 폴더",
|
||||
"2281fdc8c7": "폴더 찾아보기",
|
||||
"runtimeBrowseDescription": "이 호스트의 기존 Git 리포지토리 또는 폴더"
|
||||
|
||||
@@ -5975,8 +5975,7 @@
|
||||
"7edb8ebe24": "从 URL 克隆",
|
||||
"a6c20dca96": "从 SSH 目标打开项目",
|
||||
"serverSshTitle": "此服务器 SSH 主机上的项目",
|
||||
"serverSshDescription": "从此服务器连接的 SSH 主机打开项目文件夹",
|
||||
"serverSshUpdate": "更新此服务器后才能在其 SSH 主机上添加项目",
|
||||
"serverSshAddOnThisComputer": "无法从服务器使用。请在此电脑上添加该 SSH 主机以打开其项目。",
|
||||
"fb4fc5380e": "本地项目、Git 仓库或包含多个仓库的文件夹",
|
||||
"2281fdc8c7": "浏览文件夹",
|
||||
"runtimeBrowseDescription": "此主机上的现有 Git 仓库或文件夹"
|
||||
|
||||
Reference in New Issue
Block a user