moved login scripts processing into tabby-terminal

This commit is contained in:
Eugene Pankov
2021-07-05 23:56:38 +02:00
parent 461cd2bec7
commit bf762cc4c7
16 changed files with 270 additions and 344 deletions

View 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
}
}
}
}

View File

@@ -26,9 +26,10 @@ export class TerminalStreamProcessor {
protected outputToTerminal = new Subject<Buffer>()
private inputReadline: ReadLine
private inputPromptVisible = true
private inputPromptVisible = false
private inputReadlineInStream: Readable & Writable
private inputReadlineOutStream: Readable & Writable
private started = false
constructor (private options: StreamProcessingOptions) {
this.inputReadlineInStream = new PassThrough()
@@ -46,7 +47,16 @@ export class TerminalStreamProcessor {
this.onTerminalInput(Buffer.from(line + '\n'))
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 {

View File

@@ -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

View File

@@ -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: '' })
}
}

View File

@@ -14,6 +14,7 @@ import { ColorPickerComponent } from './components/colorPicker.component'
import { ColorSchemePreviewComponent } from './components/colorSchemePreview.component'
import { SearchPanelComponent } from './components/searchPanel.component'
import { StreamProcessingSettingsComponent } from './components/streamProcessingSettings.component'
import { LoginScriptsSettingsComponent } from './components/loginScriptsSettings.component'
import { TerminalFrontendService } from './services/terminalFrontend.service'
@@ -72,11 +73,13 @@ import { TerminalCLIHandler } from './cli'
TerminalSettingsTabComponent,
SearchPanelComponent,
StreamProcessingSettingsComponent,
LoginScriptsSettingsComponent,
] as any[],
exports: [
ColorPickerComponent,
SearchPanelComponent,
StreamProcessingSettingsComponent,
LoginScriptsSettingsComponent,
],
})
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 * from './api/interfaces'
export * from './api/streamProcessing'
export * from './api/loginScriptProcessing'
export * from './session'

View File

@@ -1,4 +1,6 @@
import { Observable, Subject } from 'rxjs'
import { Logger } from 'tabby-core'
import { LoginScriptProcessor, LoginScriptsOptions } from './api/loginScriptProcessing'
/**
* A session object for a [[BaseTerminalTabComponent]]
@@ -12,6 +14,7 @@ export abstract class BaseSession {
protected binaryOutput = new Subject<Buffer>()
protected closed = new Subject<void>()
protected destroyed = new Subject<void>()
protected loginScriptProcessor: LoginScriptProcessor | null = null
private initialDataBuffer = Buffer.from('')
private initialDataBufferReleased = false
@@ -20,12 +23,15 @@ export abstract class BaseSession {
get closed$ (): Observable<void> { return this.closed }
get destroyed$ (): Observable<void> { return this.destroyed }
constructor (protected logger: Logger) { }
emitOutput (data: Buffer): void {
if (!this.initialDataBufferReleased) {
this.initialDataBuffer = Buffer.concat([this.initialDataBuffer, data])
} else {
this.output.next(data.toString())
this.binaryOutput.next(data)
this.loginScriptProcessor?.feedFromSession(data)
}
}
@@ -36,9 +42,17 @@ export abstract class BaseSession {
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> {
if (this.open) {
this.logger.info('Destroying')
this.open = false
this.loginScriptProcessor?.close()
this.closed.next()
this.destroyed.next()
await this.gracefullyKillProcess()