diff --git a/caster.ui/.prettierrc b/caster.ui/.prettierrc
new file mode 100644
index 00000000..74ccca68
--- /dev/null
+++ b/caster.ui/.prettierrc
@@ -0,0 +1,2 @@
+tabWidth: 2
+singleQuote: true
\ No newline at end of file
diff --git a/caster.ui/package-lock.json b/caster.ui/package-lock.json
index cfa6a772..4aabe2e6 100644
--- a/caster.ui/package-lock.json
+++ b/caster.ui/package-lock.json
@@ -19860,14 +19860,14 @@
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"xterm": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/xterm/-/xterm-4.5.0.tgz",
- "integrity": "sha512-4t12tsvtYnv13FBJwewddxdI/j4kSonmbQQv50j34R/rPIFbUNGtptbprmuUlTDAKvHLMDZ/Np2XcpNimga/HQ=="
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/xterm/-/xterm-4.7.0.tgz",
+ "integrity": "sha512-UeH6U/1iknCBP94/AcKAFBeQz6ZicMugJHGXruTmsY8RcZt+mkx+vl8jLLOqNYweXdBbywCg2kK88WDKjcmSmg=="
},
"xterm-addon-fit": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.3.0.tgz",
- "integrity": "sha512-kvkiqHVrnMXgyCH9Xn0BOBJ7XaWC/4BgpSWQy3SueqximgW630t/QOankgqkvk11iTOCwWdAY9DTyQBXUMN3lw=="
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.4.0.tgz",
+ "integrity": "sha512-p4BESuV/g2L6pZzFHpeNLLnep9mp/DkF3qrPglMiucSFtD8iJxtMufEoEJbN8LZwB4i+8PFpFvVuFrGOSpW05w=="
},
"xxhashjs": {
"version": "0.2.2",
diff --git a/caster.ui/package.json b/caster.ui/package.json
index c81ce583..db88aed4 100644
--- a/caster.ui/package.json
+++ b/caster.ui/package.json
@@ -64,8 +64,8 @@
"rxjs": "~6.5.5",
"tslib": "^1.10.0",
"webpack": "^4.40.2",
- "xterm": "^4.0.1",
- "xterm-addon-fit": "^0.3.0",
+ "xterm": "^4.7.0",
+ "xterm-addon-fit": "^0.4.0",
"zone.js": "~0.10.2"
},
"devDependencies": {
diff --git a/caster.ui/src/app/project/component/project-details/project-navigation-container/directory-panel/directory-panel.component.html b/caster.ui/src/app/project/component/project-details/project-navigation-container/directory-panel/directory-panel.component.html
index dba2867e..c44bcc17 100644
--- a/caster.ui/src/app/project/component/project-details/project-navigation-container/directory-panel/directory-panel.component.html
+++ b/caster.ui/src/app/project/component/project-details/project-navigation-container/directory-panel/directory-panel.component.html
@@ -199,7 +199,7 @@
*ngIf="
file.lockedById !== null && !file.administrativelyLocked
"
- class="highlight mat-list-item-disabled"
+ class="highlight"
fxLayout="row"
fxLayoutAlign="start center"
fxFill
@@ -218,7 +218,7 @@
implements OnInit, OnChanges {
return index;
}
}
+
+ afterExpand(event, item: T) {
+ const expand = true;
+ this.expand.emit({ expand, item });
+ }
+
+ afterCollapse(event, item: T) {
+ const expand = false;
+ this.expand.emit({ expand, item });
+ }
}
diff --git a/caster.ui/src/app/workspace/components/run/run.component.ts b/caster.ui/src/app/workspace/components/run/run.component.ts
index dd43cd43..df0590eb 100644
--- a/caster.ui/src/app/workspace/components/run/run.component.ts
+++ b/caster.ui/src/app/workspace/components/run/run.component.ts
@@ -10,7 +10,6 @@ DM20-0181
import {
Component,
- OnInit,
Input,
ChangeDetectionStrategy,
OnChanges,
@@ -19,6 +18,7 @@ import {
ElementRef,
Output,
EventEmitter,
+ AfterViewInit,
} from '@angular/core';
import { Run, RunStatus } from 'src/app/generated/caster-api';
import { ISubscription } from '@microsoft/signalr';
@@ -32,7 +32,7 @@ import { FitAddon } from 'xterm-addon-fit';
styleUrls: ['./run.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class RunComponent implements OnInit, OnChanges, OnDestroy {
+export class RunComponent implements AfterViewInit, OnChanges, OnDestroy {
@Input() run: Run;
@Input() loading: boolean;
@Output() planOutput = new EventEmitter();
@@ -44,18 +44,25 @@ export class RunComponent implements OnInit, OnChanges, OnDestroy {
isPlan = false;
isApply = false;
- @ViewChild('xterm', { static: true, read: ElementRef }) eleXtern: ElementRef;
+ @ViewChild('xterm') eleXterm: ElementRef;
// there is no infinite scrolling for xterm. Set number of lines to very large number!
- xtermOptions: ITerminalOptions = { convertEol: true, scrollback: 9999999 };
+ xtermOptions: ITerminalOptions = {
+ convertEol: true,
+ scrollback: 9999999,
+ };
xterm: Terminal = new Terminal(this.xtermOptions);
fitAddon: FitAddon = new FitAddon();
constructor(private signalRService: SignalRService) {}
- ngOnInit() {
- this.xterm.open(this.eleXtern.nativeElement);
+ ngAfterViewInit() {
+ this.openTerminal();
+ }
+
+ openTerminal() {
this.xterm.loadAddon(this.fitAddon);
+ this.xterm.open(this.eleXterm.nativeElement);
this.fitAddon.fit();
}
diff --git a/console.ui/src/app/components/options-bar/options-bar.component.html b/console.ui/src/app/components/options-bar/options-bar.component.html
index 2da5ef94..afc0e68e 100644
--- a/console.ui/src/app/components/options-bar/options-bar.component.html
+++ b/console.ui/src/app/components/options-bar/options-bar.component.html
@@ -9,141 +9,244 @@
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/console.ui/src/app/components/options-bar/options-bar.component.ts b/console.ui/src/app/components/options-bar/options-bar.component.ts
index 06e8ced1..b1a28150 100644
--- a/console.ui/src/app/components/options-bar/options-bar.component.ts
+++ b/console.ui/src/app/components/options-bar/options-bar.component.ts
@@ -30,6 +30,7 @@ import {
import { Subject } from 'rxjs';
import { takeUntil, take } from 'rxjs/operators';
import { SettingsService } from '../../services/settings/settings.service';
+import { IsoResult } from '../../models/vm/iso-result';
declare var WMKS: any; // needed to check values
const MAX_COPY_RETRIES = 1;
@@ -297,10 +298,9 @@ export class OptionsBarComponent implements OnInit, OnDestroy {
// refresh the iso list
this.retrievingIsos = true;
this.vmService.getIsos().subscribe(
- (response) => {
- this.splitIsoList(response);
+ (isoResult) => {
this.retrievingIsos = false;
- this.mountIso();
+ this.mountIso(isoResult);
},
(error) => {
console.log(error);
@@ -311,28 +311,26 @@ export class OptionsBarComponent implements OnInit, OnDestroy {
);
}
- mountIso() {
+ mountIso(isoResult: IsoResult[]) {
// select the iso
const configData = {
width: '500px',
height: '540px',
};
- this.dialogService
- .mountIso(this.publicIsos, this.teamIsos, configData)
- .subscribe((result) => {
- if (!result['path']) {
- return;
- }
- // mount the iso
- this.vmService
- .mountIso(this.vmService.model.id, result['path'])
- .subscribe(
- // refresh the vm model
- (model: VmModel) => {
- this.vmService.model = model;
- }
- );
- });
+ this.dialogService.mountIso(isoResult, configData).subscribe((result) => {
+ if (!result) {
+ return;
+ }
+ // mount the iso
+ this.vmService
+ .mountIso(this.vmService.model.id, result.path + result.filename)
+ .subscribe(
+ // refresh the vm model
+ (model: VmModel) => {
+ this.vmService.model = model;
+ }
+ );
+ });
}
splitIsoList(isoList: any) {
diff --git a/console.ui/src/app/components/shared/mount-iso-dialog/mount-iso-dialog.component.html b/console.ui/src/app/components/shared/mount-iso-dialog/mount-iso-dialog.component.html
index 36f647ae..6e6d9800 100644
--- a/console.ui/src/app/components/shared/mount-iso-dialog/mount-iso-dialog.component.html
+++ b/console.ui/src/app/components/shared/mount-iso-dialog/mount-iso-dialog.component.html
@@ -11,28 +11,54 @@
-
+
-
+
-
-
-
- {{iso.filename}}
+
+
+
+
+ {{ iso.filename }}
+
+
+
+
+
+
+ {{ iso.filename }}
-
-
-
-
- {{iso.filename}}
-
-
+
+
+
-
diff --git a/console.ui/src/app/components/shared/mount-iso-dialog/mount-iso-dialog.component.ts b/console.ui/src/app/components/shared/mount-iso-dialog/mount-iso-dialog.component.ts
index 287c1570..a06457c9 100644
--- a/console.ui/src/app/components/shared/mount-iso-dialog/mount-iso-dialog.component.ts
+++ b/console.ui/src/app/components/shared/mount-iso-dialog/mount-iso-dialog.component.ts
@@ -9,58 +9,97 @@ DM20-0181
*/
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
-import { Component, Inject, OnInit } from '@angular/core';
+import {
+ Component,
+ Inject,
+ ChangeDetectionStrategy,
+ Input,
+ OnInit,
+ OnDestroy,
+ ChangeDetectorRef,
+} from '@angular/core';
+import { IsoResult, IsoFile } from '../../../models/vm/iso-result';
+import { Subject } from 'rxjs';
+import { debounceTime, takeUntil, distinctUntilChanged } from 'rxjs/operators';
@Component({
- selector: 'mount-iso-dialog',
- templateUrl: './mount-iso-dialog.component.html'
+ selector: 'mount-iso-dialog',
+ templateUrl: './mount-iso-dialog.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class MountIsoDialogComponent implements OnInit {
+export class MountIsoDialogComponent implements OnInit, OnDestroy {
public data: any;
- public publicIsos: any[];
- public teamIsos: any[];
- public filteredPublicIsos: any[];
- public filteredTeamIsos: any[];
+ public isoResults: IsoResult[];
+
+ @Input()
+ set isoResult(val: IsoResult[]) {
+ this.isoResults = val;
+ this.isoResults.forEach((x) => {
+ x.display = this.applyFilter(x.isos);
+ x.hide = false;
+
+ x.teamIsoResults.forEach((y) => {
+ y.display = this.applyFilter(y.isos);
+ y.hide = false;
+ });
+ });
+ }
+
public selectedIso: any;
- public showTeamIsos: boolean;
- public showPublicIsos: boolean;
+
+ private filterValue = '';
+ private searchSubject$ = new Subject
();
+ private unsubscribe$ = new Subject();
constructor(
- @Inject(MAT_DIALOG_DATA) data,
- private dialogRef: MatDialogRef) {
+ private dialogRef: MatDialogRef,
+ private changeDetectorRef: ChangeDetectorRef
+ ) {
this.dialogRef.disableClose = true;
- this.showPublicIsos = true;
- this.showTeamIsos = true;
}
ngOnInit() {
- this.publicIsos.sort(function(a, b) {
- return a.filename.toLowerCase().localeCompare(b.filename.toLowerCase());
- });
- this.teamIsos.sort(function(a, b) {
- return a.filename.toLowerCase().localeCompare(b.filename.toLowerCase());
- });
- this.filteredPublicIsos = [];
- this.publicIsos.forEach(iso => {
- const copyIso = Object.assign({}, iso);
- this.filteredPublicIsos.push(copyIso);
- });
- this.filteredTeamIsos = [];
- this.teamIsos.forEach(iso => {
- const copyIso = Object.assign({}, iso);
- this.filteredTeamIsos.push(copyIso);
- });
+ this.searchSubject$
+ .pipe(
+ debounceTime(200),
+ distinctUntilChanged(),
+ takeUntil(this.unsubscribe$)
+ )
+ .subscribe((searchTextValue) => {
+ this.setFilter(searchTextValue);
+ });
+ }
+
+ onSearch(searchTextValue: string) {
+ this.searchSubject$.next(searchTextValue);
}
selectThisIso(iso: string) {
this.selectedIso = iso;
}
- applyFilter(filterValue: string) {
- filterValue = filterValue.trim(); // Remove whitespace
- filterValue = filterValue.toLowerCase(); // default to lowercase matches
- this.filteredPublicIsos = this.publicIsos.filter(iso => iso.filename.toLowerCase().includes(filterValue));
- this.filteredTeamIsos = this.teamIsos.filter(iso => iso.filename.toLowerCase().includes(filterValue));
+ setFilter(filterValue: string) {
+ this.filterValue = filterValue.trim().toLowerCase();
+
+ this.isoResults.forEach((x) => {
+ if (!x.hide) {
+ x.display = this.applyFilter(x.isos);
+ }
+
+ x.teamIsoResults.forEach((y) => {
+ if (!y.hide) {
+ y.display = this.applyFilter(y.isos);
+ }
+ });
+ });
+
+ this.changeDetectorRef.markForCheck();
+ }
+
+ applyFilter(isos: IsoFile[]): IsoFile[] {
+ return isos.filter((x) =>
+ x.filename.toLowerCase().includes(this.filterValue)
+ );
}
close() {
@@ -70,6 +109,9 @@ export class MountIsoDialogComponent implements OnInit {
done() {
this.dialogRef.close(this.selectedIso);
}
-}
-
+ ngOnDestroy() {
+ this.unsubscribe$.next();
+ this.unsubscribe$.complete();
+ }
+}
diff --git a/console.ui/src/app/models/vm/iso-result.ts b/console.ui/src/app/models/vm/iso-result.ts
new file mode 100644
index 00000000..c2b5c04e
--- /dev/null
+++ b/console.ui/src/app/models/vm/iso-result.ts
@@ -0,0 +1,31 @@
+/*
+Crucible
+Copyright 2020 Carnegie Mellon University.
+NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE ENGINEERING INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
+Released under a MIT (SEI)-style license, please see license.txt or contact permission@sei.cmu.edu for full terms.
+[DISTRIBUTION STATEMENT A] This material has been approved for public release and unlimited distribution. Please see Copyright notice for non-US Government use and distribution.
+Carnegie Mellon(R) and CERT(R) are registered in the U.S. Patent and Trademark Office by Carnegie Mellon University.
+DM20-0181
+*/
+
+export interface IsoResult {
+ viewId: string;
+ viewName: string;
+ isos: IsoFile[];
+ teamIsoResults: TeamIsoResult[];
+ hide: boolean;
+ display: IsoFile[];
+}
+
+export interface TeamIsoResult {
+ teamId: string;
+ teamName: string;
+ isos: IsoFile[];
+ hide: boolean;
+ display: IsoFile[];
+}
+
+export interface IsoFile {
+ path: string;
+ filename: string;
+}
diff --git a/console.ui/src/app/services/dialog/dialog.service.ts b/console.ui/src/app/services/dialog/dialog.service.ts
index cbbdae52..ee7ed506 100644
--- a/console.ui/src/app/services/dialog/dialog.service.ts
+++ b/console.ui/src/app/services/dialog/dialog.service.ts
@@ -15,52 +15,55 @@ import { MessageDialogComponent } from '../../components/shared/message-dialog/m
import { SendTextDialogComponent } from '../../components/shared/send-text-dialog/send-text-dialog.component';
import { FileUploadInfoDialogComponent } from '../../components/shared/file-upload-info-dialog/file-upload-info-dialog.component';
import { MountIsoDialogComponent } from '../../components/shared/mount-iso-dialog/mount-iso-dialog.component';
-
+import { IsoResult, IsoFile } from '../../models/vm/iso-result';
@Injectable()
export class DialogService {
-
- constructor(private dialog: MatDialog) { }
-
- public message(title: string, message: string, data?: any): Observable {
-
- let dialogRef: MatDialogRef;
- dialogRef = this.dialog.open(MessageDialogComponent, {data: data || {} });
- dialogRef.componentInstance.title = title;
- dialogRef.componentInstance.message = message;
-
- return dialogRef.afterClosed();
- }
-
- public sendText(title: string, configData?: any): Observable {
-
- let dialogRef: MatDialogRef;
- dialogRef = this.dialog.open(SendTextDialogComponent, configData || {});
- dialogRef.componentInstance.title = title;
-
- return dialogRef.afterClosed();
- }
-
- public getFileUploadInfo(title: string, configData?: any): Observable {
-
- let dialogRef: MatDialogRef;
- dialogRef = this.dialog.open(FileUploadInfoDialogComponent, configData || {});
- dialogRef.componentInstance.title = title;
-
- return dialogRef.afterClosed();
- }
-
- public mountIso(publicIsos: string[], teamIsos: string[], configData?: any): Observable {
-
- let dialogRef: MatDialogRef;
- dialogRef = this.dialog.open(MountIsoDialogComponent, configData || {});
- dialogRef.componentInstance.publicIsos = publicIsos;
- dialogRef.componentInstance.teamIsos = teamIsos;
-
- return dialogRef.afterClosed();
- }
-
-
+ constructor(private dialog: MatDialog) {}
+
+ public message(
+ title: string,
+ message: string,
+ data?: any
+ ): Observable {
+ let dialogRef: MatDialogRef;
+ dialogRef = this.dialog.open(MessageDialogComponent, { data: data || {} });
+ dialogRef.componentInstance.title = title;
+ dialogRef.componentInstance.message = message;
+
+ return dialogRef.afterClosed();
+ }
+
+ public sendText(title: string, configData?: any): Observable {
+ let dialogRef: MatDialogRef;
+ dialogRef = this.dialog.open(SendTextDialogComponent, configData || {});
+ dialogRef.componentInstance.title = title;
+
+ return dialogRef.afterClosed();
+ }
+
+ public getFileUploadInfo(
+ title: string,
+ configData?: any
+ ): Observable {
+ let dialogRef: MatDialogRef;
+ dialogRef = this.dialog.open(
+ FileUploadInfoDialogComponent,
+ configData || {}
+ );
+ dialogRef.componentInstance.title = title;
+
+ return dialogRef.afterClosed();
+ }
+
+ public mountIso(
+ isoResult: IsoResult[],
+ configData?: any
+ ): Observable {
+ let dialogRef: MatDialogRef;
+ dialogRef = this.dialog.open(MountIsoDialogComponent, configData || {});
+ dialogRef.componentInstance.isoResult = isoResult;
+
+ return dialogRef.afterClosed();
+ }
}
-
-
diff --git a/console.ui/src/app/services/vm/vm.service.ts b/console.ui/src/app/services/vm/vm.service.ts
index f5e6eda5..0e7a2258 100644
--- a/console.ui/src/app/services/vm/vm.service.ts
+++ b/console.ui/src/app/services/vm/vm.service.ts
@@ -21,6 +21,7 @@ import { VmModel, VmResolution } from '../../models/vm/vm-model';
import { HttpClient } from '@angular/common/http';
import { Title } from '@angular/platform-browser';
import { VirtualMachineToolsStatus } from '../../models/vm/vm-model';
+import { IsoResult } from '../../models/vm/iso-result';
declare var WMKS: any; // needed to check values
@@ -409,8 +410,8 @@ export class VmService {
return this.uploadConfig.asObservable();
}
- public getIsos() {
- return this.http.get(
+ public getIsos(): Observable {
+ return this.http.get(
this.ConsoleApiUrl + this.model.id.toString() + '/isos'
);
}
diff --git a/stackstorm.api/Stackstorm.Api.Client/Executions/VSphere.cs b/stackstorm.api/Stackstorm.Api.Client/Executions/VSphere.cs
index d3e47a66..cc222045 100644
--- a/stackstorm.api/Stackstorm.Api.Client/Executions/VSphere.cs
+++ b/stackstorm.api/Stackstorm.Api.Client/Executions/VSphere.cs
@@ -28,6 +28,7 @@ public interface IVSphere
Task GuestFileDelete(Dictionary parameters);
Task GuestFileRead(Dictionary parameters);
Task GuestFileUpload(Dictionary parameters);
+ Task GuestFileUploadContent(Dictionary parameters);
Task GuestProcessRun(Dictionary parameters);
Task GuestProcessRunFast(Dictionary parameters);
Task GuestProcessStart(Dictionary parameters);
@@ -143,6 +144,14 @@ public async Task GuestFileUpload(Dictionary paramete
return await AddExecution("vsphere.guest_file_upload", parameters);
}
+ ///
+ /// Upload a file to the guest with content
+ ///
+ public async Task GuestFileUploadContent(Dictionary parameters)
+ {
+ return await AddExecution("vsphere.guest_file_upload_content", parameters);
+ }
+
///
/// Run a process inside the guest
///
diff --git a/stackstorm.api/Stackstorm.Connector/VSphere.cs b/stackstorm.api/Stackstorm.Connector/VSphere.cs
index 48988bc1..000f78b1 100644
--- a/stackstorm.api/Stackstorm.Connector/VSphere.cs
+++ b/stackstorm.api/Stackstorm.Connector/VSphere.cs
@@ -215,18 +215,29 @@ public VSphere(string url, string username, string password) : base(url, usernam
return returnObject;
}
- public async Task