mirror of
https://github.com/Eugeny/tabby
synced 2024-11-15 01:17:14 +00:00
Merge pull request #8206 from Clem-Fern/feat#7751
[feature] add configurable behavior when session ends
This commit is contained in:
commit
ff66f050e0
8 changed files with 120 additions and 52 deletions
|
@ -14,6 +14,7 @@ export interface Profile {
|
|||
icon?: string
|
||||
color?: string
|
||||
disableDynamicTitle: boolean
|
||||
behaviorOnSessionEnd: 'auto'|'keep'|'reconnect'|'close'
|
||||
|
||||
weight: number
|
||||
isBuiltin: boolean
|
||||
|
|
|
@ -24,6 +24,7 @@ export class ProfilesService {
|
|||
isBuiltin: false,
|
||||
isTemplate: false,
|
||||
terminalColorScheme: null,
|
||||
behaviorOnSessionEnd: 'auto',
|
||||
}
|
||||
|
||||
constructor (
|
||||
|
|
|
@ -28,7 +28,6 @@ export class TerminalTabComponent extends BaseTerminalTabComponent<LocalProfile>
|
|||
this.sessionOptions = this.profile.options
|
||||
|
||||
this.logger = this.log.create('terminalTab')
|
||||
this.session = new Session(this.injector)
|
||||
|
||||
const isConPTY = isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED) && this.config.store.terminal.useConPTY
|
||||
|
||||
|
@ -56,6 +55,9 @@ export class TerminalTabComponent extends BaseTerminalTabComponent<LocalProfile>
|
|||
}
|
||||
|
||||
initializeSession (columns: number, rows: number): void {
|
||||
|
||||
const session = new Session(this.injector)
|
||||
|
||||
if (this.profile.options.runAsAdministrator && this.uac?.isAvailable) {
|
||||
this.profile = {
|
||||
...this.profile,
|
||||
|
@ -63,13 +65,13 @@ export class TerminalTabComponent extends BaseTerminalTabComponent<LocalProfile>
|
|||
}
|
||||
}
|
||||
|
||||
this.session!.start({
|
||||
session.start({
|
||||
...this.profile.options,
|
||||
width: columns,
|
||||
height: rows,
|
||||
})
|
||||
|
||||
this.attachSessionHandlers(true)
|
||||
this.setSession(session)
|
||||
this.recoveryStateChangedHint.next()
|
||||
}
|
||||
|
||||
|
@ -125,4 +127,12 @@ export class TerminalTabComponent extends BaseTerminalTabComponent<LocalProfile>
|
|||
super.ngOnDestroy()
|
||||
this.session?.destroy()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the user explicitly exit the session.
|
||||
* Always return true for terminalTab as the session can only be ended by the user
|
||||
*/
|
||||
protected isSessionExplicitlyTerminated (): boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,10 +47,6 @@ export class SerialTabComponent extends BaseTerminalTabComponent<SerialProfile>
|
|||
}
|
||||
})
|
||||
|
||||
this.frontendReady$.pipe(first()).subscribe(() => {
|
||||
this.initializeSession()
|
||||
})
|
||||
|
||||
super.ngOnInit()
|
||||
|
||||
setImmediate(() => {
|
||||
|
@ -58,6 +54,11 @@ export class SerialTabComponent extends BaseTerminalTabComponent<SerialProfile>
|
|||
})
|
||||
}
|
||||
|
||||
protected onFrontendReady (): void {
|
||||
this.initializeSession()
|
||||
super.onFrontendReady()
|
||||
}
|
||||
|
||||
async initializeSession () {
|
||||
const session = new SerialSession(this.injector, this.profile)
|
||||
this.setSession(session)
|
||||
|
@ -82,12 +83,21 @@ export class SerialTabComponent extends BaseTerminalTabComponent<SerialProfile>
|
|||
this.session?.resize(this.size.columns, this.size.rows)
|
||||
})
|
||||
this.attachSessionHandler(this.session!.destroyed$, () => {
|
||||
this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
|
||||
this.input$.pipe(first()).subscribe(() => {
|
||||
if (!this.session?.open) {
|
||||
if (this.frontend) {
|
||||
// Session was closed abruptly
|
||||
this.write('\r\n' + colors.black.bgWhite(' SERIAL ') + ` session closed\r\n`)
|
||||
|
||||
if (this.profile.behaviorOnSessionEnd === 'reconnect') {
|
||||
this.reconnect()
|
||||
} else if (this.profile.behaviorOnSessionEnd === 'keep' || this.profile.behaviorOnSessionEnd === 'auto' && !this.isSessionExplicitlyTerminated()) {
|
||||
this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
|
||||
this.input$.pipe(first()).subscribe(() => {
|
||||
if (!this.session?.open) {
|
||||
this.reconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
super.attachSessionHandlers()
|
||||
}
|
||||
|
@ -116,4 +126,10 @@ export class SerialTabComponent extends BaseTerminalTabComponent<SerialProfile>
|
|||
this.session?.serial?.update({ baudRate: rate })
|
||||
this.profile.options.baudrate = rate
|
||||
}
|
||||
|
||||
protected isSessionExplicitlyTerminated (): boolean {
|
||||
return super.isSessionExplicitlyTerminated() ||
|
||||
this.recentInputs.endsWith('close\r') ||
|
||||
this.recentInputs.endsWith('quit\r')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,18 @@
|
|||
.description(translate) Connection name will be used instead
|
||||
toggle([(ngModel)]='profile.disableDynamicTitle')
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title(translate) When a session ends
|
||||
.description(*ngIf='profile.behaviorOnSessionEnd == "auto"', translate) Only close the tab when session is explicitly terminated
|
||||
select.form-control(
|
||||
[(ngModel)]='profile.behaviorOnSessionEnd',
|
||||
)
|
||||
option(ngValue='auto', translate) Auto
|
||||
option(ngValue='keep', translate) Keep
|
||||
option(*ngIf='profile.type == "serial" || profile.type == "telnet" || profile.type == "ssh"', ngValue='reconnect', translate) Reconnect
|
||||
option(ngValue='close', translate) Close
|
||||
|
||||
.mb-4
|
||||
|
||||
.col-12.col-lg-8(*ngIf='this.profileProvider.settingsComponent')
|
||||
|
|
|
@ -30,7 +30,6 @@ export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implem
|
|||
sftpPath = '/'
|
||||
enableToolbar = true
|
||||
activeKIPrompt: KeyboardInteractivePrompt|null = null
|
||||
private recentInputs = ''
|
||||
private reconnectOffered = false
|
||||
|
||||
constructor (
|
||||
|
@ -71,17 +70,14 @@ export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implem
|
|||
}
|
||||
})
|
||||
|
||||
this.frontendReady$.pipe(first()).subscribe(() => {
|
||||
this.initializeSession()
|
||||
this.input$.subscribe(data => {
|
||||
this.recentInputs += data
|
||||
this.recentInputs = this.recentInputs.substring(this.recentInputs.length - 32)
|
||||
})
|
||||
})
|
||||
|
||||
super.ngOnInit()
|
||||
}
|
||||
|
||||
protected onFrontendReady (): void {
|
||||
this.initializeSession()
|
||||
super.onFrontendReady()
|
||||
}
|
||||
|
||||
async setupOneSession (injector: Injector, profile: SSHProfile, multiplex = true): Promise<SSHSession> {
|
||||
let session = await this.sshMultiplexer.getSession(profile)
|
||||
if (!multiplex || !session || !profile.options.reuseSession) {
|
||||
|
@ -157,24 +153,22 @@ export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implem
|
|||
protected attachSessionHandlers (): void {
|
||||
const session = this.session!
|
||||
this.attachSessionHandler(session.destroyed$, () => {
|
||||
if (
|
||||
// Ctrl-D
|
||||
this.recentInputs.charCodeAt(this.recentInputs.length - 1) === 4 ||
|
||||
this.recentInputs.endsWith('exit\r')
|
||||
) {
|
||||
// User closed the session
|
||||
this.destroy()
|
||||
} else if (this.frontend) {
|
||||
if (this.frontend) {
|
||||
// Session was closed abruptly
|
||||
this.write('\r\n' + colors.black.bgWhite(' SSH ') + ` ${this.sshSession?.profile.options.host}: session closed\r\n`)
|
||||
if (!this.reconnectOffered) {
|
||||
this.reconnectOffered = true
|
||||
this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
|
||||
this.input$.pipe(first()).subscribe(() => {
|
||||
if (!this.session?.open && this.reconnectOffered) {
|
||||
this.reconnect()
|
||||
}
|
||||
})
|
||||
|
||||
if (this.profile.behaviorOnSessionEnd === 'reconnect') {
|
||||
this.reconnect()
|
||||
} else if (this.profile.behaviorOnSessionEnd === 'keep' || this.profile.behaviorOnSessionEnd === 'auto' && !this.isSessionExplicitlyTerminated()) {
|
||||
if (!this.reconnectOffered) {
|
||||
this.reconnectOffered = true
|
||||
this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
|
||||
this.input$.pipe(first()).subscribe(() => {
|
||||
if (!this.session?.open && this.reconnectOffered) {
|
||||
this.reconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -266,4 +260,10 @@ export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implem
|
|||
onClick (): void {
|
||||
this.sftpPanelVisible = false
|
||||
}
|
||||
|
||||
protected isSessionExplicitlyTerminated (): boolean {
|
||||
return super.isSessionExplicitlyTerminated() ||
|
||||
this.recentInputs.charCodeAt(this.recentInputs.length - 1) === 4 ||
|
||||
this.recentInputs.endsWith('exit\r')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,26 +36,33 @@ export class TelnetTabComponent extends BaseTerminalTabComponent<TelnetProfile>
|
|||
}
|
||||
})
|
||||
|
||||
this.frontendReady$.pipe(first()).subscribe(() => {
|
||||
this.initializeSession()
|
||||
})
|
||||
|
||||
super.ngOnInit()
|
||||
}
|
||||
|
||||
protected onFrontendReady (): void {
|
||||
this.initializeSession()
|
||||
super.onFrontendReady()
|
||||
}
|
||||
|
||||
protected attachSessionHandlers (): void {
|
||||
const session = this.session!
|
||||
this.attachSessionHandler(session.destroyed$, () => {
|
||||
if (this.frontend) {
|
||||
// Session was closed abruptly
|
||||
if (!this.reconnectOffered) {
|
||||
this.reconnectOffered = true
|
||||
this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
|
||||
this.input$.pipe(first()).subscribe(() => {
|
||||
if (!this.session?.open && this.reconnectOffered) {
|
||||
this.reconnect()
|
||||
}
|
||||
})
|
||||
this.write('\r\n' + colors.black.bgWhite(' TELNET ') + ` ${this.session?.profile.options.host}: session closed\r\n`)
|
||||
|
||||
if (this.profile.behaviorOnSessionEnd === 'reconnect') {
|
||||
this.reconnect()
|
||||
} else if (this.profile.behaviorOnSessionEnd === 'keep' || this.profile.behaviorOnSessionEnd === 'auto' && !this.isSessionExplicitlyTerminated()) {
|
||||
if (!this.reconnectOffered) {
|
||||
this.reconnectOffered = true
|
||||
this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
|
||||
this.input$.pipe(first()).subscribe(() => {
|
||||
if (!this.session?.open && this.reconnectOffered) {
|
||||
this.reconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -120,4 +127,11 @@ export class TelnetTabComponent extends BaseTerminalTabComponent<TelnetProfile>
|
|||
},
|
||||
)).response === 0
|
||||
}
|
||||
|
||||
protected isSessionExplicitlyTerminated (): boolean {
|
||||
return super.isSessionExplicitlyTerminated() ||
|
||||
this.recentInputs.endsWith('close\r') ||
|
||||
this.recentInputs.endsWith('quit\r')
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -129,6 +129,7 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
|
|||
protected output = new Subject<string>()
|
||||
protected binaryOutput = new Subject<Buffer>()
|
||||
protected sessionChanged = new Subject<BaseSession|null>()
|
||||
protected recentInputs = ''
|
||||
private bellPlayer: HTMLAudioElement
|
||||
private termContainerSubscriptions = new SubscriptionContainer()
|
||||
private sessionHandlers = new SubscriptionContainer()
|
||||
|
@ -415,6 +416,11 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
|
|||
this.frontend!.write('\r\n\r\n')
|
||||
}
|
||||
}
|
||||
|
||||
this.input$.subscribe(data => {
|
||||
this.recentInputs += data
|
||||
this.recentInputs = this.recentInputs.substring(this.recentInputs.length - 32)
|
||||
})
|
||||
}
|
||||
|
||||
async buildContextMenu (): Promise<MenuItemOptions[]> {
|
||||
|
@ -765,11 +771,12 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
|
|||
}
|
||||
})
|
||||
|
||||
if (destroyOnSessionClose) {
|
||||
this.attachSessionHandler(this.session.closed$, () => {
|
||||
this.attachSessionHandler(this.session.closed$, () => {
|
||||
const behavior = this.profile.behaviorOnSessionEnd
|
||||
if (destroyOnSessionClose || behavior === 'close' || behavior === 'auto' && this.isSessionExplicitlyTerminated()) {
|
||||
this.destroy()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.attachSessionHandler(this.session.destroyed$, () => {
|
||||
this.setSession(null)
|
||||
|
@ -835,4 +842,11 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
|
|||
cb(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the user explicitly exit the session
|
||||
*/
|
||||
protected isSessionExplicitlyTerminated (): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue