reworked login scripts - fixes #1349

This commit is contained in:
Eugene Pankov 2021-07-13 20:19:28 +02:00
parent 5053743b1b
commit 9502240480
No known key found for this signature in database
GPG Key ID: 5896FCBBDD1CF4F4
11 changed files with 117 additions and 76 deletions

View File

@ -17,6 +17,7 @@
"@types/fs-extra": "^9.0.12",
"@types/js-yaml": "^4.0.2",
"@types/node": "16.0.1",
"@types/sortablejs": "^1.10.7",
"@types/webpack-env": "^1.16.2",
"@typescript-eslint/eslint-plugin": "^4.28.2",
"@typescript-eslint/parser": "^4.28.2",
@ -40,6 +41,7 @@
"json-loader": "0.5.7",
"lru-cache": "^6.0.0",
"macos-release": "^2.5.0",
"ngx-sortablejs": "^11.1.0",
"ngx-toastr": "^14.0.0",
"node-abi": "^2.30.0",
"node-sass": "^6.0.1",
@ -55,6 +57,7 @@
"sass-loader": "^12.1.0",
"shelljs": "0.8.4",
"slugify": "^1.5.3",
"sortablejs": "^1.14.0",
"source-code-pro": "^2.38.0",
"source-map-loader": "^3.0.0",
"source-sans-pro": "3.6.0",

View File

