mirror of
https://github.com/Eugeny/tabby.git
synced 2025-08-30 21:21:52 +00:00
separated ssh session and shell session classes
This commit is contained in:
@@ -42,7 +42,7 @@ sftp-panel.bg-dark(
|
|||||||
[(path)]='sftpPath',
|
[(path)]='sftpPath',
|
||||||
*ngIf='sftpPanelVisible',
|
*ngIf='sftpPanelVisible',
|
||||||
(click)='$event.stopPropagation()',
|
(click)='$event.stopPropagation()',
|
||||||
[session]='session',
|
[session]='sshSession',
|
||||||
(closed)='sftpPanelVisible = false'
|
(closed)='sftpPanelVisible = false'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ import { SSHService } from '../services/ssh.service'
|
|||||||
import { KeyboardInteractivePrompt, SSHSession } from '../session/ssh'
|
import { KeyboardInteractivePrompt, SSHSession } from '../session/ssh'
|
||||||
import { SSHPortForwardingModalComponent } from './sshPortForwardingModal.component'
|
import { SSHPortForwardingModalComponent } from './sshPortForwardingModal.component'
|
||||||
import { SSHProfile } from '../api'
|
import { SSHProfile } from '../api'
|
||||||
|
import { SSHShellSession } from '../session/shell'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
@@ -20,7 +20,8 @@ import { SSHProfile } from '../api'
|
|||||||
export class SSHTabComponent extends BaseTerminalTabComponent {
|
export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||||
Platform = Platform
|
Platform = Platform
|
||||||
profile?: SSHProfile
|
profile?: SSHProfile
|
||||||
session: SSHSession|null = null
|
sshSession: SSHSession|null = null
|
||||||
|
session: SSHShellSession|null = null
|
||||||
sftpPanelVisible = false
|
sftpPanelVisible = false
|
||||||
sftpPath = '/'
|
sftpPath = '/'
|
||||||
enableToolbar = true
|
enableToolbar = true
|
||||||
@@ -63,8 +64,8 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
this.reconnect()
|
this.reconnect()
|
||||||
break
|
break
|
||||||
case 'launch-winscp':
|
case 'launch-winscp':
|
||||||
if (this.session) {
|
if (this.sshSession) {
|
||||||
this.ssh.launchWinSCP(this.session)
|
this.ssh.launchWinSCP(this.sshSession)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -96,7 +97,7 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
|
|
||||||
await this.setupOneSession(jumpSession, false)
|
await this.setupOneSession(jumpSession, false)
|
||||||
|
|
||||||
this.attachSessionHandler(jumpSession.destroyed$, () => {
|
this.attachSessionHandler(jumpSession.willDestroy$, () => {
|
||||||
if (session.open) {
|
if (session.open) {
|
||||||
session.destroy()
|
session.destroy()
|
||||||
}
|
}
|
||||||
@@ -127,10 +128,9 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
|
|
||||||
this.attachSessionHandler(session.serviceMessage$, msg => {
|
this.attachSessionHandler(session.serviceMessage$, msg => {
|
||||||
this.write(`\r${colors.black.bgWhite(' SSH ')} ${msg}\r\n`)
|
this.write(`\r${colors.black.bgWhite(' SSH ')} ${msg}\r\n`)
|
||||||
session.resize(this.size.columns, this.size.rows)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.attachSessionHandler(session.destroyed$, () => {
|
this.attachSessionHandler(session.willDestroy$, () => {
|
||||||
this.activeKIPrompt = null
|
this.activeKIPrompt = null
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
this.destroy()
|
this.destroy()
|
||||||
} else if (this.frontend) {
|
} else if (this.frontend) {
|
||||||
// Session was closed abruptly
|
// Session was closed abruptly
|
||||||
this.write('\r\n' + colors.black.bgWhite(' SSH ') + ` ${session.profile.options.host}: session closed\r\n`)
|
this.write('\r\n' + colors.black.bgWhite(' SSH ') + ` ${this.sshSession?.profile.options.host}: session closed\r\n`)
|
||||||
if (!this.reconnectOffered) {
|
if (!this.reconnectOffered) {
|
||||||
this.reconnectOffered = true
|
this.reconnectOffered = true
|
||||||
this.write('Press any key to reconnect\r\n')
|
this.write('Press any key to reconnect\r\n')
|
||||||
@@ -185,16 +185,23 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = new SSHSession(this.injector, this.profile)
|
this.sshSession = new SSHSession(this.injector, this.profile)
|
||||||
this.setSession(session)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.setupOneSession(session, true)
|
await this.setupOneSession(this.sshSession, true)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.write(colors.black.bgRed(' X ') + ' ' + colors.red(e.message) + '\r\n')
|
this.write(colors.black.bgRed(' X ') + ' ' + colors.red(e.message) + '\r\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.session!.resize(this.size.columns, this.size.rows)
|
const session = new SSHShellSession(this.injector, this.sshSession)
|
||||||
|
|
||||||
|
this.attachSessionHandler(session.serviceMessage$, msg => {
|
||||||
|
this.write(`\r${colors.black.bgWhite(' SSH ')} ${msg}\r\n`)
|
||||||
|
session.resize(this.size.columns, this.size.rows)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setSession(session)
|
||||||
|
await session.start()
|
||||||
|
this.session?.resize(this.size.columns, this.size.rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRecoveryToken (): Promise<RecoveryToken> {
|
async getRecoveryToken (): Promise<RecoveryToken> {
|
||||||
@@ -207,7 +214,7 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
|
|
||||||
showPortForwarding (): void {
|
showPortForwarding (): void {
|
||||||
const modal = this.ngbModal.open(SSHPortForwardingModalComponent).componentInstance as SSHPortForwardingModalComponent
|
const modal = this.ngbModal.open(SSHPortForwardingModalComponent).componentInstance as SSHPortForwardingModalComponent
|
||||||
modal.session = this.session!
|
modal.session = this.sshSession!
|
||||||
}
|
}
|
||||||
|
|
||||||
async reconnect (): Promise<void> {
|
async reconnect (): Promise<void> {
|
||||||
|
120
tabby-ssh/src/session/shell.ts
Normal file
120
tabby-ssh/src/session/shell.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import { Observable, Subject } from 'rxjs'
|
||||||
|
import colors from 'ansi-colors'
|
||||||
|
import stripAnsi from 'strip-ansi'
|
||||||
|
import { ClientChannel } from 'ssh2'
|
||||||
|
import { Injector } from '@angular/core'
|
||||||
|
import { LogService } from 'tabby-core'
|
||||||
|
import { BaseSession } from 'tabby-terminal'
|
||||||
|
import { SSHSession } from './ssh'
|
||||||
|
import { SSHProfile } from '../api'
|
||||||
|
|
||||||
|
|
||||||
|
export class SSHShellSession extends BaseSession {
|
||||||
|
shell?: ClientChannel
|
||||||
|
private profile: SSHProfile
|
||||||
|
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
||||||
|
private serviceMessage = new Subject<string>()
|
||||||
|
private ssh: SSHSession|null
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
injector: Injector,
|
||||||
|
ssh: SSHSession,
|
||||||
|
) {
|
||||||
|
super(injector.get(LogService).create(`ssh-shell-${ssh.profile.options.host}-${ssh.profile.options.port}`))
|
||||||
|
this.ssh = ssh
|
||||||
|
this.profile = ssh.profile
|
||||||
|
this.setLoginScriptsOptions(this.profile.options)
|
||||||
|
}
|
||||||
|
|
||||||
|
async start (): Promise<void> {
|
||||||
|
if (!this.ssh) {
|
||||||
|
throw new Error('SSH session not set')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ssh.ref()
|
||||||
|
this.ssh.willDestroy$.subscribe(() => {
|
||||||
|
this.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.logger.debug('Opening shell')
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.shell = await this.ssh.openShellChannel({ x11: this.profile.options.x11 ?? false })
|
||||||
|
} catch (err) {
|
||||||
|
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote rejected opening a shell channel: ${err}`)
|
||||||
|
if (err.toString().includes('Unable to request X11')) {
|
||||||
|
this.emitServiceMessage(' Make sure `xauth` is installed on the remote side')
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.open = true
|
||||||
|
this.logger.debug('Shell open')
|
||||||
|
|
||||||
|
this.loginScriptProcessor?.executeUnconditionalScripts()
|
||||||
|
|
||||||
|
this.shell.on('greeting', greeting => {
|
||||||
|
this.emitServiceMessage(`Shell greeting: ${greeting}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.shell.on('banner', banner => {
|
||||||
|
this.emitServiceMessage(`Shell banner: ${banner}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.shell.on('data', data => {
|
||||||
|
this.emitOutput(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.shell.on('end', () => {
|
||||||
|
this.logger.info('Shell session ended')
|
||||||
|
if (this.open) {
|
||||||
|
this.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
emitServiceMessage (msg: string): void {
|
||||||
|
this.serviceMessage.next(msg)
|
||||||
|
this.logger.info(stripAnsi(msg))
|
||||||
|
}
|
||||||
|
resize (columns: number, rows: number): void {
|
||||||
|
if (this.shell) {
|
||||||
|
this.shell.setWindow(rows, columns, rows, columns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write (data: Buffer): void {
|
||||||
|
if (this.shell) {
|
||||||
|
this.shell.write(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kill (signal?: string): void {
|
||||||
|
this.shell?.signal(signal ?? 'TERM')
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroy (): Promise<void> {
|
||||||
|
this.logger.debug('Closing shell')
|
||||||
|
this.serviceMessage.complete()
|
||||||
|
this.kill()
|
||||||
|
this.ssh?.unref()
|
||||||
|
this.ssh = null
|
||||||
|
await super.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getChildProcesses (): Promise<any[]> {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
async gracefullyKillProcess (): Promise<void> {
|
||||||
|
this.kill('TERM')
|
||||||
|
}
|
||||||
|
|
||||||
|
supportsWorkingDirectory (): boolean {
|
||||||
|
return !!this.reportedCWD
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWorkingDirectory (): Promise<string|null> {
|
||||||
|
return this.reportedCWD ?? null
|
||||||
|
}
|
||||||
|
}
|
@@ -7,8 +7,7 @@ import colors from 'ansi-colors'
|
|||||||
import stripAnsi from 'strip-ansi'
|
import stripAnsi from 'strip-ansi'
|
||||||
import { Injector, NgZone } from '@angular/core'
|
import { Injector, NgZone } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { ConfigService, FileProvidersService, HostAppService, NotificationsService, Platform, PlatformService, wrapPromise, PromptModalComponent, LogService } from 'tabby-core'
|
import { ConfigService, FileProvidersService, HostAppService, NotificationsService, Platform, PlatformService, wrapPromise, PromptModalComponent, LogService, Logger } from 'tabby-core'
|
||||||
import { BaseSession } from 'tabby-terminal'
|
|
||||||
import { Socket } from 'net'
|
import { Socket } from 'net'
|
||||||
import { Client, ClientChannel, SFTPWrapper } from 'ssh2'
|
import { Client, ClientChannel, SFTPWrapper } from 'ssh2'
|
||||||
import { Subject, Observable } from 'rxjs'
|
import { Subject, Observable } from 'rxjs'
|
||||||
@@ -48,7 +47,7 @@ export class KeyboardInteractivePrompt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SSHSession extends BaseSession {
|
export class SSHSession {
|
||||||
shell?: ClientChannel
|
shell?: ClientChannel
|
||||||
ssh: Client
|
ssh: Client
|
||||||
sftp?: SFTPWrapper
|
sftp?: SFTPWrapper
|
||||||
@@ -59,14 +58,20 @@ export class SSHSession extends BaseSession {
|
|||||||
savedPassword?: string
|
savedPassword?: string
|
||||||
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
||||||
get keyboardInteractivePrompt$ (): Observable<KeyboardInteractivePrompt> { return this.keyboardInteractivePrompt }
|
get keyboardInteractivePrompt$ (): Observable<KeyboardInteractivePrompt> { return this.keyboardInteractivePrompt }
|
||||||
|
get willDestroy$ (): Observable<void> { return this.willDestroy }
|
||||||
|
|
||||||
agentPath?: string
|
agentPath?: string
|
||||||
activePrivateKey: string|null = null
|
activePrivateKey: string|null = null
|
||||||
authUsername: string|null = null
|
authUsername: string|null = null
|
||||||
|
|
||||||
|
open = false
|
||||||
|
|
||||||
|
private logger: Logger
|
||||||
|
private refCount = 0
|
||||||
private remainingAuthMethods: AuthMethod[] = []
|
private remainingAuthMethods: AuthMethod[] = []
|
||||||
private serviceMessage = new Subject<string>()
|
private serviceMessage = new Subject<string>()
|
||||||
private keyboardInteractivePrompt = new Subject<KeyboardInteractivePrompt>()
|
private keyboardInteractivePrompt = new Subject<KeyboardInteractivePrompt>()
|
||||||
|
private willDestroy = new Subject<void>()
|
||||||
private keychainPasswordUsed = false
|
private keychainPasswordUsed = false
|
||||||
|
|
||||||
private passwordStorage: PasswordStorageService
|
private passwordStorage: PasswordStorageService
|
||||||
@@ -82,7 +87,7 @@ export class SSHSession extends BaseSession {
|
|||||||
private injector: Injector,
|
private injector: Injector,
|
||||||
public profile: SSHProfile,
|
public profile: SSHProfile,
|
||||||
) {
|
) {
|
||||||
super(injector.get(LogService).create(`ssh-${profile.options.host}-${profile.options.port}`))
|
this.logger = injector.get(LogService).create(`ssh-${profile.options.host}-${profile.options.port}`)
|
||||||
|
|
||||||
this.passwordStorage = injector.get(PasswordStorageService)
|
this.passwordStorage = injector.get(PasswordStorageService)
|
||||||
this.ngbModal = injector.get(NgbModal)
|
this.ngbModal = injector.get(NgbModal)
|
||||||
@@ -93,13 +98,11 @@ export class SSHSession extends BaseSession {
|
|||||||
this.fileProviders = injector.get(FileProvidersService)
|
this.fileProviders = injector.get(FileProvidersService)
|
||||||
this.config = injector.get(ConfigService)
|
this.config = injector.get(ConfigService)
|
||||||
|
|
||||||
this.destroyed$.subscribe(() => {
|
this.willDestroy$.subscribe(() => {
|
||||||
for (const port of this.forwardedPorts) {
|
for (const port of this.forwardedPorts) {
|
||||||
port.stopLocalListener()
|
port.stopLocalListener()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.setLoginScriptsOptions(profile.options)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async init (): Promise<void> {
|
async init (): Promise<void> {
|
||||||
@@ -307,39 +310,6 @@ export class SSHSession extends BaseSession {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.shell = await this.openShellChannel({ x11: this.profile.options.x11 })
|
|
||||||
} catch (err) {
|
|
||||||
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote rejected opening a shell channel: ${err}`)
|
|
||||||
if (err.toString().includes('Unable to request X11')) {
|
|
||||||
this.emitServiceMessage(' Make sure `xauth` is installed on the remote side')
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loginScriptProcessor?.executeUnconditionalScripts()
|
|
||||||
|
|
||||||
this.shell.on('greeting', greeting => {
|
|
||||||
this.emitServiceMessage(`Shell greeting: ${greeting}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.shell.on('banner', banner => {
|
|
||||||
this.emitServiceMessage(`Shell banner: ${banner}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.shell.on('data', data => {
|
|
||||||
this.emitOutput(data)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.shell.on('end', () => {
|
|
||||||
this.logger.info('Shell session ended')
|
|
||||||
if (this.open) {
|
|
||||||
this.destroy()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.ssh.on('tcp connection', (details, accept, reject) => {
|
this.ssh.on('tcp connection', (details, accept, reject) => {
|
||||||
this.logger.info(`Incoming forwarded connection: (remote) ${details.srcIP}:${details.srcPort} -> (local) ${details.destIP}:${details.destPort}`)
|
this.logger.info(`Incoming forwarded connection: (remote) ${details.srcIP}:${details.srcPort} -> (local) ${details.destIP}:${details.destPort}`)
|
||||||
const forward = this.forwardedPorts.find(x => x.port === details.destPort)
|
const forward = this.forwardedPorts.find(x => x.port === details.destPort)
|
||||||
@@ -557,49 +527,16 @@ export class SSHSession extends BaseSession {
|
|||||||
this.emitServiceMessage(`Stopped forwarding ${fw}`)
|
this.emitServiceMessage(`Stopped forwarding ${fw}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
resize (columns: number, rows: number): void {
|
|
||||||
if (this.shell) {
|
|
||||||
this.shell.setWindow(rows, columns, rows, columns)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write (data: Buffer): void {
|
|
||||||
if (this.shell) {
|
|
||||||
this.shell.write(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kill (signal?: string): void {
|
|
||||||
if (this.shell) {
|
|
||||||
this.shell.signal(signal ?? 'TERM')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async destroy (): Promise<void> {
|
async destroy (): Promise<void> {
|
||||||
|
this.logger.info('Destroying')
|
||||||
|
this.willDestroy.next()
|
||||||
|
this.willDestroy.complete()
|
||||||
this.serviceMessage.complete()
|
this.serviceMessage.complete()
|
||||||
this.proxyCommandStream?.destroy()
|
this.proxyCommandStream?.destroy()
|
||||||
this.kill()
|
|
||||||
this.ssh.end()
|
this.ssh.end()
|
||||||
await super.destroy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getChildProcesses (): Promise<any[]> {
|
openShellChannel (options: { x11: boolean }): Promise<ClientChannel> {
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
async gracefullyKillProcess (): Promise<void> {
|
|
||||||
this.kill('TERM')
|
|
||||||
}
|
|
||||||
|
|
||||||
supportsWorkingDirectory (): boolean {
|
|
||||||
return !!this.reportedCWD
|
|
||||||
}
|
|
||||||
|
|
||||||
async getWorkingDirectory (): Promise<string|null> {
|
|
||||||
return this.reportedCWD ?? null
|
|
||||||
}
|
|
||||||
|
|
||||||
private openShellChannel (options): Promise<ClientChannel> {
|
|
||||||
return new Promise<ClientChannel>((resolve, reject) => {
|
return new Promise<ClientChannel>((resolve, reject) => {
|
||||||
this.ssh.shell({ term: 'xterm-256color' }, options, (err, shell) => {
|
this.ssh.shell({ term: 'xterm-256color' }, options, (err, shell) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -674,4 +611,15 @@ export class SSHSession extends BaseSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ref (): void {
|
||||||
|
this.refCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
unref (): void {
|
||||||
|
this.refCount--
|
||||||
|
if (this.refCount === 0) {
|
||||||
|
this.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,7 +30,7 @@ export class SFTPContextMenu extends TabContextMenuItemProvider {
|
|||||||
items.push({
|
items.push({
|
||||||
label: 'Launch WinSCP',
|
label: 'Launch WinSCP',
|
||||||
click: (): void => {
|
click: (): void => {
|
||||||
this.ssh.launchWinSCP(tab.session!)
|
this.ssh.launchWinSCP(tab.sshSession!)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -313,6 +313,9 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
this.session?.releaseInitialDataBuffer()
|
this.session?.releaseInitialDataBuffer()
|
||||||
|
this.sessionChanged$.subscribe(() => {
|
||||||
|
this.session?.releaseInitialDataBuffer()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
this.alternateScreenActive$.subscribe(x => {
|
this.alternateScreenActive$.subscribe(x => {
|
||||||
|
@@ -16,13 +16,18 @@ export class DebugDecorator extends TerminalDecorator {
|
|||||||
let sessionOutputBuffer = ''
|
let sessionOutputBuffer = ''
|
||||||
const bufferLength = 8192
|
const bufferLength = 8192
|
||||||
|
|
||||||
this.subscribeUntilDetached(terminal, terminal.session!.output$.subscribe(data => {
|
const handler = data => {
|
||||||
sessionOutputBuffer += data
|
sessionOutputBuffer += data
|
||||||
if (sessionOutputBuffer.length > bufferLength) {
|
if (sessionOutputBuffer.length > bufferLength) {
|
||||||
sessionOutputBuffer = sessionOutputBuffer.substring(sessionOutputBuffer.length - bufferLength)
|
sessionOutputBuffer = sessionOutputBuffer.substring(sessionOutputBuffer.length - bufferLength)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
this.subscribeUntilDetached(terminal, terminal.sessionChanged$.subscribe(session => {
|
||||||
|
this.subscribeUntilDetached(terminal, session?.output$.subscribe(handler))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
this.subscribeUntilDetached(terminal, terminal.session?.output$.subscribe(handler))
|
||||||
|
|
||||||
terminal.addEventListenerUntilDestroyed(terminal.content.nativeElement, 'keyup', (e: KeyboardEvent) => {
|
terminal.addEventListenerUntilDestroyed(terminal.content.nativeElement, 'keyup', (e: KeyboardEvent) => {
|
||||||
// Ctrl-Shift-Alt-1
|
// Ctrl-Shift-Alt-1
|
||||||
if (e.which === 49 && e.ctrlKey && e.shiftKey && e.altKey) {
|
if (e.which === 49 && e.ctrlKey && e.shiftKey && e.altKey) {
|
||||||
|
Reference in New Issue
Block a user