提交 0c892225 编写于 作者: E Eugene Pankov

nicer terminal messages for SSH

上级 851d92a1
......@@ -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",
......
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)
}
}
......
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)
......
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()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册