@ -6,6 +6,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-scrollbar'
import { NgxFilesizeModule } from 'ngx-filesize'
import { DndModule } from 'ng2-dnd'
import { SortablejsModule } from 'ngx-sortablejs'
import { AppRootComponent } from './components/appRoot.component'
import { CheckboxComponent } from './components/checkbox.component'
@ -77,6 +78,7 @@ const PROVIDERS = [
NgxFilesizeModule,
PerfectScrollbarModule,
DndModule.forRoot(),
SortablejsModule.forRoot({ animation: 150 }),
],
declarations: [
AppRootComponent as any,
@ -118,6 +120,7 @@ const PROVIDERS = [
DropZoneDirective,
FastHtmlBindDirective,
AlwaysVisibleTypeaheadDirective,
SortablejsModule,
],
})
export default class AppModule { // eslint-disable-line @typescript-eslint/no-extraneous-class

View File

@ -56,7 +56,7 @@ h3.mb-3 Profiles
*ngIf='group.editable && group.name',
(click)='$event.stopPropagation(); deleteGroup(group)'
)
i.fas.fa-trash
i.fas.fa-trash-alt
ng-container(*ngIf='!group.collapsed')
ng-container(*ngFor='let profile of group.profiles')
.list-group-item.pl-5.d-flex.align-items-center(
@ -85,10 +85,10 @@ h3.mb-3 Profiles
button.btn.btn-link.hover-reveal.ml-1((click)='$event.stopPropagation(); newProfile(profile)')
i.fas.fa-copy
button.btn.btn-link.text-danger.hover-reveal.ml-1(
button.btn.btn-link.hover-reveal.ml-1(
*ngIf='!profile.isBuiltin',
(click)='$event.stopPropagation(); deleteProfile(profile)'
)
i.fas.fa-trash
i.fas.fa-trash-alt
.ml-1(class='badge badge-{{getTypeColorClass(profile)}}') {{getTypeLabel(profile)}}

View File

@ -189,6 +189,6 @@ ul.nav-tabs(ngbNav, #nav='ngbNav')
li(ngbNavItem)
a(ngbNavLink) Login scripts
ng-template(ngbNavContent)
login-scripts-settings([options]='profile.options')
login-scripts-settings([options]='profile.options', #loginScriptsSettings)
div([ngbNavOutlet]='nav')

View File

@ -1,8 +1,9 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component } from '@angular/core'
import { Component, ViewChild } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ConfigService, FileProvidersService, Platform, HostAppService, PromptModalComponent } from 'tabby-core'
import { LoginScriptsSettingsComponent } from 'tabby-terminal'
import { PasswordStorageService } from '../services/passwordStorage.service'
import { ForwardedPortConfig, SSHAlgorithmType, SSHProfile } from '../api'
import { SSHProfilesService } from '../profiles'
@ -20,6 +21,7 @@ export class SSHProfileSettingsComponent {
supportedAlgorithms: Record<string, string> = {}
algorithms: Record<string, Record<string, boolean>> = {}
jumpHosts: SSHProfile[]
@ViewChild('loginScriptsSettings') loginScriptsSettings: LoginScriptsSettingsComponent|null
constructor (
public hostApp: HostAppService,
@ -92,6 +94,7 @@ export class SSHProfileSettingsComponent {
if (!this.useProxyCommand) {
this.profile.options.proxyCommand = undefined
}
this.loginScriptsSettings?.save()
}
onForwardAdded (fw: ForwardedPortConfig) {

View File

@ -24,17 +24,17 @@
"@types/deep-equal": "^1.0.0",
"@types/shell-escape": "^0.2.0",
"ansi-colors": "^4.1.1",
"binstring": "^0.2.1",
"buffer-replace": "^1.0.0",
"cli-spinner": "^0.2.10",
"dataurl": "0.1.0",
"deep-equal": "2.0.5",
"hexer": "^1.5.0",
"ps-node": "^0.1.6",
"cli-spinner": "^0.2.10",
"runes": "^0.4.2",
"shell-escape": "^0.2.0",
"utils-decorators": "^1.8.1",
"xterm": "^4.9.0-beta.7",
"binstring": "^0.2.1",
"buffer-replace": "^1.0.0",
"hexer": "^1.5.0",
"xterm-addon-fit": "^0.5.0",
"xterm-addon-ligatures": "^0.5.0",
"xterm-addon-search": "^0.8.0",

View File

@ -1,3 +1,4 @@
import deepClone from 'clone-deep'
import { Subject, Observable } from 'rxjs'
import { Logger } from 'tabby-core'
@ -18,11 +19,28 @@ export class LoginScriptProcessor {
private outputToSession = new Subject<Buffer>()
private remainingScripts: LoginScript[] = []
private escapeSeqMap = {
a: '\x07',
b: '\x08',
e: '\x1b',
f: '\x0c',
n: '\x0a',
r: '\x0d',
t: '\x09',
v: '\x0b',
}
constructor (
private logger: Logger,
options: LoginScriptsOptions
) {
this.remainingScripts = options.scripts ?? []
this.remainingScripts = deepClone(options.scripts ?? [])
for (const script of this.remainingScripts) {
if (!script.isRegex) {
script.expect = this.unescape(script.expect)
}
script.send = this.unescape(script.send)
}
}
feedFromSession (data: Buffer): boolean {
@ -34,25 +52,17 @@ export class LoginScriptProcessor {
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
}
match = re.test(dataString)
} else {
if (dataString.includes(script.expect)) {
cmd = script.send
match = true
found = true
}
match = dataString.includes(script.expect)
}
if (match) {
this.logger.info('Executing script: "' + cmd + '"')
this.outputToSession.next(Buffer.from(cmd + '\n'))
found = true
this.logger.info('Executing script:', script)
this.outputToSession.next(Buffer.from(script.send + '\n'))
this.remainingScripts = this.remainingScripts.filter(x => x !== script)
} else {
if (script.optional) {
@ -83,4 +93,13 @@ export class LoginScriptProcessor {
}
}
}
unescape (line: string): string {
line = line.replace(/\\((x\d{2})|(u\d{4}))/g, (match, g) => {
return String.fromCharCode(parseInt(g.substr(1), 16))
})
return line.replace(/\\(.)/g, (match, g) => {
return this.escapeSeqMap[g] || g
})
}
}

View File

@ -1,37 +1,43 @@
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
div([sortablejs]='scripts')
.input-group.flex-grow-1(*ngFor='let script of scripts')
input.form-control(
type='text',
placeholder='Expect',
[(ngModel)]='script.expect'
)
input.form-control(
type='text',
placeholder='Send',
[(ngModel)]='script.send'
)
.input-group-append.input-group-text.p-0.border-0
.hover-reveal(ngbDropdown)
button.btn.btn-link.btn-sm(ngbDropdownToggle)
i.fas.fa-fw.fa-cog
.dropdown-menu-right(ngbDropdownMenu)
a.dropdown-item(
href='#',
(click)='script.isRegex = false',
[class.active]='!script.isRegex',
)
i.fas.fa-fw([class.fa-check]='!script.isRegex')
span Exact match
a.dropdown-item(
href='#',
(click)='script.isRegex = true',
[class.active]='script.isRegex',
)
i.fas.fa-fw([class.fa-check]='script.isRegex')
span Regex
a.dropdown-item(
href='#',
(click)='script.optional = !script.optional',
[class.active]='script.optional',
)
i.fas.fa-fw([class.fa-check]='script.optional')
span Optional
button.btn.btn-link.btn-sm.hover-reveal((click)='deleteScript(script)')
i.fas.fa-fw.fa-trash-alt
button.btn.btn-outline-info.mt-2((click)='addScript()')
i.fas.fa-plus

View File

@ -11,29 +11,14 @@ import { LoginScript, LoginScriptsOptions } from '../api/loginScriptProcessing'
})
export class LoginScriptsSettingsComponent {
@Input() options: LoginScriptsOptions
scripts: LoginScript[]
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)
}
this.scripts = this.options.scripts ?? []
}
async deleteScript (script: LoginScript) {
@ -46,11 +31,15 @@ export class LoginScriptsSettingsComponent {
defaultId: 1,
}
)).response === 1) {
this.options.scripts = this.options.scripts!.filter(x => x !== script)
this.scripts = this.scripts.filter(x => x !== script)
}
}
addScript () {
this.options.scripts!.push({ expect: '', send: '' })
this.scripts.push({ expect: '', send: '' })
}
save () {
this.options.scripts = this.scripts
}
}

