mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-04 18:39:54 +00:00
Merge branch 'master' into terminus_pinpin
This commit is contained in:
commit
56be0a1085
@ -316,6 +316,24 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"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,
|
"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/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://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://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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -139,9 +139,7 @@ export class Application {
|
|||||||
|
|
||||||
handleSecondInstance (argv: string[], cwd: string): void {
|
handleSecondInstance (argv: string[], cwd: string): void {
|
||||||
this.presentAllWindows()
|
this.presentAllWindows()
|
||||||
for (let window of this.windows) {
|
this.windows[this.windows.length - 1].handleSecondInstance(argv, cwd)
|
||||||
window.handleSecondInstance(argv, cwd)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupMenu () {
|
private setupMenu () {
|
||||||
|
@ -211,10 +211,8 @@ export class Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSecondInstance (argv: string[], cwd: string): void {
|
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 () {
|
private setupWindowManagement () {
|
||||||
this.window.on('show', () => {
|
this.window.on('show', () => {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.15.1",
|
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||||
"@sentry/cli": "^1.59.0",
|
"@sentry/cli": "^1.52.3",
|
||||||
"@sentry/electron": "^1.5.2",
|
"@sentry/electron": "^2.0.4",
|
||||||
"@types/electron-config": "^3.2.2",
|
"@types/electron-config": "^3.2.2",
|
||||||
"@types/electron-debug": "^2.1.0",
|
"@types/electron-debug": "^2.1.0",
|
||||||
"@types/js-yaml": "^3.12.5",
|
"@types/js-yaml": "^3.12.5",
|
||||||
@ -15,7 +15,7 @@
|
|||||||
"core-js": "^3.7.0",
|
"core-js": "^3.7.0",
|
||||||
"cross-env": "7.0.2",
|
"cross-env": "7.0.2",
|
||||||
"css-loader": "3.4.2",
|
"css-loader": "3.4.2",
|
||||||
"electron": "^8.5.3",
|
"electron": "^8.5.2",
|
||||||
"electron-builder": "22.6.1",
|
"electron-builder": "22.6.1",
|
||||||
"electron-download": "^4.1.1",
|
"electron-download": "^4.1.1",
|
||||||
"electron-installer-snap": "^5.1.0",
|
"electron-installer-snap": "^5.1.0",
|
||||||
|
@ -4,10 +4,13 @@ title-bar(
|
|||||||
)
|
)
|
||||||
|
|
||||||
.content(
|
.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
|
.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(
|
.tabs(
|
||||||
dnd-sortable-container,
|
dnd-sortable-container,
|
||||||
[sortableData]='app.tabs',
|
[sortableData]='app.tabs',
|
||||||
@ -18,12 +21,12 @@ title-bar(
|
|||||||
[sortableIndex]='idx',
|
[sortableIndex]='idx',
|
||||||
(onDragStart)='onTabDragStart()',
|
(onDragStart)='onTabDragStart()',
|
||||||
(onDragEnd)='onTabDragEnd()',
|
(onDragEnd)='onTabDragEnd()',
|
||||||
|
|
||||||
[index]='idx',
|
[index]='idx',
|
||||||
[tab]='tab',
|
[tab]='tab',
|
||||||
[active]='tab == app.activeTab',
|
[active]='tab == app.activeTab',
|
||||||
[hasActivity]='tab.activity$|async',
|
[hasActivity]='tab.activity$|async',
|
||||||
@animateTab,
|
@animateTab,
|
||||||
|
[@.disabled]='hasVerticalTabs()',
|
||||||
(click)='app.selectTab(tab)',
|
(click)='app.selectTab(tab)',
|
||||||
[class.fully-draggable]='hostApp.platform != Platform.macOS',
|
[class.fully-draggable]='hostApp.platform != Platform.macOS',
|
||||||
[class.drag-region]='hostApp.platform == Platform.macOS && !tabsDragging',
|
[class.drag-region]='hostApp.platform == Platform.macOS && !tabsDragging',
|
||||||
@ -87,7 +90,8 @@ title-bar(
|
|||||||
)
|
)
|
||||||
|
|
||||||
window-controls.background(
|
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')
|
start-page(*ngIf='ready && app.tabs.length == 0')
|
||||||
|
@ -15,10 +15,18 @@
|
|||||||
|
|
||||||
$tabs-height: 38px;
|
$tabs-height: 38px;
|
||||||
$tab-border-radius: 4px;
|
$tab-border-radius: 4px;
|
||||||
|
$side-tab-width: 200px;
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
display: flex;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
height: 100%;
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
flex: auto;
|
flex: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
@ -26,15 +34,50 @@ $tab-border-radius: 4px;
|
|||||||
&.tabs-on-top {
|
&.tabs-on-top {
|
||||||
flex-direction: column;
|
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 {
|
.tab-bar {
|
||||||
flex: none;
|
flex: none;
|
||||||
height: $tabs-height;
|
height: $tabs-height;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
.btn-tab-bar {
|
.btn-tab-bar {
|
||||||
line-height: $tabs-height + 2px;
|
line-height: $tabs-height + 2px;
|
||||||
|
height: $tabs-height;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -74,7 +117,10 @@ $tab-border-radius: 4px;
|
|||||||
|
|
||||||
& > .inset {
|
& > .inset {
|
||||||
width: 85px;
|
width: 85px;
|
||||||
|
height: $tabs-height;
|
||||||
flex: none;
|
flex: none;
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-app-region: drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
window-controls {
|
window-controls {
|
||||||
|
@ -184,6 +184,10 @@ export class AppRootComponent {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasVerticalTabs () {
|
||||||
|
return this.config.store.appearance.tabsLocation === 'left' || this.config.store.appearance.tabsLocation === 'right'
|
||||||
|
}
|
||||||
|
|
||||||
async updateApp () {
|
async updateApp () {
|
||||||
if ((await this.electron.showMessageBox(
|
if ((await this.electron.showMessageBox(
|
||||||
this.hostApp.getWindow(),
|
this.hostApp.getWindow(),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
.progressbar([style.width]='progress + "%"', *ngIf='progress != null')
|
.progressbar([style.width]='progress + "%"', *ngIf='progress != null')
|
||||||
.index(*ngIf='!config.store.terminal.disableTabIndex',
|
.index(*ngIf='!config.store.terminal.hideTabIndex',
|
||||||
#handle,
|
#handle,
|
||||||
[style.background-color]='tab.color',
|
[style.background-color]='tab.color',
|
||||||
) {{index + 1}}
|
) {{index + 1}}
|
||||||
.name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}}
|
.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,6 +13,11 @@ $tabs-height: 38px;
|
|||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
flex: none;
|
||||||
|
height: $tabs-height;
|
||||||
|
}
|
||||||
|
|
||||||
.index {
|
.index {
|
||||||
flex: none;
|
flex: none;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -6,6 +6,7 @@ app-root {
|
|||||||
|
|
||||||
.btn-tab-bar {
|
.btn-tab-bar {
|
||||||
line-height: 29px !important;
|
line-height: 29px !important;
|
||||||
|
height: 27px !important;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
height: 14px;
|
height: 14px;
|
||||||
|
@ -43,14 +43,28 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
|||||||
ngbButton,
|
ngbButton,
|
||||||
[value]='"top"'
|
[value]='"top"'
|
||||||
)
|
)
|
||||||
| On the top
|
| Top
|
||||||
label.btn.btn-secondary(ngbButtonLabel)
|
label.btn.btn-secondary(ngbButtonLabel)
|
||||||
input(
|
input(
|
||||||
type='radio',
|
type='radio',
|
||||||
ngbButton,
|
ngbButton,
|
||||||
[value]='"bottom"'
|
[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
|
.form-line
|
||||||
.header
|
.header
|
||||||
|
@ -65,7 +65,7 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
const onConfigChange = () => {
|
const onConfigChange = () => {
|
||||||
this.configFile = config.readRaw()
|
this.configFile = config.readRaw()
|
||||||
this.padWindowControls = hostApp.platform === Platform.macOS
|
this.padWindowControls = hostApp.platform === Platform.macOS
|
||||||
&& config.store.appearance.tabsLocation === 'bottom'
|
&& config.store.appearance.tabsLocation !== 'top'
|
||||||
}
|
}
|
||||||
|
|
||||||
this.configSubscription = config.changed$.subscribe(onConfigChange)
|
this.configSubscription = config.changed$.subscribe(onConfigChange)
|
||||||
|
@ -10,7 +10,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack --progress --color",
|
"build": "webpack --progress --color",
|
||||||
"watch": "webpack --progress --color --watch",
|
"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": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
@ -23,9 +25,12 @@
|
|||||||
"@types/ssh2": "^0.5.35",
|
"@types/ssh2": "^0.5.35",
|
||||||
"ansi-colors": "^4.1.1",
|
"ansi-colors": "^4.1.1",
|
||||||
"cli-spinner": "^0.2.10",
|
"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",
|
"ssh2-streams": "Eugeny/ssh2-streams#75f6d3425d071ac73a18fd46e2f5e738bfe897c5",
|
||||||
"sshpk": "^1.16.1",
|
"sshpk": "^1.16.1",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
"temp": "^0.9.1",
|
"temp": "^0.9.1",
|
||||||
"terminus-terminal": "^1.0.98-nightly.0"
|
"terminus-terminal": "^1.0.98-nightly.0"
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import colors from 'ansi-colors'
|
import colors from 'ansi-colors'
|
||||||
|
import stripAnsi from 'strip-ansi'
|
||||||
|
import socksv5 from 'socksv5'
|
||||||
import { BaseSession } from 'terminus-terminal'
|
import { BaseSession } from 'terminus-terminal'
|
||||||
import { Server, Socket, createServer, createConnection } from 'net'
|
import { Server, Socket, createServer, createConnection } from 'net'
|
||||||
import { Client, ClientChannel } from 'ssh2'
|
import { Client, ClientChannel } from 'ssh2'
|
||||||
@ -43,7 +45,7 @@ export interface SSHConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum PortForwardType {
|
export enum PortForwardType {
|
||||||
Local, Remote
|
Local, Remote, Dynamic
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ForwardedPort {
|
export class ForwardedPort {
|
||||||
@ -55,13 +57,40 @@ export class ForwardedPort {
|
|||||||
|
|
||||||
private listener: Server
|
private listener: Server
|
||||||
|
|
||||||
async startLocalListener (callback: (Socket) => void): Promise<void> {
|
async startLocalListener (callback: (accept: () => Socket, reject: () => void, sourceAddress: string|null, sourcePort: number|null, targetAddress: string, targetPort: number) => void): Promise<void> {
|
||||||
this.listener = createServer(callback)
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.listener.listen(this.port, '127.0.0.1')
|
this.listener.listen(this.port, this.host)
|
||||||
this.listener.on('error', reject)
|
this.listener.on('error', reject)
|
||||||
this.listener.on('listening', resolve)
|
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 {
|
stopLocalListener (): void {
|
||||||
@ -71,8 +100,10 @@ export class ForwardedPort {
|
|||||||
toString (): string {
|
toString (): string {
|
||||||
if (this.type === PortForwardType.Local) {
|
if (this.type === PortForwardType.Local) {
|
||||||
return `(local) ${this.host}:${this.port} → (remote) ${this.targetAddress}:${this.targetPort}`
|
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}`
|
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 {
|
emitServiceMessage (msg: string): void {
|
||||||
this.serviceMessage.next(msg)
|
this.serviceMessage.next(msg)
|
||||||
this.logger.info(msg)
|
this.logger.info(stripAnsi(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
async addPortForward (fw: ForwardedPort): Promise<void> {
|
async addPortForward (fw: ForwardedPort): Promise<void> {
|
||||||
if (fw.type === PortForwardType.Local) {
|
if (fw.type === PortForwardType.Local || fw.type === PortForwardType.Dynamic) {
|
||||||
await fw.startLocalListener((socket: Socket) => {
|
await fw.startLocalListener((accept, reject, sourceAddress, sourcePort, targetAddress, targetPort) => {
|
||||||
this.logger.info(`New connection on ${fw}`)
|
this.logger.info(`New connection on ${fw}`)
|
||||||
this.ssh.forwardOut(
|
this.ssh.forwardOut(
|
||||||
socket.remoteAddress || '127.0.0.1',
|
sourceAddress || '127.0.0.1',
|
||||||
socket.remotePort || 0,
|
sourcePort || 0,
|
||||||
fw.targetAddress,
|
targetAddress,
|
||||||
fw.targetPort,
|
targetPort,
|
||||||
(err, stream) => {
|
(err, stream) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwaded connection via ${fw}: ${err}`)
|
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Remote has rejected the forwarded connection to ${targetAddress}:${targetPort} via ${fw}: ${err}`)
|
||||||
socket.destroy()
|
reject()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (stream) {
|
if (stream) {
|
||||||
|
const socket = accept()
|
||||||
stream.pipe(socket)
|
stream.pipe(socket)
|
||||||
socket.pipe(stream)
|
socket.pipe(stream)
|
||||||
stream.on('close', () => {
|
stream.on('close', () => {
|
||||||
@ -263,7 +295,7 @@ export class SSHSession extends BaseSession {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.emitServiceMessage(colors.bgGreen.black(' -> ') + ` Forwaded ${fw}`)
|
this.emitServiceMessage(colors.bgGreen.black(' -> ') + ` Forwarded ${fw}`)
|
||||||
this.forwardedPorts.push(fw)
|
this.forwardedPorts.push(fw)
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Failed to forward port ${fw}: ${e}`)
|
this.emitServiceMessage(colors.bgRed.black(' X ') + ` Failed to forward port ${fw}: ${e}`)
|
||||||
@ -280,13 +312,13 @@ export class SSHSession extends BaseSession {
|
|||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
this.emitServiceMessage(colors.bgGreen.black(' <- ') + ` Forwaded ${fw}`)
|
this.emitServiceMessage(colors.bgGreen.black(' <- ') + ` Forwarded ${fw}`)
|
||||||
this.forwardedPorts.push(fw)
|
this.forwardedPorts.push(fw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async removePortForward (fw: ForwardedPort): Promise<void> {
|
async removePortForward (fw: ForwardedPort): Promise<void> {
|
||||||
if (fw.type === PortForwardType.Local) {
|
if (fw.type === PortForwardType.Local || fw.type === PortForwardType.Dynamic) {
|
||||||
fw.stopLocalListener()
|
fw.stopLocalListener()
|
||||||
this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw)
|
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')
|
.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.Local') Local
|
||||||
strong(*ngIf='fw.type === PortForwardType.Remote') Remote
|
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)')
|
button.btn.btn-link.ml-auto((click)='remove(fw)')
|
||||||
i.fas.fa-trash-alt.mr-2
|
i.fas.fa-trash-alt.mr-2
|
||||||
span Remove
|
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.form-control(type='text', [(ngModel)]='newForward.host')
|
||||||
.input-group-append
|
.input-group-append
|
||||||
.input-group-text :
|
.input-group-text :
|
||||||
@ -42,6 +52,13 @@
|
|||||||
[value]='PortForwardType.Remote'
|
[value]='PortForwardType.Remote'
|
||||||
)
|
)
|
||||||
| Remote
|
| Remote
|
||||||
|
label.btn.btn-secondary.m-0(ngbButtonLabel)
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
ngbButton,
|
||||||
|
[value]='PortForwardType.Dynamic'
|
||||||
|
)
|
||||||
|
| Dynamic
|
||||||
|
|
||||||
button.btn.btn-primary((click)='addForward()')
|
button.btn.btn-primary((click)='addForward()')
|
||||||
i.fas.fa-check.mr-2
|
i.fas.fa-check.mr-2
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import colors from 'ansi-colors'
|
import colors from 'ansi-colors'
|
||||||
|
import stripAnsi from 'strip-ansi'
|
||||||
import { open as openTemp } from 'temp'
|
import { open as openTemp } from 'temp'
|
||||||
import { Injectable, NgZone } from '@angular/core'
|
import { Injectable, NgZone } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
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
|
var windowsProcessTreeNative = require('windows-process-tree/build/Release/windows_process_tree.node') // eslint-disable-line @typescript-eslint/no-var-requires, no-var
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-type-alias
|
||||||
|
export type SSHLogCallback = (message: string) => void
|
||||||
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class SSHService {
|
export class SSHService {
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
@ -46,33 +52,24 @@ export class SSHService {
|
|||||||
return session
|
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 privateKey: string|null = null
|
||||||
let privateKeyPath = session.connection.privateKey
|
let privateKeyPath = session.connection.privateKey
|
||||||
|
|
||||||
if (!logCallback) {
|
|
||||||
logCallback = () => null
|
|
||||||
}
|
|
||||||
|
|
||||||
const log = (s: any) => {
|
|
||||||
logCallback!(s)
|
|
||||||
this.logger.info(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!privateKeyPath) {
|
if (!privateKeyPath) {
|
||||||
const userKeyPath = path.join(process.env.HOME as string, '.ssh', 'id_rsa')
|
const userKeyPath = path.join(process.env.HOME as string, '.ssh', 'id_rsa')
|
||||||
if (await fs.exists(userKeyPath)) {
|
if (await fs.exists(userKeyPath)) {
|
||||||
log('Using user\'s default private key')
|
logCallback?.('Using user\'s default private key')
|
||||||
privateKeyPath = userKeyPath
|
privateKeyPath = userKeyPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (privateKeyPath) {
|
if (privateKeyPath) {
|
||||||
log('Loading private key from ' + colors.bgWhite.blackBright(' ' + privateKeyPath + ' '))
|
logCallback?.('Loading private key from ' + colors.bgWhite.blackBright(' ' + privateKeyPath + ' '))
|
||||||
try {
|
try {
|
||||||
privateKey = (await fs.readFile(privateKeyPath)).toString()
|
privateKey = (await fs.readFile(privateKeyPath)).toString()
|
||||||
} catch (error) {
|
} 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')
|
this.toastr.error('Could not read the private key file')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +80,7 @@ export class SSHService {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof sshpk.KeyEncryptedError) {
|
if (e instanceof sshpk.KeyEncryptedError) {
|
||||||
const modal = this.ngbModal.open(PromptModalComponent)
|
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.prompt = 'Private key passphrase'
|
||||||
modal.componentInstance.password = true
|
modal.componentInstance.password = true
|
||||||
let passphrase = ''
|
let passphrase = ''
|
||||||
@ -131,6 +128,20 @@ export class SSHService {
|
|||||||
fs.unlink(temp.path)
|
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()
|
const ssh = new Client()
|
||||||
session.ssh = ssh
|
session.ssh = ssh
|
||||||
@ -213,6 +224,7 @@ export class SSHService {
|
|||||||
|
|
||||||
const authMethodsLeft = ['none']
|
const authMethodsLeft = ['none']
|
||||||
if (!session.connection.auth || session.connection.auth === 'publicKey') {
|
if (!session.connection.auth || session.connection.auth === 'publicKey') {
|
||||||
|
privateKey = await this.loadPrivateKeyForSession(session, log)
|
||||||
if (!privateKey) {
|
if (!privateKey) {
|
||||||
log('\r\nPrivate key auth selected, but no key is loaded\r\n')
|
log('\r\nPrivate key auth selected, but no key is loaded\r\n')
|
||||||
} else {
|
} else {
|
||||||
|
@ -50,6 +50,7 @@ module.exports = {
|
|||||||
'keytar',
|
'keytar',
|
||||||
'path',
|
'path',
|
||||||
'ngx-toastr',
|
'ngx-toastr',
|
||||||
|
'socksv5',
|
||||||
'windows-native-registry',
|
'windows-native-registry',
|
||||||
'windows-process-tree/build/Release/windows_process_tree.node',
|
'windows-process-tree/build/Release/windows_process_tree.node',
|
||||||
/^rxjs/,
|
/^rxjs/,
|
||||||
|
@ -32,6 +32,11 @@ ansi-colors@^4.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
||||||
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
|
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:
|
asn1@~0.2.0, asn1@~0.2.3:
|
||||||
version "0.2.4"
|
version "0.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
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"
|
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||||
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
|
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:
|
balanced-match@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
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"
|
resolved "https://registry.yarnpkg.com/cli-spinner/-/cli-spinner-0.2.10.tgz#f7d617a36f5c47a7bc6353c697fc9338ff782a47"
|
||||||
integrity sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q==
|
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:
|
concat-map@0.0.1:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
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:
|
dashdash@^1.12.0:
|
||||||
version "1.14.1"
|
version "1.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
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"
|
jsbn "~0.1.0"
|
||||||
safer-buffer "^2.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:
|
fs.realpath@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
@ -101,7 +147,7 @@ getpass@^0.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
assert-plus "^1.0.0"
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
glob@^7.1.3:
|
"glob@>= 3.1.4", glob@^7.1.3:
|
||||||
version "7.1.6"
|
version "7.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||||
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
||||||
@ -126,6 +172,20 @@ inherits@2:
|
|||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
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:
|
jsbn@~0.1.0:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
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"
|
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
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:
|
rimraf@~2.6.2:
|
||||||
version "2.6.3"
|
version "2.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
||||||
@ -169,11 +234,28 @@ rimraf@~2.6.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
glob "^7.1.3"
|
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:
|
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
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:
|
ssh2-streams@Eugeny/ssh2-streams#75f6d3425d071ac73a18fd46e2f5e738bfe897c5:
|
||||||
version "0.4.10"
|
version "0.4.10"
|
||||||
resolved "https://codeload.github.com/Eugeny/ssh2-streams/tar.gz/75f6d3425d071ac73a18fd46e2f5e738bfe897c5"
|
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"
|
bcrypt-pbkdf "^1.0.2"
|
||||||
streamsearch "~0.1.2"
|
streamsearch "~0.1.2"
|
||||||
|
|
||||||
ssh2@^0.8.2:
|
ssh2@^0.8.9:
|
||||||
version "0.8.9"
|
version "0.8.9"
|
||||||
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3"
|
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3"
|
||||||
integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==
|
integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==
|
||||||
@ -213,11 +295,23 @@ sshpk@^1.16.1:
|
|||||||
safer-buffer "^2.0.2"
|
safer-buffer "^2.0.2"
|
||||||
tweetnacl "~0.14.0"
|
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:
|
streamsearch@~0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
||||||
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
|
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:
|
temp@^0.9.1:
|
||||||
version "0.9.4"
|
version "0.9.4"
|
||||||
resolved "https://registry.yarnpkg.com/temp/-/temp-0.9.4.tgz#cd20a8580cb63635d0e4e9d4bd989d44286e7620"
|
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"
|
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||||
integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
|
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:
|
wrappy@1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
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()
|
this.resetZoom()
|
||||||
break
|
break
|
||||||
case 'previous-word':
|
case 'previous-word':
|
||||||
this.sendInput('\x1bb')
|
this.sendInput({
|
||||||
|
[Platform.Windows]: '\x1b[1;5D',
|
||||||
|
[Platform.macOS]: '\x1bb',
|
||||||
|
[Platform.Linux]: '\x1bb',
|
||||||
|
}[this.hostApp.platform])
|
||||||
break
|
break
|
||||||
case 'next-word':
|
case 'next-word':
|
||||||
this.sendInput('\x1bf')
|
this.sendInput({
|
||||||
|
[Platform.Windows]: '\x1b[1;5C',
|
||||||
|
[Platform.macOS]: '\x1bf',
|
||||||
|
[Platform.Linux]: '\x1bf',
|
||||||
|
}[this.hostApp.platform])
|
||||||
break
|
break
|
||||||
case 'delete-previous-word':
|
case 'delete-previous-word':
|
||||||
this.sendInput('\x1b\x7f')
|
this.sendInput('\x1b\x7f')
|
||||||
break
|
break
|
||||||
case 'delete-next-word':
|
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
|
break
|
||||||
case 'search':
|
case 'search':
|
||||||
this.showSearchPanel = true
|
this.showSearchPanel = true
|
||||||
@ -348,7 +360,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
|
|
||||||
this.topPadded = this.hostApp.platform === Platform.macOS
|
this.topPadded = this.hostApp.platform === Platform.macOS
|
||||||
&& this.config.store.appearance.frame === 'thin'
|
&& 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.background === 'colorScheme') {
|
||||||
if (this.config.store.terminal.colorScheme.background) {
|
if (this.config.store.terminal.colorScheme.background) {
|
||||||
|
@ -111,19 +111,19 @@ h3.mb-3 Appearance
|
|||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title Disable tab index
|
.title Hide tab index
|
||||||
|
|
||||||
toggle(
|
toggle(
|
||||||
[(ngModel)]='config.store.terminal.disableTabIndex',
|
[(ngModel)]='config.store.terminal.hideTabIndex',
|
||||||
(ngModelChange)='config.save();',
|
(ngModelChange)='config.save();',
|
||||||
)
|
)
|
||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title Disable tab close button
|
.title Hide tab close button
|
||||||
|
|
||||||
toggle(
|
toggle(
|
||||||
[(ngModel)]='config.store.terminal.disableCloseButton',
|
[(ngModel)]='config.store.terminal.hideCloseButton',
|
||||||
(ngModelChange)='config.save();',
|
(ngModelChange)='config.save();',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,8 +23,8 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
ligatures: false,
|
ligatures: false,
|
||||||
cursor: 'block',
|
cursor: 'block',
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
disableTabIndex: false,
|
hideTabIndex: false,
|
||||||
disableCloseButton: false,
|
hideCloseButton: false,
|
||||||
customShell: '',
|
customShell: '',
|
||||||
rightClick: 'menu',
|
rightClick: 'menu',
|
||||||
pasteOnMiddleClick: true,
|
pasteOnMiddleClick: true,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user