mirror of
https://github.com/Eugeny/tabby.git
synced 2025-01-18 14:04:17 +08:00
reworked login scripts - fixes #1349
This commit is contained in:
parent
5053743b1b
commit
9502240480
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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)}}
|
||||
|
@ -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')
|
||||
|
@ -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) {
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -120,3 +120,4 @@ export * from './api/interfaces'
|
||||
export * from './api/streamProcessing'
|
||||
export * from './api/loginScriptProcessing'
|
||||
export * from './session'
|
||||
export { LoginScriptsSettingsComponent, StreamProcessingSettingsComponent }
|
||||
|
17
yarn.lock
17
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user