@celljs/ta-web 4.6.1 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +90 -0
- package/README.md +12 -6
- package/esm/browser/chat/chat-view.js +1 -1
- package/esm/browser/chat/chat-view.js.map +1 -1
- package/esm/browser/components/account-menu.d.ts +3 -2
- package/esm/browser/components/account-menu.js +21 -16
- package/esm/browser/components/account-menu.js.map +1 -1
- package/esm/browser/components/app-view.js +3 -1
- package/esm/browser/components/app-view.js.map +1 -1
- package/esm/browser/components/chat-list.d.ts +3 -1
- package/esm/browser/components/chat-list.js +6 -1
- package/esm/browser/components/chat-list.js.map +1 -1
- package/esm/browser/components/folder-picker.d.ts +37 -6
- package/esm/browser/components/folder-picker.js +155 -33
- package/esm/browser/components/folder-picker.js.map +1 -1
- package/esm/browser/components/remote-hosts.d.ts +7 -0
- package/esm/browser/components/remote-hosts.js +94 -0
- package/esm/browser/components/remote-hosts.js.map +1 -0
- package/esm/browser/components/remote-prompts.d.ts +7 -0
- package/esm/browser/components/remote-prompts.js +72 -0
- package/esm/browser/components/remote-prompts.js.map +1 -0
- package/esm/browser/components/settings-view.js +8 -1
- package/esm/browser/components/settings-view.js.map +1 -1
- package/esm/browser/components/web-composer.d.ts +4 -1
- package/esm/browser/components/web-composer.js +2 -2
- package/esm/browser/components/web-composer.js.map +1 -1
- package/esm/browser/components/working-directory.js +1 -1
- package/esm/browser/components/working-directory.js.map +1 -1
- package/esm/browser/extension/icons.js +3 -1
- package/esm/browser/extension/icons.js.map +1 -1
- package/esm/browser/extension/web-extension-api.d.ts +4 -2
- package/esm/browser/extension/web-extensions.js +11 -1
- package/esm/browser/extension/web-extensions.js.map +1 -1
- package/esm/browser/i18n/locales.d.ts +32 -1
- package/esm/browser/i18n/messages.d.ts +31 -0
- package/esm/browser/i18n/messages.js +75 -13
- package/esm/browser/i18n/messages.js.map +1 -1
- package/esm/browser/style/base.d.ts +1 -1
- package/esm/browser/style/base.js +6 -1
- package/esm/browser/style/base.js.map +1 -1
- package/esm/browser/style/layout.d.ts +1 -1
- package/esm/browser/style/layout.js +48 -15
- package/esm/browser/style/layout.js.map +1 -1
- package/esm/browser/util/message.d.ts +1 -0
- package/esm/browser/util/message.js +7 -5
- package/esm/browser/util/message.js.map +1 -1
- package/esm/browser/workbench-protocol.d.ts +7 -1
- package/esm/browser/workbench-protocol.js.map +1 -1
- package/esm/browser/workbench.d.ts +7 -1
- package/esm/browser/workbench.js +23 -0
- package/esm/browser/workbench.js.map +1 -1
- package/lib/browser/chat/chat-view.js +1 -1
- package/lib/browser/chat/chat-view.js.map +1 -1
- package/lib/browser/components/account-menu.d.ts +3 -2
- package/lib/browser/components/account-menu.d.ts.map +1 -1
- package/lib/browser/components/account-menu.js +21 -16
- package/lib/browser/components/account-menu.js.map +1 -1
- package/lib/browser/components/app-view.d.ts.map +1 -1
- package/lib/browser/components/app-view.js +3 -1
- package/lib/browser/components/app-view.js.map +1 -1
- package/lib/browser/components/chat-list.d.ts +3 -1
- package/lib/browser/components/chat-list.d.ts.map +1 -1
- package/lib/browser/components/chat-list.js +6 -1
- package/lib/browser/components/chat-list.js.map +1 -1
- package/lib/browser/components/folder-picker.d.ts +37 -6
- package/lib/browser/components/folder-picker.d.ts.map +1 -1
- package/lib/browser/components/folder-picker.js +156 -33
- package/lib/browser/components/folder-picker.js.map +1 -1
- package/lib/browser/components/folder-picker.spec.js +15 -0
- package/lib/browser/components/folder-picker.spec.js.map +1 -1
- package/lib/browser/components/remote-hosts.d.ts +8 -0
- package/lib/browser/components/remote-hosts.d.ts.map +1 -0
- package/lib/browser/components/remote-hosts.js +130 -0
- package/lib/browser/components/remote-hosts.js.map +1 -0
- package/lib/browser/components/remote-prompts.d.ts +8 -0
- package/lib/browser/components/remote-prompts.d.ts.map +1 -0
- package/lib/browser/components/remote-prompts.js +108 -0
- package/lib/browser/components/remote-prompts.js.map +1 -0
- package/lib/browser/components/settings-view.d.ts.map +1 -1
- package/lib/browser/components/settings-view.js +7 -0
- package/lib/browser/components/settings-view.js.map +1 -1
- package/lib/browser/components/web-composer.d.ts +4 -1
- package/lib/browser/components/web-composer.d.ts.map +1 -1
- package/lib/browser/components/web-composer.js +2 -2
- package/lib/browser/components/web-composer.js.map +1 -1
- package/lib/browser/components/working-directory.js +1 -1
- package/lib/browser/components/working-directory.js.map +1 -1
- package/lib/browser/extension/icons.d.ts.map +1 -1
- package/lib/browser/extension/icons.js +2 -0
- package/lib/browser/extension/icons.js.map +1 -1
- package/lib/browser/extension/web-extension-api.d.ts +4 -2
- package/lib/browser/extension/web-extension-api.d.ts.map +1 -1
- package/lib/browser/extension/web-extensions.d.ts.map +1 -1
- package/lib/browser/extension/web-extensions.js +11 -1
- package/lib/browser/extension/web-extensions.js.map +1 -1
- package/lib/browser/i18n/locales.d.ts +32 -1
- package/lib/browser/i18n/locales.d.ts.map +1 -1
- package/lib/browser/i18n/messages.d.ts +31 -0
- package/lib/browser/i18n/messages.d.ts.map +1 -1
- package/lib/browser/i18n/messages.js +75 -13
- package/lib/browser/i18n/messages.js.map +1 -1
- package/lib/browser/notices/notices.spec.js +1 -1
- package/lib/browser/notices/notices.spec.js.map +1 -1
- package/lib/browser/style/base.d.ts +1 -1
- package/lib/browser/style/base.d.ts.map +1 -1
- package/lib/browser/style/base.js +6 -1
- package/lib/browser/style/base.js.map +1 -1
- package/lib/browser/style/layout.d.ts +1 -1
- package/lib/browser/style/layout.d.ts.map +1 -1
- package/lib/browser/style/layout.js +48 -15
- package/lib/browser/style/layout.js.map +1 -1
- package/lib/browser/util/message.d.ts +1 -0
- package/lib/browser/util/message.d.ts.map +1 -1
- package/lib/browser/util/message.js +7 -5
- package/lib/browser/util/message.js.map +1 -1
- package/lib/browser/util/message.spec.js +2 -0
- package/lib/browser/util/message.spec.js.map +1 -1
- package/lib/browser/workbench-protocol.d.ts +7 -1
- package/lib/browser/workbench-protocol.d.ts.map +1 -1
- package/lib/browser/workbench-protocol.js.map +1 -1
- package/lib/browser/workbench.d.ts +7 -1
- package/lib/browser/workbench.d.ts.map +1 -1
- package/lib/browser/workbench.js +23 -0
- package/lib/browser/workbench.js.map +1 -1
- package/package.json +7 -7
- package/src/browser/chat/chat-view.tsx +1 -1
- package/src/browser/components/account-menu.tsx +23 -20
- package/src/browser/components/app-view.tsx +2 -0
- package/src/browser/components/chat-list.tsx +9 -2
- package/src/browser/components/folder-picker.spec.ts +17 -0
- package/src/browser/components/folder-picker.tsx +221 -39
- package/src/browser/components/remote-hosts.tsx +149 -0
- package/src/browser/components/remote-prompts.tsx +111 -0
- package/src/browser/components/settings-view.tsx +8 -0
- package/src/browser/components/web-composer.tsx +17 -3
- package/src/browser/components/working-directory.tsx +1 -1
- package/src/browser/extension/icons.ts +4 -0
- package/src/browser/extension/web-extension-api.ts +4 -2
- package/src/browser/extension/web-extensions.ts +12 -2
- package/src/browser/i18n/messages.ts +75 -13
- package/src/browser/notices/notices.spec.ts +1 -1
- package/src/browser/style/base.ts +6 -1
- package/src/browser/style/layout.ts +48 -15
- package/src/browser/util/message.spec.ts +2 -0
- package/src/browser/util/message.ts +7 -4
- package/src/browser/workbench-protocol.ts +8 -0
- package/src/browser/workbench.ts +29 -0
|
@@ -5,7 +5,7 @@ import { useT } from '../i18n/use-t';
|
|
|
5
5
|
import type { TaNotices } from '../notices/notices-protocol';
|
|
6
6
|
import { ComposerUtil } from '../util/composer';
|
|
7
7
|
import { ComposerBox } from './composer-box';
|
|
8
|
-
import { DirectoryButton } from './folder-picker';
|
|
8
|
+
import { DirectoryButton, type FolderPickerRemote } from './folder-picker';
|
|
9
9
|
|
|
10
10
|
export interface WebComposerHostProps extends WebComposerProps {
|
|
11
11
|
/** Where a failure to send goes. */
|
|
@@ -14,10 +14,24 @@ export interface WebComposerHostProps extends WebComposerProps {
|
|
|
14
14
|
listDirectory?(directory: string): Promise<TaDirEntry[]>;
|
|
15
15
|
/** The directory of ta serve: that of a `cwd` left empty. */
|
|
16
16
|
serverCwd?: string;
|
|
17
|
+
/** The hosts of ssh, which the button offers too. */
|
|
18
|
+
remote?: FolderPickerRemote;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/** The message box as web extensions get it (`pi.ui.Composer`): text only, `@` for the names they give; a failure is a notice of the page. */
|
|
20
|
-
export function WebComposer({
|
|
22
|
+
export function WebComposer({
|
|
23
|
+
notices,
|
|
24
|
+
placeholder,
|
|
25
|
+
onSend,
|
|
26
|
+
mentions,
|
|
27
|
+
mentionLabel,
|
|
28
|
+
actions,
|
|
29
|
+
cwd,
|
|
30
|
+
onPickCwd,
|
|
31
|
+
listDirectory,
|
|
32
|
+
serverCwd,
|
|
33
|
+
remote,
|
|
34
|
+
}: WebComposerHostProps) {
|
|
21
35
|
const t = useT();
|
|
22
36
|
const [query, setQuery] = React.useState<string>();
|
|
23
37
|
return (
|
|
@@ -40,7 +54,7 @@ export function WebComposer({ notices, placeholder, onSend, mentions, mentionLab
|
|
|
40
54
|
}
|
|
41
55
|
mentionLabel={mentions ? mentionLabel : undefined}
|
|
42
56
|
actions={actions}
|
|
43
|
-
beside={onPickCwd && listDirectory && <DirectoryButton cwd={cwd || serverCwd || ''} list={listDirectory} onPick={onPickCwd} />}
|
|
57
|
+
beside={onPickCwd && listDirectory && <DirectoryButton cwd={cwd || serverCwd || ''} list={listDirectory} remote={remote} onPick={onPickCwd} />}
|
|
44
58
|
onError={cause => notices.error(cause)}
|
|
45
59
|
/>
|
|
46
60
|
);
|
|
@@ -18,5 +18,5 @@ export function WorkingDirectoryPicker({ onClose }: { onClose(): void }) {
|
|
|
18
18
|
notices.error(error);
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
|
-
return <FolderPicker list={list} initial={workbench.server?.cwd ?? '.'} onPick={pick} onClose={onClose} />;
|
|
21
|
+
return <FolderPicker list={list} initial={workbench.server?.cwd ?? '.'} remote={bench} onPick={pick} onClose={onClose} />;
|
|
22
22
|
}
|
|
@@ -62,11 +62,13 @@ import {
|
|
|
62
62
|
Save,
|
|
63
63
|
ScrollText,
|
|
64
64
|
Search,
|
|
65
|
+
Server,
|
|
65
66
|
Sparkles,
|
|
66
67
|
Square,
|
|
67
68
|
SquareMinus,
|
|
68
69
|
SquarePlus,
|
|
69
70
|
SquareTerminal,
|
|
71
|
+
Store,
|
|
70
72
|
Text,
|
|
71
73
|
Trash2,
|
|
72
74
|
Undo2,
|
|
@@ -144,11 +146,13 @@ export const WEB_ICONS: Readonly<Record<string, WebIcon>> = Object.freeze({
|
|
|
144
146
|
Save,
|
|
145
147
|
ScrollText,
|
|
146
148
|
Search,
|
|
149
|
+
Server,
|
|
147
150
|
Sparkles,
|
|
148
151
|
Square,
|
|
149
152
|
SquareMinus,
|
|
150
153
|
SquarePlus,
|
|
151
154
|
SquareTerminal,
|
|
155
|
+
Store,
|
|
152
156
|
Text,
|
|
153
157
|
Trash2,
|
|
154
158
|
Undo2,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TaEvent, TaPackageServer, TaServer, TaSessionInfo, TaTerminalServer, TaWorkspaceServer } from '@celljs/ta-protocol';
|
|
1
|
+
import type { TaEvent, TaPackageServer, TaRemoteServer, TaServer, TaSessionInfo, TaTerminalServer, TaWorkspaceServer } from '@celljs/ta-protocol';
|
|
2
2
|
import type * as React from 'react';
|
|
3
3
|
import type { Mood } from '../avatar/dino-avatar';
|
|
4
4
|
import type { Language, Theme } from '../settings/settings-protocol';
|
|
@@ -13,12 +13,14 @@ export interface WebExtensionAPI {
|
|
|
13
13
|
readonly React: typeof React;
|
|
14
14
|
/** The sessions of ta serve, for what the rest of the API does not do: the commands of pi's RPC mode. */
|
|
15
15
|
readonly server: TaServer;
|
|
16
|
-
/** The files and programs of a directory on the machine of ta serve. */
|
|
16
|
+
/** The files and programs of a directory, on the machine of ta serve or of an `ssh://` directory. */
|
|
17
17
|
readonly workspace: TaWorkspaceServer;
|
|
18
18
|
/** Shells; their output comes by {@link onTerminalData}. */
|
|
19
19
|
readonly terminals: TaTerminalServer;
|
|
20
20
|
/** The packages pi loads; for the data of the extension's own package, {@link storage}. */
|
|
21
21
|
readonly packages: TaPackageServer;
|
|
22
|
+
/** The other machines of `ssh://` directories (TaLocation): their ports, for a page served there. */
|
|
23
|
+
readonly remote: TaRemoteServer;
|
|
22
24
|
readonly ui: WebExtensionUI;
|
|
23
25
|
/** A view of its own: a page or a sidebar view, in the navigation on the left, or a tool of the workbench on the right. */
|
|
24
26
|
registerView(name: string, view: WebView): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Autowired, Component, Optional, PostConstruct } from '@celljs/core';
|
|
2
|
-
import type { TaEvent, TaPackageServer, TaServer, TaTerminalServer, TaWebExtensionInfo, TaWorkspaceServer } from '@celljs/ta-protocol';
|
|
2
|
+
import type { TaEvent, TaPackageServer, TaRemoteServer, TaServer, TaTerminalServer, TaWebExtensionInfo, TaWorkspaceServer } from '@celljs/ta-protocol';
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { CodeEditor } from '../components/code-editor';
|
|
5
5
|
import { DiffEditor } from '../components/diff-editor';
|
|
@@ -474,11 +474,18 @@ export class TaWebExtensionsImpl implements TaWebExtensions {
|
|
|
474
474
|
protected readonly listDirectory = (directory: string) => this.workbench.workspace.listDirectory('.', directory);
|
|
475
475
|
/** The message box, whose button of the directory lists those of the server. */
|
|
476
476
|
protected readonly composer = (props: WebComposerProps) =>
|
|
477
|
-
React.createElement(WebComposer, {
|
|
477
|
+
React.createElement(WebComposer, {
|
|
478
|
+
...props,
|
|
479
|
+
notices: this.notices,
|
|
480
|
+
listDirectory: this.listDirectory,
|
|
481
|
+
serverCwd: this.workbench.state.server?.cwd,
|
|
482
|
+
remote: this.workbench,
|
|
483
|
+
});
|
|
478
484
|
protected readonly folderPicker = ({ initial, onPick, onClose }: WebFolderPickerProps) =>
|
|
479
485
|
React.createElement(FolderPicker, {
|
|
480
486
|
list: this.listDirectory,
|
|
481
487
|
initial: initial || this.workbench.state.server?.cwd || '.',
|
|
488
|
+
remote: this.workbench,
|
|
482
489
|
onPick: async (path: string) => onPick(path),
|
|
483
490
|
onClose,
|
|
484
491
|
});
|
|
@@ -508,6 +515,9 @@ export class TaWebExtensionsImpl implements TaWebExtensions {
|
|
|
508
515
|
get packages(): TaPackageServer {
|
|
509
516
|
return workbench.packages;
|
|
510
517
|
},
|
|
518
|
+
get remote(): TaRemoteServer {
|
|
519
|
+
return workbench.remote;
|
|
520
|
+
},
|
|
511
521
|
ui: {
|
|
512
522
|
setWidget: (key, component, options = {}) =>
|
|
513
523
|
register(() => (component ? run.widgets.set(key, { component, options }) : run.widgets.delete(key))),
|
|
@@ -44,15 +44,15 @@ export const EN_MESSAGES = {
|
|
|
44
44
|
'ta.editor.discardClose': 'Close without saving',
|
|
45
45
|
'ta.online': 'Online',
|
|
46
46
|
'ta.reconnecting': 'Reconnecting',
|
|
47
|
-
'ta.unreachable': '
|
|
47
|
+
'ta.unreachable': 'The Ta service is not running. Start it with ta serve in a terminal; this page connects by itself.',
|
|
48
48
|
'ta.pwa.install': 'Install as an app',
|
|
49
|
-
// The connection to ta serve, and the notices of the page
|
|
50
|
-
'ta.connection.lost': 'The connection to
|
|
49
|
+
// The connection to the Ta service (ta serve), and the notices of the page
|
|
50
|
+
'ta.connection.lost': 'The connection to the Ta service dropped.',
|
|
51
51
|
'ta.connection.retryIn': 'Trying again in {seconds}s',
|
|
52
52
|
'ta.connection.retrying': 'Connecting…',
|
|
53
53
|
'ta.connection.retryNow': 'Retry now',
|
|
54
54
|
'ta.connection.back': 'Connected again',
|
|
55
|
-
'ta.connection.notDone': 'Not done:
|
|
55
|
+
'ta.connection.notDone': 'Not done: the Ta service is not connected',
|
|
56
56
|
'ta.connection.notDoneHint': 'Try again once the connection is back.',
|
|
57
57
|
'ta.notices': 'Notifications',
|
|
58
58
|
'ta.packages.removeDetail': 'Its pages, cards and commands go with it.',
|
|
@@ -62,10 +62,10 @@ export const EN_MESSAGES = {
|
|
|
62
62
|
'ta.home': 'Home',
|
|
63
63
|
'ta.menu': 'Menu',
|
|
64
64
|
'ta.signin.title': 'Connect to Ta',
|
|
65
|
-
'ta.signin.hint': 'Run ta serve --web, which opens this page signed in, or paste the token from ~/.ta/agent/serve.json.',
|
|
65
|
+
'ta.signin.hint': 'Run ta serve --web in a terminal, which opens this page signed in, or paste the token from ~/.ta/agent/serve.json.',
|
|
66
66
|
'ta.signin.token': 'Access token',
|
|
67
67
|
'ta.signin.connect': 'Connect',
|
|
68
|
-
'ta.signin.refused': '
|
|
68
|
+
'ta.signin.refused': 'The Ta service did not accept the token, or it is not running.',
|
|
69
69
|
'ta.signin.pairHint': 'On your computer, run ta pair <relay> and paste the link it prints here. What goes through the relay is encrypted end to end.',
|
|
70
70
|
'ta.signin.pairing': 'Pairing link',
|
|
71
71
|
'ta.signin.pairRefused': 'Your Ta does not know this device: it may have been unpaired. Run ta pair again.',
|
|
@@ -197,6 +197,36 @@ export const EN_MESSAGES = {
|
|
|
197
197
|
'ta.folder.empty': 'No folders here.',
|
|
198
198
|
'ta.folder.failed': 'Cannot open {path}.',
|
|
199
199
|
'ta.folder.open': 'Use this directory',
|
|
200
|
+
'ta.remote.local': 'This computer',
|
|
201
|
+
'ta.remote.add': 'Add a host',
|
|
202
|
+
'ta.remote.addHint': 'A Host of ~/.ssh/config, user@host:port, or an ssh command',
|
|
203
|
+
'ta.remote.connected': 'Connected',
|
|
204
|
+
'ta.remote.failed': 'Cannot connect to {host}',
|
|
205
|
+
'ta.remote.title': 'Remote hosts',
|
|
206
|
+
'ta.remote.description': 'Conversations can work in directories of these machines. Ta connects with the ssh of this computer and its ~/.ssh/config.',
|
|
207
|
+
'ta.remote.none': 'No hosts yet.',
|
|
208
|
+
'ta.remote.invalid': '{host} names no host: write a Host of ~/.ssh/config, user@host:port, or ssh user@host -p port.',
|
|
209
|
+
'ta.remote.options': 'Ta takes no {options} here: put it in a Host of ~/.ssh/config, and add that Host.',
|
|
210
|
+
'ta.remote.disconnected': 'Not connected',
|
|
211
|
+
'ta.remote.fromConfig': 'From ~/.ssh/config',
|
|
212
|
+
'ta.remote.added': 'Added',
|
|
213
|
+
'ta.remote.connect': 'Connect',
|
|
214
|
+
'ta.remote.disconnect': 'Disconnect',
|
|
215
|
+
'ta.remote.connectedTo': 'Connected to {host}',
|
|
216
|
+
'ta.remote.remove': 'Remove {host}',
|
|
217
|
+
'ta.remote.port': 'Port {port} of {host}',
|
|
218
|
+
'ta.remote.closePort': 'Stop forwarding',
|
|
219
|
+
'ta.remote.connecting': 'Connecting to {host}…',
|
|
220
|
+
'ta.remote.step.connecting': 'Connecting to {host}…',
|
|
221
|
+
'ta.remote.step.checking': 'Looking at {host}…',
|
|
222
|
+
'ta.remote.step.installing-node': 'Installing Node.js {detail} on {host}…',
|
|
223
|
+
'ta.remote.step.copying-server': 'Copying Ta to {host}…',
|
|
224
|
+
'ta.remote.step.starting': 'Starting on {host}…',
|
|
225
|
+
'ta.remote.lost': 'Lost the connection to {host}; it comes back with the next step.',
|
|
226
|
+
'ta.remote.prompt.title': 'Connect to {host}',
|
|
227
|
+
'ta.remote.prompt.trust': 'Trust and connect',
|
|
228
|
+
'ta.remote.prompt.continue': 'Continue',
|
|
229
|
+
'ta.remote.forwarded': 'Port {port} of {host} is at {local} here',
|
|
200
230
|
'ta.chat.search': 'Search chats',
|
|
201
231
|
'ta.chat.all': 'All',
|
|
202
232
|
'ta.chat.noMatch': 'No chat matches.',
|
|
@@ -286,6 +316,7 @@ export const EN_MESSAGES = {
|
|
|
286
316
|
'ta.composer.queue.takeBack': 'Take back',
|
|
287
317
|
'ta.composer.queue.takeBackTitle': 'Take the waiting messages back into the box',
|
|
288
318
|
'ta.account': 'Account',
|
|
319
|
+
'ta.account.service': 'Ta service',
|
|
289
320
|
// The settings view and the menu of the gear
|
|
290
321
|
'ta.manage': 'Manage',
|
|
291
322
|
'ta.manage.packages': 'Extensions',
|
|
@@ -303,7 +334,7 @@ export const EN_MESSAGES = {
|
|
|
303
334
|
'ta.settings.scopeHint.user': 'In {file}, for every project.',
|
|
304
335
|
'ta.settings.scopeHint.project': 'In {file}; overrides the user settings in this project.',
|
|
305
336
|
'ta.settings.untrusted': 'Ta does not trust this project yet, so it ignores its settings. Run ta in it once and trust it to change them here.',
|
|
306
|
-
'ta.settings.offline': 'Connect to
|
|
337
|
+
'ta.settings.offline': 'Connect to the Ta service to see these settings.',
|
|
307
338
|
'ta.settings.loading': 'Reading the settings…',
|
|
308
339
|
'ta.settings.failed': 'Not saved: {error}',
|
|
309
340
|
'ta.settings.default': 'Default ({value})',
|
|
@@ -475,14 +506,14 @@ export const ZH_MESSAGES: Record<MessageId, string> = {
|
|
|
475
506
|
'ta.editor.discardClose': '不保存并关闭',
|
|
476
507
|
'ta.online': '在线',
|
|
477
508
|
'ta.reconnecting': '重连中',
|
|
478
|
-
'ta.unreachable': '
|
|
509
|
+
'ta.unreachable': 'Ta 服务没有在运行。在终端运行 ta serve 启动它,本页会自动连上。',
|
|
479
510
|
'ta.pwa.install': '安装为应用',
|
|
480
|
-
'ta.connection.lost': '与
|
|
511
|
+
'ta.connection.lost': '与 Ta 服务的连接断开了',
|
|
481
512
|
'ta.connection.retryIn': '{seconds} 秒后重试',
|
|
482
513
|
'ta.connection.retrying': '正在连接…',
|
|
483
514
|
'ta.connection.retryNow': '立即重试',
|
|
484
515
|
'ta.connection.back': '已重新连接',
|
|
485
|
-
'ta.connection.notDone': '
|
|
516
|
+
'ta.connection.notDone': '没有完成:未连接到 Ta 服务',
|
|
486
517
|
'ta.connection.notDoneHint': '连接恢复后再试一次。',
|
|
487
518
|
'ta.notices': '通知',
|
|
488
519
|
'ta.packages.removeDetail': '它的页面、卡片和命令也会一起移除。',
|
|
@@ -492,10 +523,10 @@ export const ZH_MESSAGES: Record<MessageId, string> = {
|
|
|
492
523
|
'ta.home': '首页',
|
|
493
524
|
'ta.menu': '菜单',
|
|
494
525
|
'ta.signin.title': '连接 Ta',
|
|
495
|
-
'ta.signin.hint': '
|
|
526
|
+
'ta.signin.hint': '在终端运行 ta serve --web,会自动打开本页并登录;也可以粘贴 ~/.ta/agent/serve.json 里的 token。',
|
|
496
527
|
'ta.signin.token': '访问 token',
|
|
497
528
|
'ta.signin.connect': '连接',
|
|
498
|
-
'ta.signin.refused': '
|
|
529
|
+
'ta.signin.refused': 'Ta 服务没有接受这个 token,或者它没有在运行。',
|
|
499
530
|
'ta.signin.pairHint': '在电脑上运行 ta pair <Relay 地址>,把它打印的链接粘贴到这里。经 Relay 的内容端到端加密。',
|
|
500
531
|
'ta.signin.pairing': '配对链接',
|
|
501
532
|
'ta.signin.pairRefused': '你的 Ta 不认识这台设备,可能已取消配对。请重新运行 ta pair。',
|
|
@@ -626,6 +657,36 @@ export const ZH_MESSAGES: Record<MessageId, string> = {
|
|
|
626
657
|
'ta.folder.empty': '这里没有子目录。',
|
|
627
658
|
'ta.folder.failed': '打不开 {path}。',
|
|
628
659
|
'ta.folder.open': '使用此目录',
|
|
660
|
+
'ta.remote.local': '本机',
|
|
661
|
+
'ta.remote.add': '添加主机',
|
|
662
|
+
'ta.remote.addHint': '~/.ssh/config 里的主机、user@host:port,或 ssh 命令',
|
|
663
|
+
'ta.remote.connected': '已连接',
|
|
664
|
+
'ta.remote.failed': '连不上 {host}',
|
|
665
|
+
'ta.remote.title': '远程主机',
|
|
666
|
+
'ta.remote.description': '对话可以在这些机器的目录里工作。Ta 用本机的 ssh 和 ~/.ssh/config 连接。',
|
|
667
|
+
'ta.remote.none': '还没有主机。',
|
|
668
|
+
'ta.remote.invalid': '{host} 不是主机:写 ~/.ssh/config 里的 Host、user@host:port,或 ssh user@host -p 端口。',
|
|
669
|
+
'ta.remote.options': '这里不支持 {options}:把它写进 ~/.ssh/config 的一个 Host 里,再添加那个 Host。',
|
|
670
|
+
'ta.remote.disconnected': '未连接',
|
|
671
|
+
'ta.remote.fromConfig': '来自 ~/.ssh/config',
|
|
672
|
+
'ta.remote.added': '手动添加',
|
|
673
|
+
'ta.remote.connect': '连接',
|
|
674
|
+
'ta.remote.disconnect': '断开',
|
|
675
|
+
'ta.remote.connectedTo': '已连接 {host}',
|
|
676
|
+
'ta.remote.remove': '移除 {host}',
|
|
677
|
+
'ta.remote.port': '{host} 的 {port} 端口',
|
|
678
|
+
'ta.remote.closePort': '停止转发',
|
|
679
|
+
'ta.remote.connecting': '正在连接 {host}…',
|
|
680
|
+
'ta.remote.step.connecting': '正在连接 {host}…',
|
|
681
|
+
'ta.remote.step.checking': '正在检查 {host}…',
|
|
682
|
+
'ta.remote.step.installing-node': '正在为 {host} 安装 Node.js {detail}…',
|
|
683
|
+
'ta.remote.step.copying-server': '正在把 Ta 复制到 {host}…',
|
|
684
|
+
'ta.remote.step.starting': '正在 {host} 上启动…',
|
|
685
|
+
'ta.remote.lost': '与 {host} 的连接断开了,下次操作时会自动重连。',
|
|
686
|
+
'ta.remote.prompt.title': '连接 {host}',
|
|
687
|
+
'ta.remote.prompt.trust': '信任并连接',
|
|
688
|
+
'ta.remote.prompt.continue': '继续',
|
|
689
|
+
'ta.remote.forwarded': '{host} 的 {port} 端口已转发到本机 {local}',
|
|
629
690
|
'ta.chat.search': '搜索对话',
|
|
630
691
|
'ta.chat.all': '全部',
|
|
631
692
|
'ta.chat.noMatch': '没有匹配的对话。',
|
|
@@ -715,6 +776,7 @@ export const ZH_MESSAGES: Record<MessageId, string> = {
|
|
|
715
776
|
'ta.composer.queue.takeBack': '撤回',
|
|
716
777
|
'ta.composer.queue.takeBackTitle': '把排队的消息取回输入框',
|
|
717
778
|
'ta.account': '账号',
|
|
779
|
+
'ta.account.service': 'Ta 服务',
|
|
718
780
|
// The settings view and the menu of the gear
|
|
719
781
|
'ta.manage': '管理',
|
|
720
782
|
'ta.manage.packages': '扩展',
|
|
@@ -732,7 +794,7 @@ export const ZH_MESSAGES: Record<MessageId, string> = {
|
|
|
732
794
|
'ta.settings.scopeHint.user': '保存在 {file},对所有项目生效。',
|
|
733
795
|
'ta.settings.scopeHint.project': '保存在 {file},在此项目中覆盖用户设置。',
|
|
734
796
|
'ta.settings.untrusted': 'Ta 还没有信任这个项目,因此不会读取它的设置。先在该目录运行一次 ta 并信任它,再回来修改。',
|
|
735
|
-
'ta.settings.offline': '
|
|
797
|
+
'ta.settings.offline': '连接到 Ta 服务后才能查看这些设置。',
|
|
736
798
|
'ta.settings.loading': '正在读取设置…',
|
|
737
799
|
'ta.settings.failed': '未保存:{error}',
|
|
738
800
|
'ta.settings.default': '默认({value})',
|
|
@@ -55,7 +55,7 @@ describe('TaNoticesImpl', () => {
|
|
|
55
55
|
notices.error(new TaRemoteError('The connection to ta serve was lost.', TaErrorCode.CONNECTION_LOST));
|
|
56
56
|
expect(notices.state.notices.map(({ message, detail, level, count }) => ({ message, detail, level, count }))).toEqual([
|
|
57
57
|
{ message: 'Locked.', detail: 'Close it there first.', level: 'error', count: 1 },
|
|
58
|
-
{ message: '
|
|
58
|
+
{ message: '没有完成:未连接到 Ta 服务', detail: '连接恢复后再试一次。', level: 'error', count: 2 },
|
|
59
59
|
]);
|
|
60
60
|
});
|
|
61
61
|
|
|
@@ -80,7 +80,12 @@ export const BASE_STYLE = `
|
|
|
80
80
|
}
|
|
81
81
|
* { box-sizing: border-box; }
|
|
82
82
|
html, body, #cell-root, #root { height: 100%; margin: 0; }
|
|
83
|
-
|
|
83
|
+
/* The page and its scrolling areas do not bounce at the ends. Only scrolling areas of their own: on any
|
|
84
|
+
other element (one with overflow: hidden is a scroll container too) it stops the wheel from reaching
|
|
85
|
+
the area under it, and nothing scrolls while the pointer is over a card or a code block. */
|
|
86
|
+
html, body, .ta-scroll, .ta-chat-scroll, .ta-side-scroll, .ta-rail-items, .ta-side-view-body, .ta-editor-body,
|
|
87
|
+
.ta-workbench-launcher, .ta-workbench-menu, .ta-settings-scroll, .ta-settings-toc, .ta-plus-items, .ta-commands,
|
|
88
|
+
.ta-folder-list, .ta-auth-list { overscroll-behavior: none; }
|
|
84
89
|
body { background: var(--ta-main); color: var(--ta-text); font: 14px/1.6 var(--ta-sans); -webkit-font-smoothing: antialiased; -webkit-tap-highlight-color: transparent; }
|
|
85
90
|
button, input, select, textarea { font: inherit; color: inherit; }
|
|
86
91
|
button { cursor: pointer; }
|
|
@@ -169,20 +169,25 @@ export const LAYOUT_STYLE = `
|
|
|
169
169
|
.ta-queue-back { align-self: flex-end; color: var(--ta-muted); }
|
|
170
170
|
.ta-queue-back:hover { color: var(--ta-text); }
|
|
171
171
|
.ta-account { position: relative; display: grid; place-items: center; width: 40px; height: 40px; flex: none; }
|
|
172
|
-
.ta-account-button { position: relative; width:
|
|
173
|
-
.ta-account-button:hover, .ta-account-button.ta-on {
|
|
174
|
-
.ta-account-
|
|
175
|
-
.ta-account-
|
|
176
|
-
.ta-account-dot
|
|
177
|
-
.ta-account-menu { position: absolute; left: calc(100% + 10px); bottom: 0; z-index: 45; width:
|
|
178
|
-
.ta-account-head { display: flex; align-items: center; gap: 10px; padding:
|
|
179
|
-
.ta-account-
|
|
180
|
-
.ta-account-
|
|
181
|
-
.ta-account-
|
|
182
|
-
.ta-account-
|
|
183
|
-
.ta-account-
|
|
172
|
+
.ta-account-button { position: relative; width: 40px; height: 40px; display: grid; place-items: center; border: 0; border-radius: 10px; background: transparent; padding: 0; transition: background 150ms; }
|
|
173
|
+
.ta-account-button:hover, .ta-account-button.ta-on { background: var(--ta-hover); }
|
|
174
|
+
.ta-account-figure { width: 28px; height: 28px; flex: none; display: grid; place-items: center; border-radius: 50%; background: color-mix(in srgb, var(--ta-green) 14%, var(--ta-main)); color: color-mix(in srgb, var(--ta-green) 75%, var(--ta-text)); font-size: 13px; font-weight: 600; line-height: 1; letter-spacing: 0; user-select: none; }
|
|
175
|
+
.ta-account-figure.ta-large { width: 36px; height: 36px; font-size: 15px; }
|
|
176
|
+
.ta-account-dot { position: absolute; right: 5px; bottom: 5px; width: 8px; height: 8px; border-radius: 50%; background: var(--ta-yellow); box-shadow: 0 0 0 2px var(--ta-side); }
|
|
177
|
+
.ta-account-menu { position: absolute; left: calc(100% + 10px); bottom: 0; z-index: 45; width: 248px; display: flex; flex-direction: column; gap: 2px; padding: 6px; border-radius: 12px; border: 1px solid var(--ta-border); background: var(--ta-side); box-shadow: var(--ta-shadow-pop); animation: ta-fade 120ms ease-out; }
|
|
178
|
+
.ta-account-head { display: flex; align-items: center; gap: 10px; padding: 8px 8px 10px; min-width: 0; }
|
|
179
|
+
.ta-account-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; line-height: 1.35; }
|
|
180
|
+
.ta-account-title { font-size: 13.5px; font-weight: 600; color: var(--ta-text); }
|
|
181
|
+
.ta-account-sub { font-size: 12px; color: var(--ta-muted); }
|
|
182
|
+
.ta-account-service { display: flex; align-items: center; gap: 8px; height: 32px; margin: 0 2px 2px; padding: 0 10px; border-radius: 8px; background: var(--ta-raised); font-size: 12.5px; color: var(--ta-secondary); }
|
|
183
|
+
.ta-account-service-name { flex: 1; min-width: 0; }
|
|
184
|
+
.ta-account-service-state { flex: none; font-size: 12px; color: var(--ta-muted); font-variant-numeric: tabular-nums; }
|
|
185
|
+
.ta-account-state { width: 7px; height: 7px; flex: none; border-radius: 50%; background: var(--ta-muted); }
|
|
186
|
+
.ta-account-state.ta-ok { background: var(--ta-green); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ta-green) 18%, transparent); }
|
|
187
|
+
.ta-account-state.ta-warn { background: var(--ta-yellow); }
|
|
184
188
|
.ta-account-sep { height: 1px; margin: 4px 2px; background: var(--ta-border-muted); }
|
|
185
|
-
.ta-account-item { display: flex; align-items: center; gap: 10px; height: 32px; padding: 0
|
|
189
|
+
.ta-account-item { display: flex; align-items: center; gap: 10px; height: 32px; padding: 0 10px; border: 0; border-radius: 8px; background: transparent; text-align: left; font-size: 13px; color: var(--ta-text); }
|
|
190
|
+
.ta-account-item > svg, .ta-account-item > .ta-icon { flex: none; color: var(--ta-secondary); }
|
|
186
191
|
.ta-account-item:hover { background: var(--ta-hover); }
|
|
187
192
|
.ta-account-item.ta-danger { color: var(--ta-red); }
|
|
188
193
|
@media (max-width: 860px) {
|
|
@@ -250,7 +255,7 @@ export const LAYOUT_STYLE = `
|
|
|
250
255
|
.ta-web-dialog .ta-modal-title { font-size: 16px; }
|
|
251
256
|
.ta-folder-bar { display: flex; align-items: center; gap: 6px; }
|
|
252
257
|
.ta-folder-bar .ta-field input { font-family: var(--ta-mono); font-size: 12.5px; }
|
|
253
|
-
.ta-folder-list { list-style: none; flex:
|
|
258
|
+
.ta-folder-list { list-style: none; flex: none; height: min(340px, 44vh); overflow-y: auto; margin: 0; padding: 4px; border: 1px solid var(--ta-border-muted); border-radius: 10px; }
|
|
254
259
|
.ta-folder-item { display: flex; align-items: center; gap: 10px; width: 100%; height: 34px; padding: 0 10px; border: 0; border-radius: 8px; background: transparent; color: var(--ta-body); font-size: 13px; text-align: left; }
|
|
255
260
|
.ta-folder-item:hover { background: var(--ta-hover); color: var(--ta-text); }
|
|
256
261
|
.ta-folder-item > .ta-icon:first-child, .ta-folder-chevron { color: var(--ta-muted); }
|
|
@@ -258,7 +263,35 @@ export const LAYOUT_STYLE = `
|
|
|
258
263
|
.ta-folder-item:hover .ta-folder-chevron { opacity: 1; }
|
|
259
264
|
.ta-folder-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
260
265
|
.ta-folder-note { display: flex; justify-content: center; padding: 28px 8px; color: var(--ta-muted); font-size: 12.5px; }
|
|
261
|
-
.ta-folder-failed { color: var(--ta-red); font-size: 12.5px; }
|
|
266
|
+
.ta-folder-failed { color: var(--ta-red); font-size: 12.5px; overflow-wrap: anywhere; }
|
|
267
|
+
.ta-folder-note { gap: 8px; align-items: center; }
|
|
268
|
+
.ta-folder-step { color: var(--ta-secondary); }
|
|
269
|
+
.ta-folder-unreachable { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 36px 16px; text-align: center; font-size: 13px; color: var(--ta-text); }
|
|
270
|
+
.ta-folder-unreachable span { max-width: 100%; color: var(--ta-muted); font-family: var(--ta-mono); font-size: 12px; overflow-wrap: anywhere; }
|
|
271
|
+
/* The machines of the picker: this one, the hosts of ssh, + another */
|
|
272
|
+
.ta-modal.ta-folder { width: min(640px, 100%); }
|
|
273
|
+
.ta-folder-machines { flex-wrap: nowrap; overflow-x: auto; margin: 0; padding: 0 0 2px; gap: 6px; scrollbar-width: none; }
|
|
274
|
+
.ta-folder-machines::-webkit-scrollbar { display: none; }
|
|
275
|
+
.ta-folder-machines .ta-icon-button { flex: none; width: 28px; height: 28px; border-radius: 14px; }
|
|
276
|
+
.ta-folder-add input { width: 230px; height: 28px; padding: 0 11px; border-radius: 14px; border: 1px solid var(--ta-border-field); background: var(--ta-input); color: var(--ta-text); font-size: 12.5px; outline: none; }
|
|
277
|
+
.ta-folder-add input:focus { border-color: var(--ta-border-focus); }
|
|
278
|
+
.ta-folder-add input[aria-invalid='true'] { border-color: var(--ta-red); }
|
|
279
|
+
.ta-folder-add input { width: 300px; }
|
|
280
|
+
.ta-remote-state { display: inline-flex; align-items: center; gap: 6px; }
|
|
281
|
+
.ta-remote-light { width: 7px; height: 7px; border-radius: 50%; background: var(--ta-border-field); flex: none; }
|
|
282
|
+
.ta-remote-light.ta-on { background: var(--ta-green); }
|
|
283
|
+
.ta-remote-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ta-green); flex: none; }
|
|
284
|
+
/* The host of a directory of another machine, before its name */
|
|
285
|
+
.ta-host-tag { display: inline-flex; align-items: center; flex: none; max-width: 120px; height: 17px; padding: 0 6px; margin-right: 5px; border-radius: 5px; background: var(--ta-hover-strong); color: var(--ta-secondary); font-size: 11px; font-weight: 500; line-height: 17px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: 1px; }
|
|
286
|
+
.ta-cwd-chip .ta-host-tag { margin: 0 0 0 2px; }
|
|
287
|
+
.ta-cwd-chip .ta-ellipsis { min-width: 0; }
|
|
288
|
+
/* What ssh asks, and the line while a host connects */
|
|
289
|
+
.ta-remote-prompt { width: min(440px, 100%); gap: 12px; }
|
|
290
|
+
.ta-remote-prompt .ta-confirm-title { display: flex; align-items: center; gap: 8px; }
|
|
291
|
+
.ta-remote-message { margin: 0; padding: 8px 10px; border-radius: 8px; background: var(--ta-hover); color: var(--ta-body); font-family: var(--ta-mono); font-size: 12px; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; max-height: 160px; overflow-y: auto; }
|
|
292
|
+
.ta-remote-status { position: fixed; left: 50%; top: calc(12px + env(safe-area-inset-top) + var(--ta-titlebar, 0px)); z-index: 65; transform: translateX(-50%); display: inline-flex; align-items: center; gap: 8px; max-width: calc(100vw - 32px); height: 32px; padding: 0 14px; border-radius: 16px; border: 1px solid var(--ta-border); background: var(--ta-side); box-shadow: var(--ta-shadow-pop); color: var(--ta-secondary); font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; animation: ta-fade 160ms ease-out; }
|
|
293
|
+
/* The picker says it itself */
|
|
294
|
+
body:has(.ta-folder) .ta-remote-status { display: none; }
|
|
262
295
|
@media (max-width: 860px) {
|
|
263
296
|
.ta-modal-scrim { align-items: flex-end; padding: 0; }
|
|
264
297
|
.ta-modal { max-height: 88dvh; border-radius: 16px 16px 0 0; padding-bottom: calc(14px + env(safe-area-inset-bottom)); animation: ta-up 260ms var(--ta-ease); }
|
|
@@ -26,6 +26,8 @@ describe('MessageUtil', () => {
|
|
|
26
26
|
expect(MessageUtil.basename('/home/me/project/')).toBe('project');
|
|
27
27
|
expect(MessageUtil.shortPath('/home/me/work/project')).toBe('…/work/project');
|
|
28
28
|
expect(MessageUtil.shortPath('/srv')).toBe('/srv');
|
|
29
|
+
expect(MessageUtil.shortPath('ssh://dev/data/my/workspace')).toBe('dev:…/my/workspace');
|
|
30
|
+
expect(MessageUtil.shortPath('ssh://dev/srv')).toBe('dev:/srv');
|
|
29
31
|
});
|
|
30
32
|
|
|
31
33
|
it('finds the entry of a message by its role and time, the newest first', () => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SessionMessage, ToolRun } from '@celljs/ta-client';
|
|
2
|
+
import { TaLocation } from '@celljs/ta-protocol';
|
|
2
3
|
|
|
3
4
|
export interface ContentBlock {
|
|
4
5
|
type: string;
|
|
@@ -177,12 +178,14 @@ export namespace MessageUtil {
|
|
|
177
178
|
|
|
178
179
|
/** The last two parts of a path: enough to recognize a project. */
|
|
179
180
|
export function shortPath(path: string): string {
|
|
180
|
-
const
|
|
181
|
-
|
|
181
|
+
const remote = TaLocation.parse(path);
|
|
182
|
+
const parts = (remote ? remote.path : path).split(/[\\/]/).filter(Boolean);
|
|
183
|
+
const short = parts.length > 2 ? `…/${parts.slice(-2).join('/')}` : remote ? remote.path || '~' : path;
|
|
184
|
+
return remote ? `${remote.host}:${short}` : short;
|
|
182
185
|
}
|
|
183
186
|
|
|
187
|
+
/** The last name of a path, or of an `ssh://` location (TaLocation). */
|
|
184
188
|
export function basename(path: string): string {
|
|
185
|
-
|
|
186
|
-
return parts[parts.length - 1] ?? path;
|
|
189
|
+
return TaLocation.basename(path);
|
|
187
190
|
}
|
|
188
191
|
}
|
|
@@ -4,6 +4,8 @@ import type {
|
|
|
4
4
|
TaAuthServer,
|
|
5
5
|
TaPackageServer,
|
|
6
6
|
TaRelayServer,
|
|
7
|
+
TaRemoteEvent,
|
|
8
|
+
TaRemoteServer,
|
|
7
9
|
TaServer,
|
|
8
10
|
TaServerInfo,
|
|
9
11
|
TaSessionInfo,
|
|
@@ -42,6 +44,7 @@ export interface TaWorkbench {
|
|
|
42
44
|
readonly packages: TaPackageServer;
|
|
43
45
|
readonly auth: TaAuthServer;
|
|
44
46
|
readonly relay: TaRelayServer;
|
|
47
|
+
readonly remote: TaRemoteServer;
|
|
45
48
|
subscribe(listener: (state: WorkbenchState) => void): () => void;
|
|
46
49
|
/** Signs in with the token of the URL fragment (`ta serve --web`) or the one this page used before. */
|
|
47
50
|
start(): Promise<void>;
|
|
@@ -63,4 +66,9 @@ export interface TaWorkbench {
|
|
|
63
66
|
onExtensionData(listener: (name: string, keys: string[], cwd?: string) => void): () => void;
|
|
64
67
|
/** What happens during the sign-ins of this page (TaAuthServer.login); the listener stays across reconnections. */
|
|
65
68
|
onAuthEvent(listener: (loginId: string, event: TaAuthEvent) => void): () => void;
|
|
69
|
+
/**
|
|
70
|
+
* What happens with the remote machines (TaRemoteServer.watch, which the page calls on each connection):
|
|
71
|
+
* the prompts that wait come again as `prompt` events after a reconnection. The listener stays.
|
|
72
|
+
*/
|
|
73
|
+
onRemoteEvent(listener: (event: TaRemoteEvent) => void): () => void;
|
|
66
74
|
}
|
package/src/browser/workbench.ts
CHANGED
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
type TaAuthServer,
|
|
7
7
|
type TaPackageServer,
|
|
8
8
|
type TaRelayServer,
|
|
9
|
+
type TaRemoteEvent,
|
|
10
|
+
type TaRemoteServer,
|
|
9
11
|
type TaServer,
|
|
10
12
|
type TaSettingsServer,
|
|
11
13
|
type TaTerminalServer,
|
|
@@ -83,6 +85,7 @@ export class TaWorkbenchImpl implements TaWorkbench {
|
|
|
83
85
|
protected readonly terminalListeners = new Set<(terminalId: string, data: string, exitCode?: number) => void>();
|
|
84
86
|
protected readonly extensionDataListeners = new Set<(name: string, keys: string[], cwd?: string) => void>();
|
|
85
87
|
protected readonly authListeners = new Set<(loginId: string, event: TaAuthEvent) => void>();
|
|
88
|
+
protected readonly remoteListeners = new Set<(event: TaRemoteEvent) => void>();
|
|
86
89
|
|
|
87
90
|
get state(): WorkbenchState {
|
|
88
91
|
return this.current;
|
|
@@ -116,6 +119,10 @@ export class TaWorkbenchImpl implements TaWorkbench {
|
|
|
116
119
|
return this.signedIn().relay;
|
|
117
120
|
}
|
|
118
121
|
|
|
122
|
+
get remote(): TaRemoteServer {
|
|
123
|
+
return this.signedIn().remote;
|
|
124
|
+
}
|
|
125
|
+
|
|
119
126
|
protected signedIn(): TaConnection {
|
|
120
127
|
if (!this.connection) {
|
|
121
128
|
throw new Error('Not signed in to ta serve.');
|
|
@@ -138,6 +145,11 @@ export class TaWorkbenchImpl implements TaWorkbench {
|
|
|
138
145
|
return () => this.authListeners.delete(listener);
|
|
139
146
|
}
|
|
140
147
|
|
|
148
|
+
onRemoteEvent(listener: (event: TaRemoteEvent) => void): () => void {
|
|
149
|
+
this.remoteListeners.add(listener);
|
|
150
|
+
return () => this.remoteListeners.delete(listener);
|
|
151
|
+
}
|
|
152
|
+
|
|
141
153
|
async setWorkingDirectory(cwd: string): Promise<void> {
|
|
142
154
|
this.update({ server: await this.server.setWorkingDirectory(cwd) });
|
|
143
155
|
}
|
|
@@ -239,12 +251,15 @@ export class TaWorkbenchImpl implements TaWorkbench {
|
|
|
239
251
|
listener(loginId, event);
|
|
240
252
|
}
|
|
241
253
|
});
|
|
254
|
+
connection.onRemoteEvent(event => this.emitRemote(event));
|
|
242
255
|
connection.onClose(() => this.update({ status: 'offline' }));
|
|
243
256
|
connection.onReconnecting(delayMs => this.update({ retryAt: Date.now() + delayMs }));
|
|
244
257
|
connection.onOpen(() => {
|
|
245
258
|
this.update({ status: 'online', retryAt: undefined });
|
|
246
259
|
void this.refresh();
|
|
260
|
+
void this.watchRemote(connection);
|
|
247
261
|
});
|
|
262
|
+
void this.watchRemote(connection);
|
|
248
263
|
this.update({ status: 'online', error: undefined, retryAt: undefined, server: await connection.server.getServerInfo() });
|
|
249
264
|
await this.refresh();
|
|
250
265
|
}
|
|
@@ -280,6 +295,20 @@ export class TaWorkbenchImpl implements TaWorkbench {
|
|
|
280
295
|
return new SessionStore(this.connection, sessionId, options);
|
|
281
296
|
}
|
|
282
297
|
|
|
298
|
+
/** The prompts of ssh that wait for an answer, from now and those already there. */
|
|
299
|
+
protected async watchRemote(connection: TaConnection): Promise<void> {
|
|
300
|
+
const prompts = await connection.remote.watch().catch(() => []);
|
|
301
|
+
for (const prompt of prompts) {
|
|
302
|
+
this.emitRemote({ type: 'prompt', prompt });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
protected emitRemote(event: TaRemoteEvent): void {
|
|
307
|
+
for (const listener of this.remoteListeners) {
|
|
308
|
+
listener(event);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
283
312
|
protected stopRetrying(): void {
|
|
284
313
|
this.generation++;
|
|
285
314
|
clearTimeout(this.retryTimer);
|