Skip to content

Commit

Permalink
Merge pull request #5 from rhpds/acasanov-stage-scripts
Browse files Browse the repository at this point in the history
feat: Execute stage scripts
  • Loading branch information
aleixhub authored Feb 21, 2024
2 parents e7e57c9 + d801b64 commit bdf4efd
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 21 deletions.
63 changes: 42 additions & 21 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import React, { useState, useRef, useEffect } from "react";
import yaml from 'js-yaml';
import fetch from 'unfetch';
import useSWR from "swr";
import { Button } from '@patternfly/react-core';
import { Alert, AlertActionCloseButton, Button } from '@patternfly/react-core';
import Split from 'react-split';
import ProgressHeader from './progress-header';

import { executeStageAndGetStatus } from "./utils";
import Loading from './loading';
import './app.css';

type TTab = {name: string, url?: string, port?: string, secondary_port?: string, path?: string, secondary_path?: string, secondary_url?: string};
Expand Down Expand Up @@ -37,10 +38,11 @@ type Session = {sessionUuid: string, catalogItemName: string, start: string, sto
export default function() {
const ref = useRef();
const instructionsPanelRef = useRef();
const [loaderStatus, setLoaderStatus] = useState<{isLoading: boolean, stage: 'setup' | 'validation' | 'solve' | null}>({ isLoading: false, stage: null });
const searchParams = new URLSearchParams(document.location.search);
const s = searchParams.get('s');
const session: Session = s ? JSON.parse(s) : null;
const {data: dataResponses, error} = useSWR(['zero-config.yaml', 'zero-config.yml', './zero-touch-config.yaml','./zero-touch-config.yml', './nookbag.yml'], (urls) => Promise.all(urls.map(url => fetch(url))).then((responses) => Promise.all(
const {data: dataResponses, error} = useSWR(['./zero-config.yaml', './zero-config.yml', './zero-touch-config.yaml','./zero-touch-config.yml', './nookbag.yml'], (urls) => Promise.all(urls.map(url => fetch(url))).then((responses) => Promise.all(
responses
.map((response) => {
if (response.status === 200) {
Expand All @@ -56,6 +58,7 @@ export default function() {
const antoraDir = config.antora.dir || 'antora';
const version = config.antora.version;
const s_name = config.antora.name;
const [validationMsg, setValidationMsg] = useState<{type: 'error'|'success', message: string}>(null);
const tabs = config.tabs.map(s => createUrlsFromVars(s));
const PROGRESS_KEY = session ? `PROGRESS-${session.sessionUuid}` : null;
const initProgressStr = PROGRESS_KEY ? window.localStorage.getItem(PROGRESS_KEY) : null;
Expand All @@ -78,33 +81,37 @@ export default function() {
const page = iframe.contentWindow.location.pathname.split('/');
let key = "";
if (page[page.length - 2] === version || !version) {
key = page[page.length - 1].split(".")[0]
key = page[page.length - 1].split(".")[0];
} else {
key = `${page[page.length - 2]}/${page[page.length - 1].split(".")[0]}`
key = `${page[page.length - 2]}/${page[page.length - 1].split(".")[0]}`;
}
const _progress = {...progress};
let pivotPassed = false;
modules.forEach(m => {
if (m.name === key) {
pivotPassed = true;
} else if (pivotPassed) {
_progress.notStarted.push(m.name)
_progress.notStarted.push(m.name);
} else {
_progress.completed.push(m.name)
_progress.completed.push(m.name);
}

})
_progress.inProgress = [key]
_progress.current = key
/*if (m.validation_script) {
// TODO: hit api to execute validation
}*/
setProgress(_progress);
_progress.inProgress = [key];
_progress.current = key;
setLoaderStatus({ isLoading: true, stage: 'setup' });
executeStageAndGetStatus(key, 'setup').then(_ => {
setProgress(_progress);
setLoaderStatus({ isLoading: false, stage: null });
}).catch(() => {
setProgress(_progress);
setLoaderStatus({ isLoading: false, stage: null });
})
}
}

function handleTabClick(tab: TTab) {
setCurrentTab(tab)
setCurrentTab(tab);
}

function goToTop() {
Expand All @@ -116,25 +123,37 @@ export default function() {

function handlePrevious() {
if (currIndex > 0) {
setValidationMsg(null);
setIframeModule(modules[currIndex-1].name);
goToTop();
}
}
function handleNext() {
if (currIndex+1 < modules.length) {
setIframeModule(modules[currIndex+1].name);
goToTop();

async function handleNext() {
setValidationMsg(null);
setLoaderStatus({ isLoading: true, stage: 'validation' });
const res = await executeStageAndGetStatus(modules[currIndex].name, 'validation');
setLoaderStatus({ isLoading: false, stage: null });
if (res.Status === 'successful') {
if (currIndex+1 < modules.length) {
setIframeModule(modules[currIndex+1].name);
goToTop();
} else {
setValidationMsg({message:'Lab completed!', type: 'success'});
window.parent.postMessage("COMPLETED", "*");
}
} else {
console.log('Lab completed!');
window.parent.postMessage("COMPLETED", "*");
setValidationMsg({message: res.Output || '', type:'error'});
}
}

if (error) {
return <div>Configuration file not defined</div>
}

return <div className="app-wrapper">
return <div>
<Loading text={loaderStatus.stage === 'setup' ? 'Environment Loading... Almost Ready!' : loaderStatus.stage === 'validation' ? 'Validating... Standby.' : loaderStatus.stage === 'solve' ? 'Solving... Standby.' : 'Loading...'} isVisible={loaderStatus.isLoading} />
<div className="app-wrapper">
<Split
sizes={tabs.length > 0 ? [25, 75] : [100]}
minSize={100}
Expand All @@ -151,6 +170,7 @@ export default function() {
{currIndex > 0 ? <Button onClick={handlePrevious}>Previous</Button> : null}
<Button style={{marginLeft: 'auto'}} onClick={handleNext}>{currIndex+1 < modules.length ? 'Next':'End'}</Button>
</div>
{validationMsg ? <Alert variant={validationMsg.type === 'error' ? 'danger':'success'} title={validationMsg.type === 'error'?'Validation Error':'Lab Completed'} actionClose={<AlertActionCloseButton onClose={() => setValidationMsg(null)} />} >{validationMsg.message}</Alert>:null}
</div>
{tabs.length > 0 ? <div className="split right">
{tabs.length > 1 ?
Expand All @@ -173,4 +193,5 @@ export default function() {
</div> : null}
</Split>
</div>
</div>
}
126 changes: 126 additions & 0 deletions src/loading.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/* Absolute Center Spinner */
.loading-wrapper {
position: fixed;
z-index: 999;
height: 2em;
width: 2em;
overflow: show;
margin: auto;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: radial-gradient(rgba(20, 20, 20,.9), rgba(0, 0, 0, .9));
background: -webkit-radial-gradient(rgba(20, 20, 20,.9), rgba(0, 0, 0,.9));
width: 100%;
height: 100%;
}

.loading-text {
margin-top: 25px;
color: #fff;
}

/* Transparent Overlay */
.loading:before {
content: '';
display: block;
}

/* :not(:required) hides these rules from IE9 and below */
.loading:not(:required) {
/* hide "loading..." text */
font: 0/0 a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}

.loading:not(:required):after {
content: '';
display: block;
font-size: 10px;
width: 1em;
height: 1em;
margin-top: -0.5em;
-webkit-animation: spinner 150ms infinite linear;
-moz-animation: spinner 150ms infinite linear;
-ms-animation: spinner 150ms infinite linear;
-o-animation: spinner 150ms infinite linear;
animation: spinner 150ms infinite linear;
border-radius: 0.5em;
-webkit-box-shadow: rgba(255,255,255, 0.75) 1.5em 0 0 0, rgba(255,255,255, 0.75) 1.1em 1.1em 0 0, rgba(255,255,255, 0.75) 0 1.5em 0 0, rgba(255,255,255, 0.75) -1.1em 1.1em 0 0, rgba(255,255,255, 0.75) -1.5em 0 0 0, rgba(255,255,255, 0.75) -1.1em -1.1em 0 0, rgba(255,255,255, 0.75) 0 -1.5em 0 0, rgba(255,255,255, 0.75) 1.1em -1.1em 0 0;
box-shadow: rgba(255,255,255, 0.75) 1.5em 0 0 0, rgba(255,255,255, 0.75) 1.1em 1.1em 0 0, rgba(255,255,255, 0.75) 0 1.5em 0 0, rgba(255,255,255, 0.75) -1.1em 1.1em 0 0, rgba(255,255,255, 0.75) -1.5em 0 0 0, rgba(255,255,255, 0.75) -1.1em -1.1em 0 0, rgba(255,255,255, 0.75) 0 -1.5em 0 0, rgba(255,255,255, 0.75) 1.1em -1.1em 0 0;
}

/* Animation */

@-webkit-keyframes spinner {
0% {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-moz-keyframes spinner {
0% {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-o-keyframes spinner {
0% {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes spinner {
0% {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
16 changes: 16 additions & 0 deletions src/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from "react";

import './loading.css';

export default function({text, isVisible}:{text: string, isVisible: boolean}) {
if (!isVisible) {
return null;
}

return <div className="loading-wrapper">
<div className="loading" />
<div className="loading-text">
{text}
</div>
</div>
}
34 changes: 34 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export async function getJobStatus(jobId: string): Promise<({'Status': 'successful' | 'error', 'Output'?: string})> {
async function jobStatusFn() {
await new Promise(resolve => setTimeout(resolve, 1000));
return getJobStatus(jobId);
}
const response = await fetch(`/runner/api/job/${jobId}`);
const data = await response.json();
const status = data['Status'];

if (status) {
if (status === 'scheduled' || status === 'running') {
return await jobStatusFn();
} else {
return data;
}
}
return {
'Status': 'error'
}
}

export async function executeStage(moduleName: string, stage: 'setup' | 'validation' | 'solve') {
const response = await fetch(`/runner/api/${moduleName}/${stage}`, {method: 'POST'});
const data = await response.json();
return data['Job_id'] ?? null;
}

export async function executeStageAndGetStatus(moduleName: string, stage: 'setup' | 'validation' | 'solve') {
const jobId = await executeStage(moduleName, stage);
if (jobId) {
return await getJobStatus(jobId);
}
return Promise.resolve({Status: 'successful', Output: 'Script not found'});
}

0 comments on commit bdf4efd

Please sign in to comment.