mirror of
https://github.com/Eugeny/tabby.git
synced 2024-12-23 18:44:20 +03:00
added SSH connection manager (fixes #220)
This commit is contained in:
parent
13a76db9af
commit
5cdb7527c8
@ -31,10 +31,6 @@
|
||||
"rxjs": "5.3.0",
|
||||
"zone.js": "0.8.12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"wincredmgr": "^2.0.0",
|
||||
"xkeychain": "^0.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mz": "0.0.31"
|
||||
}
|
||||
|
@ -268,14 +268,6 @@ util@^0.10.3:
|
||||
dependencies:
|
||||
inherits "2.0.1"
|
||||
|
||||
wincredmgr@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wincredmgr/-/wincredmgr-2.0.0.tgz#cab8e3c6d98f0ea255d7638e82fd9f393ca7f515"
|
||||
|
||||
xkeychain@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/xkeychain/-/xkeychain-0.0.6.tgz#1c58b3dd2f80481f8f67949c3511aa14027c2b9b"
|
||||
|
||||
zone.js@0.8.12:
|
||||
version "0.8.12"
|
||||
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.12.tgz#86ff5053c98aec291a0bf4bbac501d694a05cfbb"
|
||||
|
@ -3,5 +3,5 @@ const rebuild = require('electron-rebuild').default
|
||||
const path = require('path')
|
||||
const vars = require('./vars')
|
||||
|
||||
rebuild(path.resolve(__dirname, '../app'), vars.electronVersion, process.arch, [], true)
|
||||
rebuild(path.resolve(__dirname, '../terminus-ssh'), vars.electronVersion, process.arch, [], true)
|
||||
rebuild(path.resolve(__dirname, '../terminus-terminal'), vars.electronVersion, process.arch, [], true)
|
||||
|
@ -14,6 +14,7 @@ exports.builtinPlugins = [
|
||||
'terminus-terminal',
|
||||
'terminus-community-color-schemes',
|
||||
'terminus-plugin-manager',
|
||||
'terminus-ssh',
|
||||
]
|
||||
exports.nativeModules = ['node-pty-tmp', 'font-manager', 'xkeychain']
|
||||
exports.electronVersion = pkgInfo.devDependencies.electron
|
||||
|
@ -43,3 +43,4 @@ hotkeys:
|
||||
tab-10:
|
||||
- 'Alt-0'
|
||||
- ['Ctrl-A', '0']
|
||||
pluginBlacklist: ['ssh']
|
||||
|
@ -43,3 +43,4 @@ hotkeys:
|
||||
tab-10:
|
||||
- '⌘-0'
|
||||
- ['Ctrl-A', '0']
|
||||
pluginBlacklist: ['ssh']
|
||||
|
@ -43,3 +43,4 @@ hotkeys:
|
||||
tab-10:
|
||||
- 'Alt-0'
|
||||
- ['Ctrl-A', '0']
|
||||
pluginBlacklist: []
|
||||
|
@ -6,4 +6,3 @@ appearance:
|
||||
theme: Standard
|
||||
frame: thin
|
||||
css: '/* * { color: blue !important; } */'
|
||||
pluginBlacklist: []
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { SettingsTabProvider, ComponentType } from 'terminus-settings'
|
||||
import { SettingsTabProvider } from 'terminus-settings'
|
||||
|
||||
import { PluginsSettingsTabComponent } from './components/pluginsSettingsTab.component'
|
||||
|
||||
@ -8,7 +8,7 @@ export class PluginsSettingsTabProvider extends SettingsTabProvider {
|
||||
id = 'plugins'
|
||||
title = 'Plugins'
|
||||
|
||||
getComponentType (): ComponentType {
|
||||
getComponentType (): any {
|
||||
return PluginsSettingsTabComponent
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
export declare type ComponentType = new (...args: any[]) => any
|
||||
|
||||
export abstract class SettingsTabProvider {
|
||||
id: string
|
||||
title: string
|
||||
|
||||
getComponentType (): ComponentType {
|
||||
getComponentType (): any {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
48
terminus-ssh/package.json
Normal file
48
terminus-ssh/package.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "terminus-ssh",
|
||||
"version": "0.0.1",
|
||||
"description": "SSH connection manager for Terminus",
|
||||
"keywords": [
|
||||
"terminus-builtin-plugin"
|
||||
],
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "webpack --progress --color",
|
||||
"watch": "webpack --progress --color --watch"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"author": "Eugene Pankov",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/ssh2": "^0.5.35",
|
||||
"@types/webpack-env": "^1.13.0",
|
||||
"apply-loader": "^2.0.0",
|
||||
"awesome-typescript-loader": "^3.1.2",
|
||||
"electron": "^1.6.11",
|
||||
"pug": "^2.0.0-rc.3",
|
||||
"pug-loader": "^2.3.0",
|
||||
"rxjs": "^5.4.0",
|
||||
"typescript": "^2.2.2",
|
||||
"webpack": "^2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^4.1.3",
|
||||
"@angular/core": "^4.1.3",
|
||||
"@angular/forms": "^4.1.3",
|
||||
"@ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.29",
|
||||
"terminus-core": "*",
|
||||
"terminus-settings": "*",
|
||||
"terminus-terminal": "*"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"wincredmgr": "^2.0.0",
|
||||
"xkeychain": "^0.0.6"
|
||||
},
|
||||
"repository": "eugeny/terminus-ssh",
|
||||
"dependencies": {
|
||||
"ssh2": "^0.5.5"
|
||||
}
|
||||
}
|
51
terminus-ssh/src/api.ts
Normal file
51
terminus-ssh/src/api.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { BaseSession } from 'terminus-terminal'
|
||||
|
||||
export interface SSHConnection {
|
||||
name?: string
|
||||
host: string
|
||||
user: string
|
||||
password?: string
|
||||
privateKey?: string
|
||||
}
|
||||
|
||||
export class SSHSession extends BaseSession {
|
||||
constructor (private shell: any) {
|
||||
super()
|
||||
|
||||
this.open = true
|
||||
|
||||
this.shell.on('data', data => {
|
||||
this.emitOutput(data.toString())
|
||||
})
|
||||
|
||||
this.shell.on('end', () => {
|
||||
if (this.open) {
|
||||
this.destroy()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
resize (columns, rows) {
|
||||
this.shell.setWindow(rows, columns)
|
||||
}
|
||||
|
||||
write (data) {
|
||||
this.shell.write(data)
|
||||
}
|
||||
|
||||
kill (signal?: string) {
|
||||
this.shell.signal(signal || 'TERM')
|
||||
}
|
||||
|
||||
async getChildProcesses (): Promise<any[]> {
|
||||
return []
|
||||
}
|
||||
|
||||
async gracefullyKillProcess (): Promise<void> {
|
||||
this.kill('TERM')
|
||||
}
|
||||
|
||||
async getWorkingDirectory (): Promise<string> {
|
||||
return null
|
||||
}
|
||||
}
|
37
terminus-ssh/src/buttonProvider.ts
Normal file
37
terminus-ssh/src/buttonProvider.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { HotkeysService, ToolbarButtonProvider, IToolbarButton } from 'terminus-core'
|
||||
import { SSHModalComponent } from './components/sshModal.component'
|
||||
|
||||
@Injectable()
|
||||
export class ButtonProvider extends ToolbarButtonProvider {
|
||||
constructor (
|
||||
private ngbModal: NgbModal,
|
||||
hotkeys: HotkeysService,
|
||||
) {
|
||||
super()
|
||||
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
|
||||
if (hotkey === 'ssh') {
|
||||
this.activate()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
activate () {
|
||||
let modal = this.ngbModal.open(SSHModalComponent)
|
||||
modal.result.then(() => {
|
||||
//this.terminal.openTab(shell)
|
||||
})
|
||||
}
|
||||
|
||||
provide (): IToolbarButton[] {
|
||||
return [{
|
||||
icon: 'globe',
|
||||
weight: 5,
|
||||
title: 'SSH connections',
|
||||
click: async () => {
|
||||
this.activate()
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
.modal-body
|
||||
.form-group
|
||||
label Name
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='connection.name',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Host
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='connection.host',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Username
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='connection.user',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Private key
|
||||
.input-group
|
||||
input.form-control(
|
||||
type='text',
|
||||
placeholder='Key file path',
|
||||
[(ngModel)]='connection.privateKey'
|
||||
)
|
||||
.input-group-btn
|
||||
button.btn.btn-secondary((click)='selectPrivateKey()')
|
||||
i.fa.fa-folder-open
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-outline-primary((click)='save()') Save
|
||||
button.btn.btn-outline-danger((click)='cancel()') Cancel
|
38
terminus-ssh/src/components/editConnectionModal.component.ts
Normal file
38
terminus-ssh/src/components/editConnectionModal.component.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ElectronService, HostAppService } from 'terminus-core'
|
||||
import { SSHConnection } from '../api'
|
||||
|
||||
@Component({
|
||||
template: require('./editConnectionModal.component.pug'),
|
||||
})
|
||||
export class EditConnectionModalComponent {
|
||||
connection: SSHConnection
|
||||
|
||||
constructor (
|
||||
private modalInstance: NgbActiveModal,
|
||||
private electron: ElectronService,
|
||||
private hostApp: HostAppService,
|
||||
) { }
|
||||
|
||||
selectPrivateKey () {
|
||||
let path = this.electron.dialog.showOpenDialog(
|
||||
this.hostApp.getWindow(),
|
||||
{
|
||||
title: 'Select private key',
|
||||
properties: ['openDirectory']
|
||||
}
|
||||
)
|
||||
if (path) {
|
||||
this.connection.privateKey = path[0]
|
||||
}
|
||||
}
|
||||
|
||||
save () {
|
||||
this.modalInstance.close(this.connection)
|
||||
}
|
||||
|
||||
cancel () {
|
||||
this.modalInstance.dismiss()
|
||||
}
|
||||
}
|
9
terminus-ssh/src/components/promptModal.component.pug
Normal file
9
terminus-ssh/src/components/promptModal.component.pug
Normal file
@ -0,0 +1,9 @@
|
||||
.modal-body
|
||||
input.form-control(
|
||||
[type]='password ? "password" : "text"',
|
||||
[(ngModel)]='value',
|
||||
#input,
|
||||
[placeholder]='prompt',
|
||||
(keyup.enter)='ok()',
|
||||
(keyup.esc)='cancel()',
|
||||
)
|
27
terminus-ssh/src/components/promptModal.component.ts
Normal file
27
terminus-ssh/src/components/promptModal.component.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Component, Input, ViewChild, ElementRef } from '@angular/core'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
|
||||
@Component({
|
||||
template: require('./promptModal.component.pug'),
|
||||
})
|
||||
export class PromptModalComponent {
|
||||
@Input() value: string
|
||||
@Input() password: boolean
|
||||
@ViewChild('input') input: ElementRef
|
||||
|
||||
constructor (
|
||||
private modalInstance: NgbActiveModal,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
this.input.nativeElement.focus()
|
||||
}
|
||||
|
||||
ok () {
|
||||
this.modalInstance.close(this.value)
|
||||
}
|
||||
|
||||
cancel () {
|
||||
this.modalInstance.close('')
|
||||
}
|
||||
}
|
24
terminus-ssh/src/components/sshModal.component.pug
Normal file
24
terminus-ssh/src/components/sshModal.component.pug
Normal file
@ -0,0 +1,24 @@
|
||||
.modal-body
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='quickTarget',
|
||||
autofocus,
|
||||
placeholder='Quick connect: [user@]host',
|
||||
(keyup.enter)='quickConnect()'
|
||||
)
|
||||
|
||||
.list-group.mt-3(*ngIf='lastConnection')
|
||||
a.list-group-item.list-group-item-action((click)='connect(lastConnection)')
|
||||
i.fa.fa-fw.fa-history
|
||||
span {{lastConnection.name}}
|
||||
|
||||
.list-group.mt-3
|
||||
a.list-group-item.list-group-item-action(*ngFor='let connection of connections', (click)='connect(connection)')
|
||||
i.fa.fa-fw.fa-globe
|
||||
span {{connection.name}}
|
||||
a.list-group-item.list-group-item-action((click)='manageConnections()')
|
||||
i.fa.fa-fw.fa-wrench
|
||||
span Manage connections
|
||||
|
||||
//.modal-footer
|
||||
button.btn.btn-outline-primary((click)='close()') Cancel
|
60
terminus-ssh/src/components/sshModal.component.ts
Normal file
60
terminus-ssh/src/components/sshModal.component.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ConfigService, AppService } from 'terminus-core'
|
||||
import { SettingsTabComponent } from 'terminus-settings'
|
||||
import { SSHService } from '../services/ssh.service'
|
||||
import { SSHConnection } from '../api'
|
||||
|
||||
@Component({
|
||||
template: require('./sshModal.component.pug'),
|
||||
//styles: [require('./sshModal.component.scss')],
|
||||
})
|
||||
export class SSHModalComponent {
|
||||
connections: SSHConnection[]
|
||||
quickTarget: string
|
||||
lastConnection: SSHConnection
|
||||
|
||||
constructor (
|
||||
public modalInstance: NgbActiveModal,
|
||||
private config: ConfigService,
|
||||
private ssh: SSHService,
|
||||
private app: AppService,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
this.connections = this.config.store.ssh.connections
|
||||
if (window.localStorage.lastConnection) {
|
||||
this.lastConnection = JSON.parse(window.localStorage.lastConnection)
|
||||
}
|
||||
}
|
||||
|
||||
quickConnect () {
|
||||
let user = 'root'
|
||||
let host = this.quickTarget
|
||||
if (host.includes('@')) {
|
||||
[user, host] = host.split('@')
|
||||
}
|
||||
let connection: SSHConnection = {
|
||||
name: this.quickTarget,
|
||||
host, user,
|
||||
}
|
||||
window.localStorage.lastConnection = JSON.stringify(connection)
|
||||
this.connect(connection)
|
||||
}
|
||||
|
||||
connect (connection: SSHConnection) {
|
||||
this.close()
|
||||
this.ssh.connect(connection).catch(error => {
|
||||
alert(`Could not connect: ${error}`)
|
||||
})
|
||||
}
|
||||
|
||||
manageConnections () {
|
||||
this.close()
|
||||
this.app.openNewTab(SettingsTabComponent, { activeTab: 'ssh' })
|
||||
}
|
||||
|
||||
close () {
|
||||
this.modalInstance.close()
|
||||
}
|
||||
}
|
15
terminus-ssh/src/components/sshSettingsTab.component.pug
Normal file
15
terminus-ssh/src/components/sshSettingsTab.component.pug
Normal file
@ -0,0 +1,15 @@
|
||||
h3 Connections
|
||||
|
||||
.list-group.mt-3.mb-3
|
||||
.list-group-item(*ngFor='let connection of connections')
|
||||
.d-flex.w-100
|
||||
.mr-auto
|
||||
div
|
||||
span {{connection.name}}
|
||||
.text-muted {{connection.host}}
|
||||
button.btn.btn-outline-info.ml-2((click)='editConnection(connection)')
|
||||
i.fa.fa-pencil
|
||||
button.btn.btn-outline-danger.ml-1((click)='deleteConnection(connection)')
|
||||
i.fa.fa-trash-o
|
||||
|
||||
button.btn.btn-outline-primary((click)='createConnection()') Add connection
|
52
terminus-ssh/src/components/sshSettingsTab.component.ts
Normal file
52
terminus-ssh/src/components/sshSettingsTab.component.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ConfigService } from 'terminus-core'
|
||||
import { SSHConnection } from '../api'
|
||||
import { EditConnectionModalComponent } from './editConnectionModal.component'
|
||||
|
||||
@Component({
|
||||
template: require('./sshSettingsTab.component.pug'),
|
||||
})
|
||||
export class SSHSettingsTabComponent {
|
||||
connections: SSHConnection[]
|
||||
|
||||
constructor (
|
||||
public config: ConfigService,
|
||||
private ngbModal: NgbModal,
|
||||
) {
|
||||
this.connections = this.config.store.ssh.connections
|
||||
}
|
||||
|
||||
async ngOnInit () {
|
||||
}
|
||||
|
||||
createConnection () {
|
||||
let connection: SSHConnection = {
|
||||
name: '',
|
||||
host: '',
|
||||
user: 'root',
|
||||
}
|
||||
let modal = this.ngbModal.open(EditConnectionModalComponent)
|
||||
modal.componentInstance.connection = connection
|
||||
modal.result.then(result => {
|
||||
this.connections.push(result)
|
||||
this.config.store.ssh.connections = this.connections
|
||||
})
|
||||
}
|
||||
|
||||
editConnection (connection: SSHConnection) {
|
||||
let modal = this.ngbModal.open(EditConnectionModalComponent)
|
||||
modal.componentInstance.connection = Object.assign({}, connection)
|
||||
modal.result.then(result => {
|
||||
Object.assign(connection, result)
|
||||
this.config.save()
|
||||
})
|
||||
}
|
||||
|
||||
deleteConnection (connection: SSHConnection) {
|
||||
if (confirm(`Delete "${connection.name}"?`)) {
|
||||
this.connections = this.connections.filter(x => x !== connection)
|
||||
this.config.store.ssh.connections = this.connections
|
||||
}
|
||||
}
|
||||
}
|
18
terminus-ssh/src/config.ts
Normal file
18
terminus-ssh/src/config.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { ConfigProvider } from 'terminus-core'
|
||||
|
||||
export class SSHConfigProvider extends ConfigProvider {
|
||||
defaults = {
|
||||
ssh: {
|
||||
connections: [],
|
||||
options: {
|
||||
}
|
||||
},
|
||||
hotkeys: {
|
||||
'ssh': [
|
||||
'Alt-S',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
platformDefaults = { }
|
||||
}
|
43
terminus-ssh/src/index.ts
Normal file
43
terminus-ssh/src/index.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ToolbarButtonProvider, ConfigProvider } from 'terminus-core'
|
||||
import { SettingsTabProvider } from 'terminus-settings'
|
||||
|
||||
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
|
||||
import { SSHModalComponent } from './components/sshModal.component'
|
||||
import { PromptModalComponent } from './components/promptModal.component'
|
||||
import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
|
||||
import { SSHService } from './services/ssh.service'
|
||||
|
||||
import { ButtonProvider } from './buttonProvider'
|
||||
import { SSHConfigProvider } from './config'
|
||||
import { SSHSettingsTabProvider } from './settings'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
NgbModule,
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
],
|
||||
providers: [
|
||||
SSHService,
|
||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||
{ provide: ConfigProvider, useClass: SSHConfigProvider, multi: true },
|
||||
{ provide: SettingsTabProvider, useClass: SSHSettingsTabProvider, multi: true },
|
||||
],
|
||||
entryComponents: [
|
||||
EditConnectionModalComponent,
|
||||
PromptModalComponent,
|
||||
SSHModalComponent,
|
||||
SSHSettingsTabComponent,
|
||||
],
|
||||
declarations: [
|
||||
EditConnectionModalComponent,
|
||||
PromptModalComponent,
|
||||
SSHModalComponent,
|
||||
SSHSettingsTabComponent,
|
||||
],
|
||||
})
|
||||
export default class SSHModule { }
|
128
terminus-ssh/src/services/ssh.service.ts
Normal file
128
terminus-ssh/src/services/ssh.service.ts
Normal file
@ -0,0 +1,128 @@
|
||||
import { Injectable, NgZone } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { Client } from 'ssh2'
|
||||
import * as fs from 'mz/fs'
|
||||
import { AppService } from 'terminus-core'
|
||||
import { TerminalTabComponent } from 'terminus-terminal'
|
||||
import { SSHConnection, SSHSession } from '../api'
|
||||
import { PromptModalComponent } from '../components/promptModal.component'
|
||||
|
||||
const { SSH2Stream } = require('ssh2-streams')
|
||||
const keychain = require('xkeychain')
|
||||
|
||||
@Injectable()
|
||||
export class SSHService {
|
||||
constructor (
|
||||
private app: AppService,
|
||||
private zone: NgZone,
|
||||
private ngbModal: NgbModal,
|
||||
) {
|
||||
}
|
||||
|
||||
async connect (connection: SSHConnection): Promise<TerminalTabComponent> {
|
||||
let privateKey: string = null
|
||||
if (connection.privateKey) {
|
||||
try {
|
||||
privateKey = (await fs.readFile(connection.privateKey)).toString()
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
|
||||
let ssh = new Client()
|
||||
let connected = false
|
||||
await new Promise((resolve, reject) => {
|
||||
ssh.on('ready', () => {
|
||||
connected = true
|
||||
this.zone.run(resolve)
|
||||
})
|
||||
ssh.on('error', error => {
|
||||
this.zone.run(() => {
|
||||
if (connected) {
|
||||
alert(`SSH error: ${error}`)
|
||||
} else {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
ssh.on('keyboard-interactive', (name, instructions, instructionsLang, prompts, finish) => this.zone.run(async () => {
|
||||
console.log(name, instructions, instructionsLang)
|
||||
let results = []
|
||||
for (let prompt of prompts) {
|
||||
let modal = this.ngbModal.open(PromptModalComponent)
|
||||
modal.componentInstance.prompt = prompt.prompt
|
||||
modal.componentInstance.password = !prompt.echo
|
||||
results.push(await modal.result)
|
||||
}
|
||||
finish(results)
|
||||
}))
|
||||
ssh.connect({
|
||||
host: connection.host,
|
||||
username: connection.user,
|
||||
password: privateKey ? undefined : '',
|
||||
privateKey,
|
||||
tryKeyboard: true,
|
||||
})
|
||||
|
||||
let keychainPasswordUsed = false
|
||||
|
||||
;(ssh as any).config.password = () => this.zone.run(async () => {
|
||||
if (connection.password) {
|
||||
return connection.password
|
||||
}
|
||||
|
||||
if (!keychainPasswordUsed && keychain.isSupported()) {
|
||||
let password = await new Promise(resolve => {
|
||||
keychain.getPassword({
|
||||
account: connection.user,
|
||||
service: `ssh@${connection.host}`,
|
||||
}, (_, result) => resolve(result))
|
||||
})
|
||||
if (password) {
|
||||
keychainPasswordUsed = true
|
||||
return password
|
||||
}
|
||||
}
|
||||
|
||||
let modal = this.ngbModal.open(PromptModalComponent)
|
||||
modal.componentInstance.prompt = `Password for ${connection.user}@${connection.host}`
|
||||
modal.componentInstance.password = true
|
||||
let password = await modal.result
|
||||
|
||||
keychain.setPassword({
|
||||
account: connection.user,
|
||||
service: `ssh@${connection.host}`,
|
||||
password
|
||||
}, () => null)
|
||||
|
||||
return password
|
||||
})
|
||||
})
|
||||
|
||||
try {
|
||||
let shell = await new Promise((resolve, reject) => {
|
||||
ssh.shell({ term: 'xterm-256color' }, (err, shell) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(shell)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
let session = new SSHSession(shell)
|
||||
|
||||
return this.zone.run(() => this.app.openNewTab(
|
||||
TerminalTabComponent,
|
||||
{ session, sessionOptions: {} }
|
||||
) as TerminalTabComponent)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const _authPassword = SSH2Stream.prototype.authPassword
|
||||
SSH2Stream.prototype.authPassword = async function (username, passwordFn) {
|
||||
_authPassword.bind(this)(username, await passwordFn())
|
||||
}
|
14
terminus-ssh/src/settings.ts
Normal file
14
terminus-ssh/src/settings.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { SettingsTabProvider } from 'terminus-settings'
|
||||
|
||||
import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
|
||||
|
||||
@Injectable()
|
||||
export class SSHSettingsTabProvider extends SettingsTabProvider {
|
||||
id = 'ssh'
|
||||
title = 'SSH'
|
||||
|
||||
getComponentType (): any {
|
||||
return SSHSettingsTabComponent
|
||||
}
|
||||
}
|
8
terminus-ssh/tsconfig.json
Normal file
8
terminus-ssh/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"declarationDir": "dist"
|
||||
}
|
||||
}
|
46
terminus-ssh/webpack.config.js
Normal file
46
terminus-ssh/webpack.config.js
Normal file
@ -0,0 +1,46 @@
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
target: 'node',
|
||||
entry: 'src/index.ts',
|
||||
devtool: 'source-map',
|
||||
context: __dirname,
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'index.js',
|
||||
pathinfo: true,
|
||||
libraryTarget: 'umd',
|
||||
devtoolModuleFilenameTemplate: 'webpack-terminus-ssh:///[resource-path]',
|
||||
},
|
||||
resolve: {
|
||||
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
|
||||
extensions: ['.ts', '.js'],
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
loader: 'awesome-typescript-loader',
|
||||
query: {
|
||||
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
||||
typeRoots: [path.resolve(__dirname, 'node_modules/@types')],
|
||||
paths: {
|
||||
"terminus-*": [path.resolve(__dirname, '../terminus-*')],
|
||||
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
||||
}
|
||||
}
|
||||
},
|
||||
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
||||
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
||||
]
|
||||
},
|
||||
externals: [
|
||||
'fs',
|
||||
'node-ssh',
|
||||
'xkeychain',
|
||||
/^rxjs/,
|
||||
/^@angular/,
|
||||
/^@ng-bootstrap/,
|
||||
/^terminus-/,
|
||||
]
|
||||
}
|
2968
terminus-ssh/yarn.lock
Normal file
2968
terminus-ssh/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { SettingsTabProvider, ComponentType } from 'terminus-settings'
|
||||
import { SettingsTabProvider } from 'terminus-settings'
|
||||
|
||||
import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.component'
|
||||
|
||||
@ -8,7 +8,7 @@ export class TerminalSettingsTabProvider extends SettingsTabProvider {
|
||||
id = 'terminal'
|
||||
title = 'Terminal'
|
||||
|
||||
getComponentType (): ComponentType {
|
||||
getComponentType (): any {
|
||||
return TerminalSettingsTabComponent
|
||||
}
|
||||
}
|
||||
|
@ -5,4 +5,5 @@ module.exports = [
|
||||
require('./terminus-terminal/webpack.config.js'),
|
||||
require('./terminus-community-color-schemes/webpack.config.js'),
|
||||
require('./terminus-plugin-manager/webpack.config.js'),
|
||||
require('./terminus-ssh/webpack.config.js'),
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user