Skip to content
This repository has been archived by the owner on Jan 24, 2023. It is now read-only.

Commit

Permalink
Merge pull request #150 from SUSE/app-ssh
Browse files Browse the repository at this point in the history
Add ability to SSH into an application instance
  • Loading branch information
richard-cox committed Jan 24, 2018
2 parents b3961ad + 72d2215 commit abca8f0
Show file tree
Hide file tree
Showing 22 changed files with 440 additions and 58 deletions.
3 changes: 2 additions & 1 deletion .angular-cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"styles.scss"
"styles.scss",
"../node_modules/xterm/dist/xterm.css"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"rxjs": "^5.5.5",
"rxjs-spy": "^6.1.0",
"rxjs-websockets": "^4.0.0",
"xterm": "^2.9.1",
"zone.js": "^0.8.14"
},
"devDependencies": {
Expand All @@ -55,6 +56,7 @@
"@types/jasminewd2": "~2.0.2",
"@types/karma": "^1.7.1",
"@types/node": "~6.0.60",
"@types/xterm": "^2.0.3",
"codecov": "^3.0.0",
"codelyzer": "~3.1.1",
"jasmine-core": "~2.6.2",
Expand Down
1 change: 1 addition & 0 deletions proxy.conf.template.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const PROXY_CONFIG = {
"protocol": "https:",
"port": 443
},
"ws": true,
"secure": false,
"changeOrigin": true,
"ws": true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ export class ApplicationTabsBaseComponent implements OnInit, OnDestroy {
{ link: 'services', label: 'Services' },
{ link: 'variables', label: 'Variables' },
{ link: 'events', label: 'Events' },
{ link: 'ssh', label: 'SSH' }
];

autoRefreshString = 'auto-refresh';
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

6 changes: 3 additions & 3 deletions src/app/features/applications/applications.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { ApplicationBaseComponent } from './application/application-base.compone
import { EventsTabComponent } from './application/application-tabs-base/tabs/events-tab/events-tab.component';
import { LogStreamTabComponent } from './application/application-tabs-base/tabs/log-stream-tab/log-stream-tab.component';
import { ServicesTabComponent } from './application/application-tabs-base/tabs/services-tab/services-tab.component';
import { SshTabComponent } from './application/application-tabs-base/tabs/ssh-tab/ssh-tab.component';
import { ApplicationEnvVarsService } from './application/application-tabs-base/tabs/build-tab/application-env-vars.service';
import { BuildTabComponent } from './application/application-tabs-base/tabs/build-tab/build-tab.component';
import { ViewBuildpackComponent } from './application/application-tabs-base/tabs/build-tab/view-buildpack/view-buildpack.component';
Expand All @@ -16,6 +15,7 @@ import { ApplicationsRoutingModule } from './applications.routing';
import { ApplicationTabsBaseComponent } from './application/application-tabs-base/application-tabs-base.component';
import { DatePipe } from '@angular/common';
import { EditApplicationComponent } from './edit-application/edit-application.component';
import { SshApplicationComponent } from './ssh-application/ssh-application.component';
import { InstancesTabComponent } from './application/application-tabs-base/tabs/instances-tab/instances-tab.component';
import { ApplicationMonitorService } from './application-monitor.service';
import { RoutesComponent } from './routes/routes.component';
Expand All @@ -33,15 +33,15 @@ import { AddRoutesComponent } from './routes/add-routes/add-routes.component';
EventsTabComponent,
LogStreamTabComponent,
ServicesTabComponent,
SshTabComponent,
BuildTabComponent,
VariablesTabComponent,
ViewBuildpackComponent,
ApplicationTabsBaseComponent,
SshApplicationComponent,
RoutesComponent,
EditApplicationComponent,
InstancesTabComponent,
AddRoutesComponent
AddRoutesComponent,
],
providers: [
ApplicationService,
Expand Down
11 changes: 7 additions & 4 deletions src/app/features/applications/applications.routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import { ApplicationBaseComponent } from './application/application-base.compone
import { EventsTabComponent } from './application/application-tabs-base/tabs/events-tab/events-tab.component';
import { LogStreamTabComponent } from './application/application-tabs-base/tabs/log-stream-tab/log-stream-tab.component';
import { ServicesTabComponent } from './application/application-tabs-base/tabs/services-tab/services-tab.component';
import { SshTabComponent } from './application/application-tabs-base/tabs/ssh-tab/ssh-tab.component';
import { BuildTabComponent } from './application/application-tabs-base/tabs/build-tab/build-tab.component';
import { VariablesTabComponent } from './application/application-tabs-base/tabs/variables-tab/variables-tab.component';
import { CreateApplicationComponent } from './create-application/create-application.component';
import { CreateApplicationModule } from './create-application/create-application.module';
import { SshApplicationComponent } from './ssh-application/ssh-application.component';
import { EditApplicationComponent } from './edit-application/edit-application.component';
import { ApplicationTabsBaseComponent } from './application/application-tabs-base/application-tabs-base.component';
const appplicationsRoutes: Routes = [
{
path: 'new',
component: CreateApplicationComponent
component: CreateApplicationComponent,
},
{
path: '',
Expand All @@ -34,7 +34,11 @@ const appplicationsRoutes: Routes = [
children: [
{
path: 'edit',
component: EditApplicationComponent
component: EditApplicationComponent,
},
{
path: 'ssh/:index',
component: SshApplicationComponent,
},
{
path: '',
Expand All @@ -50,7 +54,6 @@ const appplicationsRoutes: Routes = [
{ path: 'services', component: ServicesTabComponent },
{ path: 'variables', component: VariablesTabComponent },
{ path: 'events', component: EventsTabComponent },
{ path: 'ssh', component: SshTabComponent }
]
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<app-page-header>
<h1>Application SSH ({{ (applicationService.app$ | async)?.entity?.entity.name }} #{{ instanceId }})</h1>
<div class="page-header-right">
<span>
<button mat-icon-button name="stop" *ngIf="sshViewer.isConnected" (click)="sshViewer.disconnect()" [disabled]="sshViewer.attemptingConnection">
<mat-icon>stop</mat-icon>
</button>
<button mat-icon-button name="start" *ngIf="!sshViewer.isConnected" (click)="sshViewer.reconnect()" [disabled]="sshViewer.attemptingConnection">
<mat-icon>play_arrow</mat-icon>
</button>
<button mat-icon-button [routerLink]="appInstanceLink">
<mat-icon>close</mat-icon>
</button>
</span>
</div>
</app-page-header>

<app-ssh-viewer #sshViewer [errorMessage]="errorMessage" [sshStream]="messages" [sshInput]="sshInput" [connectionStatus]="connectionStatus"></app-ssh-viewer>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { SshApplicationComponent } from './ssh-application.component';
import { SharedModule } from '../../../shared/shared.module';
import { CoreModule } from '../../../core/core.module';
import { RouterTestingModule } from '@angular/router/testing';
import { createBasicStoreModule } from '../../../test-framework/store-test-helper';
import { ApplicationService } from '../application.service';
import { ApplicationServiceMock } from '../../../test-framework/application-service-helper';

describe('SshApplicationComponent', () => {
let component: SshApplicationComponent;
let fixture: ComponentFixture<SshApplicationComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SshApplicationComponent ],
imports: [
CoreModule,
SharedModule,
RouterTestingModule,
createBasicStoreModule()
],
providers: [
{ provide: ApplicationService, useClass: ApplicationServiceMock },
]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(SshApplicationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';

import { Observable } from 'rxjs/Observable';
import { Store } from '@ngrx/store';
import { Subscription, Subject } from 'rxjs/Rx';
import websocketConnect from 'rxjs-websockets';

import { ApplicationService } from '../application.service';
import { QueueingSubject } from 'queueing-subject';
import { LoggerService } from '../../../core/logger.service';
import { SshViewerComponent } from '../../../shared/components/ssh-viewer/ssh-viewer.component';
import { ShowSnackBar } from '../../../store/actions/snackBar.actions';
import { AppState } from '../../../store/app-state';
import { EntityService } from '../../../core/entity-service';
import { GetApplication, ApplicationSchema } from '../../../store/actions/application.actions';

@Component({
selector: 'app-ssh-application',
templateUrl: './ssh-application.component.html',
styleUrls: ['./ssh-application.component.scss'],
})
export class SshApplicationComponent implements OnInit {

public messages: Observable<string>;

public connectionStatus: Observable<number>;

public sshInput: QueueingSubject<string>;

public errorMessage: string;

public sshRoute: string;

public connected: boolean;

public appInstanceLink: string;

private connection: Subscription;

public instanceId: string;

@ViewChild('sshViewer') sshViewer: SshViewerComponent;

constructor(
private activatedRoute: ActivatedRoute,
private store: Store<AppState>,
private applicationService: ApplicationService,
) { }

ngOnInit() {

const { cfGuid, appGuid } = this.applicationService;
const routeParams = this.activatedRoute.snapshot.params;
this.instanceId = routeParams.index;

this.appInstanceLink = (
`/applications/${cfGuid}/${appGuid}/instances`
);

if (!cfGuid || !appGuid || !this.instanceId) {
this.messages = Observable.never();
this.connectionStatus = Observable.never();
} else {
const host = window.location.host;
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
const streamUrl = (
`${protocol}://${host}/pp/v1/${cfGuid}/apps/${appGuid}/ssh/${this.instanceId}`
);
this.sshInput = new QueueingSubject<string>();
const connection = websocketConnect(
streamUrl,
this.sshInput
);

this.messages = connection.messages
.catch(e => {
if (e.type === 'error') {
this.errorMessage = 'Error connecting to web socket';
}
return [];
});

this.connectionStatus = connection.connectionStatus;
}
}
}
15 changes: 15 additions & 0 deletions src/app/shared/components/ssh-viewer/ssh-viewer.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div class="ssh-viewer">
<div class="ssh-viewer__overlay" *ngIf="errorMessage || !isConnected">
<div class="ssh-viewer__error" *ngIf="errorMessage">
Error occurred establishing SSH connection
</div>
<div class="ssh-viewer__disconnected" *ngIf="!errorMessage && !isConnected && !isConnecting">
Disconnected
</div>
</div>
<mat-progress-bar *ngIf="isConnecting" mode="indeterminate"></mat-progress-bar>
<div class="ssh-terminal" #container [ngClass]="{'ssh-terminal-disconnected': !isConnected}">
<div id="terminal" #terminal></div>
</div>
</div>

46 changes: 46 additions & 0 deletions src/app/shared/components/ssh-viewer/ssh-viewer.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.ssh-viewer {
display: flex;
flex-direction: column;
height: 100%;
position: relative;

.ssh-terminal {
display: flex;
flex: 1;
flex-direction: column;
opacity: 1;
transition: opacity .5s ease-in-out;

&.ssh-terminal-disconnected {
opacity: .4;
}

> div {
display: flex;
flex: 1;
flex-direction: column;
}
}

&__overlay {
align-items: flex-end;
display: flex;
height: 100%;
justify-content: center;
position: absolute;
width: 100%;
z-index: 1;
}

&__error {
padding: 8px;
text-align: center;
width: 100%;
}

&__disconnected {
padding: 8px;
text-align: center;
width: 100%;
}
}
Loading

0 comments on commit abca8f0

Please sign in to comment.