ソースを参照

better ssh connection list management - fixes #1351

Eugene Pankov 4 年 前
コミット
3c6374be19

+ 15 - 0
app/src/global.scss

@@ -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;
+    }
+  }

+ 5 - 0
terminus-core/src/components/selectorModal.component.scss

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

+ 0 - 4
terminus-core/src/theme.scss

@@ -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;
         }

+ 6 - 1
terminus-core/src/theme.vars.scss

@@ -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;
 

+ 1 - 0
terminus-settings/src/components/settingsTab.component.scss

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

+ 0 - 5
terminus-ssh/src/api.ts

@@ -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',

+ 1 - 0
terminus-ssh/src/components/editConnectionModal.component.pug

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

+ 15 - 0
terminus-ssh/src/components/editConnectionModal.component.ts

@@ -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 ?? {}

+ 47 - 25
terminus-ssh/src/components/sshSettingsTab.component.pug

@@ -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
 

+ 3 - 0
terminus-ssh/src/components/sshSettingsTab.component.scss

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

+ 17 - 2
terminus-ssh/src/components/sshSettingsTab.component.ts

@@ -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())
+    }
 }

+ 1 - 1
terminus-terminal/src/components/appearanceSettingsTab.component.pug

@@ -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()',
 )