better ssh connection list management - fixes #1351

This commit is contained in:
Eugene Pankov
2021-04-25 21:06:23 +02:00
parent d32e31d45e
commit 3c6374be19
12 changed files with 111 additions and 38 deletions

View File

@@ -17,6 +17,10 @@ body {
}
.btn {
display: inline-flex;
align-items: center;
flex-wrap: nowrap;
& > svg {
pointer-events: none;
}
@@ -104,3 +108,14 @@ ngb-typeahead-window {
max-height: 60vh;
overflow: auto;
}
.hover-reveal {
opacity: 0;
.hover-reveal-parent:hover &,
*:hover > &,
&:hover {
opacity: 1;
}
}

View File

@@ -17,3 +17,8 @@
.title {
margin-left: 10px;
}
input {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}

View File

@@ -239,10 +239,6 @@ hotkey-input-modal {
border: none;
border-top: 1px solid rgba(255, 255, 255, .1);
&:not(.combi) {
padding: $list-group-item-padding-y $list-group-item-padding-x;
}
&:first-child {
border-top: none;
}

View File

@@ -46,7 +46,6 @@ $body-color: #ccc;
$body-bg: #131d27;
$body-bg2: #20333e;
$font-family-sans-serif: "Source Sans Pro";
$font-family-monospace: "Source Code Pro";
$font-size-base: 14rem / 16;
@@ -55,6 +54,12 @@ $font-size-sm: .85rem;
$line-height-base: 1.6;
$border-radius: .4rem;
$border-radius-lg: .6rem;
$border-radius-sm: .2rem;
// -----
$headings-color: #ced9e2;
$headings-font-weight: lighter;

View File

@@ -17,6 +17,7 @@
> .nav {
padding: 20px 10px;
width: 190px;
flex: none;
overflow-y: auto;
}

View File

@@ -416,11 +416,6 @@ export class SSHSession extends BaseSession {
}
}
export interface SSHConnectionGroup {
name: string
connections: SSHConnection[]
}
export const ALGORITHM_BLACKLIST = [
// cause native crashes in node crypto, use EC instead
'diffie-hellman-group-exchange-sha256',

View File

@@ -17,6 +17,7 @@
type='text',
placeholder='Ungrouped',
[(ngModel)]='connection.group',
[ngbTypeahead]='groupTypeahead',
)
.d-flex.w-100(*ngIf='!useProxyCommand')

View File

@@ -1,6 +1,9 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component } from '@angular/core'
import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { Observable } from 'rxjs'
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
import { ElectronService, HostAppService, ConfigService } from 'terminus-core'
import { PasswordStorageService } from '../services/passwordStorage.service'
import { SSHConnection, LoginScript, SSHAlgorithmType, ALGORITHM_BLACKLIST } from '../api'
@@ -20,6 +23,8 @@ export class EditConnectionModalComponent {
defaultAlgorithms: Record<string, string[]> = {}
algorithms: Record<string, Record<string, boolean>> = {}
private groupNames: string[]
constructor (
public config: ConfigService,
private modalInstance: NgbActiveModal,
@@ -44,8 +49,18 @@ export class EditConnectionModalComponent {
this.supportedAlgorithms[k] = ALGORITHMS[supportedAlg].filter(x => !ALGORITHM_BLACKLIST.includes(x))
this.defaultAlgorithms[k] = ALGORITHMS[defaultAlg].filter(x => !ALGORITHM_BLACKLIST.includes(x))
}
this.groupNames = [...new Set(config.store.ssh.connections.map(x => x.group))] as string[]
this.groupNames = this.groupNames.filter(x => x).sort()
}
groupTypeahead = (text$: Observable<string>) =>
text$.pipe(
debounceTime(200),
distinctUntilChanged(),
map(q => this.groupNames.filter(x => !q || x.toLowerCase().includes(q.toLowerCase())))
)
async ngOnInit () {
this.hasSavedPassword = !!await this.passwordStorage.loadPassword(this.connection)
this.connection.algorithms = this.connection.algorithms ?? {}

View File

@@ -1,33 +1,55 @@
h3 Connections
.d-flex.align-items-center.mb-3
h3.m-0 SSH Connections
.list-group.list-group-flush.mt-3.mb-3
button.btn.btn-primary.ml-auto((click)='createConnection()')
i.fas.fa-fw.fa-plus
span.ml-2 Add connection
.input-group.mb-3
.input-group-prepend
.input-group-text
i.fas.fa-fw.fa-search
input.form-control(type='search', placeholder='Filter', [(ngModel)]='filter')
.list-group.list-group-light.mt-3.mb-3
ng-container(*ngFor='let group of childGroups')
.list-group-item.list-group-item-action.d-flex.align-items-center(
(click)='groupCollapsed[group.name] = !groupCollapsed[group.name]'
)
.fa.fa-fw.fa-chevron-right(*ngIf='groupCollapsed[group.name]')
.fa.fa-fw.fa-chevron-down(*ngIf='!groupCollapsed[group.name]')
span.ml-3.mr-auto {{group.name || "Ungrouped"}}
button.btn.btn-outline-info.ml-2((click)='editGroup(group)')
i.fas.fa-edit
button.btn.btn-outline-danger.ml-1((click)='deleteGroup(group)')
i.fas.fa-trash
ng-container(*ngIf='!groupCollapsed[group.name]')
.list-group-item.list-group-item-action.pl-5.d-flex.align-items-center(
*ngFor='let connection of group.connections',
(click)='editConnection(connection)'
ng-container(*ngIf='isGroupVisible(group)')
.list-group-item.list-group-item-action.d-flex.align-items-center(
(click)='groupCollapsed[group.name] = !groupCollapsed[group.name]'
)
.mr-auto
div {{connection.name}}
.text-muted {{connection.host}}
button.btn.btn-outline-info.ml-1((click)='$event.stopPropagation(); copyConnection(connection)')
i.fas.fa-copy
button.btn.btn-outline-danger.ml-1((click)='$event.stopPropagation(); deleteConnection(connection)')
.fa.fa-fw.fa-chevron-right(*ngIf='groupCollapsed[group.name]')
.fa.fa-fw.fa-chevron-down(*ngIf='!groupCollapsed[group.name]')
span.ml-3.mr-auto {{group.name || "Ungrouped"}}
button.btn.btn-sm.btn-link.hover-reveal.ml-2(
[class.invisible]='!group.name',
(click)='$event.stopPropagation(); editGroup(group)'
)
i.fas.fa-edit
button.btn.btn-sm.btn-link.hover-reveal.ml-2(
[class.invisible]='!group.name',
(click)='$event.stopPropagation(); deleteGroup(group)'
)
i.fas.fa-trash
button.btn.btn-primary((click)='createConnection()')
i.fas.fa-fw.fa-plus
span.ml-2 Add connection
ng-container(*ngIf='!groupCollapsed[group.name]')
ng-container(*ngFor='let connection of group.connections')
.list-group-item.list-group-item-action.pl-5.d-flex.align-items-center(
*ngIf='isConnectionVisible(connection)',
(click)='editConnection(connection)'
)
.mr-3 {{connection.name}}
.mr-auto.text-muted {{connection.host}}
.hover-reveal(ngbDropdown, placement='bottom-right')
button.btn.btn-link(ngbDropdownToggle, (click)='$event.stopPropagation()')
i.fas.fa-fw.fa-ellipsis-v
div(ngbDropdownMenu)
button.dropdown-item((click)='$event.stopPropagation(); copyConnection(connection)')
i.fas.fa-copy
span Duplicate
button.dropdown-item((click)='$event.stopPropagation(); deleteConnection(connection)')
i.fas.fa-trash
span Delete
h3.mt-5 Options

View File

@@ -0,0 +1,3 @@
.list-group-item {
padding: 0.3rem 1rem;
}

View File

@@ -4,18 +4,25 @@ import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ConfigService, ElectronService, HostAppService } from 'terminus-core'
import { PasswordStorageService } from '../services/passwordStorage.service'
import { SSHConnection, SSHConnectionGroup } from '../api'
import { SSHConnection } from '../api'
import { EditConnectionModalComponent } from './editConnectionModal.component'
import { PromptModalComponent } from './promptModal.component'
interface SSHConnectionGroup {
name: string|null
connections: SSHConnection[]
}
/** @hidden */
@Component({
template: require('./sshSettingsTab.component.pug'),
styles: [require('./sshSettingsTab.component.scss')],
})
export class SSHSettingsTabComponent {
connections: SSHConnection[]
childGroups: SSHConnectionGroup[]
groupCollapsed: Record<string, boolean> = {}
filter = ''
constructor (
public config: ConfigService,
@@ -133,7 +140,7 @@ export class SSHSettingsTabComponent {
let group = this.childGroups.find(x => x.name === connection.group)
if (!group) {
group = {
name: connection.group!,
name: connection.group,
connections: [],
}
this.childGroups.push(group)
@@ -141,4 +148,12 @@ export class SSHSettingsTabComponent {
group.connections.push(connection)
}
}
isGroupVisible (group: SSHConnectionGroup): boolean {
return !this.filter || group.connections.some(x => this.isConnectionVisible(x))
}
isConnectionVisible (connection: SSHConnection): boolean {
return !this.filter || `${connection.name}$${connection.host}`.toLowerCase().includes(this.filter.toLowerCase())
}
}

View File

@@ -114,7 +114,7 @@ h3.mb-3 Appearance
.header
.title Custom CSS
textarea.form-control(
textarea.form-control.mb-5(
[(ngModel)]='config.store.appearance.css',
(ngModelChange)='saveConfiguration()',
)