mirror of
https://github.com/Eugeny/tabby.git
synced 2025-10-05 06:24:56 +00:00
moved login scripts processing into tabby-terminal
This commit is contained in:
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>()
|
||||
|
||||
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 {
|
||||
|
@@ -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 { 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'
|
||||
|
@@ -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()
|
||||
|
Reference in New Issue
Block a user