mirror of
https://github.com/Eugeny/tabby.git
synced 2025-03-01 15:06:27 +08:00
Merge branch 'master' into terminus_pinpin
This commit is contained in:
commit
56be0a1085
@ -316,6 +316,24 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "pinpins",
|
||||
"name": "pinpin",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/36234677?v=4",
|
||||
"profile": "https://zergpool.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "TakuroOnoda",
|
||||
"name": "Takuro Onoda",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/1407926?v=4",
|
||||
"profile": "https://github.com/TakuroOnoda",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
@ -116,6 +116,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<td align="center"><a href="https://github.com/JonathanBeverley"><img src="https://avatars1.githubusercontent.com/u/20328966?v=4" width="100px;" alt=""/><br /><sub><b>Jonathan Beverley</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=JonathanBeverley" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/zend"><img src="https://avatars1.githubusercontent.com/u/25160?v=4" width="100px;" alt=""/><br /><sub><b>Zenghai Liang</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=zend" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://about.me/matishadow"><img src="https://avatars0.githubusercontent.com/u/9083085?v=4" width="100px;" alt=""/><br /><sub><b>Mateusz Tracz</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=matishadow" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://zergpool.com"><img src="https://avatars3.githubusercontent.com/u/36234677?v=4" width="100px;" alt=""/><br /><sub><b>pinpin</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=pinpins" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/TakuroOnoda"><img src="https://avatars0.githubusercontent.com/u/1407926?v=4" width="100px;" alt=""/><br /><sub><b>Takuro Onoda</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=TakuroOnoda" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
@ -139,9 +139,7 @@ export class Application {
|
||||
|
||||
handleSecondInstance (argv: string[], cwd: string): void {
|
||||
this.presentAllWindows()
|
||||
for (let window of this.windows) {
|
||||
window.handleSecondInstance(argv, cwd)
|
||||
}
|
||||
this.windows[this.windows.length - 1].handleSecondInstance(argv, cwd)
|
||||
}
|
||||
|
||||
private setupMenu () {
|
||||
|
@ -211,9 +211,7 @@ export class Window {
|
||||
}
|
||||
|
||||
handleSecondInstance (argv: string[], cwd: string): void {
|
||||
if (!this.configStore.appearance?.dock) {
|
||||
this.send('host:second-instance', parseArgs(argv, cwd), cwd)
|
||||
}
|
||||
this.send('host:second-instance', parseArgs(argv, cwd), cwd)
|
||||
}
|
||||
|
||||
private setupWindowManagement () {
|
||||
|
@ -1,8 +1,8 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.1",
|
||||
"@sentry/cli": "^1.59.0",
|
||||
"@sentry/electron": "^1.5.2",
|
||||
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||
"@sentry/cli": "^1.52.3",
|
||||
"@sentry/electron": "^2.0.4",
|
||||
"@types/electron-config": "^3.2.2",
|
||||
"@types/electron-debug": "^2.1.0",
|
||||
"@types/js-yaml": "^3.12.5",
|
||||
@ -15,7 +15,7 @@
|
||||
"core-js": "^3.7.0",
|
||||
"cross-env": "7.0.2",
|
||||
"css-loader": "3.4.2",
|
||||
"electron": "^8.5.3",
|
||||
"electron": "^8.5.2",
|
||||
"electron-builder": "22.6.1",
|
||||
"electron-download": "^4.1.1",
|
||||
"electron-installer-snap": "^5.1.0",
|
||||
|
@ -4,10 +4,13 @@ title-bar(
|
||||
)
|
||||
|
||||
.content(
|
||||
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top"'
|
||||
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top" || config.store.appearance.tabsLocation == "left"',
|
||||
[class.tabs-on-side]='hasVerticalTabs()',
|
||||
)
|
||||
.tab-bar
|
||||
.inset.background(*ngIf='hostApp.platform == Platform.macOS && config.store.appearance.frame == "thin" && config.store.appearance.tabsLocation == "top"')
|
||||
.inset.background(*ngIf='hostApp.platform == Platform.macOS \
|
||||
&& config.store.appearance.frame == "thin" \
|
||||
&& (config.store.appearance.tabsLocation == "top" || config.store.appearance.tabsLocation == "left")')
|
||||
.tabs(
|
||||
dnd-sortable-container,
|
||||
[sortableData]='app.tabs',
|
||||
@ -18,12 +21,12 @@ title-bar(
|
||||
[sortableIndex]='idx',
|
||||
(onDragStart)='onTabDragStart()',
|
||||
(onDragEnd)='onTabDragEnd()',
|
||||
|
||||
[index]='idx',
|
||||
[tab]='tab',
|
||||
[active]='tab == app.activeTab',
|
||||
[hasActivity]='tab.activity$|async',
|
||||
@animateTab,
|
||||
[@.disabled]='hasVerticalTabs()',
|
||||
(click)='app.selectTab(tab)',
|
||||
[class.fully-draggable]='hostApp.platform != Platform.macOS',
|
||||
[class.drag-region]='hostApp.platform == Platform.macOS && !tabsDragging',
|
||||
@ -87,7 +90,8 @@ title-bar(
|
||||
)
|
||||
|
||||
window-controls.background(
|
||||
*ngIf='config.store.appearance.frame == "thin" && (hostApp.platform == Platform.Windows || hostApp.platform == Platform.Linux)',
|
||||
*ngIf='config.store.appearance.frame == "thin" \
|
||||
&& (hostApp.platform == Platform.Windows || hostApp.platform == Platform.Linux)',
|
||||
)
|
||||
|
||||
start-page(*ngIf='ready && app.tabs.length == 0')
|
||||
|
@ -15,10 +15,18 @@
|
||||
|
||||
$tabs-height: 38px;
|
||||
$tab-border-radius: 4px;
|
||||
$side-tab-width: 200px;
|
||||
|
||||
.wrap {
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.content {
|
||||
height: 100%;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
@ -26,15 +34,50 @@ $tab-border-radius: 4px;
|
||||
&.tabs-on-top {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.tabs-on-side {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
&.tabs-on-top {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.content.tabs-on-side > .tab-bar {
|
||||
height: 100%;
|
||||
width: $side-tab-width;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
flex-direction: column;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
|
||||
.tabs {
|
||||
width: $side-tab-width;
|
||||
flex: none;
|
||||
flex-direction: column;
|
||||
|
||||
tab-header {
|
||||
flex: 0 0 $tabs-height;
|
||||
}
|
||||
}
|
||||
|
||||
.drag-space {
|
||||
flex: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.tab-bar {
|
||||
flex: none;
|
||||
height: $tabs-height;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.btn-tab-bar {
|
||||
line-height: $tabs-height + 2px;
|
||||
height: $tabs-height;
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
@ -74,7 +117,10 @@ $tab-border-radius: 4px;
|
||||
|
||||
& > .inset {
|
||||
width: 85px;
|
||||
height: $tabs-height;
|
||||
flex: none;
|
||||
opacity: 0;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
window-controls {
|
||||
|
@ -184,6 +184,10 @@ export class AppRootComponent {
|
||||
return false
|
||||
}
|
||||
|
||||
hasVerticalTabs () {
|
||||
return this.config.store.appearance.tabsLocation === 'left' || this.config.store.appearance.tabsLocation === 'right'
|
||||
}
|
||||
|
||||
async updateApp () {
|
||||
if ((await this.electron.showMessageBox(
|
||||
this.hostApp.getWindow(),
|
||||
|
@ -1,7 +1,7 @@
|
||||
.progressbar([style.width]='progress + "%"', *ngIf='progress != null')
|
||||
.index(*ngIf='!config.store.terminal.disableTabIndex',
|
||||
.index(*ngIf='!config.store.terminal.hideTabIndex',
|
||||
#handle,
|
||||
[style.background-color]='tab.color',
|
||||
) {{index + 1}}
|
||||
.name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}}
|
||||
button(*ngIf='!config.store.terminal.disableCloseButton',(click)='app.closeTab(tab, true)') ×
|
||||
button(*ngIf='!config.store.terminal.hideCloseButton',(click)='app.closeTab(tab, true)') ×
|
||||
|
@ -13,7 +13,12 @@ $tabs-height: 38px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
.index {
|
||||
&.vertical {
|
||||
flex: none;
|
||||
height: $tabs-height;
|
||||
}
|
||||
|
||||
.index {
|
||||
flex: none;
|
||||
font-weight: bold;
|
||||
-webkit-app-region: no-drag;
|
||||
|
@ -6,6 +6,7 @@ app-root {
|
||||
|
||||
.btn-tab-bar {
|
||||
line-height: 29px !important;
|
||||
height: 27px !important;
|
||||
|
||||
svg {
|
||||
height: 14px;
|
||||
|
@ -43,14 +43,28 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
||||
ngbButton,
|
||||
[value]='"top"'
|
||||
)
|
||||
| On the top
|
||||
| Top
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
[value]='"bottom"'
|
||||
)
|
||||
| At the bottom
|
||||
| Bottom
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
[value]='"left"'
|
||||
)
|
||||
| Left
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
[value]='"right"'
|
||||
)
|
||||
| Right
|
||||
|
||||
.form-line
|
||||
.header
|
||||
|
@ -65,7 +65,7 @@ export class SettingsTabComponent extends BaseTabComponent {
|
||||
const onConfigChange = () => {
|
||||
this.configFile = config.readRaw()
|
||||
this.padWindowControls = hostApp.platform === Platform.macOS
|
||||
&& config.store.appearance.tabsLocation === 'bottom'
|
||||
&& config.store.appearance.tabsLocation !== 'top'
|
||||
}
|
||||
|
||||
this.configSubscription = config.changed$.subscribe(onConfigChange)
|
||||
|
@ -10,7 +10,9 @@
|
||||
"scripts": {
|
||||
"build": "webpack --progress --color",
|
||||
"watch": "webpack --progress --color --watch",
|
||||
"postinstall:win32": "xcopy /i node_modules\\ssh2\\util\\pagent.exe util\\"
|
||||
"postinstall": "run-script-os",
|
||||
"postinstall:darwin:linux": "exit",
|
||||
"postinstall:win32": "xcopy /i /y node_modules\\ssh2\\util\\pagent.exe util\\"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
@ -23,9 +25,12 @@
|
||||
"@types/ssh2": "^0.5.35",
|
||||
"ansi-colors": "^4.1.1",
|
||||
"cli-spinner": "^0.2.10",
|
||||
"ssh2": "^0.8.2",
|
||||
"run-script-os": "^1.1.3",
|
||||
"socksv5": "^0.0.6",
|
||||
"ssh2": "^0.8.9",
|
||||
"ssh2-streams": "Eugeny/ssh2-streams#75f6d3425d071ac73a18fd46e2f5e738bfe897c5",
|
||||
"sshpk": "^1.16.1",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"temp": "^0.9.1",
|
||||
"terminus-terminal": "^1.0.98-nightly.0"
|
||||
},
|
||||
|
@ -1,4 +1,6 @@
|
||||
import colors from 'ansi-colors'
|
||||
import stripAnsi from 'strip-ansi'
|
||||
import socksv5 from 'socksv5'
|
||||
import { BaseSession } from 'terminus-terminal'
|
||||
import { Server, Socket, createServer, createConnection } from 'net'
|
||||
import { Client, ClientChannel } from 'ssh2'
|
||||
@ -43,7 +45,7 @@ export interface SSHConnection {
|
||||
}
|
||||
|
||||
export enum PortForwardType {
|
||||
Local, Remote
|
||||
Local, Remote, Dynamic
|
||||
}
|
||||
|
||||
export class ForwardedPort {
|
||||
@ -55,13 +57,40 @@ export class ForwardedPort {
|
||||
|
||||
private listener: Server
|
||||
|
||||
async startLocalListener (callback: (Socket) => void): Promise<void> {
|
||||
this.listener = createServer(callback)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.listener.listen(this.port, '127.0.0.1')
|
||||
this.listener.on('error', reject)
|
||||
this.listener.on('listening', resolve)
|
||||
})
|
||||
async startLocalListener (callback: (accept: () => Socket, reject: () => void, sourceAddress: string|null, sourcePort: number|null, targetAddress: string, targetPort: number) => void): Promise<void> {
|
||||
if (this.type === PortForwardType.Local) {
|
||||
this.listener = createServer(s => callback(
|
||||
() => s,
|
||||
() => s.destroy(),
|
||||
s.remoteAddress ?? null,
|
||||
s.remotePort ?? null,
|
||||
this.targetAddress,
|
||||
this.targetPort,
|
||||
))
|
||||
return new Promise((resolve, reject) => {
|
||||
this.listener.listen(this.port, this.host)
|
||||
this.listener.on('error', reject)
|
||||
this.listener.on('listening', resolve)
|
||||
})
|
||||
} else if (this.type === PortForwardType.Dynamic) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.listener = socksv5.createServer((info, accept, reject) => {
|
||||
callback(
|
||||
() => accept(true),
|
||||
() => reject(),
|
||||
null,
|
||||
null,
|
||||
info.dstAddr,
|
||||
info.dstPort,
|
||||
)
|
||||
})
|
||||
this.listener.on('error', reject)
|
||||
this.listener.listen(this.port, this.host, resolve)
|
||||
;(this.listener as any).useAuth(socksv5.auth.None())
|
||||
})
|
||||
} else {
|
||||
throw new Error('Invalid forward type for a local listener')
|
||||
}
|
||||
}
|
||||
|
||||
stopLocalListener (): void {
|
||||
@ -71,8 +100,10 @@ export class ForwardedPort {
|
||||
toString (): string {
|
||||
if (this.type === PortForwardType.Local) {
|
||||
return `(local) ${this.host}:${this.port} → (remote) ${this.targetAddress}:${this.targetPort}`
|
||||
} else {
|
||||
} if (this.type === PortForwardType.Remote) {
|
||||
return `(remote) ${this.host}:${this.port} → (local) ${this.targetAddress}:${this.targetPort}`
|
||||
} else {
|
||||
return `(dynamic) ${this.host}:${this.port}`
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -232,25 +263,26 @@ export class SSHSession extends BaseSession {
|
||||
|
||||
emitServiceMessage (msg: string): void {
|
||||
this.serviceMessage.next(msg)
|
||||
this.logger.info(msg)
|
||||
this.logger.info(stripAnsi(msg))
|
||||
}
|
||||
|
||||
async addPortForward (fw: ForwardedPort): Promise<void> {
|
||||
if (fw.type === PortForwardType.Local) {
|
||||
await fw.startLocalListener((socket: Socket) => {
|
||||
if (fw.type === PortForwardType.Local || fw.type === PortForwardType.Dynamic) {
|
||||
await fw.startLocalListener((accept, reject, sourceAddress, sourcePort, targetAddress, targetPort) => {
|
||||
this.logger.info(`New connection on ${fw}`)
|
||||
this.ssh.forwardOut(
|
||||
socket.remoteAddress || '127.0.0.1',
|
||||
socket.remotePort || 0,
|
||||
fw.targetAddress,
|
||||
fw.targetPort,
|
||||
sourceAddress || '127.0.0.1',
|
||||
sourcePort || 0,
|
||||
targetAddress,
|
||||
targetPort,
|
||||
(err, stream) => {
|
||||
if (err) {
|
||||
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwaded connection via ${fw}: ${err}`)
|
||||
socket.destroy()
|
||||
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection to ${targetAddress}:${targetPort} via ${fw}: ${err}`)
|
||||
reject()
|
||||
return
|
||||
}
|
||||
if (stream) {
|
||||
const socket = accept()
|
||||
stream.pipe(socket)
|
||||
socket.pipe(stream)
|
||||
stream.on('close', () => {
|
||||
@ -263,7 +295,7 @@ export class SSHSession extends BaseSession {
|
||||
}
|
||||
)
|
||||
}).then(() => {
|
||||
this.emitServiceMessage(colors.bgGreen.black(' -> ') + ` Forwaded ${fw}`)
|
||||
this.emitServiceMessage(colors.bgGreen.black(' -> ') + ` Forwarded ${fw}`)
|
||||
this.forwardedPorts.push(fw)
|
||||
}).catch(e => {
|
||||
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Failed to forward port ${fw}: ${e}`)
|
||||
@ -280,13 +312,13 @@ export class SSHSession extends BaseSession {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
this.emitServiceMessage(colors.bgGreen.black(' <- ') + ` Forwaded ${fw}`)
|
||||
this.emitServiceMessage(colors.bgGreen.black(' <- ') + ` Forwarded ${fw}`)
|
||||
this.forwardedPorts.push(fw)
|
||||
}
|
||||
}
|
||||
|
||||
async removePortForward (fw: ForwardedPort): Promise<void> {
|
||||
if (fw.type === PortForwardType.Local) {
|
||||
if (fw.type === PortForwardType.Local || fw.type === PortForwardType.Dynamic) {
|
||||
fw.stopLocalListener()
|
||||
this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw)
|
||||
}
|
||||
|
@ -6,12 +6,22 @@
|
||||
.list-group-item.d-flex.align-items-center(*ngFor='let fw of session.forwardedPorts')
|
||||
strong(*ngIf='fw.type === PortForwardType.Local') Local
|
||||
strong(*ngIf='fw.type === PortForwardType.Remote') Remote
|
||||
.ml-3 {{fw.host}}:{{fw.port}} → {{fw.targetAddress}}:{{fw.targetPort}}
|
||||
strong(*ngIf='fw.type === PortForwardType.Dynamic') Dynamic
|
||||
.ml-3 {{fw.host}}:{{fw.port}}
|
||||
.ml-2 →
|
||||
.ml-2(*ngIf='fw.type !== PortForwardType.Dynamic') {{fw.targetAddress}}:{{fw.targetPort}}
|
||||
.ml-2(*ngIf='fw.type === PortForwardType.Dynamic') SOCKS proxy
|
||||
button.btn.btn-link.ml-auto((click)='remove(fw)')
|
||||
i.fas.fa-trash-alt.mr-2
|
||||
span Remove
|
||||
|
||||
.input-group.mb-2
|
||||
.input-group.mb-2(*ngIf='newForward.type === PortForwardType.Dynamic')
|
||||
input.form-control(type='text', [(ngModel)]='newForward.host')
|
||||
.input-group-append
|
||||
.input-group-text :
|
||||
input.form-control(type='number', [(ngModel)]='newForward.port')
|
||||
|
||||
.input-group.mb-2(*ngIf='newForward.type !== PortForwardType.Dynamic')
|
||||
input.form-control(type='text', [(ngModel)]='newForward.host')
|
||||
.input-group-append
|
||||
.input-group-text :
|
||||
@ -42,6 +52,13 @@
|
||||
[value]='PortForwardType.Remote'
|
||||
)
|
||||
| Remote
|
||||
label.btn.btn-secondary.m-0(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
[value]='PortForwardType.Dynamic'
|
||||
)
|
||||
| Dynamic
|
||||
|
||||
button.btn.btn-primary((click)='addForward()')
|
||||
i.fas.fa-check.mr-2
|
||||
|
@ -1,4 +1,5 @@
|
||||
import colors from 'ansi-colors'
|
||||
import stripAnsi from 'strip-ansi'
|
||||
import { open as openTemp } from 'temp'
|
||||
import { Injectable, NgZone } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
@ -22,6 +23,11 @@ try {
|
||||
var windowsProcessTreeNative = require('windows-process-tree/build/Release/windows_process_tree.node') // eslint-disable-line @typescript-eslint/no-var-requires, no-var
|
||||
} catch { }
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-type-alias
|
||||
export type SSHLogCallback = (message: string) => void
|
||||
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SSHService {
|
||||
private logger: Logger
|
||||
@ -46,33 +52,24 @@ export class SSHService {
|
||||
return session
|
||||
}
|
||||
|
||||
async connectSession (session: SSHSession, logCallback?: (s: any) => void): Promise<void> {
|
||||
async loadPrivateKeyForSession (session: SSHSession, logCallback?: SSHLogCallback): Promise<string|null> {
|
||||
let privateKey: string|null = null
|
||||
let privateKeyPath = session.connection.privateKey
|
||||
|
||||
if (!logCallback) {
|
||||
logCallback = () => null
|
||||
}
|
||||
|
||||
const log = (s: any) => {
|
||||
logCallback!(s)
|
||||
this.logger.info(s)
|
||||
}
|
||||
|
||||
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')
|
||||
logCallback?.('Using user\'s default private key')
|
||||
privateKeyPath = userKeyPath
|
||||
}
|
||||
}
|
||||
|
||||
if (privateKeyPath) {
|
||||
log('Loading private key from ' + colors.bgWhite.blackBright(' ' + privateKeyPath + ' '))
|
||||
logCallback?.('Loading private key from ' + colors.bgWhite.blackBright(' ' + privateKeyPath + ' '))
|
||||
try {
|
||||
privateKey = (await fs.readFile(privateKeyPath)).toString()
|
||||
} catch (error) {
|
||||
log(colors.bgRed.black(' X ') + 'Could not read the private key file')
|
||||
logCallback?.(colors.bgRed.black(' X ') + 'Could not read the private key file')
|
||||
this.toastr.error('Could not read the private key file')
|
||||
}
|
||||
|
||||
@ -83,7 +80,7 @@ export class SSHService {
|
||||
} catch (e) {
|
||||
if (e instanceof sshpk.KeyEncryptedError) {
|
||||
const modal = this.ngbModal.open(PromptModalComponent)
|
||||
log(colors.bgYellow.yellow.black(' ! ') + ' Key requires passphrase')
|
||||
logCallback?.(colors.bgYellow.yellow.black(' ! ') + ' Key requires passphrase')
|
||||
modal.componentInstance.prompt = 'Private key passphrase'
|
||||
modal.componentInstance.password = true
|
||||
let passphrase = ''
|
||||
@ -131,6 +128,20 @@ export class SSHService {
|
||||
fs.unlink(temp.path)
|
||||
}
|
||||
}
|
||||
return privateKey
|
||||
}
|
||||
|
||||
async connectSession (session: SSHSession, logCallback?: SSHLogCallback): Promise<void> {
|
||||
if (!logCallback) {
|
||||
logCallback = () => null
|
||||
}
|
||||
|
||||
const log = (s: any) => {
|
||||
logCallback!(s)
|
||||
this.logger.info(stripAnsi(s))
|
||||
}
|
||||
|
||||
let privateKey: string|null = null
|
||||
|
||||
const ssh = new Client()
|
||||
session.ssh = ssh
|
||||
@ -213,6 +224,7 @@ export class SSHService {
|
||||
|
||||
const authMethodsLeft = ['none']
|
||||
if (!session.connection.auth || session.connection.auth === 'publicKey') {
|
||||
privateKey = await this.loadPrivateKeyForSession(session, log)
|
||||
if (!privateKey) {
|
||||
log('\r\nPrivate key auth selected, but no key is loaded\r\n')
|
||||
} else {
|
||||
|
@ -50,6 +50,7 @@ module.exports = {
|
||||
'keytar',
|
||||
'path',
|
||||
'ngx-toastr',
|
||||
'socksv5',
|
||||
'windows-native-registry',
|
||||
'windows-process-tree/build/Release/windows_process_tree.node',
|
||||
/^rxjs/,
|
||||
|
@ -32,6 +32,11 @@ ansi-colors@^4.1.1:
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
||||
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
|
||||
|
||||
ansi-regex@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
|
||||
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
|
||||
|
||||
asn1@~0.2.0, asn1@~0.2.3:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||
@ -44,6 +49,11 @@ assert-plus@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
|
||||
|
||||
async@0.2.x:
|
||||
version "0.2.10"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
|
||||
integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E=
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
@ -69,11 +79,42 @@ cli-spinner@^0.2.10:
|
||||
resolved "https://registry.yarnpkg.com/cli-spinner/-/cli-spinner-0.2.10.tgz#f7d617a36f5c47a7bc6353c697fc9338ff782a47"
|
||||
integrity sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q==
|
||||
|
||||
cli@0.4.x:
|
||||
version "0.4.5"
|
||||
resolved "https://registry.yarnpkg.com/cli/-/cli-0.4.5.tgz#78f9485cd161b566e9a6c72d7170c4270e81db61"
|
||||
integrity sha1-ePlIXNFhtWbppsctcXDEJw6B22E=
|
||||
dependencies:
|
||||
glob ">= 3.1.4"
|
||||
|
||||
cliff@0.1.x:
|
||||
version "0.1.10"
|
||||
resolved "https://registry.yarnpkg.com/cliff/-/cliff-0.1.10.tgz#53be33ea9f59bec85609ee300ac4207603e52013"
|
||||
integrity sha1-U74z6p9ZvshWCe4wCsQgdgPlIBM=
|
||||
dependencies:
|
||||
colors "~1.0.3"
|
||||
eyes "~0.1.8"
|
||||
winston "0.8.x"
|
||||
|
||||
colors@0.6.x:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc"
|
||||
integrity sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=
|
||||
|
||||
colors@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
|
||||
integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||
|
||||
cycle@1.0.x:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
|
||||
integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI=
|
||||
|
||||
dashdash@^1.12.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||
@ -89,6 +130,11 @@ ecc-jsbn@~0.1.1:
|
||||
jsbn "~0.1.0"
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
eyes@0.1.x, eyes@~0.1.8:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
|
||||
integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
@ -101,7 +147,7 @@ getpass@^0.1.1:
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
glob@^7.1.3:
|
||||
"glob@>= 3.1.4", glob@^7.1.3:
|
||||
version "7.1.6"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
||||
@ -126,6 +172,20 @@ inherits@2:
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ipv6@*:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ipv6/-/ipv6-3.1.3.tgz#4d9064f9c2dafa0dd10b8b7d76ffca4aad31b3b9"
|
||||
integrity sha1-TZBk+cLa+g3RC4t9dv/KSq0xs7k=
|
||||
dependencies:
|
||||
cli "0.4.x"
|
||||
cliff "0.1.x"
|
||||
sprintf "0.1.x"
|
||||
|
||||
isstream@0.1.x:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
|
||||
|
||||
jsbn@~0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
||||
@ -162,6 +222,11 @@ path-is-absolute@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||
|
||||
pkginfo@0.3.x:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21"
|
||||
integrity sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=
|
||||
|
||||
rimraf@~2.6.2:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
||||
@ -169,11 +234,28 @@ rimraf@~2.6.2:
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
run-script-os@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/run-script-os/-/run-script-os-1.1.3.tgz#1069b418307f4fd36ff056b5eda309c273fca8b0"
|
||||
integrity sha512-xPlzE6533nvWVea5z7e5J7+JAIepfpxTu/HLGxcjJYlemVukOCWJBaRCod/DWXJFRIWEFOgSGbjd2m1QWTJi5w==
|
||||
|
||||
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
socksv5@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/socksv5/-/socksv5-0.0.6.tgz#1327235ff7e8de21ac434a0a579dc69c3f071061"
|
||||
integrity sha1-EycjX/fo3iGsQ0oKV53GnD8HEGE=
|
||||
dependencies:
|
||||
ipv6 "*"
|
||||
|
||||
sprintf@0.1.x:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/sprintf/-/sprintf-0.1.5.tgz#8f83e39a9317c1a502cb7db8050e51c679f6edcf"
|
||||
integrity sha1-j4PjmpMXwaUCy324BQ5Rxnn27c8=
|
||||
|
||||
ssh2-streams@Eugeny/ssh2-streams#75f6d3425d071ac73a18fd46e2f5e738bfe897c5:
|
||||
version "0.4.10"
|
||||
resolved "https://codeload.github.com/Eugeny/ssh2-streams/tar.gz/75f6d3425d071ac73a18fd46e2f5e738bfe897c5"
|
||||
@ -191,7 +273,7 @@ ssh2-streams@~0.4.10:
|
||||
bcrypt-pbkdf "^1.0.2"
|
||||
streamsearch "~0.1.2"
|
||||
|
||||
ssh2@^0.8.2:
|
||||
ssh2@^0.8.9:
|
||||
version "0.8.9"
|
||||
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3"
|
||||
integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==
|
||||
@ -213,11 +295,23 @@ sshpk@^1.16.1:
|
||||
safer-buffer "^2.0.2"
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
stack-trace@0.0.x:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
|
||||
integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=
|
||||
|
||||
streamsearch@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
||||
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
|
||||
|
||||
strip-ansi@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
|
||||
integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.0"
|
||||
|
||||
temp@^0.9.1:
|
||||
version "0.9.4"
|
||||
resolved "https://registry.yarnpkg.com/temp/-/temp-0.9.4.tgz#cd20a8580cb63635d0e4e9d4bd989d44286e7620"
|
||||
@ -236,6 +330,19 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
|
||||
|
||||
winston@0.8.x:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/winston/-/winston-0.8.3.tgz#64b6abf4cd01adcaefd5009393b1d8e8bec19db0"
|
||||
integrity sha1-ZLar9M0Brcrv1QCTk7HY6L7BnbA=
|
||||
dependencies:
|
||||
async "0.2.x"
|
||||
colors "0.6.x"
|
||||
cycle "1.0.x"
|
||||
eyes "0.1.x"
|
||||
isstream "0.1.x"
|
||||
pkginfo "0.3.x"
|
||||
stack-trace "0.0.x"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
|
@ -156,16 +156,28 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||
this.resetZoom()
|
||||
break
|
||||
case 'previous-word':
|
||||
this.sendInput('\x1bb')
|
||||
this.sendInput({
|
||||
[Platform.Windows]: '\x1b[1;5D',
|
||||
[Platform.macOS]: '\x1bb',
|
||||
[Platform.Linux]: '\x1bb',
|
||||
}[this.hostApp.platform])
|
||||
break
|
||||
case 'next-word':
|
||||
this.sendInput('\x1bf')
|
||||
this.sendInput({
|
||||
[Platform.Windows]: '\x1b[1;5C',
|
||||
[Platform.macOS]: '\x1bf',
|
||||
[Platform.Linux]: '\x1bf',
|
||||
}[this.hostApp.platform])
|
||||
break
|
||||
case 'delete-previous-word':
|
||||
this.sendInput('\x1b\x7f')
|
||||
break
|
||||
case 'delete-next-word':
|
||||
this.sendInput('\x1bd')
|
||||
this.sendInput({
|
||||
[Platform.Windows]: '\x1bd\x1b[3;5~',
|
||||
[Platform.macOS]: '\x1bd',
|
||||
[Platform.Linux]: '\x1bd',
|
||||
}[this.hostApp.platform])
|
||||
break
|
||||
case 'search':
|
||||
this.showSearchPanel = true
|
||||
@ -348,7 +360,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||
|
||||
this.topPadded = this.hostApp.platform === Platform.macOS
|
||||
&& this.config.store.appearance.frame === 'thin'
|
||||
&& this.config.store.appearance.tabsLocation === 'bottom'
|
||||
&& this.config.store.appearance.tabsLocation !== 'top'
|
||||
|
||||
if (this.config.store.terminal.background === 'colorScheme') {
|
||||
if (this.config.store.terminal.colorScheme.background) {
|
||||
|
@ -111,19 +111,19 @@ h3.mb-3 Appearance
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Disable tab index
|
||||
.title Hide tab index
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.disableTabIndex',
|
||||
[(ngModel)]='config.store.terminal.hideTabIndex',
|
||||
(ngModelChange)='config.save();',
|
||||
)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Disable tab close button
|
||||
.title Hide tab close button
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.disableCloseButton',
|
||||
[(ngModel)]='config.store.terminal.hideCloseButton',
|
||||
(ngModelChange)='config.save();',
|
||||
)
|
||||
|
||||
|
@ -23,8 +23,8 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
ligatures: false,
|
||||
cursor: 'block',
|
||||
cursorBlink: true,
|
||||
disableTabIndex: false,
|
||||
disableCloseButton: false,
|
||||
hideTabIndex: false,
|
||||
hideCloseButton: false,
|
||||
customShell: '',
|
||||
rightClick: 'menu',
|
||||
pasteOnMiddleClick: true,
|
||||
|
Loading…
Reference in New Issue
Block a user