diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 09ce80b4..782fdc3a 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -14,11 +14,11 @@ import { PrintableComponent } from './printable/printable.component';
import { VmtemplatesComponent } from './configuration/vmtemplates/vmtemplates.component';
import { DashboardsComponent } from './dashboards/dashboards.component';
import { StepComponent } from './step/step-component/step.component';
-import { TerminalComponent } from './step/terminal/terminal.component';
import { RolesComponent } from './configuration/roles/roles/roles.component';
import { SessionStatisticsComponent } from './session-statistics/session-statistics.component';
import { SettingsComponent } from './configuration/settings/settings.component';
import { DashboardDetailsComponent } from './dashboards/dashboard-details/dashboard-details.component';
+import { TerminalViewComponent } from './step/terminal/terminal-view.component';
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
@@ -101,7 +101,7 @@ const routes: Routes = [
path: 'session/:session/steps/:step',
component: StepComponent,
},
- { path: 'terminal', component: TerminalComponent },
+ { path: 'terminal/:vmId/:wsEndpoint', component: TerminalViewComponent, canActivate: [AuthGuard] },
{
path: 'scenario/:scenario/printable',
component: PrintableComponent,
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index b48058a9..b6768e55 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -55,6 +55,7 @@ import { ProgressDashboardComponent } from './dashboards/progress-dashboard/prog
import { DashboardsComponent } from './dashboards/dashboards.component';
import { VmDashboardComponent } from './dashboards/vm-dashboard/vm-dashboard.component';
import { UsersDashboardComponent } from './dashboards/users-dashboard/users-dashboard.component';
+import { SharedVmDashboardComponent } from './dashboards/shared-vm-dashboard/shared-vm-dashboard.component';
import { StepComponent } from './step/step-component/step.component';
import { CtrService } from './data/ctr.service';
import { SessionService } from './data/session.service';
@@ -159,6 +160,7 @@ import {
downloadIcon,
plusCircleIcon,
exclamationTriangleIcon,
+ refreshIcon
} from '@cds/core/icon';
import { ReadonlyTaskComponent } from './scenario/task/readonly-task/readonly-task.component';
import { HiddenMdComponent } from './step/hidden-md-component/hidden-md.component';
@@ -172,6 +174,8 @@ import { TooltipComponent } from './tooltip/tooltip.component';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { AuthnService } from './data/authn.service';
import { SessionProgressService } from './progress/session-progress.service';
+import { TerminalViewComponent } from './step/terminal/terminal-view.component';
+import { WebinterfaceWindowComponent } from './step/terminal/webinterface-window/webinterface-window.component';
ClarityIcons.addIcons(
plusIcon,
@@ -214,6 +218,7 @@ ClarityIcons.addIcons(
downloadIcon,
plusCircleIcon,
exclamationTriangleIcon,
+ refreshIcon
);
const appInitializerFn = (appConfig: AppConfigService) => {
@@ -222,6 +227,19 @@ const appInitializerFn = (appConfig: AppConfigService) => {
};
};
+
+//TODO: Check if this still works after resolving all merge conflicts considering Angular 17 upgrade!
+export const jwtAllowedDomains = [
+ environment.server.replace(/(^\w+:|^)\/\//, ''),
+];
+
+export function addJwtAllowedDomain(domain: string) {
+ const newDomain = domain.replace(/(^\w+:|^)\/\//, '');
+ if (!jwtAllowedDomains.includes(newDomain)) {
+ jwtAllowedDomains.push(newDomain);
+ }
+}
+
export function jwtOptionsFactory(): JwtConfig {
const allowedDomainsRegex = environment.server.match(/.*\:\/\/?([^\/]+)/);
let allowedDomains: string[] | undefined;
@@ -279,9 +297,11 @@ export function jwtOptionsFactory(): JwtConfig {
DashboardsComponent,
VmDashboardComponent,
UsersDashboardComponent,
+ SharedVmDashboardComponent,
StepComponent,
HfMarkdownComponent,
TerminalComponent,
+ TerminalViewComponent,
CtrComponent,
RbacDirective,
ClarityDisableSelectionDirective,
@@ -318,6 +338,7 @@ export function jwtOptionsFactory(): JwtConfig {
TaskFormComponent,
ReadonlyTaskComponent,
SingleTaskVerificationMarkdownComponent,
+ WebinterfaceWindowComponent,
GlossaryMdComponent,
HiddenMdComponent,
MermaidMdComponent,
diff --git a/src/app/configuration/vmtemplates/vmtemplates.component.html b/src/app/configuration/vmtemplates/vmtemplates.component.html
index e78c45b2..5731e042 100644
--- a/src/app/configuration/vmtemplates/vmtemplates.component.html
+++ b/src/app/configuration/vmtemplates/vmtemplates.component.html
@@ -75,3 +75,4 @@
VM Templates
#deleteConfirmation
(delete)="doDelete()"
>
+
\ No newline at end of file
diff --git a/src/app/dashboards/dashboard-details/dashboard-details.component.html b/src/app/dashboards/dashboard-details/dashboard-details.component.html
index 47baaf36..05d46ff5 100644
--- a/src/app/dashboards/dashboard-details/dashboard-details.component.html
+++ b/src/app/dashboards/dashboard-details/dashboard-details.component.html
@@ -8,6 +8,8 @@ VMs for {{ selectedEvent.event_name }}
Users participating in {{ selectedEvent.event_name }}
} @else if (statisticsDashboardActive && rbacSuccessSessions) {
Statistics for {{ selectedEvent.event_name }}
+ } @else if (sharedVmDashboardActive && rbacSuccessVms) {
+ VMs for {{ selectedEvent.event_name }}
}
@@ -50,6 +52,18 @@ Statistics for {{ selectedEvent.event_name }}
}
+ @if (rbacSuccessVms) {
+
+ Shared VMs
+
+
+
+
+
+
+
+
+ }
@if (rbacSuccessSessions) {
Users
@@ -74,7 +88,7 @@ Statistics for {{ selectedEvent.event_name }}
- }
+ }
}
diff --git a/src/app/dashboards/dashboard-details/dashboard-details.component.ts b/src/app/dashboards/dashboard-details/dashboard-details.component.ts
index ec5aa847..0319b8cd 100644
--- a/src/app/dashboards/dashboard-details/dashboard-details.component.ts
+++ b/src/app/dashboards/dashboard-details/dashboard-details.component.ts
@@ -17,6 +17,7 @@ export class DashboardDetailsComponent implements OnInit, OnDestroy {
public statisticsDashboardActive: boolean = false;
public usersDashboardActive: boolean = false;
+ public sharedVmDashboardActive: boolean = false;
public selectedEvent?: DashboardScheduledEvent;
public loggedInAdminEmail: string;
diff --git a/src/app/dashboards/dashboards.component.ts b/src/app/dashboards/dashboards.component.ts
index 2fc02e1f..da3d85bd 100644
--- a/src/app/dashboards/dashboards.component.ts
+++ b/src/app/dashboards/dashboards.component.ts
@@ -6,7 +6,7 @@ import { UserService } from '../data/user.service';
import { RbacService } from '../data/rbac.service';
import { ProgressCount } from '../data/progress';
import { ProgressService } from '../data/progress.service';
-import { VmService } from '../data/vm.service';
+import { AdminVmService } from '../data/admin-vm.service';
import { Router } from '@angular/router';
@Component({
@@ -33,7 +33,7 @@ export class DashboardsComponent implements OnInit, OnDestroy {
private userService: UserService,
private rbacService: RbacService,
private progressService: ProgressService,
- private vmService: VmService,
+ private vmService: AdminVmService,
private router: Router
) {}
diff --git a/src/app/dashboards/shared-vm-dashboard/shared-vm-dashboard.component.html b/src/app/dashboards/shared-vm-dashboard/shared-vm-dashboard.component.html
new file mode 100644
index 00000000..07544fd0
--- /dev/null
+++ b/src/app/dashboards/shared-vm-dashboard/shared-vm-dashboard.component.html
@@ -0,0 +1,100 @@
+
+ @if (vmSets.length < 1) {
+
No active VMs
+ }
+
+ @for(set of vmSets; track $index) {
+
+
+
+ Environment: {{ set.environment }} Count: {{ set.count }}
+
+
+
+
+
+ Access Terminal
+ Status
+ Name
+ IP
+ VM-Template
+
+ VM Id
+ Hostname
+
+
+
+ >_
+
+
+ @if (vm.status === 'running' && !vm.tainted) {
+ {{ vm.status }}
+ }
+ @if (vm.status !== 'running' && !vm.tainted) {
+ {{ vm.status }}
+ }
+ @if (!!vm.tainted) {
+ tainted
+ }
+
+ {{ vm.name }}
+ {{
+ vm.public_ip
+ }}
+ {{
+ vm.vm_template_id
+ }}
+ {{ vm.id }}
+ {{
+ vm.hostname
+ }}
+
+
+
+
+
+
+
+ }
+
+
diff --git a/src/app/dashboards/shared-vm-dashboard/shared-vm-dashboard.component.scss b/src/app/dashboards/shared-vm-dashboard/shared-vm-dashboard.component.scss
new file mode 100644
index 00000000..18ccbfec
--- /dev/null
+++ b/src/app/dashboards/shared-vm-dashboard/shared-vm-dashboard.component.scss
@@ -0,0 +1,12 @@
+.dashboardCell {
+ display: flex;
+ align-items: center;
+}
+
+.joinButton {
+ justify-content: center;
+}
+
+.badge{
+ padding: 10px;
+}
\ No newline at end of file
diff --git a/src/app/dashboards/shared-vm-dashboard/shared-vm-dashboard.component.ts b/src/app/dashboards/shared-vm-dashboard/shared-vm-dashboard.component.ts
new file mode 100644
index 00000000..0d46dc01
--- /dev/null
+++ b/src/app/dashboards/shared-vm-dashboard/shared-vm-dashboard.component.ts
@@ -0,0 +1,138 @@
+import { ChangeDetectorRef, Component, Input, OnChanges } from '@angular/core';
+import { Router } from '@angular/router';
+import { ScheduledEvent } from 'src/app/data/scheduledevent';
+import {
+ VirtualMachine,
+ VirtualMachineTypeShared,
+} from 'src/app/data/virtualmachine';
+import { AdminVmService } from 'src/app/data/admin-vm.service';
+import { VmSet } from 'src/app/data/vmset';
+
+interface DashboardVm extends VirtualMachine {
+ name?: string;
+}
+
+interface dashboardVmSet extends VmSet {
+ setVMs: DashboardVm[];
+ stepOpen: boolean;
+ dynamic: boolean;
+}
+
+@Component({
+ selector: 'shared-vm-dashboard',
+ templateUrl: './shared-vm-dashboard.component.html',
+ styleUrls: ['./shared-vm-dashboard.component.scss'],
+})
+export class SharedVmDashboardComponent implements OnChanges {
+ @Input()
+ selectedEvent: ScheduledEvent;
+
+ constructor(
+ public vmService: AdminVmService,
+ private router: Router,
+ private cd: ChangeDetectorRef
+ ) {}
+
+ public vms: VirtualMachine[] = [];
+ public vmSets: dashboardVmSet[] = [];
+
+ public selectedVM: VirtualMachine = new VirtualMachine();
+ public openPanels: Set = new Set();
+
+ ngOnChanges() {
+ this.getVmList();
+ }
+
+ setStepOpen(set) {
+ this.openPanels.has(set.base_name)
+ ? this.openPanels.delete(set.base_name)
+ : this.openPanels.add(set.base_name);
+ }
+
+ getVmList() {
+ this.vmService
+ .listByScheduledEvent(this.selectedEvent.id)
+ .subscribe((vmList) => {
+ this.vms = vmList
+ .filter((vm) => vm.vm_type == VirtualMachineTypeShared)
+ .map((vm) => ({
+ ...vm,
+ }));
+ if (this.vms.length > 0) {
+ this.loadVmsFromScheduledEvent();
+ }
+ this.cd.detectChanges();
+ });
+ }
+
+ // Used to load either dynamic or shared virtualMachines for an event
+ private loadVmsFromScheduledEvent() {
+ // dynamic machines have no associated vmSet
+ if (this.vms.length > 0) {
+ let groupedVms: Map = this.groupByEnvironment(
+ this.vms
+ );
+ // (shared) vms grouped by environment
+ groupedVms.forEach((element, environment) => {
+ element.forEach((vm) => this.setVmName(vm));
+ let vmSet: dashboardVmSet = {
+ ...new VmSet(),
+ base_name: environment,
+ stepOpen: this.openPanels.has(environment),
+ dynamic: false,
+ setVMs: element
+ };
+ vmSet.count = element.length;
+ vmSet.available = element.filter((vm) => vm.status == 'running').length;
+ vmSet.environment = environment;
+ this.vmSets.push(vmSet);
+ });
+ }
+ }
+
+ setVmName(vm: DashboardVm) {
+ vm.name =
+ this.selectedEvent.shared_vms.find((sVM) => sVM.vm_id == vm.id)?.name ??
+ '';
+ }
+
+ openTerminal(vm: DashboardVm) {
+ //build url with params, then use router to navigate to it
+ if (!vm.name) this.setVmName(vm)
+ const queryParams = {
+ vmName: vm.name,
+ vmId: vm.id,
+ wsEndpoint: vm.ws_endpoint,
+ };
+
+ const url = this.router.createUrlTree(
+ ['/terminal', vm.id, vm.ws_endpoint],
+ { queryParams }
+ );
+ const serializedUrl = this.router.serializeUrl(url);
+
+ window.open(serializedUrl, '_blank');
+ return;
+
+ // const url = this.router.serializeUrl(
+ // this.router.createUrlTree(['/terminal', vm.id, vm.ws_endpoint])
+ // );
+ // window.open(url, '_blank');
+ // return;
+ }
+
+ groupByEnvironment(vms: VirtualMachine[]) {
+ let envMap = new Map();
+ vms.forEach((element) => {
+ if (envMap.has(element.environment_id)) {
+ let envVms = envMap.get(element.environment_id)!;
+ envVms.push(element);
+ envMap.set(element.environment_id, envVms);
+ } else {
+ let envVms: VirtualMachine[] = [element];
+ envMap.set(element.environment_id, envVms);
+ }
+ });
+ return envMap;
+ }
+}
diff --git a/src/app/dashboards/vm-dashboard/vm-dashboard.component.html b/src/app/dashboards/vm-dashboard/vm-dashboard.component.html
index 85dec10a..363ad140 100644
--- a/src/app/dashboards/vm-dashboard/vm-dashboard.component.html
+++ b/src/app/dashboards/vm-dashboard/vm-dashboard.component.html
@@ -1,7 +1,7 @@
-
@if (vmSets.length < 1) {
@@ -74,9 +74,11 @@
- @if (vm.status === "running") {
+ @if (!!vm.tainted) {
+ tainted
+ } @else if (vm.status === "running") {
{{ vm.status }}
- } @else {
+ } @else if (vm.status !== 'running'){
{{ vm.status }}
}
diff --git a/src/app/dashboards/vm-dashboard/vm-dashboard.component.ts b/src/app/dashboards/vm-dashboard/vm-dashboard.component.ts
index 93f59530..cdcf7b24 100644
--- a/src/app/dashboards/vm-dashboard/vm-dashboard.component.ts
+++ b/src/app/dashboards/vm-dashboard/vm-dashboard.component.ts
@@ -1,12 +1,12 @@
-import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
+import { ChangeDetectorRef, Component, Input, OnChanges } from '@angular/core';
import { Router } from '@angular/router';
import { combineLatest } from 'rxjs';
import { Progress } from 'src/app/data/progress';
import { ProgressService } from 'src/app/data/progress.service';
import { ScheduledEventBase } from 'src/app/data/scheduledevent';
import { UserService } from 'src/app/data/user.service';
-import { VirtualMachine } from 'src/app/data/virtualmachine';
-import { VmService } from 'src/app/data/vm.service';
+import { VirtualMachine, VirtualMachineTypeShared } from 'src/app/data/virtualmachine';
+import { AdminVmService } from 'src/app/data/admin-vm.service';
import { VmSet } from 'src/app/data/vmset';
import { VmSetService } from 'src/app/data/vmset.service';
@@ -21,12 +21,12 @@ interface dashboardVmSet extends VmSet {
templateUrl: './vm-dashboard.component.html',
styleUrls: ['./vm-dashboard.component.scss'],
})
-export class VmDashboardComponent implements OnInit {
+export class VmDashboardComponent implements OnChanges {
@Input()
selectedEvent: ScheduledEventBase;
constructor(
- public vmService: VmService,
+ public vmService: AdminVmService,
public vmSetService: VmSetService,
public userService: UserService,
public progressService: ProgressService,
@@ -40,10 +40,6 @@ export class VmDashboardComponent implements OnInit {
public selectedVM: VirtualMachine = new VirtualMachine();
public openPanels: Set
= new Set();
- ngOnInit(): void {
- this.getVmList();
- }
-
ngOnChanges() {
this.getVmList();
}
@@ -54,54 +50,65 @@ export class VmDashboardComponent implements OnInit {
: this.openPanels.add(set.base_name);
}
- getVmList() {
- combineLatest([
- this.vmService.listByScheduledEvent(this.selectedEvent.id),
- this.vmSetService.getVMSetByScheduledEvent(this.selectedEvent.id),
- this.userService.list(),
- ]).subscribe(([vmList, vmSet, users]) => {
- const userMap = new Map(users.map((u) => [u.id, u.email]));
- this.vms = vmList.map((vm) => ({
- ...vm,
- user: userMap.get(vm.user) ?? '-',
- }));
+ getVmList() {
- this.vmSets = vmSet.map((set) => ({
- ...set,
- setVMs: this.vms.filter((vm) => vm.vm_set_id === set.id),
- stepOpen: this.openPanels.has(set.base_name),
- dynamic: false,
- available: this.vms.filter(
- (vm) => vm.vm_set_id === set.id && vm.status == 'running'
- ).length,
- }));
- // dynamic machines have no associated vmSet
- if (this.vms.filter((vm) => vm.vm_set_id == '').length > 0) {
- let groupedVms: Map = this.groupByEnvironment(
- this.vms.filter((vm) => vm.vm_set_id == '')
- );
- groupedVms.forEach((element, environment) => {
- let vmSet: dashboardVmSet = {
- ...new VmSet(),
- base_name: environment,
- stepOpen: this.openPanels.has(environment),
- dynamic: true,
- };
- vmSet.setVMs = element;
- vmSet.count = element.length;
- vmSet.available = element.filter(
- (vm) => vm.status == 'running'
- ).length;
- vmSet.environment = environment;
- this.vmSets.push(vmSet);
- });
- }
- this.cd.detectChanges(); //The async Code above updates values after Angulars usual change-detection so we call this Method to prevent Errors
- });
+ combineLatest([
+ this.vmService.listByScheduledEvent(this.selectedEvent.id),
+ this.vmSetService.getVMSetByScheduledEvent(this.selectedEvent.id),
+ this.userService.list(),
+ ]).subscribe(([vmList, vmSet, users]) => {
+ const userMap = new Map(users.map((u) => [u.id, u.email]));
+ this.vms = vmList.filter(vm => vm.vm_type != VirtualMachineTypeShared )
+ .map((vm) => ({
+ ...vm,
+ user: userMap.get(vm.user) ?? '-',
+ }));
+
+ this.vmSets = vmSet.map((set) => ({
+ ...set,
+ setVMs: this.vms.filter((vm) => vm.vm_set_id === set.id),
+ stepOpen: this.openPanels.has(set.base_name),
+ dynamic: false,
+ available: this.vms.filter(
+ (vm) => vm.vm_set_id === set.id && vm.status == 'running'
+ ).length,
+ }));
+ // dynamic machines have no associated vmSet
+ if (this.vms.filter((vm) => vm.vm_set_id == '').length > 0) {
+ this.loadVmsFromScheduledEvent(true);
+ }
+ this.cd.detectChanges(); //The async Code above updates values after Angulars usual change-detection so we call this Method to prevent Errors
+ });
+ }
+
+ // Used to load either dynamic or shared virtualMachines for an event
+ private loadVmsFromScheduledEvent(dynamic: boolean) {
+ // dynamic machines have no associated vmSet
+ if (this.vms.filter((vm) => vm.vm_set_id == '').length > 0) {
+ // -> is always true for shared vms...
+ let groupedVms: Map = this.groupByEnvironment(
+ this.vms.filter((vm) => vm.vm_set_id == '')
+ );
+ // (shared) vms grouped by environment
+ groupedVms.forEach((element, environment) => {
+ let vmSet: dashboardVmSet = {
+ ...new VmSet(),
+ base_name: environment,
+ stepOpen: this.openPanels.has(environment),
+ dynamic: dynamic,
+ };
+ vmSet.setVMs = element;
+ vmSet.count = element.length;
+ vmSet.available = element.filter(
+ (vm) => vm.status == 'running'
+ ).length;
+ vmSet.environment = environment;
+ this.vmSets.push(vmSet);
+ });
+ }
}
openUsersTerminal(vm: VirtualMachine) {
- if (!vm.user) return;
let userId: string | undefined; //get the Users ID who has the VM allocated to him
this.userService.list().subscribe((users) => {
userId = users.filter((user) => user.email === vm.user)[0]?.id;
diff --git a/src/app/data/Session.ts b/src/app/data/Session.ts
index f4dd9f6b..8d15515b 100644
--- a/src/app/data/Session.ts
+++ b/src/app/data/Session.ts
@@ -5,4 +5,5 @@ export class Session {
keep_course_vm: boolean;
user: string;
vm_claim: string[];
+ access_code: string;
}
diff --git a/src/app/data/admin-vm.service.ts b/src/app/data/admin-vm.service.ts
new file mode 100644
index 00000000..fa76af22
--- /dev/null
+++ b/src/app/data/admin-vm.service.ts
@@ -0,0 +1,51 @@
+import { Injectable } from '@angular/core';
+import { HttpErrorResponse } from '@angular/common/http';
+import { catchError, switchMap } from 'rxjs/operators';
+import { ServerResponse } from './serverresponse';
+import { of, throwError } from 'rxjs';
+import { atou } from '../unicode';
+import { ResourceClient, GargantuaClientFactory } from './gargantua.service';
+import { VirtualMachine } from './virtualmachine';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class AdminVmService extends ResourceClient {
+ constructor(gcf: GargantuaClientFactory) {
+ super(gcf.scopedClient('/a/vm'));
+ }
+
+ public list() {
+ return this.garg.get('/list').pipe(
+ catchError((e: HttpErrorResponse) => {
+ return throwError(() => e.error);
+ }),
+ switchMap((s: ServerResponse) => {
+ return of(JSON.parse(atou(s.content)));
+ })
+ );
+ }
+ public listByScheduledEvent(id: String) {
+ return this.garg
+ .get('/scheduledevent/' + id)
+ .pipe(
+ catchError((e: HttpErrorResponse) => {
+ return throwError(() => e.error);
+ }),
+ switchMap((s: ServerResponse) => {
+ return of(JSON.parse(atou(s.content)));
+ })
+ );
+ }
+
+ public count() {
+ return this.garg.get('/count').pipe(
+ catchError((e: HttpErrorResponse) => {
+ return throwError(() => e.error);
+ }),
+ switchMap((s: ServerResponse) => {
+ return of(JSON.parse(atou(s.content)));
+ })
+ );
+ }
+}
diff --git a/src/app/data/scheduledevent.service.ts b/src/app/data/scheduledevent.service.ts
index e37d62c1..85df17c4 100644
--- a/src/app/data/scheduledevent.service.ts
+++ b/src/app/data/scheduledevent.service.ts
@@ -63,6 +63,7 @@ export class ScheduledeventService extends ListableResourceClient {
- return of(JSON.parse(atou(s.content)));
- }),
- );
- }
- public listByScheduledEvent(id: String) {
- return this.gargAdmin.get(`/scheduledevent/${id}`).pipe(
- switchMap((s: ServerResponse) => {
- return of(JSON.parse(atou(s.content)));
- }),
- );
- }
-
- public getVmById(id: number) {
- return this.garg.get(`?${id}`).pipe(
- switchMap((s: ServerResponse) => {
- return of(JSON.parse(atou(s.content)));
- }),
- );
- }
-
- public count() {
- return this.gargAdmin.get('/count').pipe(
- switchMap((s: ServerResponse) => {
- return of(JSON.parse(atou(s.content)));
- }),
- );
- }
-}
diff --git a/src/app/event/new-scheduled-event/new-scheduled-event.component.html b/src/app/event/new-scheduled-event/new-scheduled-event.component.html
index 0585e65f..8aa3fd2a 100644
--- a/src/app/event/new-scheduled-event/new-scheduled-event.component.html
+++ b/src/app/event/new-scheduled-event/new-scheduled-event.component.html
@@ -5,7 +5,7 @@
(clrWizardOnFinish)="save()"
(clrWizardOnCancel)="close()"
class="customized"
->
+ >
New Scheduled Event
Cancel
@@ -33,6 +33,9 @@
Select Virtual Machines
+
+ Select Shared Virtual Machines
+
Finalize
@@ -49,853 +52,965 @@
name="event_name"
formControlName="event_name"
required
- />
+ />
Event name is required
- Event name must be longer than 4 characters
- Event name must be unique
-
-
- Description
-
- Event description is required
- Event description must be longer than 4 characters
-
-
-
- Access Code
-
- Access code is required
- Access code must be longer than 5 characters
- Access code must
-
-
- contain only lowercase alphanumeric characters, '-', or '.'
-
- start and end with an alphanumber character
-
-
- Access code is already in use
-
-
-
-
- Restricted Bind
-
- Restricted bind prevents users from reserving VM resources not
- associated with this Scheduled Event.
-
-
-
-
- On Demand
-
-
- On demand allocates VM resources when requested by a user instead of
- pre-provisioning.
-
-
-
-
-
- Printable
-
-
- Printable enables an option for users to print scenario content or
- save it as PDF file.
-
-
-
-
-
+ Event name must be longer than 4 characters
+ Event name must be unique
+
+
+ Description
+
+ Event description is required
+ Event description must be longer than 4 characters
+
+
+
+ Access Code
+
+ Access code is required
+ Access code must be longer than 5 characters
+ Access code must
+
+
+ contain only lowercase alphanumeric characters, '-', or '.'
+
+ start and end with an alphanumber character
+
+
+ Access code is already in use
+
+
+
+
+ Restricted Bind
+
+ Restricted bind prevents users from reserving VM resources not
+ associated with this Scheduled Event.
+
+
+
+
+ On Demand
+
+
+ On demand allocates VM resources when requested by a user instead of
+ pre-provisioning.
+
+
+
+
+
+ Printable
+
+
+ Printable enables an option for users to print scenario content or
+ save it as PDF file.
+
+
+
+
+ = se.end_time
"
- >
- Event Times
-
-
- Start Time:
- {{ se.start_time ? (se.start_time | date: "long") : "" }}
-
-
- Set Start Time
-
-
-
-
-
-
-
-
-
- Start now
-
-
-
-
- End Time: {{ se.end_time ? (se.end_time | date: "long") : "" }}
-
-
-
- Set End Time
-
-
-
-
-
-
-
-
+
+ All times are in {{ tz }} (browser detected)
+
+ @if (se.start_time && se.end_time && se.start_time >= se.end_time) {
+
+
+
+ Start time must occur before end time!
+
+
+
+ }
+
+
+ Select Course(s)
+ Expand a row to view scenarios within that course
+
+ Id
+ Name
+ Description
+
+ {{ c.id }}
+ {{ c.name }}
+ {{ c.description }}
+
+
+
+ Scenario
+ Description
+
+
+ @for (s of c.scenarios; track s) {
+
+ {{ s.name }}
+ {{ s.description }}
+
+ }
+
+
+
+
+
+
+
- Select Scenario(s)
-
-
- Id
- Name
- Description
-
- {{ s.id }}
- {{ s.name }}
- {{ s.description }}
-
-
- @if (selectedcourses.length == 0 && selectedscenarios.length == 0) {
-
-
-
- You must select at least one course or scenario to proceed
-
-
-
- }
-
-
+ Select Scenario(s)
+
+
+ Id
+ Name
+ Description
+
+ {{ s.id }}
+ {{ s.name }}
+ {{ s.description }}
+
+
+ @if (selectedcourses.length == 0 && selectedscenarios.length == 0) {
+
+
+
+ You must select at least one course or scenario to proceed
+
+
+
+ }
+
+
- Select Environment(s)
- @if (checkingEnvironments) {
- Please wait...
- }
- @if (noEnvironmentsAvailable && noVirtualMachinesNeeded) {
- VirtualMachines are not needed. Only scenarios/courses without
- Machines were selected.
- } @else {
- No suitable environments found.
- }
- @if (unavailableVMTs.length > 0) {
-
- No suitable environments found for the following VM Templates:
-
-
-
- ID
-
-
-
- @for (vmt of unavailableVMTs; track vmt) {
-
-
- {{ vmt }}
-
-
- }
-
- }
- @if (
- !checkingEnvironments &&
- !noEnvironmentsAvailable &&
- unavailableVMTs.length == 0
- ) {
-
-
- Environment
-
-
- Count
-
-
- {{ ea.environment }}
-
- @for (item of ea.available_count | keyvalue; track item) {
- {{ getVirtualMachineTemplateName(item.key) }}
- {{
- item.value
- }}
- }
-
-
-
- }
-
-
+ Select Environment(s)
+ @if (checkingEnvironments) {
+ Please wait...
+ }
+ @if (noEnvironmentsAvailable && noVirtualMachinesNeeded) {
+ VirtualMachines are not needed. Only scenarios/courses without
+ Machines were selected.
+ } @else {
+ No suitable environments found.
+ }
+ @if (unavailableVMTs.length > 0) {
+
+ No suitable environments found for the following VM Templates:
+
+
+
+ ID
+
+
+
+ @for (vmt of unavailableVMTs; track vmt) {
+
+
+ {{ vmt }}
+
+
+ }
+
+ }
+ @if (
+ !checkingEnvironments &&
+ !noEnvironmentsAvailable &&
+ unavailableVMTs.length == 0
+ ) {
+
+
+ Environment
+
+
+ Count
+
+
+ {{ ea.environment }}
+
+ @for (item of ea.available_count | keyvalue; track item) {
+ {{ getVirtualMachineTemplateName(item.key) }}
+ {{
+ item.value
+ }}
+ }
+
+
+
+ }
+
+
- Select Virtual Machines
- @if (noVirtualMachinesNeeded) {
- VirtualMachines are not needed. Only scenarios/courses without Machines
- were selected.
- } @else {
-
-
- Simple Mode
-
- @if (simpleMode) {
- In simple mode, define the number of users per environment. Virtual
- machines are calculated for you.
-
-
- @if (invalidSimpleEnvironments.length != 0) {
- The following environments are incompatible with simple mode:
-
-
-
- Simple Mode Compatibility
-
- An environment must be able to support the creation of all
- types of required VMs, as well as have enough capacity for at
- least one user (and all their associated VMs).
-
-
-
-
- @for (s of invalidSimpleEnvironments; track s) {
- {{ s }}
- }
-
- }
- } @else {
- In advanced mode, define the number of virtual machines per
- environment. Remember to account for the number of expected users,
- thus number_of_users * number_of_required_vms
.
-
-
- The following VMs are required per user :
- @for (item of requiredVmCounts | keyvalue; track item) {
-
{{ item.key }}: {{ item.value }}
- }
-
-
- }
- }
-
- @if (!isEditMode) {
-
- Finalize
- Confirm the following details before finishing:
- Basic Information
-
-
-
- Option
- Value
-
-
-
-
- Name
- {{ se.event_name }}
-
-
- Description
- {{ se.description }}
-
-
- Access code
- {{ se.access_code }}
-
-
- Restricted Bind
- {{ !se.disable_restriction }}
-
-
- On demand
- {{ se.on_demand }}
-
-
- Start Time
- {{ se.start_time | date: "long" }}
-
-
- End Time
- {{ se.end_time | date: "long" }}
-
-
- Courses
-
- @for (c of se.courses; track c) {
- {{ c }}
- }
-
-
-
- Scenarios
-
- @for (s of se.scenarios; track s) {
- {{ s }}
- }
-
-
-
-
- VM Information
- @if (noVirtualMachinesNeeded) {
- VirtualMachines are not needed for this ScheduledEvent.
- } @else {
-
-
-
- Environment
- Virtual Machines
-
-
-
- @for (i of se.required_vms | keyvalue; track i) {
-
- {{ getEnvironmentName(i.key) }}
-
- @for (q of i.value | keyvalue; track q) {
- {{ getVirtualMachineTemplateName(q.key) }}
- {{
- q.value
- }}
- }
-
-
- }
-
-
- }
-
- }
- @if (isEditMode) {
-
- Finalize
- Confirm the following details before finishing:
- Basic Information
-
-
-
- Option
- Value
-
-
-
-
- Name
- @if (se.event_name == uneditedScheduledEvent.event_name) {
-
- {{ se.event_name }}
-
- } @else {
-
- {{
- uneditedScheduledEvent.event_name
- }}
- {{ se.event_name }}
-
- }
-
-
- Description
- @if (se.description == uneditedScheduledEvent.description) {
-
- {{ se.description }}
-
- } @else {
-
- {{
- uneditedScheduledEvent.description
- }}
- {{ se.description }}
-
- }
-
-
- Access code
- @if (se.access_code == uneditedScheduledEvent.access_code) {
-
- {{ se.access_code }}
-
- } @else {
-
- {{
- uneditedScheduledEvent.access_code
- }}
- {{ se.access_code }}
-
- }
-
-
- Restricted Bind
- @if (
- se.disable_restriction ==
- uneditedScheduledEvent.disable_restriction
- ) {
-
- {{ !se.disable_restriction }}
-
- } @else {
-
- {{
- !uneditedScheduledEvent.disable_restriction
- }}
- {{ !se.disable_restriction }}
-
- }
-
-
- On demand
- @if (se.on_demand == uneditedScheduledEvent.on_demand) {
-
- {{ se.on_demand }}
-
- } @else {
-
- {{
- uneditedScheduledEvent.on_demand
- }}
- {{ se.on_demand }}
-
- }
-
-
- Start Time
- @if (!isStartDateAsEditedCheck()) {
-
- {{ se.start_time | date: "long" }}
-
- } @else {
-
- {{
- uneditedScheduledEvent.start_time | date: "long"
- }}
- {{
- se.start_time | date: "long"
- }}
-
- }
-
-
- End Time
- @if (!isEndDateAsEditedCheck()) {
-
- {{ se.end_time | date: "long" }}
-
- } @else {
-
- {{
- uneditedScheduledEvent.end_time | date: "long"
- }}
- {{ se.end_time | date: "long" }}
-
- }
-
-
- Courses
-
- @for (s of uneditedScheduledEvent.courses; track s) {
- @if (isCourseInList(s, se.courses)) {
- {{ s }}
- } @else {
- {{ s }}
-
- }
- }
- @for (s of se.courses; track s) {
- @if (!isCourseInList(s, uneditedScheduledEvent.courses)) {
- {{ s }}
-
- }
- }
-
-
-
- Scenarios
-
- @for (s of uneditedScheduledEvent.scenarios; track s) {
- @if (isScenarioInList(s, se.scenarios)) {
- {{ s }}
- } @else {
- {{ s }}
- }
- }
- @for (s of se.scenarios; track s) {
- @if (!isScenarioInList(s, uneditedScheduledEvent.scenarios)) {
- {{ s }}
- }
- }
-
-
-
-
- @if (!se.on_demand) {
-
-
- VMs will be started right away
-
-
- }
- VM Information
- @if (noVirtualMachinesNeeded) {
- VirtualMachines are not needed for this ScheduledEvent.
- } @else {
-
-
-
- Environment
- Virtual Machines
-
-
-
- @for (i of se.required_vms | keyvalue; track i) {
-
- {{ getEnvironmentName(i.key) }}
-
- @for (q of i.value | keyvalue; track q) {
-
- {{ getVirtualMachineTemplateName(q.key) }}
- @if (
- getUneditedScheduledEventVMCount(i.key, q.key) ==
- q.value
- ) {
-
- {{ q.value }}
-
- } @else {
-
-
- {{
- getUneditedScheduledEventVMCount(i.key, q.key)
- }}
-
- {{ q.value }}
-
- }
-
- }
-
-
- }
-
-
- }
-
- }
- }
-
+ Select Virtual Machines
+ @if (noVirtualMachinesNeeded) {
+ VirtualMachines are not needed. Only scenarios/courses without Machines
+ were selected.
+ } @else {
+
+
+ Simple Mode
+
+ @if (simpleMode) {
+ In simple mode, define the number of users per environment. Virtual
+ machines are calculated for you.
+
+
+ @if (invalidSimpleEnvironments.length != 0) {
+ The following environments are incompatible with simple mode:
+
+
+
+ Simple Mode Compatibility
+
+ An environment must be able to support the creation of all
+ types of required VMs, as well as have enough capacity for at
+ least one user (and all their associated VMs).
+
+
+
+
+ @for (s of invalidSimpleEnvironments; track s) {
+ {{ s }}
+ }
+
+ }
+ } @else {
+ In advanced mode, define the number of virtual machines per
+ environment. Remember to account for the number of expected users,
+ thus number_of_users * number_of_required_vms
.
+
+
+ The following VMs are required per user :
+ @for (item of requiredVmCounts | keyvalue; track item) {
+
{{ item.key }}: {{ item.value }}
+ }
+
+
+ }
+ }
+
+
+ Select Shared Virtual Machines
+
+
+
+
+ Add
+
+
+
+ SharedVM Information
+
+
+
+ Name
+ Environment
+ Virtual Machine Template
+
+
+
+
+
+
+
+ @for (sharedVm of se.shared_vms; track sharedVm; let i = $index) {
+
+ {{ sharedVm.name }}
+ {{ sharedVm.environment }}
+ {{ sharedVm.vm_template }}
+ Delete
+
+ }
+
+
+
+
+ @if (!isEditMode) {
+
+ Finalize
+ Confirm the following details before finishing:
+ Basic Information
+
+
+
+ Option
+ Value
+
+
+
+
+ Name
+ {{ se.event_name }}
+
+
+ Description
+ {{ se.description }}
+
+
+ Access code
+ {{ se.access_code }}
+
+
+ Restricted Bind
+ {{ !se.disable_restriction }}
+
+
+ On demand
+ {{ se.on_demand }}
+
+
+ Start Time
+ {{ se.start_time | date: "long" }}
+
+
+ End Time
+ {{ se.end_time | date: "long" }}
+
+
+ Courses
+
+ @for (c of se.courses; track c) {
+ {{ c }}
+ }
+
+
+
+ Scenarios
+
+ @for (s of se.scenarios; track s) {
+ {{ s }}
+ }
+
+
+
+
+ VM Information
+ @if (noVirtualMachinesNeeded) {
+ VirtualMachines are not needed for this ScheduledEvent.
+ } @else {
+
+
+
+ Environment
+ Virtual Machines
+
+
+
+ @for (i of se.required_vms | keyvalue; track i) {
+
+ {{ getEnvironmentName(i.key) }}
+
+ @for (q of i.value | keyvalue; track q) {
+ {{ getVirtualMachineTemplateName(q.key) }}
+ {{
+ q.value
+ }}
+ }
+
+
+ }
+
+
+ }
+
+ }
+ @if (isEditMode) {
+
+ Finalize
+ Confirm the following details before finishing:
+ Basic Information
+
+
+
+ Option
+ Value
+
+
+
+
+ Name
+ @if (se.event_name == uneditedScheduledEvent.event_name) {
+
+ {{ se.event_name }}
+
+ } @else {
+
+ {{
+ uneditedScheduledEvent.event_name
+ }}
+ {{ se.event_name }}
+
+ }
+
+
+ Description
+ @if (se.description == uneditedScheduledEvent.description) {
+
+ {{ se.description }}
+
+ } @else {
+
+ {{
+ uneditedScheduledEvent.description
+ }}
+ {{ se.description }}
+
+ }
+
+
+ Access code
+ @if (se.access_code == uneditedScheduledEvent.access_code) {
+
+ {{ se.access_code }}
+
+ } @else {
+
+ {{
+ uneditedScheduledEvent.access_code
+ }}
+ {{ se.access_code }}
+
+ }
+
+
+ Restricted Bind
+ @if (
+ se.disable_restriction ==
+ uneditedScheduledEvent.disable_restriction
+ ) {
+
+ {{ !se.disable_restriction }}
+
+ } @else {
+
+ {{
+ !uneditedScheduledEvent.disable_restriction
+ }}
+ {{ !se.disable_restriction }}
+
+ }
+
+
+ On demand
+ @if (se.on_demand == uneditedScheduledEvent.on_demand) {
+
+ {{ se.on_demand }}
+
+ } @else {
+
+ {{
+ uneditedScheduledEvent.on_demand
+ }}
+ {{ se.on_demand }}
+
+ }
+
+
+ Start Time
+ @if (!isStartDateAsEditedCheck()) {
+
+ {{ se.start_time | date: "long" }}
+
+ } @else {
+
+ {{
+ uneditedScheduledEvent.start_time | date: "long"
+ }}
+ {{
+ se.start_time | date: "long"
+ }}
+
+ }
+
+
+ End Time
+ @if (!isEndDateAsEditedCheck()) {
+
+ {{ se.end_time | date: "long" }}
+
+ } @else {
+
+ {{
+ uneditedScheduledEvent.end_time | date: "long"
+ }}
+ {{ se.end_time | date: "long" }}
+
+ }
+
+
+ Courses
+
+ @for (s of uneditedScheduledEvent.courses; track s) {
+ @if (isCourseInList(s, se.courses)) {
+ {{ s }}
+ } @else {
+ {{ s }}
+
+ }
+ }
+ @for (s of se.courses; track s) {
+ @if (!isCourseInList(s, uneditedScheduledEvent.courses)) {
+ {{ s }}
+
+ }
+ }
+
+
+
+ Scenarios
+
+ @for (s of uneditedScheduledEvent.scenarios; track s) {
+ @if (isScenarioInList(s, se.scenarios)) {
+ {{ s }}
+ } @else {
+ {{ s }}
+ }
+ }
+ @for (s of se.scenarios; track s) {
+ @if (!isScenarioInList(s, uneditedScheduledEvent.scenarios)) {
+ {{ s }}
+ }
+ }
+
+
+
+
+ @if (!se.on_demand) {
+
+
+ VMs will be started right away
+
+
+ }
+ VM Information
+ @if (noVirtualMachinesNeeded) {
+ VirtualMachines are not needed for this ScheduledEvent.
+ } @else {
+
+
+
+ Environment
+ Virtual Machines
+
+
+
+ @for (i of se.required_vms | keyvalue; track i) {
+
+ {{ getEnvironmentName(i.key) }}
+
+ @for (q of i.value | keyvalue; track q) {
+
+ {{ getVirtualMachineTemplateName(q.key) }}
+ @if (
+ getUneditedScheduledEventVMCount(i.key, q.key) ==
+ q.value
+ ) {
+
+ {{ q.value }}
+
+ } @else {
+
+
+ {{
+ getUneditedScheduledEventVMCount(i.key, q.key)
+ }}
+
+ {{ q.value }}
+
+ }
+
+ }
+
+
+ }
+
+
+ }
+ SharedVM Information
+
+
+
+ N
+ Environment
+ Virtual Machines
+
+
+
+
+
+ @for(sharedVm of se.shared_vms;track sharedVm;let i = $index) {
+
+ {{ sharedVm.name }}
+ {{ sharedVm.environment }}
+ {{ sharedVm.vm_template }}
+
+ }
+
+
+
+
+
+
+ } }
+
diff --git a/src/app/event/new-scheduled-event/new-scheduled-event.component.ts b/src/app/event/new-scheduled-event/new-scheduled-event.component.ts
index 28d5b849..a39a12a9 100644
--- a/src/app/event/new-scheduled-event/new-scheduled-event.component.ts
+++ b/src/app/event/new-scheduled-event/new-scheduled-event.component.ts
@@ -116,6 +116,34 @@ export class NewScheduledEventComponent
private onCloseFn: Function;
private wizardSubscription: Subscription;
+ public newSharedVM: Record>;
+
+ public sharedVmForm = new FormGroup({
+ vm_name: new FormControl('', {
+ validators: [
+ Validators.required,
+ Validators.minLength(4),
+ this.noWhitespaceValidator(),
+ this.uniqueSharedVMNameValidator(),
+ ],
+ nonNullable: true,
+ }),
+ vm_env: new FormControl('', {
+ validators: [
+ Validators.required,
+ ],
+ nonNullable: true,
+ }),
+ vm_template: new FormControl('', {
+ validators: [
+ Validators.required,
+ this.templateMatchesEnvValidator(),
+ ],
+ nonNullable: true,
+ }),
+ });
+
+
constructor(
private _fb: NonNullableFormBuilder,
@@ -159,6 +187,14 @@ export class NewScheduledEventComponent
wizardPages.first.makeCurrent();
});
});
+
+ this.sharedVmForm.controls.vm_env.valueChanges.subscribe((env) => {
+ this.sharedVmForm.controls.vm_template.setValue(this.getTemplates(env)[0] ?? "")
+ })
+
+ this.sharedVmForm.valueChanges.subscribe(() => {
+ this.sharedVmForm.controls.vm_template.updateValueAndValidity()
+ })
}
public eventDetails: FormGroup<{
@@ -309,6 +345,68 @@ export class NewScheduledEventComponent
return null;
}
+ private uniqueSharedVMNameValidator(): (
+ control: AbstractControl,
+ ) => { notUnique: boolean } | null {
+ return (control: AbstractControl) => this.uniqueSharedVMName(control);
+ }
+
+ private uniqueSharedVMName(
+ control: AbstractControl,
+ ): { notUnique: boolean } | null {
+ if (
+ !control.value ||
+ this.scheduledEvents.filter((el) =>
+ el.shared_vms.map((vm) => vm.name).includes(control.value)
+ ).length > 0
+ ) {
+ return {
+ notUnique: true,
+ };
+ }
+ return null;
+ }
+
+ private noWhitespaceValidator(): (
+ control: AbstractControl,
+ ) => { whitespace: boolean } | null {
+ return (control: AbstractControl) => this.noWhitespace(control);
+ }
+
+ private noWhitespace(
+ control: AbstractControl,
+ ): { whitespace: boolean } | null {
+ if (control.value.includes(' ')) {
+ return {
+ whitespace: true,
+ };
+ }
+ return null;
+ }
+
+ private templateMatchesEnvValidator(): (
+ control: AbstractControl,
+ ) => { matchEnv: boolean } | null {
+ return (control: AbstractControl) => this.templateMatchesEnv(control);
+ }
+
+ private templateMatchesEnv(
+ control: AbstractControl,
+ ): { matchEnv: boolean } | null {
+ if (
+ !control.value ||
+ !this.sharedVmForm.controls.vm_env ||
+ !(this.getTemplates(this.sharedVmForm.controls.vm_env.value).includes(control.value))
+ ) {
+ return {
+ matchEnv: true,
+ };
+ }
+ return null;
+ }
+
+
+
@ViewChild('wizard', { static: true }) wizard: ClrWizard;
@ViewChildren(ClrWizardPage) wizardPages: QueryList;
@ViewChild('startTimeSignpost') startTimeSignpost: ClrSignpostContent;
@@ -1155,4 +1253,29 @@ export class NewScheduledEventComponent
this.uneditedScheduledEvent.required_vms[environment]?.[vmtemplate] ?? 0
);
}
+
+ getTemplatesForEnv() {
+ const templates = this.getTemplates(this.sharedVmForm.controls.vm_env.value)
+ let availableTemplates = new Map()
+ this.virtualMachineTemplateList.forEach((k, v) => {
+ if (templates.includes(k)) availableTemplates.set(k, v)
+ })
+ return availableTemplates;
+ }
+
+ public addSharedVM() {
+ if (this.se.shared_vms == null) {
+ this.se.shared_vms = [];
+ }
+ this.se.shared_vms.push({
+ vm_id: '',
+ name: this.sharedVmForm.controls.vm_name.value,
+ environment: this.sharedVmForm.controls.vm_env.value,
+ vm_template: this.sharedVmForm.controls.vm_template.value,
+ });
+ }
+
+ deleteSharedVm(index: number) {
+ this.se.shared_vms.splice(index, 1);
+ }
}
diff --git a/src/app/scenario/md-editor/markdownActions.ts b/src/app/scenario/md-editor/markdownActions.ts
index bd301ae2..ca71ae51 100644
--- a/src/app/scenario/md-editor/markdownActions.ts
+++ b/src/app/scenario/md-editor/markdownActions.ts
@@ -105,11 +105,18 @@ export const ACTIONS: MDEditorAction[] = [
},
{
name: 'VM-Info',
- actionBefore: '${vmInfo::}',
+ actionBefore: '${vminfo::}',
actionAfter: '',
actionEmpty: '',
icon: 'host',
},
+ {
+ name: 'Shared-VM-Info',
+ actionBefore: '${shared::}',
+ actionAfter: '',
+ actionEmpty: '',
+ icon: 'rack-server',
+ },
{
name: 'Task',
actionBefore: '```verifyTask::',
diff --git a/src/app/session-statistics/session-statistics.component.ts b/src/app/session-statistics/session-statistics.component.ts
index 900d2a62..94c14f1b 100644
--- a/src/app/session-statistics/session-statistics.component.ts
+++ b/src/app/session-statistics/session-statistics.component.ts
@@ -425,7 +425,6 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
event?: ChartEvent;
active?: {}[];
}): void {
- // console.log(event, active);
}
public chartHovered({
@@ -435,7 +434,6 @@ export class SessionStatisticsComponent implements OnInit, OnChanges {
event?: ChartEvent;
active?: {}[];
}): void {
- // console.log(event, active);
}
private setupScenariosWithSessions(progressData: Progress[]) {
diff --git a/src/app/session-statistics/session-time-statistics/session-time-statistics.component.ts b/src/app/session-statistics/session-time-statistics/session-time-statistics.component.ts
index 8eb7d8a9..81dbe1b1 100644
--- a/src/app/session-statistics/session-time-statistics/session-time-statistics.component.ts
+++ b/src/app/session-statistics/session-time-statistics/session-time-statistics.component.ts
@@ -252,7 +252,6 @@ export class SessionTimeStatisticsComponent implements OnInit {
event?: ChartEvent;
active?: {}[];
}): void {
- // console.log(event, active);
}
public chartHovered({
@@ -262,7 +261,6 @@ export class SessionTimeStatisticsComponent implements OnInit {
event?: ChartEvent;
active?: {}[];
}): void {
- // console.log(event, active);
}
private prepareBarchartDatasets() {
diff --git a/src/app/step/hf-markdown.component.ts b/src/app/step/hf-markdown.component.ts
index afbad29b..946b660c 100644
--- a/src/app/step/hf-markdown.component.ts
+++ b/src/app/step/hf-markdown.component.ts
@@ -139,7 +139,7 @@ ${token}`;
private renderHighlightedCode(
code: string,
language: string,
- fileName?: string,
+ fileName?: string
) {
const fileNameTag = fileName
? `${fileName}
`
@@ -212,7 +212,7 @@ ${token}`;
}
const contentWithReplacedTokens = this.replaceSessionToken(
- this.replaceVmInfoTokens(this.content),
+ this.replaceVmInfoTokens(this.replaceSharedVmInfoTokens(this.content))
);
// the parse method internally uses the Angular Dom Sanitizer and is therefore safe to use
const parsedContent = this.markdownService.parse(contentWithReplacedTokens);
@@ -234,8 +234,22 @@ ${token}`;
/\$\{vminfo:([^:]*):([^}]*)\}/g,
(match, vmName, propName) => {
const vm = this.context.vmInfo?.[vmName.toLowerCase()];
- return String(vm?.[propName as keyof VM] ?? match);
- },
+ return String(
+ vm?.vm_type != 'SHARED' ? vm?.[propName as keyof VM] : match
+ );
+ }
+ );
+ }
+
+ private replaceSharedVmInfoTokens(content: string) {
+ return content.replace(
+ /\$\{shared:([^:]*):([^}]*)\}/g,
+ (match, vmName, propName) => {
+ const vm = this.context.vmInfo?.[vmName.toLowerCase()];
+ return String(
+ vm?.vm_type == 'SHARED' ? vm?.[propName as keyof VM] : match
+ );
+ }
);
}
diff --git a/src/app/step/step-component/step.component.html b/src/app/step/step-component/step.component.html
index 2ea61ba5..a8e01283 100644
--- a/src/app/step/step-component/step.component.html
+++ b/src/app/step/step-component/step.component.html
@@ -1,98 +1,168 @@