mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-20 02:18:01 +00:00
moved login scripts processing into tabby-terminal
This commit is contained in:
@@ -2,7 +2,7 @@ import * as psNode from 'ps-node'
|
|||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import { Injector } from '@angular/core'
|
import { Injector } from '@angular/core'
|
||||||
import { HostAppService, ConfigService, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild, Platform, BootstrapData, BOOTSTRAP_DATA } from 'tabby-core'
|
import { HostAppService, ConfigService, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild, Platform, BootstrapData, BOOTSTRAP_DATA, LogService } from 'tabby-core'
|
||||||
import { BaseSession } from 'tabby-terminal'
|
import { BaseSession } from 'tabby-terminal'
|
||||||
import { ipcRenderer } from 'electron'
|
import { ipcRenderer } from 'electron'
|
||||||
import { getWorkingDirectoryFromPID } from 'native-process-working-directory'
|
import { getWorkingDirectoryFromPID } from 'native-process-working-directory'
|
||||||
@@ -97,7 +97,7 @@ export class Session extends BaseSession {
|
|||||||
private bootstrapData: BootstrapData
|
private bootstrapData: BootstrapData
|
||||||
|
|
||||||
constructor (injector: Injector) {
|
constructor (injector: Injector) {
|
||||||
super()
|
super(injector.get(LogService).create('local'))
|
||||||
this.config = injector.get(ConfigService)
|
this.config = injector.get(ConfigService)
|
||||||
this.hostApp = injector.get(HostAppService)
|
this.hostApp = injector.get(HostAppService)
|
||||||
this.bootstrapData = injector.get(BOOTSTRAP_DATA)
|
this.bootstrapData = injector.get(BOOTSTRAP_DATA)
|
||||||
|
@@ -1,22 +1,15 @@
|
|||||||
import stripAnsi from 'strip-ansi'
|
import stripAnsi from 'strip-ansi'
|
||||||
import SerialPort from 'serialport'
|
import SerialPort from 'serialport'
|
||||||
import { Logger, LogService, NotificationsService, Profile } from 'tabby-core'
|
import { LogService, NotificationsService, Profile } from 'tabby-core'
|
||||||
import { Subject, Observable } from 'rxjs'
|
import { Subject, Observable } from 'rxjs'
|
||||||
import { Injector, NgZone } from '@angular/core'
|
import { Injector, NgZone } from '@angular/core'
|
||||||
import { BaseSession, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal'
|
import { BaseSession, LoginScriptsOptions, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal'
|
||||||
|
|
||||||
export interface LoginScript {
|
|
||||||
expect: string
|
|
||||||
send: string
|
|
||||||
isRegex?: boolean
|
|
||||||
optional?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SerialProfile extends Profile {
|
export interface SerialProfile extends Profile {
|
||||||
options: SerialProfileOptions
|
options: SerialProfileOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SerialProfileOptions extends StreamProcessingOptions {
|
export interface SerialProfileOptions extends StreamProcessingOptions, LoginScriptsOptions {
|
||||||
port: string
|
port: string
|
||||||
baudrate?: number
|
baudrate?: number
|
||||||
databits?: number
|
databits?: number
|
||||||
@@ -26,7 +19,6 @@ export interface SerialProfileOptions extends StreamProcessingOptions {
|
|||||||
xon?: boolean
|
xon?: boolean
|
||||||
xoff?: boolean
|
xoff?: boolean
|
||||||
xany?: boolean
|
xany?: boolean
|
||||||
scripts?: LoginScript[]
|
|
||||||
color?: string
|
color?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,9 +32,7 @@ export interface SerialPortInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SerialSession extends BaseSession {
|
export class SerialSession extends BaseSession {
|
||||||
scripts?: LoginScript[]
|
|
||||||
serial: SerialPort
|
serial: SerialPort
|
||||||
logger: Logger
|
|
||||||
|
|
||||||
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
||||||
private serviceMessage = new Subject<string>()
|
private serviceMessage = new Subject<string>()
|
||||||
@@ -51,62 +41,20 @@ export class SerialSession extends BaseSession {
|
|||||||
private notifications: NotificationsService
|
private notifications: NotificationsService
|
||||||
|
|
||||||
constructor (injector: Injector, public profile: SerialProfile) {
|
constructor (injector: Injector, public profile: SerialProfile) {
|
||||||
super()
|
super(injector.get(LogService).create(`serial-${profile.options.port}`))
|
||||||
|
|
||||||
this.logger = injector.get(LogService).create(`serial-${profile.options.port}`)
|
|
||||||
this.zone = injector.get(NgZone)
|
this.zone = injector.get(NgZone)
|
||||||
this.notifications = injector.get(NotificationsService)
|
this.notifications = injector.get(NotificationsService)
|
||||||
|
|
||||||
this.scripts = profile.options.scripts ?? []
|
|
||||||
this.streamProcessor = new TerminalStreamProcessor(profile.options)
|
this.streamProcessor = new TerminalStreamProcessor(profile.options)
|
||||||
this.streamProcessor.outputToSession$.subscribe(data => {
|
this.streamProcessor.outputToSession$.subscribe(data => {
|
||||||
this.serial?.write(data.toString())
|
this.serial?.write(data.toString())
|
||||||
})
|
})
|
||||||
this.streamProcessor.outputToTerminal$.subscribe(data => {
|
this.streamProcessor.outputToTerminal$.subscribe(data => {
|
||||||
this.emitOutput(data)
|
this.emitOutput(data)
|
||||||
|
this.loginScriptProcessor?.feedFromSession(data)
|
||||||
const dataString = data.toString()
|
|
||||||
|
|
||||||
if (this.scripts) {
|
|
||||||
let found = false
|
|
||||||
for (const script of this.scripts) {
|
|
||||||
let match = false
|
|
||||||
let cmd = ''
|
|
||||||
if (script.isRegex) {
|
|
||||||
const re = new RegExp(script.expect, 'g')
|
|
||||||
if (re.test(dataString)) {
|
|
||||||
cmd = dataString.replace(re, script.send)
|
|
||||||
match = true
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (dataString.includes(script.expect)) {
|
|
||||||
cmd = script.send
|
|
||||||
match = true
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
this.logger.info('Executing script: "' + cmd + '"')
|
|
||||||
this.serial.write(cmd + '\n')
|
|
||||||
this.scripts = this.scripts.filter(x => x !== script)
|
|
||||||
} else {
|
|
||||||
if (script.optional) {
|
|
||||||
this.logger.debug('Skip optional script: ' + script.expect)
|
|
||||||
found = true
|
|
||||||
this.scripts = this.scripts.filter(x => x !== script)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found) {
|
|
||||||
this.executeUnconditionalScripts()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.setLoginScriptsOptions(profile.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
async start (): Promise<void> {
|
async start (): Promise<void> {
|
||||||
@@ -151,6 +99,7 @@ export class SerialSession extends BaseSession {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.open = true
|
this.open = true
|
||||||
|
setTimeout(() => this.streamProcessor.start())
|
||||||
|
|
||||||
this.serial.on('readable', () => {
|
this.serial.on('readable', () => {
|
||||||
this.streamProcessor.feedFromSession(this.serial.read())
|
this.streamProcessor.feedFromSession(this.serial.read())
|
||||||
@@ -163,7 +112,7 @@ export class SerialSession extends BaseSession {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.executeUnconditionalScripts()
|
this.loginScriptProcessor?.executeUnconditionalScripts()
|
||||||
}
|
}
|
||||||
|
|
||||||
write (data: Buffer): void {
|
write (data: Buffer): void {
|
||||||
@@ -205,18 +154,4 @@ export class SerialSession extends BaseSession {
|
|||||||
async getWorkingDirectory (): Promise<string|null> {
|
async getWorkingDirectory (): Promise<string|null> {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private executeUnconditionalScripts () {
|
|
||||||
if (this.scripts) {
|
|
||||||
for (const script of this.scripts) {
|
|
||||||
if (!script.expect) {
|
|
||||||
console.log('Executing script:', script.send)
|
|
||||||
this.serial.write(script.send + '\n')
|
|
||||||
this.scripts = this.scripts.filter(x => x !== script)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -80,43 +80,6 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
|||||||
li(ngbNavItem)
|
li(ngbNavItem)
|
||||||
a(ngbNavLink) Login scripts
|
a(ngbNavLink) Login scripts
|
||||||
ng-template(ngbNavContent)
|
ng-template(ngbNavContent)
|
||||||
table(*ngIf='profile.options.scripts.length > 0')
|
login-scripts-settings([options]='profile.options')
|
||||||
tr
|
|
||||||
th String to expect
|
|
||||||
th String to be sent
|
|
||||||
th.pl-2 Regex
|
|
||||||
th.pl-2 Optional
|
|
||||||
th.pl-2 Actions
|
|
||||||
tr(*ngFor='let script of profile.options.scripts')
|
|
||||||
td.pr-2
|
|
||||||
input.form-control(
|
|
||||||
type='text',
|
|
||||||
[(ngModel)]='script.expect'
|
|
||||||
)
|
|
||||||
td
|
|
||||||
input.form-control(
|
|
||||||
type='text',
|
|
||||||
[(ngModel)]='script.send'
|
|
||||||
)
|
|
||||||
td.pl-2
|
|
||||||
checkbox(
|
|
||||||
[(ngModel)]='script.isRegex',
|
|
||||||
)
|
|
||||||
td.pl-2
|
|
||||||
checkbox(
|
|
||||||
[(ngModel)]='script.optional',
|
|
||||||
)
|
|
||||||
td.pl-2
|
|
||||||
.input-group.flex-nowrap
|
|
||||||
button.btn.btn-outline-info.ml-0((click)='moveScriptUp(script)')
|
|
||||||
i.fas.fa-arrow-up
|
|
||||||
button.btn.btn-outline-info.ml-0((click)='moveScriptDown(script)')
|
|
||||||
i.fas.fa-arrow-down
|
|
||||||
button.btn.btn-outline-danger.ml-0((click)='deleteScript(script)')
|
|
||||||
i.fas.fa-trash
|
|
||||||
|
|
||||||
button.btn.btn-outline-info.mt-2((click)='addScript()')
|
|
||||||
i.fas.fa-plus
|
|
||||||
span New item
|
|
||||||
|
|
||||||
div([ngbNavOutlet]='nav')
|
div([ngbNavOutlet]='nav')
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
|
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
|
||||||
import { PlatformService, ProfileSettingsComponent } from 'tabby-core'
|
import { ProfileSettingsComponent } from 'tabby-core'
|
||||||
import { LoginScript, SerialPortInfo, BAUD_RATES, SerialProfile } from '../api'
|
import { SerialPortInfo, BAUD_RATES, SerialProfile } from '../api'
|
||||||
import { SerialService } from '../services/serial.service'
|
import { SerialService } from '../services/serial.service'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@@ -14,7 +14,6 @@ export class SerialProfileSettingsComponent implements ProfileSettingsComponent
|
|||||||
foundPorts: SerialPortInfo[]
|
foundPorts: SerialPortInfo[]
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private platform: PlatformService,
|
|
||||||
private serial: SerialService,
|
private serial: SerialService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@@ -40,50 +39,6 @@ export class SerialProfileSettingsComponent implements ProfileSettingsComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit () {
|
async ngOnInit () {
|
||||||
this.profile.options.scripts = this.profile.options.scripts ?? []
|
|
||||||
this.foundPorts = await this.serial.listPorts()
|
this.foundPorts = await this.serial.listPorts()
|
||||||
}
|
}
|
||||||
|
|
||||||
moveScriptUp (script: LoginScript) {
|
|
||||||
if (!this.profile.options.scripts) {
|
|
||||||
this.profile.options.scripts = []
|
|
||||||
}
|
|
||||||
const index = this.profile.options.scripts.indexOf(script)
|
|
||||||
if (index > 0) {
|
|
||||||
this.profile.options.scripts.splice(index, 1)
|
|
||||||
this.profile.options.scripts.splice(index - 1, 0, script)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveScriptDown (script: LoginScript) {
|
|
||||||
if (!this.profile.options.scripts) {
|
|
||||||
this.profile.options.scripts = []
|
|
||||||
}
|
|
||||||
const index = this.profile.options.scripts.indexOf(script)
|
|
||||||
if (index >= 0 && index < this.profile.options.scripts.length - 1) {
|
|
||||||
this.profile.options.scripts.splice(index, 1)
|
|
||||||
this.profile.options.scripts.splice(index + 1, 0, script)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteScript (script: LoginScript) {
|
|
||||||
if (this.profile.options.scripts && (await this.platform.showMessageBox(
|
|
||||||
{
|
|
||||||
type: 'warning',
|
|
||||||
message: 'Delete this script?',
|
|
||||||
detail: script.expect,
|
|
||||||
buttons: ['Keep', 'Delete'],
|
|
||||||
defaultId: 1,
|
|
||||||
}
|
|
||||||
)).response === 1) {
|
|
||||||
this.profile.options.scripts = this.profile.options.scripts.filter(x => x !== script)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addScript () {
|
|
||||||
if (!this.profile.options.scripts) {
|
|
||||||
this.profile.options.scripts = []
|
|
||||||
}
|
|
||||||
this.profile.options.scripts.push({ expect: '', send: '' })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -10,8 +10,8 @@ import stripAnsi from 'strip-ansi'
|
|||||||
import socksv5 from 'socksv5'
|
import socksv5 from 'socksv5'
|
||||||
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, Logger, NotificationsService, Platform, PlatformService, wrapPromise, PromptModalComponent, Profile, LogService } from 'tabby-core'
|
import { ConfigService, FileProvidersService, HostAppService, NotificationsService, Platform, PlatformService, wrapPromise, PromptModalComponent, Profile, LogService } from 'tabby-core'
|
||||||
import { BaseSession } from 'tabby-terminal'
|
import { BaseSession, LoginScriptsOptions } from 'tabby-terminal'
|
||||||
import { Server, Socket, createServer, createConnection } from 'net'
|
import { Server, Socket, createServer, createConnection } from 'net'
|
||||||
import { Client, ClientChannel, SFTPWrapper } from 'ssh2'
|
import { Client, ClientChannel, SFTPWrapper } from 'ssh2'
|
||||||
import type { FileEntry, Stats } from 'ssh2-streams'
|
import type { FileEntry, Stats } from 'ssh2-streams'
|
||||||
@@ -22,13 +22,6 @@ import { promisify } from 'util'
|
|||||||
|
|
||||||
const WINDOWS_OPENSSH_AGENT_PIPE = '\\\\.\\pipe\\openssh-ssh-agent'
|
const WINDOWS_OPENSSH_AGENT_PIPE = '\\\\.\\pipe\\openssh-ssh-agent'
|
||||||
|
|
||||||
export interface LoginScript {
|
|
||||||
expect: string
|
|
||||||
send: string
|
|
||||||
isRegex?: boolean
|
|
||||||
optional?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SSHAlgorithmType {
|
export enum SSHAlgorithmType {
|
||||||
HMAC = 'hmac',
|
HMAC = 'hmac',
|
||||||
KEX = 'kex',
|
KEX = 'kex',
|
||||||
@@ -40,14 +33,13 @@ export interface SSHProfile extends Profile {
|
|||||||
options: SSHProfileOptions
|
options: SSHProfileOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SSHProfileOptions {
|
export interface SSHProfileOptions extends LoginScriptsOptions {
|
||||||
host: string
|
host: string
|
||||||
port?: number
|
port?: number
|
||||||
user: string
|
user: string
|
||||||
auth?: null|'password'|'publicKey'|'agent'|'keyboardInteractive'
|
auth?: null|'password'|'publicKey'|'agent'|'keyboardInteractive'
|
||||||
password?: string
|
password?: string
|
||||||
privateKeys?: string[]
|
privateKeys?: string[]
|
||||||
scripts?: LoginScript[]
|
|
||||||
keepaliveInterval?: number
|
keepaliveInterval?: number
|
||||||
keepaliveCountMax?: number
|
keepaliveCountMax?: number
|
||||||
readyTimeout?: number
|
readyTimeout?: number
|
||||||
@@ -255,12 +247,10 @@ export class SFTPSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SSHSession extends BaseSession {
|
export class SSHSession extends BaseSession {
|
||||||
scripts?: LoginScript[]
|
|
||||||
shell?: ClientChannel
|
shell?: ClientChannel
|
||||||
ssh: Client
|
ssh: Client
|
||||||
sftp?: SFTPWrapper
|
sftp?: SFTPWrapper
|
||||||
forwardedPorts: ForwardedPort[] = []
|
forwardedPorts: ForwardedPort[] = []
|
||||||
logger: Logger
|
|
||||||
jumpStream: any
|
jumpStream: any
|
||||||
proxyCommandStream: ProxyCommandStream|null = null
|
proxyCommandStream: ProxyCommandStream|null = null
|
||||||
savedPassword?: string
|
savedPassword?: string
|
||||||
@@ -286,8 +276,7 @@ export class SSHSession extends BaseSession {
|
|||||||
injector: Injector,
|
injector: Injector,
|
||||||
public profile: SSHProfile,
|
public profile: SSHProfile,
|
||||||
) {
|
) {
|
||||||
super()
|
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)
|
||||||
@@ -298,7 +287,6 @@ 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.scripts = profile.options.scripts ?? []
|
|
||||||
this.destroyed$.subscribe(() => {
|
this.destroyed$.subscribe(() => {
|
||||||
for (const port of this.forwardedPorts) {
|
for (const port of this.forwardedPorts) {
|
||||||
if (port.type === PortForwardType.Local) {
|
if (port.type === PortForwardType.Local) {
|
||||||
@@ -306,6 +294,8 @@ export class SSHSession extends BaseSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.setLoginScriptsOptions(profile.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
async init (): Promise<void> {
|
async init (): Promise<void> {
|
||||||
@@ -389,6 +379,8 @@ export class SSHSession extends BaseSession {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.loginScriptProcessor?.executeUnconditionalScripts()
|
||||||
|
|
||||||
this.shell.on('greeting', greeting => {
|
this.shell.on('greeting', greeting => {
|
||||||
this.emitServiceMessage(`Shell greeting: ${greeting}`)
|
this.emitServiceMessage(`Shell greeting: ${greeting}`)
|
||||||
})
|
})
|
||||||
@@ -398,48 +390,7 @@ export class SSHSession extends BaseSession {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.shell.on('data', data => {
|
this.shell.on('data', data => {
|
||||||
const dataString = data.toString()
|
|
||||||
this.emitOutput(data)
|
this.emitOutput(data)
|
||||||
|
|
||||||
if (this.scripts) {
|
|
||||||
let found = false
|
|
||||||
for (const script of this.scripts) {
|
|
||||||
let match = false
|
|
||||||
let cmd = ''
|
|
||||||
if (script.isRegex) {
|
|
||||||
const re = new RegExp(script.expect, 'g')
|
|
||||||
if (dataString.match(re)) {
|
|
||||||
cmd = dataString.replace(re, script.send)
|
|
||||||
match = true
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (dataString.includes(script.expect)) {
|
|
||||||
cmd = script.send
|
|
||||||
match = true
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match) {
|
|
||||||
this.logger.info('Executing script: "' + cmd + '"')
|
|
||||||
this.shell?.write(cmd + '\n')
|
|
||||||
this.scripts = this.scripts.filter(x => x !== script)
|
|
||||||
} else {
|
|
||||||
if (script.optional) {
|
|
||||||
this.logger.debug('Skip optional script: ' + script.expect)
|
|
||||||
found = true
|
|
||||||
this.scripts = this.scripts.filter(x => x !== script)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found) {
|
|
||||||
this.executeUnconditionalScripts()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.shell.on('end', () => {
|
this.shell.on('end', () => {
|
||||||
@@ -513,8 +464,6 @@ export class SSHSession extends BaseSession {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
this.executeUnconditionalScripts()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emitServiceMessage (msg: string): void {
|
emitServiceMessage (msg: string): void {
|
||||||
@@ -714,20 +663,6 @@ export class SSHSession extends BaseSession {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private executeUnconditionalScripts () {
|
|
||||||
if (this.scripts) {
|
|
||||||
for (const script of this.scripts) {
|
|
||||||
if (!script.expect) {
|
|
||||||
console.log('Executing script:', script.send)
|
|
||||||
this.shell?.write(script.send + '\n')
|
|
||||||
this.scripts = this.scripts.filter(x => x !== script)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadPrivateKey (privateKeyContents?: Buffer): Promise<string|null> {
|
async loadPrivateKey (privateKeyContents?: Buffer): Promise<string|null> {
|
||||||
if (!privateKeyContents) {
|
if (!privateKeyContents) {
|
||||||
const userKeyPath = path.join(process.env.HOME!, '.ssh', 'id_rsa')
|
const userKeyPath = path.join(process.env.HOME!, '.ssh', 'id_rsa')
|
||||||
|
@@ -189,43 +189,6 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
|
|||||||
li(ngbNavItem)
|
li(ngbNavItem)
|
||||||
a(ngbNavLink) Login scripts
|
a(ngbNavLink) Login scripts
|
||||||
ng-template(ngbNavContent)
|
ng-template(ngbNavContent)
|
||||||
table(*ngIf='profile.options.scripts.length > 0')
|
login-scripts-settings([options]='profile.options')
|
||||||
tr
|
|
||||||
th String to expect
|
|
||||||
th String to be sent
|
|
||||||
th.pl-2 Regex
|
|
||||||
th.pl-2 Optional
|
|
||||||
th.pl-2 Actions
|
|
||||||
tr(*ngFor='let script of profile.options.scripts')
|
|
||||||
td.pr-2
|
|
||||||
input.form-control(
|
|
||||||
type='text',
|
|
||||||
[(ngModel)]='script.expect'
|
|
||||||
)
|
|
||||||
td
|
|
||||||
input.form-control(
|
|
||||||
type='text',
|
|
||||||
[(ngModel)]='script.send'
|
|
||||||
)
|
|
||||||
td.pl-2
|
|
||||||
checkbox(
|
|
||||||
[(ngModel)]='script.isRegex',
|
|
||||||
)
|
|
||||||
td.pl-2
|
|
||||||
checkbox(
|
|
||||||
[(ngModel)]='script.optional',
|
|
||||||
)
|
|
||||||
td.pl-2
|
|
||||||
.input-group.flex-nowrap
|
|
||||||
button.btn.btn-outline-info.ml-0((click)='moveScriptUp(script)')
|
|
||||||
i.fas.fa-arrow-up
|
|
||||||
button.btn.btn-outline-info.ml-0((click)='moveScriptDown(script)')
|
|
||||||
i.fas.fa-arrow-down
|
|
||||||
button.btn.btn-outline-danger.ml-0((click)='deleteScript(script)')
|
|
||||||
i.fas.fa-trash
|
|
||||||
|
|
||||||
button.btn.btn-outline-info.mt-2((click)='addScript()')
|
|
||||||
i.fas.fa-plus
|
|
||||||
span New item
|
|
||||||
|
|
||||||
div([ngbNavOutlet]='nav')
|
div([ngbNavOutlet]='nav')
|
||||||
|
@@ -2,9 +2,9 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
import { ConfigService, PlatformService, FileProvidersService, Platform, HostAppService, PromptModalComponent } from 'tabby-core'
|
import { ConfigService, FileProvidersService, Platform, HostAppService, PromptModalComponent } from 'tabby-core'
|
||||||
import { PasswordStorageService } from '../services/passwordStorage.service'
|
import { PasswordStorageService } from '../services/passwordStorage.service'
|
||||||
import { LoginScript, ForwardedPortConfig, SSHAlgorithmType, ALGORITHM_BLACKLIST, SSHProfile } from '../api'
|
import { ForwardedPortConfig, SSHAlgorithmType, ALGORITHM_BLACKLIST, SSHProfile } from '../api'
|
||||||
import * as ALGORITHMS from 'ssh2/lib/protocol/constants'
|
import * as ALGORITHMS from 'ssh2/lib/protocol/constants'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@@ -23,9 +23,8 @@ export class SSHProfileSettingsComponent {
|
|||||||
jumpHosts: SSHProfile[]
|
jumpHosts: SSHProfile[]
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public config: ConfigService,
|
|
||||||
public hostApp: HostAppService,
|
public hostApp: HostAppService,
|
||||||
private platform: PlatformService,
|
private config: ConfigService,
|
||||||
private passwordStorage: PasswordStorageService,
|
private passwordStorage: PasswordStorageService,
|
||||||
private ngbModal: NgbModal,
|
private ngbModal: NgbModal,
|
||||||
private fileProviders: FileProvidersService,
|
private fileProviders: FileProvidersService,
|
||||||
@@ -63,7 +62,6 @@ export class SSHProfileSettingsComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.profile.options.scripts = this.profile.options.scripts ?? []
|
|
||||||
this.profile.options.auth = this.profile.options.auth ?? null
|
this.profile.options.auth = this.profile.options.auth ?? null
|
||||||
this.profile.options.privateKeys ??= []
|
this.profile.options.privateKeys ??= []
|
||||||
|
|
||||||
@@ -116,49 +114,6 @@ export class SSHProfileSettingsComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveScriptUp (script: LoginScript) {
|
|
||||||
if (!this.profile.options.scripts) {
|
|
||||||
this.profile.options.scripts = []
|
|
||||||
}
|
|
||||||
const index = this.profile.options.scripts.indexOf(script)
|
|
||||||
if (index > 0) {
|
|
||||||
this.profile.options.scripts.splice(index, 1)
|
|
||||||
this.profile.options.scripts.splice(index - 1, 0, script)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
moveScriptDown (script: LoginScript) {
|
|
||||||
if (!this.profile.options.scripts) {
|
|
||||||
this.profile.options.scripts = []
|
|
||||||
}
|
|
||||||
const index = this.profile.options.scripts.indexOf(script)
|
|
||||||
if (index >= 0 && index < this.profile.options.scripts.length - 1) {
|
|
||||||
this.profile.options.scripts.splice(index, 1)
|
|
||||||
this.profile.options.scripts.splice(index + 1, 0, script)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteScript (script: LoginScript) {
|
|
||||||
if (this.profile.options.scripts && (await this.platform.showMessageBox(
|
|
||||||
{
|
|
||||||
type: 'warning',
|
|
||||||
message: 'Delete this script?',
|
|
||||||
detail: script.expect,
|
|
||||||
buttons: ['Keep', 'Delete'],
|
|
||||||
defaultId: 1,
|
|
||||||
}
|
|
||||||
)).response === 1) {
|
|
||||||
this.profile.options.scripts = this.profile.options.scripts.filter(x => x !== script)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addScript () {
|
|
||||||
if (!this.profile.options.scripts) {
|
|
||||||
this.profile.options.scripts = []
|
|
||||||
}
|
|
||||||
this.profile.options.scripts.push({ expect: '', send: '' })
|
|
||||||
}
|
|
||||||
|
|
||||||
onForwardAdded (fw: ForwardedPortConfig) {
|
onForwardAdded (fw: ForwardedPortConfig) {
|
||||||
this.profile.options.forwardedPorts = this.profile.options.forwardedPorts ?? []
|
this.profile.options.forwardedPorts = this.profile.options.forwardedPorts ?? []
|
||||||
this.profile.options.forwardedPorts.push(fw)
|
this.profile.options.forwardedPorts.push(fw)
|
||||||
|
@@ -1,3 +1,7 @@
|
|||||||
|
ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||||
|
li(ngbNavItem)
|
||||||
|
a(ngbNavLink) General
|
||||||
|
ng-template(ngbNavContent)
|
||||||
.form-group
|
.form-group
|
||||||
label Host
|
label Host
|
||||||
input.form-control(
|
input.form-control(
|
||||||
@@ -14,3 +18,10 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
stream-processing-settings([options]='profile.options')
|
stream-processing-settings([options]='profile.options')
|
||||||
|
|
||||||
|
li(ngbNavItem)
|
||||||
|
a(ngbNavLink) Login scripts
|
||||||
|
ng-template(ngbNavContent)
|
||||||
|
login-scripts-settings([options]='profile.options')
|
||||||
|
|
||||||
|
div([ngbNavOutlet]='nav')
|
||||||
|
@@ -20,7 +20,7 @@ export class TelnetProfilesService extends ProfileProvider {
|
|||||||
options: {
|
options: {
|
||||||
host: '',
|
host: '',
|
||||||
port: 23,
|
port: 23,
|
||||||
inputMode: 'local-echo',
|
inputMode: 'readline',
|
||||||
outputMode: null,
|
outputMode: null,
|
||||||
inputNewlines: null,
|
inputNewlines: null,
|
||||||
outputNewlines: 'crlf',
|
outputNewlines: 'crlf',
|
||||||
@@ -58,7 +58,7 @@ export class TelnetProfilesService extends ProfileProvider {
|
|||||||
options: {
|
options: {
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
inputMode: 'local-echo',
|
inputMode: 'readline',
|
||||||
outputNewlines: 'crlf',
|
outputNewlines: 'crlf',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -2,8 +2,8 @@ import { Socket } from 'net'
|
|||||||
import colors from 'ansi-colors'
|
import colors from 'ansi-colors'
|
||||||
import stripAnsi from 'strip-ansi'
|
import stripAnsi from 'strip-ansi'
|
||||||
import { Injector } from '@angular/core'
|
import { Injector } from '@angular/core'
|
||||||
import { Logger, Profile, LogService } from 'tabby-core'
|
import { Profile, LogService } from 'tabby-core'
|
||||||
import { BaseSession, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal'
|
import { BaseSession, LoginScriptsOptions, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal'
|
||||||
import { Subject, Observable } from 'rxjs'
|
import { Subject, Observable } from 'rxjs'
|
||||||
|
|
||||||
|
|
||||||
@@ -11,13 +11,12 @@ export interface TelnetProfile extends Profile {
|
|||||||
options: TelnetProfileOptions
|
options: TelnetProfileOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TelnetProfileOptions extends StreamProcessingOptions {
|
export interface TelnetProfileOptions extends StreamProcessingOptions, LoginScriptsOptions {
|
||||||
host: string
|
host: string
|
||||||
port?: number
|
port?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TelnetSession extends BaseSession {
|
export class TelnetSession extends BaseSession {
|
||||||
logger: Logger
|
|
||||||
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
||||||
|
|
||||||
private serviceMessage = new Subject<string>()
|
private serviceMessage = new Subject<string>()
|
||||||
@@ -28,8 +27,7 @@ export class TelnetSession extends BaseSession {
|
|||||||
injector: Injector,
|
injector: Injector,
|
||||||
public profile: TelnetProfile,
|
public profile: TelnetProfile,
|
||||||
) {
|
) {
|
||||||
super()
|
super(injector.get(LogService).create(`telnet-${profile.options.host}-${profile.options.port}`))
|
||||||
this.logger = injector.get(LogService).create(`telnet-${profile.options.host}-${profile.options.port}`)
|
|
||||||
this.streamProcessor = new TerminalStreamProcessor(profile.options)
|
this.streamProcessor = new TerminalStreamProcessor(profile.options)
|
||||||
this.streamProcessor.outputToSession$.subscribe(data => {
|
this.streamProcessor.outputToSession$.subscribe(data => {
|
||||||
this.socket.write(data)
|
this.socket.write(data)
|
||||||
@@ -37,6 +35,7 @@ export class TelnetSession extends BaseSession {
|
|||||||
this.streamProcessor.outputToTerminal$.subscribe(data => {
|
this.streamProcessor.outputToTerminal$.subscribe(data => {
|
||||||
this.emitOutput(data)
|
this.emitOutput(data)
|
||||||
})
|
})
|
||||||
|
this.setLoginScriptsOptions(profile.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
async start (): Promise<void> {
|
async start (): Promise<void> {
|
||||||
@@ -57,6 +56,8 @@ export class TelnetSession extends BaseSession {
|
|||||||
this.socket.connect(this.profile.options.port ?? 23, this.profile.options.host, () => {
|
this.socket.connect(this.profile.options.port ?? 23, this.profile.options.host, () => {
|
||||||
this.emitServiceMessage('Connected')
|
this.emitServiceMessage('Connected')
|
||||||
this.open = true
|
this.open = true
|
||||||
|
setTimeout(() => this.streamProcessor.start())
|
||||||
|
this.loginScriptProcessor?.executeUnconditionalScripts()
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
86
tabby-terminal/src/api/loginScriptProcessing.ts
Normal file
86
tabby-terminal/src/api/loginScriptProcessing.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { Subject, Observable } from 'rxjs'
|
||||||
|
import { Logger } from 'tabby-core'
|
||||||
|
|
||||||
|
export interface LoginScript {
|
||||||
|
expect: string
|
||||||
|
send: string
|
||||||
|
isRegex?: boolean
|
||||||
|
optional?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginScriptsOptions {
|
||||||
|
scripts?: LoginScript[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LoginScriptProcessor {
|
||||||
|
get outputToSession$ (): Observable<Buffer> { return this.outputToSession }
|
||||||
|
|
||||||
|
private outputToSession = new Subject<Buffer>()
|
||||||
|
private remainingScripts: LoginScript[] = []
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private logger: Logger,
|
||||||
|
options: LoginScriptsOptions
|
||||||
|
) {
|
||||||
|
this.remainingScripts = options.scripts ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
feedFromSession (data: Buffer): boolean {
|
||||||
|
const dataString = data.toString()
|
||||||
|
|
||||||
|
let found = false
|
||||||
|
for (const script of this.remainingScripts) {
|
||||||
|
if (!script.expect) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let match = false
|
||||||
|
let cmd = ''
|
||||||
|
if (script.isRegex) {
|
||||||
|
const re = new RegExp(script.expect, 'g')
|
||||||
|
if (re.exec(dataString)) {
|
||||||
|
cmd = dataString.replace(re, script.send)
|
||||||
|
match = true
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (dataString.includes(script.expect)) {
|
||||||
|
cmd = script.send
|
||||||
|
match = true
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
this.logger.info('Executing script: "' + cmd + '"')
|
||||||
|
this.outputToSession.next(Buffer.from(cmd + '\n'))
|
||||||
|
this.remainingScripts = this.remainingScripts.filter(x => x !== script)
|
||||||
|
} else {
|
||||||
|
if (script.optional) {
|
||||||
|
this.logger.debug('Skip optional script: ' + script.expect)
|
||||||
|
found = true
|
||||||
|
this.remainingScripts = this.remainingScripts.filter(x => x !== script)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
close (): void {
|
||||||
|
this.outputToSession.complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
executeUnconditionalScripts (): void {
|
||||||
|
for (const script of this.remainingScripts) {
|
||||||
|
if (!script.expect) {
|
||||||
|
this.logger.info('Executing script:', script.send)
|
||||||
|
this.outputToSession.next(Buffer.from(script.send + '\n'))
|
||||||
|
this.remainingScripts = this.remainingScripts.filter(x => x !== script)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -26,9 +26,10 @@ export class TerminalStreamProcessor {
|
|||||||
protected outputToTerminal = new Subject<Buffer>()
|
protected outputToTerminal = new Subject<Buffer>()
|
||||||
|
|
||||||
private inputReadline: ReadLine
|
private inputReadline: ReadLine
|
||||||
private inputPromptVisible = true
|
private inputPromptVisible = false
|
||||||
private inputReadlineInStream: Readable & Writable
|
private inputReadlineInStream: Readable & Writable
|
||||||
private inputReadlineOutStream: Readable & Writable
|
private inputReadlineOutStream: Readable & Writable
|
||||||
|
private started = false
|
||||||
|
|
||||||
constructor (private options: StreamProcessingOptions) {
|
constructor (private options: StreamProcessingOptions) {
|
||||||
this.inputReadlineInStream = new PassThrough()
|
this.inputReadlineInStream = new PassThrough()
|
||||||
@@ -46,7 +47,16 @@ export class TerminalStreamProcessor {
|
|||||||
this.onTerminalInput(Buffer.from(line + '\n'))
|
this.onTerminalInput(Buffer.from(line + '\n'))
|
||||||
this.resetInputPrompt()
|
this.resetInputPrompt()
|
||||||
})
|
})
|
||||||
this.outputToTerminal$.pipe(debounce(() => interval(500))).subscribe(() => this.onOutputSettled())
|
this.outputToTerminal$.pipe(debounce(() => interval(500))).subscribe(() => {
|
||||||
|
if (this.started) {
|
||||||
|
this.onOutputSettled()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
start (): void {
|
||||||
|
this.started = true
|
||||||
|
this.onOutputSettled()
|
||||||
}
|
}
|
||||||
|
|
||||||
feedFromSession (data: Buffer): void {
|
feedFromSession (data: Buffer): void {
|
||||||
|
@@ -0,0 +1,38 @@
|
|||||||
|
table(*ngIf='options.scripts.length > 0')
|
||||||
|
tr
|
||||||
|
th String to expect
|
||||||
|
th String to be sent
|
||||||
|
th.pl-2 Regex
|
||||||
|
th.pl-2 Optional
|
||||||
|
th.pl-2 Actions
|
||||||
|
tr(*ngFor='let script of options.scripts')
|
||||||
|
td.pr-2
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='script.expect'
|
||||||
|
)
|
||||||
|
td
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='script.send'
|
||||||
|
)
|
||||||
|
td.pl-2
|
||||||
|
checkbox(
|
||||||
|
[(ngModel)]='script.isRegex',
|
||||||
|
)
|
||||||
|
td.pl-2
|
||||||
|
checkbox(
|
||||||
|
[(ngModel)]='script.optional',
|
||||||
|
)
|
||||||
|
td.pl-2
|
||||||
|
.input-group.flex-nowrap
|
||||||
|
button.btn.btn-outline-info.ml-0((click)='moveScriptUp(script)')
|
||||||
|
i.fas.fa-arrow-up
|
||||||
|
button.btn.btn-outline-info.ml-0((click)='moveScriptDown(script)')
|
||||||
|
i.fas.fa-arrow-down
|
||||||
|
button.btn.btn-outline-danger.ml-0((click)='deleteScript(script)')
|
||||||
|
i.fas.fa-trash
|
||||||
|
|
||||||
|
button.btn.btn-outline-info.mt-2((click)='addScript()')
|
||||||
|
i.fas.fa-plus
|
||||||
|
span New item
|
@@ -0,0 +1,56 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
import { Component, Input } from '@angular/core'
|
||||||
|
|
||||||
|
import { PlatformService } from 'tabby-core'
|
||||||
|
import { LoginScript, LoginScriptsOptions } from '../api/loginScriptProcessing'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
selector: 'login-scripts-settings',
|
||||||
|
template: require('./loginScriptsSettings.component.pug'),
|
||||||
|
})
|
||||||
|
export class LoginScriptsSettingsComponent {
|
||||||
|
@Input() options: LoginScriptsOptions
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private platform: PlatformService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
this.options.scripts ??= []
|
||||||
|
}
|
||||||
|
|
||||||
|
moveScriptUp (script: LoginScript) {
|
||||||
|
const index = this.options.scripts!.indexOf(script)
|
||||||
|
if (index > 0) {
|
||||||
|
this.options.scripts!.splice(index, 1)
|
||||||
|
this.options.scripts!.splice(index - 1, 0, script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveScriptDown (script: LoginScript) {
|
||||||
|
const index = this.options.scripts!.indexOf(script)
|
||||||
|
if (index >= 0 && index < this.options.scripts!.length - 1) {
|
||||||
|
this.options.scripts!.splice(index, 1)
|
||||||
|
this.options.scripts!.splice(index + 1, 0, script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteScript (script: LoginScript) {
|
||||||
|
if ((await this.platform.showMessageBox(
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Delete this script?',
|
||||||
|
detail: script.expect,
|
||||||
|
buttons: ['Keep', 'Delete'],
|
||||||
|
defaultId: 1,
|
||||||
|
}
|
||||||
|
)).response === 1) {
|
||||||
|
this.options.scripts = this.options.scripts!.filter(x => x !== script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addScript () {
|
||||||
|
this.options.scripts!.push({ expect: '', send: '' })
|
||||||
|
}
|
||||||
|
}
|
@@ -14,6 +14,7 @@ import { ColorPickerComponent } from './components/colorPicker.component'
|
|||||||
import { ColorSchemePreviewComponent } from './components/colorSchemePreview.component'
|
import { ColorSchemePreviewComponent } from './components/colorSchemePreview.component'
|
||||||
import { SearchPanelComponent } from './components/searchPanel.component'
|
import { SearchPanelComponent } from './components/searchPanel.component'
|
||||||
import { StreamProcessingSettingsComponent } from './components/streamProcessingSettings.component'
|
import { StreamProcessingSettingsComponent } from './components/streamProcessingSettings.component'
|
||||||
|
import { LoginScriptsSettingsComponent } from './components/loginScriptsSettings.component'
|
||||||
|
|
||||||
import { TerminalFrontendService } from './services/terminalFrontend.service'
|
import { TerminalFrontendService } from './services/terminalFrontend.service'
|
||||||
|
|
||||||
@@ -72,11 +73,13 @@ import { TerminalCLIHandler } from './cli'
|
|||||||
TerminalSettingsTabComponent,
|
TerminalSettingsTabComponent,
|
||||||
SearchPanelComponent,
|
SearchPanelComponent,
|
||||||
StreamProcessingSettingsComponent,
|
StreamProcessingSettingsComponent,
|
||||||
|
LoginScriptsSettingsComponent,
|
||||||
] as any[],
|
] as any[],
|
||||||
exports: [
|
exports: [
|
||||||
ColorPickerComponent,
|
ColorPickerComponent,
|
||||||
SearchPanelComponent,
|
SearchPanelComponent,
|
||||||
StreamProcessingSettingsComponent,
|
StreamProcessingSettingsComponent,
|
||||||
|
LoginScriptsSettingsComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export default class TerminalModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
|
export default class TerminalModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||||
@@ -115,4 +118,5 @@ export { Frontend, XTermFrontend, XTermWebGLFrontend, HTermFrontend }
|
|||||||
export { BaseTerminalTabComponent } from './api/baseTerminalTab.component'
|
export { BaseTerminalTabComponent } from './api/baseTerminalTab.component'
|
||||||
export * from './api/interfaces'
|
export * from './api/interfaces'
|
||||||
export * from './api/streamProcessing'
|
export * from './api/streamProcessing'
|
||||||
|
export * from './api/loginScriptProcessing'
|
||||||
export * from './session'
|
export * from './session'
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
import { Observable, Subject } from 'rxjs'
|
import { Observable, Subject } from 'rxjs'
|
||||||
|
import { Logger } from 'tabby-core'
|
||||||
|
import { LoginScriptProcessor, LoginScriptsOptions } from './api/loginScriptProcessing'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A session object for a [[BaseTerminalTabComponent]]
|
* A session object for a [[BaseTerminalTabComponent]]
|
||||||
@@ -12,6 +14,7 @@ export abstract class BaseSession {
|
|||||||
protected binaryOutput = new Subject<Buffer>()
|
protected binaryOutput = new Subject<Buffer>()
|
||||||
protected closed = new Subject<void>()
|
protected closed = new Subject<void>()
|
||||||
protected destroyed = new Subject<void>()
|
protected destroyed = new Subject<void>()
|
||||||
|
protected loginScriptProcessor: LoginScriptProcessor | null = null
|
||||||
private initialDataBuffer = Buffer.from('')
|
private initialDataBuffer = Buffer.from('')
|
||||||
private initialDataBufferReleased = false
|
private initialDataBufferReleased = false
|
||||||
|
|
||||||
@@ -20,12 +23,15 @@ export abstract class BaseSession {
|
|||||||
get closed$ (): Observable<void> { return this.closed }
|
get closed$ (): Observable<void> { return this.closed }
|
||||||
get destroyed$ (): Observable<void> { return this.destroyed }
|
get destroyed$ (): Observable<void> { return this.destroyed }
|
||||||
|
|
||||||
|
constructor (protected logger: Logger) { }
|
||||||
|
|
||||||
emitOutput (data: Buffer): void {
|
emitOutput (data: Buffer): void {
|
||||||
if (!this.initialDataBufferReleased) {
|
if (!this.initialDataBufferReleased) {
|
||||||
this.initialDataBuffer = Buffer.concat([this.initialDataBuffer, data])
|
this.initialDataBuffer = Buffer.concat([this.initialDataBuffer, data])
|
||||||
} else {
|
} else {
|
||||||
this.output.next(data.toString())
|
this.output.next(data.toString())
|
||||||
this.binaryOutput.next(data)
|
this.binaryOutput.next(data)
|
||||||
|
this.loginScriptProcessor?.feedFromSession(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,9 +42,17 @@ export abstract class BaseSession {
|
|||||||
this.initialDataBuffer = Buffer.from('')
|
this.initialDataBuffer = Buffer.from('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLoginScriptsOptions (options: LoginScriptsOptions): void {
|
||||||
|
this.loginScriptProcessor?.close()
|
||||||
|
this.loginScriptProcessor = new LoginScriptProcessor(this.logger, options)
|
||||||
|
this.loginScriptProcessor.outputToSession$.subscribe(data => this.write(data))
|
||||||
|
}
|
||||||
|
|
||||||
async destroy (): Promise<void> {
|
async destroy (): Promise<void> {
|
||||||
if (this.open) {
|
if (this.open) {
|
||||||
|
this.logger.info('Destroying')
|
||||||
this.open = false
|
this.open = false
|
||||||
|
this.loginScriptProcessor?.close()
|
||||||
this.closed.next()
|
this.closed.next()
|
||||||
this.destroyed.next()
|
this.destroyed.next()
|
||||||
await this.gracefullyKillProcess()
|
await this.gracefullyKillProcess()
|
||||||
|
Reference in New Issue
Block a user