View File

@ -120,3 +120,4 @@ export * from './api/interfaces'
export * from './api/streamProcessing'
export * from './api/loginScriptProcessing'
export * from './session'
export { LoginScriptsSettingsComponent, StreamProcessingSettingsComponent }

View File

@ -509,6 +509,11 @@
dependencies:
"@types/node" "*"
"@types/sortablejs@^1.10.7":
version "1.10.7"
resolved "https://registry.yarnpkg.com/@types/sortablejs/-/sortablejs-1.10.7.tgz#ab9039c85429f0516955ec6dbc0bb20139417b15"
integrity sha512-lGCwwgpj8zW/ZmaueoPVSP7nnc9t8VqVWXS+ASX3eoUUENmiazv0rlXyTRludXzuX9ALjPsMqBu85TgJNWbTOg==
"@types/verror@^1.10.3":
version "1.10.4"
resolved "https://registry.npmjs.org/@types/verror/-/verror-1.10.4.tgz"
@ -5036,6 +5041,13 @@ neo-async@^2.6.0, neo-async@^2.6.2:
resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
ngx-sortablejs@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/ngx-sortablejs/-/ngx-sortablejs-11.1.0.tgz#14e50c48db908c1cb4b37722b28c2d3867c6140a"
integrity sha512-eM4dHwWSmXDcvF5gUmyMMQ0qqcqBXWCSZ9IRpqx4UkBKfo4N7pk/QuYh5io2fXVHWVFDaxW1yhn2FNpqxV6Jqw==
dependencies:
tslib "^2.0.0"
ngx-toastr@^14.0.0:
version "14.0.0"
resolved "https://registry.yarnpkg.com/ngx-toastr/-/ngx-toastr-14.0.0.tgz#20e4737ef330b892a453768cd98b980558aeb286"
@ -7142,6 +7154,11 @@ socks@^1.1.10:
ip "^1.1.4"
smart-buffer "^1.0.13"
sortablejs@^1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.14.0.tgz#6d2e17ccbdb25f464734df621d4f35d4ab35b3d8"
integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==
sorted-object@~2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/sorted-object/-/sorted-object-2.0.1.tgz"