Skip to content

Commit

Permalink
Verification checks against vm (hobbyfarm#171)
Browse files Browse the repository at this point in the history
* add verification service and ui element

* combine ui elements to one interactive element
improve service to handle multiple shell clients for multiple vms
keep order of verification tasks provided by scenario

* combine ui elements to one interactive element
improve service to handle multiple shell clients for multiple vms
keep order of verification tasks provided by scenario

* only show task progress if scenario has tasks

* fix keeping old Task information
when switching to new scenario

* add markdown component to verify single Task

* adapt to support darkmode variables

* fix linting errors and warnings

* fix formatting issues

* add animation to Button

* adapt to new angular version

* adapt to new /verify response

* Display only relevant expected and actual Output/Returncode

* fix infinite loading spinner if not all nodes have at least 1 Task

* Run prettier & Dark mode fixes

* prettier

* Change verify endpoint to https to avoid browser stopping request

* Improve design

* Improve design for tasklist

* Display checkmark if all tasks are successful

* Green circle if all tasks complete

* Show VM Name and fix icon position on task progress modal

---------

Co-authored-by: Philip Prinz <[email protected]>
Co-authored-by: Jan-Gerrit Goebel <[email protected]>
  • Loading branch information
3 people authored May 13, 2024
1 parent c24016c commit e190a67
Show file tree
Hide file tree
Showing 20 changed files with 1,027 additions and 3 deletions.
3 changes: 2 additions & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@
}
},
"cli": {
"schematicCollections": ["@angular-eslint/schematics"]
"schematicCollections": ["@angular-eslint/schematics"],
"analytics": false
},
"schematics": {
"@angular-eslint/schematics:application": {
Expand Down
24 changes: 23 additions & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ import { GuacTerminalComponent } from './scenario/guacTerminal.component';
import { IdeWindowComponent } from './scenario/ideWindow.component';
import { ContextService } from './services/context.service';
import { TypedSettingsService } from './services/typedSettings.service';
import { VerificationService } from './services/verification.service';
import { TaskProgressComponent } from './scenario/task-progress/task-progress.component';
import { TaskModalComponent } from './scenario/task-modal/task-modal.component';
import { SingleTaskVerificationMarkdownComponent } from './hf-markdown/single-task-verification-markdown/single-task-verification-markdown.component';
import '@cds/core/icon/register.js';
import {
ClarityIcons,
Expand All @@ -69,6 +73,7 @@ import {
windowCloseIcon,
arrowIcon,
hostIcon,
syncIcon,
eyeIcon,
eyeHideIcon,
clockIcon,
Expand Down Expand Up @@ -99,6 +104,7 @@ ClarityIcons.addIcons(
windowCloseIcon,
arrowIcon,
hostIcon,
syncIcon,
eyeIcon,
eyeHideIcon,
clockIcon,
Expand All @@ -114,10 +120,21 @@ const appInitializerFn = (appConfig: AppConfigService) => {
};
};

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() {
return {
tokenGetter: tokenGetter,
allowedDomains: [environment.server.replace(/(^\w+:|^)\/\//, '')],
allowedDomains: jwtAllowedDomains,
disallowedRoutes: [
environment.server.replace(/(^\w+:|^)\/\//, '') + '/auth/authenticate',
],
Expand Down Expand Up @@ -147,6 +164,9 @@ export function jwtOptionsFactory() {
HfMarkdownComponent,
PrintableComponent,
IdeWindowComponent,
TaskProgressComponent,
TaskModalComponent,
SingleTaskVerificationMarkdownComponent,
],
imports: [
BrowserModule,
Expand All @@ -167,6 +187,7 @@ export function jwtOptionsFactory() {
},
globalParsers: [
{ component: CtrComponent },
{ component: SingleTaskVerificationMarkdownComponent },
{ component: QuizComponent },
],
}),
Expand All @@ -193,6 +214,7 @@ export function jwtOptionsFactory() {
ProgressService,
ContextService,
TypedSettingsService,
VerificationService,
{
provide: APP_INITIALIZER,
useFactory: appInitializerFn,
Expand Down
8 changes: 8 additions & 0 deletions src/app/hf-markdown/hf-markdown.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ ${token}`;
>${this.renderHighlightedCode(code, language, filename)}</ctr>`;
},

verifyTask(code: string, target: string, taskName: string) {
return `<app-single-task-verification-markdown
target="${target}"
message="${code}"
taskName="${taskName}"
></app-single-task-verification-markdown>`;
},

mermaid(code: string) {
const n = 5;
const containerId = `mermaid-${this.uniqueString(n)}`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<details
[ngClass]="task?.success ? 'greenBorder' : 'redBorder'"
(toggle)="detailsOpen = !detailsOpen"
>
<summary class="flex-container">
<div>
<cds-icon
shape="angle"
[ngClass]="detailsOpen ? 'down' : 'sideways'"
inverse
></cds-icon>
Verify: {{ taskName }} on {{ target }}
</div>
<div class="label-container">
<button
id="refreshButton"
(click)="elementClicked()"
class="label label-info"
label="Revalidate"
>
<cds-icon
[@rotatedState]="rotationState"
shape="sync"
inverse
></cds-icon>
</button>
<div *ngIf="task?.success" class="label label-status label-success">
<cds-icon shape="check" inverse></cds-icon>
</div>
<div *ngIf="!task?.success" class="label label-status label-danger">
<cds-icon shape="times" inverse></cds-icon>
</div>
</div>
</summary>

<ul class="list-group" *ngIf="task">
<li class="list-group-item">
<p>
Description: <b>{{ task.description }}</b>
</p>
<p>
Command: <code>{{ task.command }}</code>
</p>
</li>
<li class="list-group-item" *ngIf="isOfReturnType(task, ['Match_Regex'])">
<p>
Regular Expression: <code>{{ task.expected_output_value }}</code>
</p>
<p>
Actual Output: <code>{{ task.actual_output_value }}</code>
</p>
</li>
<li
class="list-group-item"
*ngIf="isOfReturnType(task, ['Return_Text', 'Return_Code_And_Text'])"
>
<p>
Expected Output: <code>{{ task.expected_output_value }}</code>
</p>
<p>
Actual Output: <code>{{ task.actual_output_value }}</code>
</p>
</li>
<li
class="list-group-item"
*ngIf="isOfReturnType(task, ['Return_Code', 'Return_Code_And_Text'])"
>
<p>
Expected Returncode: <code>{{ task.expected_return_code }}</code>
</p>
<p>
Actual Returncode: <code>{{ task.actual_return_code }}</code>
</p>
</li>
<li class="list-group-item" *ngIf="taskUnset()">
<button
id="refreshButton"
(click)="elementClicked()"
class="label label-info"
label="Revalidate"
>
<cds-icon
[@rotatedState]="rotationState"
shape="sync"
inverse
></cds-icon>
</button>
Run the validation checks once to see the result.
</li>
<li class="list-group-item" *ngIf="!taskUnset()">
<span *ngIf="task.success" class="label label-success">
<cds-icon shape="check" inverse></cds-icon>
</span>
<span *ngIf="!task.success" class="label label-danger">
<cds-icon shape="times" inverse></cds-icon>
</span>
<ng-container *ngIf="isOfReturnType(task, ['Match_Regex'])">
<span *ngIf="task.success">The Regex matches the output string.</span>
<span *ngIf="!task.success"
>The Regex does not match the output string.</span
>
</ng-container>
<ng-container *ngIf="isOfReturnType(task, ['Return_Code'])">
<span *ngIf="task.success"
>Returned Code matches the expected Code</span
>
<span *ngIf="!task.success">
Returned Code does not match the expected Code
</span>
</ng-container>
<ng-container *ngIf="isOfReturnType(task, ['Return_Text'])">
<span *ngIf="task.success"
>Returned Text matches the expected Text</span
>
<span *ngIf="!task.success">
Returned Text does not match the expected Text
</span>
</ng-container>
<ng-container *ngIf="isOfReturnType(task, ['Return_Code_And_Text'])">
<span *ngIf="task.success">
Returned Text and Code match the expected Text and Code
</span>
<span *ngIf="!task.success">
Either returned Text, Returned Code or both do not match the expected
Text and Code
</span>
</ng-container>
</li>
</ul>
</details>
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
.task-verification-box {
margin-top: 10px;
margin-bottom: 10px;
border: 0;
border-left: 3px solid;
border-radius: 5px;
}

.down {
transform: rotate(180deg);
}

.sideways {
transform: rotate(90deg);
}

.flex-container {
display: flex !important;
flex-direction: row;
justify-content: space-between;
}

.label-container {
display: flex !important;
flex-direction: row;
justify-content: space-between;
width: fit-content;
}

.label-status {
margin: auto;
margin-left: 4px;
margin-bottom: 7%;
}

.label:hover {
cursor: default;
}

#refreshButton:hover {
cursor: pointer !important;
}

.list-group {
width: 100%;
}

.greenBorder {
border-left-color: var(--clr-color-success-500, green) !important;
}

.redBorder {
border-left-color: #f0ad4e !important;
}

details {
border: 1px solid var(--clr-color-neutral-400, #cccccc);
border-left: 3px solid;
border-radius: 4px;
padding: 0.5em 0.5em 0;
&[open] {
padding: 0.5em;
summary {
margin-bottom: 0.5em;
}
}
}

summary {
font-weight: bold;
margin: -0.5em -0.5em 0;
padding: 0.5em;
display: list-item;
cursor: pointer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
animate,
state,
style,
transition,
trigger,
} from '@angular/animations';
import { Component, Input, OnInit } from '@angular/core';
import { Task } from 'src/app/scenario/taskVerification.type';
import { VerificationService } from 'src/app/services/verification.service';

@Component({
selector: 'app-single-task-verification-markdown',
templateUrl: './single-task-verification-markdown.component.html',
animations: [
trigger('rotatedState', [
state('default', style({ transform: 'rotate(0)' })),
state('rotating', style({ transform: 'rotate(360deg)' })),
transition('default => rotating', animate('1500ms')),
]),
],
styleUrls: ['./single-task-verification-markdown.component.scss'],
})
export class SingleTaskVerificationMarkdownComponent implements OnInit {
@Input() target: string;
@Input() message: string;
@Input() taskName: string;

detailsOpen = false;

rotationState = 'default';

task?: Task;

constructor(private verificationService: VerificationService) {}

ngOnInit(): void {
this.verificationService.currentVerifications.subscribe(
(verificationMap) => {
const temp = verificationMap.get(this.target);
this.task = temp?.tasks?.filter(
(task) => task.name == this.taskName,
)[0];
},
);
}

isOfReturnType(task: Task, returnTypes: string[]): boolean {
return returnTypes.includes(task.return_type);
}

elementClicked() {
this.rotationState = 'rotating';
setTimeout(() => {
this.rotationState = 'default';
}, 1500);
this.verificationService
.verifyTask(this.target, this.taskName)
?.subscribe();
}

taskUnset(): boolean {
return this.task == undefined || this.task.success == undefined;
}
}
3 changes: 3 additions & 0 deletions src/app/scenario/Scenario.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { TaskVerification } from './taskVerification.type';

export class Scenario {
id: string;
name: string;
Expand All @@ -6,4 +8,5 @@ export class Scenario {
virtualmachines: Map<string, string>[];
pauseable: boolean;
printable: boolean;
vm_tasks: TaskVerification[];
}
Loading

0 comments on commit e190a67

Please sign in to comment.