diff --git a/src/actions/InstallationHandler.ts b/src/actions/InstallationHandler.ts index 40e6a2f2..486b7e69 100644 --- a/src/actions/InstallationHandler.ts +++ b/src/actions/InstallationHandler.ts @@ -115,21 +115,21 @@ class Installation { } public async initSecurity(connectionArgs: IIpcConnectionArgs, - installationArgs: {installationDir: string}, zoweConfig: any): Promise{ + installationArgs: {installationDir: string}, zoweConfig: any): Promise{ console.log('writing current yaml to disk'); const filePath = path.join(app.getPath('temp'), 'zowe.yaml') await fs.writeFile(filePath, stringify(zoweConfig), (err: any) => { if (err) { console.warn("Can't save configuration to zowe.yaml"); - return ProgressStore.set('initSecurity.writeYaml', false); + ProgressStore.set('initSecurity.writeYaml', false); + return {status: false, details: `Can't save configuration to zowe.yaml`}; } }); ProgressStore.set('initSecurity.writeYaml', true); console.log("uploading yaml..."); const uploadYaml = await this.uploadYaml(connectionArgs, installationArgs.installationDir); if(!uploadYaml.status){ - return ProgressStore.set('initSecurity.uploadYaml', false); - + return {status: false, details: `Error uploading yaml configuration: ${uploadYaml.details}`}; } ProgressStore.set('initSecurity.uploadYaml', uploadYaml.status); const script = `cd ${installationArgs.installationDir}/runtime/bin;\n./zwe init security -c ${installationArgs.installationDir}/zowe.yaml`; diff --git a/src/renderer/components/common/EditorDialog.tsx b/src/renderer/components/common/EditorDialog.tsx index 4ab1a8d5..c0220b1a 100644 --- a/src/renderer/components/common/EditorDialog.tsx +++ b/src/renderer/components/common/EditorDialog.tsx @@ -105,7 +105,6 @@ const EditorDialog = ({contentType, isEditorVisible, toggleEditorVisibility, onC setZoweConfig(jsonData); } else if(isSchemaValid && jsonData) { setZoweConfig(jsonData); - dispatch(setNextStepEnabled(true)); setSetupYaml(jsonData); updateConfig(jsonData); } @@ -119,7 +118,9 @@ const EditorDialog = ({contentType, isEditorVisible, toggleEditorVisibility, onC properties.map(prop => { setConfiguration(prop, setup[prop]); }); - onChange(setup, true); + if (onChange) { + onChange(setup, true); + } } } @@ -174,9 +175,14 @@ const EditorDialog = ({contentType, isEditorVisible, toggleEditorVisibility, onC - - - + {contentType === 'yaml' && ( + <> + + + + + )} + {contentType === 'jcl' && } diff --git a/src/renderer/components/common/Stepper.tsx b/src/renderer/components/common/Stepper.tsx index 7c9afc71..c06b035e 100644 --- a/src/renderer/components/common/Stepper.tsx +++ b/src/renderer/components/common/Stepper.tsx @@ -20,16 +20,52 @@ import { selectConnectionStatus } from '../stages/connection/connectionSlice'; import { useAppSelector, useAppDispatch } from '../../hooks'; import { selectNextStepEnabled } from '../configuration-wizard/wizardSlice'; import { alertEmitter } from '../Header'; +import EditorDialog from "./EditorDialog"; +import { createTheme } from '@mui/material/styles'; + // TODO: define props, stages, stage interfaces // TODO: One rule in the store to enable/disable button export default function HorizontalLinearStepper(props: any) { + const theme = createTheme(); + + const TYPE_YAML = "yaml"; + const TYPE_JCL = "jcl"; + const TYPE_OUTPUT = "output"; + const {stages} = props; const [activeStep, setActiveStep] = useState(0); const [activeSubStep, setActiveSubStep] = useState(0); const [skipped, setSkipped] = useState(new Set()); + const [nextText, setNextText] = useState("Continue"); + const [contentType, setContentType] = useState('output'); + const [editorVisible, setEditorVisible] = useState(false); + const [editorContent, setEditorContent] = useState(''); + const [currStep, setCurrStep] = useState(1); + + const toggleEditorVisibility = (type?: any) => { + if (type) { + setContentType(type); + } + setEditorVisible(!editorVisible); + }; + + const getContinueText = () => { + return 'Continue to next step';//'+stages[activeStep+1].label; + }; + + const getSkipText = () => { + return 'Skip step';//+stages[activeStep+1].label; + }; + + const handleSubmit = () => { + //here: + // submit -> open editor with result -> mark skip button as continue button + stages[activeStep].successful = true; + toggleEditorVisibility(TYPE_OUTPUT); + }; const handleNext = () => { alertEmitter.emit('hideAlert'); @@ -42,12 +78,14 @@ export default function HorizontalLinearStepper(props: any) { return; } setActiveStep((prevActiveStep) => prevActiveStep + 1); + setNextText(getContinueText()); } }; const handleBack = () => { alertEmitter.emit('hideAlert'); stages[activeStep].subStages && activeSubStep > 0 ? setActiveSubStep((prevActiveSubStep) => prevActiveSubStep - 1) : setActiveStep((prevActiveStep) => prevActiveStep - 1); + setNextText(getContinueText()); }; const handleReset = () => { @@ -55,8 +93,16 @@ export default function HorizontalLinearStepper(props: any) { setActiveStep(0); }; + const handlePreview = (test_jcl: any) => { + toggleEditorVisibility(TYPE_JCL); + setEditorContent(test_jcl); + }; + + const isNextStepEnabled = useAppSelector(selectNextStepEnabled); + return ( + {stages.map((stage: any, index: number) => { const stepProps = {}; @@ -100,33 +146,51 @@ export default function HorizontalLinearStepper(props: any) {
{stages[activeStep].subStages ? stages[activeStep].subStages[activeSubStep].component : stages[activeStep].component}
- + + + {stages[activeStep].label === 'Planning' ? ( + + ) : null} + - + {stages[activeStep].isSkippable && + + } - + + + + + - + {!isFormValid &&
{formError}
} diff --git a/src/renderer/components/stages/Planning.tsx b/src/renderer/components/stages/Planning.tsx index f6466562..f71a3fbe 100644 --- a/src/renderer/components/stages/Planning.tsx +++ b/src/renderer/components/stages/Planning.tsx @@ -25,6 +25,7 @@ import { IResponse } from '../../../types/interfaces'; import Alert from "@mui/material/Alert"; import { alertEmitter } from "../Header"; import { Checkbox, FormControlLabel } from "@mui/material"; +import EditorDialog from "../common/EditorDialog"; const serverSchema = { "$schema": "https://json-schema.org/draft/2019-09/schema", @@ -140,6 +141,17 @@ const Planning = () => { const installationArgs: any = useAppSelector(selectInstallationArgs); const [requiredSpace, setRequiredSpace] = useState(1300); //in megabytes + const [contentType, setContentType] = useState('output'); + const [editorVisible, setEditorVisible] = useState(false); + const [editorContent, setEditorContent] = useState(''); + + const toggleEditorVisibility = (type?: any) => { + if (type) { + setContentType(type); + } + setEditorVisible(!editorVisible); + }; + useEffect(() => { dispatch(setNextStepEnabled(false)); // FIXME: Add a popup warning in case failed to get config files @@ -224,6 +236,8 @@ const Planning = () => { window.electron.ipcRenderer.saveJobHeader(connectionArgs.jobStatement) .then(() => getENVVars()) .then((res: IResponse) => { + setEditorContent(res.details); + setContentType('output'); if (!res.status) { // Failure case setJobStatementValidation(res.details); console.warn('Failed to verify job statement'); @@ -239,6 +253,8 @@ const Planning = () => { dispatch(setLoading(false)); }) .catch((err: Error) => { + setEditorContent(err.message); + setContentType('output'); console.warn(err); setJobStatementValidation(err.message); alertEmitter.emit('showAlert', err.message, 'error'); @@ -260,9 +276,11 @@ const Planning = () => { Promise.all([ window.electron.ipcRenderer.checkJava(connectionArgs, installationArgs.javaHome), window.electron.ipcRenderer.checkNode(connectionArgs, installationArgs.nodeHome), - window.electron.ipcRenderer.checkSpace(connectionArgs, installationArgs.installationDir) + //Do not check space because space on ZFS is dynamic. you can have more space than USS thinks. ]).then((res: Array) => { - const details = {javaVersion: '', nodeVersion: '', spaceAvailableMb: '', error: ''} + const details = {javaVersion: '', nodeVersion: '', spaceAvailableMb: '', error: ''}; + setEditorContent(res.map(item=>item.details).join('\n')); + setContentType('output'); try { details.javaVersion = res[0].details.split('\n').filter((i: string) => i.trim().startsWith('java version'))[0].trim().slice(14, -1); } catch (error) { @@ -275,17 +293,7 @@ const Planning = () => { details.error = details.error + `Can't get node version; `; console.warn(res[1].details); } - try { - const dfOut: string = res[2].details.split('\n').filter((i: string) => i.trim().startsWith(installationArgs.installationDir.slice(0, 3)))[0]; - details.spaceAvailableMb = dfOut.match(/\d+\/\d+/g)[0].split('/')[0]; - // FIXME: Space requirement is made up, Zowe 2.9.0 convenience build is 515Mb and growing per version. Make it double for extracted files. - if (parseInt(details.spaceAvailableMb, 10) < requiredSpace) { - details.error = details.error + `Not enough space, you need at least ${requiredSpace}MB; `; - } - } catch (error) { - details.error = details.error + `Can't check space available; `; - console.warn(res[2].details); - } + setValidationDetails(details); dispatch(setLoading(false)); if (!details.error) { @@ -300,6 +308,7 @@ const Planning = () => { return ( + {/* TODO: Allow to choose Zowe version here by click here, support for other instalation types? */} @@ -560,7 +569,7 @@ Please customize job statement below to match your system requirements. {step > 1 ? - {`Found Java version: ${validationDetails.javaVersion}, Node version: ${validationDetails.nodeVersion}, Space available: ${validationDetails.spaceAvailableMb}MB + {`Found Java version: ${validationDetails.javaVersion}, Node version: ${validationDetails.nodeVersion} All set, ready to proceed. diff --git a/src/renderer/components/stages/Security.tsx b/src/renderer/components/stages/Security.tsx index a9665f65..f5a3d680 100644 --- a/src/renderer/components/stages/Security.tsx +++ b/src/renderer/components/stages/Security.tsx @@ -22,15 +22,18 @@ import { selectConnectionArgs } from "./connection/connectionSlice"; import { IResponse } from "../../../types/interfaces"; import ProgressCard from "../common/ProgressCard"; import React from "react"; +import { createTheme } from '@mui/material/styles'; const Security = () => { + const theme = createTheme(); const dispatch = useAppDispatch(); const schema = useAppSelector(selectSchema); const yaml = useAppSelector(selectYaml); const setupSchema = schema ? schema.properties.zowe.properties.setup.properties.security : ""; const [setupYaml, setSetupYaml] = useState(yaml?.zowe.setup.security); - const [init, setInit] = useState(false); + const [isFormInit, setIsFormInit] = useState(false); + const [initializeForm, setInitializeForm] = useState(false); const [editorVisible, setEditorVisible] = useState(false); const [isFormValid, setIsFormValid] = useState(false); const [formError, setFormError] = useState(''); @@ -64,6 +67,15 @@ const Security = () => { validate = ajv.compile(securitySchema); } + useEffect(() => { + dispatch(setNextStepEnabled(false)); + if(Object.keys(initConfig) && Object.keys(initConfig).length != 0) { + setSetupYaml(initConfig); + } + setInitializeForm(true); + setIsFormInit(true); + }, []); + useEffect(() => { timer = setInterval(() => { window.electron.ipcRenderer.getInitSecurityProgress().then((res: any) => { @@ -74,22 +86,30 @@ const Security = () => { nextPosition.scrollIntoView({behavior: 'smooth'}); }, [showProgress]); - useEffect(() => { - dispatch(setNextStepEnabled(false)); - if(Object.keys(initConfig) && Object.keys(initConfig).length != 0) { - setSetupYaml(initConfig); - } - setInit(true); - }, []); - const toggleEditorVisibility = (type: any) => { setContentType(type); setEditorVisible(!editorVisible); }; - const handleFormChange = (data: any, isYamlUpdated?: boolean) => { - let newData = init ? (Object.keys(initConfig).length > 0 ? initConfig: data) : (data ? data : initConfig); - setInit(false); + const process = (event: any) => { + event.preventDefault(); + toggleProgress(true); + window.electron.ipcRenderer.initSecurityButtonOnClick(connectionArgs, installationArgs, getZoweConfig()).then((res: IResponse) => { + dispatch(setNextStepEnabled(res.status)); + clearInterval(timer); + }).catch(() => { + clearInterval(timer); + dispatch(setNextStepEnabled(false)); + console.warn('zwe init security failed'); + }); + } + + const handleFormChange = (data: any, isYamlUpdated?: boolean, customParam?: boolean) => { + if(!initializeForm) { + return; + } + let newData = isFormInit ? (Object.keys(initConfig).length > 0 ? initConfig: data) : (data ? data : initConfig); + setIsFormInit(false); if (newData) { newData = isYamlUpdated ? data.security : newData; @@ -99,48 +119,33 @@ const Security = () => { if(validate.errors) { const errPath = validate.errors[0].schemaPath; const errMsg = validate.errors[0].message; - setStageConfig(false, errPath+' '+errMsg, newData, false); + setStageConfig(false, errPath+' '+errMsg, newData); } else { setConfiguration(section, newData, true); - setStageConfig(true, '', newData, true); + setStageConfig(true, '', newData); } } } }; - const setStageConfig = (isValid: boolean, errorMsg: string, data: any, proceed: boolean) => { + const setStageConfig = (isValid: boolean, errorMsg: string, data: any) => { setIsFormValid(isValid); setFormError(errorMsg); setSetupYaml(data); - dispatch(setNextStepEnabled(proceed)); - } - - const process = (event: any) => { - event.preventDefault(); - toggleProgress(true); - window.electron.ipcRenderer.initSecurityButtonOnClick(connectionArgs, installationArgs, getZoweConfig()).then((res: IResponse) => { - dispatch(setNextStepEnabled(res.status)); - clearInterval(timer); - }).catch(() => { - clearInterval(timer); - console.warn('zwe init security failed'); - }); - } return (
-
- {/* */} - - - -
+ + + + + {!isFormValid &&
{formError}
} - + handleFormChange(data, isYamlUpdated, true)} formData={setupYaml}/> {!showProgress ? null : diff --git a/src/renderer/components/stages/installation/Installation.tsx b/src/renderer/components/stages/installation/Installation.tsx index a8196f04..04a7bc30 100644 --- a/src/renderer/components/stages/installation/Installation.tsx +++ b/src/renderer/components/stages/installation/Installation.tsx @@ -22,9 +22,12 @@ import JsonForm from '../../common/JsonForms'; import EditorDialog from "../../common/EditorDialog"; import Ajv from "ajv"; import { alertEmitter } from "../../Header"; +import { createTheme } from '@mui/material/styles'; const Installation = () => { + const theme = createTheme(); + // TODO: Display granular details of installation - downloading - unpacking - running zwe command const dispatch = useAppDispatch(); @@ -34,10 +37,11 @@ const Installation = () => { const setupSchema = schema ? schema.properties.zowe.properties.setup.properties.dataset : ""; const [setupYaml, setSetupYaml] = useState(yaml?.zowe.setup.dataset); const [showProgress, toggleProgress] = useState(false); - const [init, setInit] = useState(false); + const [isFormInit, setIsFormInit] = useState(false); const [editorVisible, setEditorVisible] = useState(false); const [isFormValid, setIsFormValid] = useState(false); const [formError, setFormError] = useState(''); + const [contentType, setContentType] = useState(''); const [installationProgress, setInstallationProgress] = useState({ uploadYaml: false, download: false, @@ -55,6 +59,8 @@ const Installation = () => { const initConfig = getConfiguration(section); const TYPE_YAML = "yaml"; + const TYPE_JCL = "jcl"; + const TYPE_OUTPUT = "output"; const ajv = new Ajv(); ajv.addKeyword("$anchor"); @@ -73,7 +79,7 @@ const Installation = () => { if(Object.keys(initConfig) && Object.keys(initConfig).length != 0) { setSetupYaml(initConfig); } - setInit(true); + setIsFormInit(true); }, []); useEffect(() => { @@ -86,7 +92,8 @@ const Installation = () => { nextPosition.scrollIntoView({behavior: 'smooth'}); }, [showProgress]); - const toggleEditorVisibility = () => { + const toggleEditorVisibility = (type: any) => { + setContentType(type); setEditorVisible(!editorVisible); }; @@ -117,15 +124,16 @@ const Installation = () => { clearInterval(timer); }).catch(() => { clearInterval(timer); + dispatch(setNextStepEnabled(false)); console.warn('Installation failed'); }); }) } - const editHLQ = (data: any, isYamlUpdated?: boolean) => { - let updatedData = init ? (Object.keys(initConfig).length > 0 ? initConfig: data) : (data ? data : initConfig); + const handleFormChange = (data: any, isYamlUpdated?: boolean) => { + let updatedData = isFormInit ? (Object.keys(initConfig).length > 0 ? initConfig: data) : (data ? data : initConfig); - setInit(false); + setIsFormInit(false); updatedData = isYamlUpdated ? data.dataset : updatedData; if (updatedData && setupYaml && setupYaml.prefix !== updatedData.prefix) { @@ -143,34 +151,35 @@ const Installation = () => { if(validate.errors) { const errPath = validate.errors[0].schemaPath; const errMsg = validate.errors[0].message; - setStageConfig(false, errPath+' '+errMsg, updatedData, false); + setStageConfig(false, errPath+' '+errMsg, updatedData); } else { setConfiguration(section, updatedData, true); - setStageConfig(true, '', updatedData, true); + setStageConfig(true, '', updatedData); } } } - const setStageConfig = (isValid: boolean, errorMsg: string, data: any, proceed: boolean) => { + const setStageConfig = (isValid: boolean, errorMsg: string, data: any) => { setIsFormValid(isValid); setFormError(errorMsg); setSetupYaml(data); - dispatch(setNextStepEnabled(proceed)); } return (
-
- -
+ + + + + - + {`Ready to download Zowe ${version} and deploy it to the ${installationArgs.installationDir}\nThen we will install MVS data sets, please provide HLQ below\n`} {!isFormValid &&
{formError}
} - +
{!showProgress ? diff --git a/src/renderer/preload.ts b/src/renderer/preload.ts index 66dda78e..1b5e08a2 100644 --- a/src/renderer/preload.ts +++ b/src/renderer/preload.ts @@ -72,7 +72,7 @@ contextBridge.exposeInMainWorld('electron', { }, on(channel: string, func: any) { // REVIEW: Used to have channel validation with ipcRenderer.send, do we need something similar for ipcRenderer.invoke? - const validChannels = ['install-mvs']; + const validChannels = ['install-mvs', 'init-security']; if (validChannels.includes(channel)) { ipcRenderer.on(channel, (event, ...args) => func(...args)); }