mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-08 21:40:03 +00:00
nicer terminal messages for SSH
This commit is contained in:
parent
851d92a140
commit
0c892225f3
@ -19,6 +19,8 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "12.7.3",
|
||||
"@types/ssh2": "^0.5.35",
|
||||
"ansi-colors": "^4.1.1",
|
||||
"cli-spinner": "^0.2.10",
|
||||
"ssh2": "^0.8.2",
|
||||
"ssh2-streams": "^0.4.2",
|
||||
"sshpk": "^1.16.1",
|
||||
|
@ -1,3 +1,4 @@
|
||||
import colors from 'ansi-colors'
|
||||
import { BaseSession } from 'terminus-terminal'
|
||||
import { Server, Socket, createServer, createConnection } from 'net'
|
||||
import { Client, ClientChannel } from 'ssh2'
|
||||
@ -92,7 +93,7 @@ export class SSHSession extends BaseSession {
|
||||
try {
|
||||
this.shell = await this.openShellChannel({ x11: this.connection.x11 })
|
||||
} catch (err) {
|
||||
this.emitServiceMessage(`Remote rejected opening a shell channel: ${err}`)
|
||||
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote rejected opening a shell channel: ${err}`)
|
||||
}
|
||||
|
||||
this.shell.on('greeting', greeting => {
|
||||
@ -159,13 +160,13 @@ export class SSHSession extends BaseSession {
|
||||
this.logger.info(`Incoming forwarded connection: (remote) ${details.srcIP}:${details.srcPort} -> (local) ${details.destIP}:${details.destPort}`)
|
||||
const forward = this.forwardedPorts.find(x => x.port === details.destPort)
|
||||
if (!forward) {
|
||||
this.emitServiceMessage(`Rejected incoming forwarded connection for unrecognized port ${details.destPort}`)
|
||||
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Rejected incoming forwarded connection for unrecognized port ${details.destPort}`)
|
||||
return reject()
|
||||
}
|
||||
const socket = new Socket()
|
||||
socket.connect(forward.targetPort, forward.targetAddress)
|
||||
socket.on('error', e => {
|
||||
this.emitServiceMessage(`Could not forward the remote connection to ${forward.targetAddress}:${forward.targetPort}: ${e}`)
|
||||
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not forward the remote connection to ${forward.targetAddress}:${forward.targetPort}: ${e}`)
|
||||
reject()
|
||||
})
|
||||
socket.on('connect', () => {
|
||||
@ -195,7 +196,7 @@ export class SSHSession extends BaseSession {
|
||||
socket.connect(xPort, xHost)
|
||||
}
|
||||
socket.on('error', e => {
|
||||
this.emitServiceMessage(`Could not connect to the X server ${xHost}:${xPort}: ${e}`)
|
||||
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not connect to the X server ${xHost}:${xPort}: ${e}`)
|
||||
reject()
|
||||
})
|
||||
socket.on('connect', () => {
|
||||
@ -231,7 +232,7 @@ export class SSHSession extends BaseSession {
|
||||
fw.targetPort,
|
||||
(err, stream) => {
|
||||
if (err) {
|
||||
this.emitServiceMessage(`Remote has rejected the forwaded connection via ${fw}: ${err}`)
|
||||
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwaded connection via ${fw}: ${err}`)
|
||||
socket.destroy()
|
||||
return
|
||||
}
|
||||
@ -246,10 +247,10 @@ export class SSHSession extends BaseSession {
|
||||
}
|
||||
)
|
||||
}).then(() => {
|
||||
this.emitServiceMessage(`Forwaded ${fw}`)
|
||||
this.emitServiceMessage(colors.bgGreen.black(' -> ') + ` Forwaded ${fw}`)
|
||||
this.forwardedPorts.push(fw)
|
||||
}).catch(e => {
|
||||
this.emitServiceMessage(`Failed to forward port ${fw}: ${e}`)
|
||||
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Failed to forward port ${fw}: ${e}`)
|
||||
throw e
|
||||
})
|
||||
}
|
||||
@ -257,13 +258,13 @@ export class SSHSession extends BaseSession {
|
||||
await new Promise((resolve, reject) => {
|
||||
this.ssh.forwardIn(fw.host, fw.port, err => {
|
||||
if (err) {
|
||||
this.emitServiceMessage(`Remote rejected port forwarding for ${fw}: ${err}`)
|
||||
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote rejected port forwarding for ${fw}: ${err}`)
|
||||
return reject(err)
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
this.emitServiceMessage(`Forwaded ${fw}`)
|
||||
this.emitServiceMessage(colors.bgGreen.black(' <- ') + ` Forwaded ${fw}`)
|
||||
this.forwardedPorts.push(fw)
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import colors from 'ansi-colors'
|
||||
import { Spinner } from 'cli-spinner'
|
||||
import { Component } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { first } from 'rxjs/operators'
|
||||
@ -43,23 +45,32 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||
|
||||
this.session = this.ssh.createSession(this.connection)
|
||||
this.session.serviceMessage$.subscribe(msg => {
|
||||
this.write(`\r\n[SSH] ${msg}\r\n`)
|
||||
this.write('\r\n' + colors.black.bgWhite(' SSH ') + ' ' + msg + '\r\n')
|
||||
this.session.resize(this.size.columns, this.size.rows)
|
||||
})
|
||||
this.attachSessionHandlers()
|
||||
this.write(`Connecting to ${this.connection.host}`)
|
||||
const interval = setInterval(() => this.write('.'), 500)
|
||||
|
||||
const spinner = new Spinner({
|
||||
text: 'Connecting',
|
||||
stream: {
|
||||
write: x => this.write(x),
|
||||
},
|
||||
})
|
||||
spinner.setSpinnerString(6)
|
||||
spinner.start()
|
||||
|
||||
try {
|
||||
await this.ssh.connectSession(this.session, (message: string) => {
|
||||
this.write('\r\n' + message)
|
||||
spinner.stop(true)
|
||||
this.write(message + '\r\n')
|
||||
spinner.start()
|
||||
})
|
||||
spinner.stop(true)
|
||||
} catch (e) {
|
||||
this.write('\r\n')
|
||||
this.write(e.message)
|
||||
spinner.stop(true)
|
||||
this.write(colors.black.bgRed(' X ') + ' ' + colors.red(e.message) + '\r\n')
|
||||
return
|
||||
} finally {
|
||||
clearInterval(interval)
|
||||
this.write('\r\n')
|
||||
}
|
||||
await this.session.start()
|
||||
this.session.resize(this.size.columns, this.size.rows)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import colors from 'ansi-colors'
|
||||
import { Injectable, NgZone } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { Client } from 'ssh2'
|
||||
@ -65,17 +66,17 @@ export class SSHService {
|
||||
if (!privateKeyPath) {
|
||||
const userKeyPath = path.join(process.env.HOME as string, '.ssh', 'id_rsa')
|
||||
if (await fs.exists(userKeyPath)) {
|
||||
log(`Using user's default private key: ${userKeyPath}`)
|
||||
log('Using user\'s default private key')
|
||||
privateKeyPath = userKeyPath
|
||||
}
|
||||
}
|
||||
|
||||
if (privateKeyPath) {
|
||||
log(`Loading private key from ${privateKeyPath}`)
|
||||
log('Loading private key from ' + colors.bgWhite.blackBright(' ' + privateKeyPath + ' '))
|
||||
try {
|
||||
privateKey = (await fs.readFile(privateKeyPath)).toString()
|
||||
} catch (error) {
|
||||
log('Could not read the private key file')
|
||||
log(colors.bgRed.black(' X ') + 'Could not read the private key file')
|
||||
this.toastr.error('Could not read the private key file')
|
||||
}
|
||||
|
||||
@ -86,7 +87,7 @@ export class SSHService {
|
||||
} catch (e) {
|
||||
if (e instanceof sshpk.KeyEncryptedError) {
|
||||
const modal = this.ngbModal.open(PromptModalComponent)
|
||||
log('Key requires passphrase')
|
||||
log(colors.bgYellow.yellow.black(' ! ') + ' Key requires passphrase')
|
||||
modal.componentInstance.prompt = 'Private key passphrase'
|
||||
modal.componentInstance.password = true
|
||||
let passphrase = ''
|
||||
@ -133,7 +134,7 @@ export class SSHService {
|
||||
})
|
||||
})
|
||||
ssh.on('keyboard-interactive', (name, instructions, instructionsLang, prompts, finish) => this.zone.run(async () => {
|
||||
log(`Keyboard-interactive auth requested: ${name}`)
|
||||
log(colors.bgBlackBright(' ') + ` Keyboard-interactive auth requested: ${name}`)
|
||||
this.logger.info('Keyboard-interactive auth:', name, instructions, instructionsLang)
|
||||
const results: string[] = []
|
||||
for (const prompt of prompts) {
|
||||
@ -182,7 +183,8 @@ export class SSHService {
|
||||
keepaliveCountMax: session.connection.keepaliveCountMax,
|
||||
readyTimeout: session.connection.readyTimeout,
|
||||
hostVerifier: digest => {
|
||||
log('SHA256 fingerprint: ' + digest)
|
||||
log(colors.bgWhite(' ') + ' Host key fingerprint:')
|
||||
log(colors.bgWhite(' ') + ' ' + colors.black.bgWhite(' SHA256 ') + colors.bgBlackBright(' ' + digest + ' '))
|
||||
return true
|
||||
},
|
||||
hostHash: 'sha256' as any,
|
||||
|
@ -27,6 +27,11 @@
|
||||
"@types/node" "*"
|
||||
"@types/ssh2-streams" "*"
|
||||
|
||||
ansi-colors@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
||||
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
|
||||
|
||||
asn1@~0.2.0, asn1@~0.2.3:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||
@ -46,6 +51,11 @@ bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
|
||||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
cli-spinner@^0.2.10:
|
||||
version "0.2.10"
|
||||
resolved "https://registry.yarnpkg.com/cli-spinner/-/cli-spinner-0.2.10.tgz#f7d617a36f5c47a7bc6353c697fc9338ff782a47"
|
||||
integrity sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q==
|
||||
|
||||
dashdash@^1.12.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||
|
@ -174,6 +174,14 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||
this.size = { columns, rows }
|
||||
this.frontendReady.next()
|
||||
|
||||
this.config.enabledServices(this.decorators).forEach(decorator => {
|
||||
try {
|
||||
decorator.attach(this)
|
||||
} catch (e) {
|
||||
this.logger.warn('Decorator attach() throws', e)
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.session.resize(columns, rows)
|
||||
}, 1000)
|
||||
@ -204,14 +212,6 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||
|
||||
this.configure()
|
||||
|
||||
this.config.enabledServices(this.decorators).forEach((decorator) => {
|
||||
try {
|
||||
decorator.attach(this)
|
||||
} catch (e) {
|
||||
this.logger.warn('Decorator attach() throws', e)
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.output.subscribe(() => {
|
||||
this.displayActivity()
|
||||
|
Loading…
x
Reference in New Issue
Block a user