Skip to content

Commit

Permalink
Add zowe.network support
Browse files Browse the repository at this point in the history
Signed-off-by: Sergei Kurnevich <[email protected]>
  • Loading branch information
skurnevich committed Nov 1, 2024
1 parent ab45c53 commit e501e5f
Showing 1 changed file with 137 additions and 63 deletions.
200 changes: 137 additions & 63 deletions src/renderer/components/stages/Networking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
* Copyright Contributors to the Zowe Project.
*/

import { useState, useEffect, useRef } from "react";
import { useState, useEffect, useRef, Fragment } from "react";
import { Accordion, AccordionDetails, AccordionSummary, Box, Button, Checkbox, FormControlLabel, IconButton, SvgIcon, SvgIconProps, TextField, Typography } from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { ExpandMore, Add, DeleteForever} from '@mui/icons-material';
import { useAppSelector, useAppDispatch } from '../../hooks';
import { selectYaml, setNextStepEnabled, setYaml, selectSchema } from '../configuration-wizard/wizardSlice';
import ContainerCard from '../common/ContainerCard';
Expand All @@ -28,24 +28,68 @@ import { alertEmitter } from "../Header";
import JsonForm from '../common/JsonForms';

// TODO: Fix the schema usage, remove schema validation as useless
// Add helper text describing ports usage, and important details
// Hide components under collapsible customize panel
// Validate that ports are unique between components

function AddIcon(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z"/></svg>
</SvgIcon>
);
}

function DeleteIcon(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg>
</SvgIcon>
);
// FIXME: Hardcoded altered network schema as JsonForms can't work with complete schema syntax ("attls": {"const": true, ...})
const networkSchema = {
"type": "object",
"additionalProperties": false,
"description": "Optional, advanced network configuration parameters",
"properties": {
"server": {
"type": "object",
"description": "Optional, advanced network configuration parameters for Zowe servers",
"properties": {
"tls": {
"type": "object",
"additionalProperties": false,
"required": ["maxTls", "minTls"],
"properties": {
"attls": {
"description": "Enables AT-TLS for server operations. AT-TLS should only be enabled in a z/OS host environment. Servers will be switched into HTTP mode to accomodate z/OS the specific AT-TLS feature which wraps network calls in TLS.",
"type": "boolean",
"default": false
},
"maxTls": {
"type": "string",
"enum": [
"TLSv1.2",
"TLSv1.3"
],
"default": "TLSv1.3",
"description": "Maximum TLS version allowed for network connections if AT-TLS is not used"
},
"minTls": {
"type": "string",
"enum": [
"TLSv1.2",
"TLSv1.3"
],
"default": "TLSv1.2",
"description": "Minimum TLS version allowed for network connections if AT-TLS is not used, and less than or equal to network.maxTls."
}
}
}
}
},
"client": {
"type": "object",
"additionalProperties": false,
"description": "Optional, advanced network configuration parameters for Zowe servers when sending requests as clients.",
"properties": {
"tls": {
"type": "object",
"additionalProperties": false,
"properties": {
"attls": {
"description": "Enables AT-TLS for client operations. AT-TLS should only be enabled in a z/OS host environment. Servers will be switched into HTTP mode to accomodate z/OS the specific AT-TLS feature which wraps network calls in TLS.",
"type": "boolean",
"default": false
}
}
}
}
}
}
}

const Networking = () => {
Expand All @@ -72,20 +116,19 @@ const Networking = () => {
// const validate = ajv.getSchema("https://zowe.org/schemas/v2/server-base") || ajv.compile(customSchema); // REVIEW: What is returned by getSchema, do we need ajv? maybe only once on customSchema retrieving.
const LOOP_LIMIT = 1024;

// const [stateUpdated, setStateUpdated] = useState(false);
const [stageStatus, setStageStatus] = useState(stages[STAGE_ID].subStages[SUB_STAGE_ID].isSkipped);
const stageStatusRef = useRef(stageStatus);

// const isInitializationSkipped = !useAppSelector(selectInitializationStatus);

useEffect(() => {
stageStatusRef.current = stageStatus; // REVIEW: What it does?
stageStatusRef.current = stageStatus;
}, [stageStatus]);

useEffect(() => {
// const nextPosition = document.getElementById('container-box-id');
// if(nextPosition) nextPosition.scrollIntoView({behavior: 'smooth'});
if (yaml.zowe.externalDomains?.length === 1 && yaml.zowe.externalDomains[0] === 'sample-domain.com') {
if (yaml.zowe?.externalDomains?.length === 1 && yaml.zowe.externalDomains[0] === 'sample-domain.com') {
dispatch(setYaml({...yaml, zowe: {...yaml.zowe, externalDomains: [connectionArgs.host]}}));
}
dispatch(setNextStepEnabled(getProgress('networkingStatus')));
Expand Down Expand Up @@ -142,9 +185,27 @@ const Networking = () => {
// setStageConfig(true, '', newYaml);
// };

const handleNetworkTLSChange = async (data: any, section: string) => {
const newYaml = {...yaml, zowe: {...yaml.zowe, network: {...yaml.zowe.network, [section]: {...yaml.zowe.network[section], tls: data}}}};
await window.electron.ipcRenderer.setConfigByKeyAndValidate(`zowe.network.${section}.tls`, data);
dispatch(setYaml(newYaml));
dispatch(setNetworkingStatus(false));
}

const onSaveYaml = (e: any) => {
e.preventDefault();
updateProgress(false);

// Check for duplicated ports in components
const ports = Object.keys(yaml.components)
.filter((c: any) => yaml.components[c].enabled && yaml.components[c].port)
.map(c => Number(yaml.components[c].port));
const duplicatedPorts = ports.filter((p, ind) => ports.indexOf(p) !== ind);
if (duplicatedPorts.length) {
alertEmitter.emit('showAlert', `Port ${duplicatedPorts[0]} is assigned to a multiple enabled components`, 'error');
return;
}

alertEmitter.emit('showAlert', 'Uploading yaml...', 'info');
if(!installationArgs.dryRunMode){
window.electron.ipcRenderer.uploadLatestYaml(connectionArgs, installationArgs).then((res: IResponse) => {
Expand All @@ -164,7 +225,7 @@ const Networking = () => {
dispatch(setInitializationStatus(isInitializationStageComplete()));
}
}
// console.log(JSON.stringify(schema));

return (
yaml && schema && <div id="container-box-id">
<Box sx={{ position:'absolute', bottom: '1px', display: 'flex', flexDirection: 'row', p: 1, justifyContent: 'flex-start', [theme.breakpoints.down('lg')]: {flexDirection: 'column',alignItems: 'flex-start'}}}>
Expand All @@ -181,15 +242,31 @@ const Networking = () => {
if(data.components){
newYaml = {...newYaml, components: data.components};
}
if(data.zowe && data.zowe.network){
newYaml = {...yaml, zowe: {...yaml.zowe, network: data.zowe.network}};
}
setStageConfig(true, '', newYaml);
}}/>}
<Box sx={{ width: '60vw' }}
// onBlur={async () => dispatch(setYaml((await window.electron.ipcRenderer.getConfig()).details ?? yaml))} // REVIEW: Why?
>
{!isFormValid && <div style={{color: 'red', fontSize: 'small', marginBottom: '20px'}}>{formError}</div>}
{/* REVIEW: button as child of <p> */}
<p key="external-domains" style={{fontSize: "20px"}}>
External Domains
<Typography variant="h6">External Domains</Typography>
<Typography variant="caption" sx={{opacity: '0.8'}}>You can list your external domains on how you want to access Zowe.<br></br>
This should be the domain list you would like to put into your web browser's address bar. </Typography>
{yaml.zowe?.externalDomains != undefined && yaml.zowe.externalDomains.map((domain: string, index: number) => <Box key={`box-${index}`} sx={{display: "flex", flexDirection: "row", mt: '6px'}}>
<TextField
variant="standard"
value={domain}
onChange={async (e) => {
let domains = [...yaml.zowe?.externalDomains];
domains[index] = e.target.value;
const newYaml = {...yaml, zowe: {...yaml.zowe, externalDomains: domains}};
window.electron.ipcRenderer.setConfig(newYaml)
dispatch(setYaml(newYaml));
dispatch(setNetworkingStatus(false));
}}
/>
<IconButton
sx={{ml: '16px'}}
size='small'
Expand All @@ -200,41 +277,28 @@ const Networking = () => {
dispatch(setYaml(newYaml));
dispatch(setNetworkingStatus(false));
}}>
<AddIcon />
<Add/>
</IconButton>
</p>
{yaml.zowe.externalDomains != undefined && yaml.zowe.externalDomains.map((domain: string, index: number) => <Box key={`box-${index}`} sx={{display: "flex", flexDirection: "row"}}><TextField
variant="standard"
value={domain}
onChange={async (e) => {
let domains = [...yaml.zowe?.externalDomains];
domains[index] = e.target.value;
const newYaml = {...yaml, zowe: {...yaml.zowe, externalDomains: domains}};
// console.log(domains);
window.electron.ipcRenderer.setConfig(newYaml )
dispatch(setYaml(newYaml));
dispatch(setNetworkingStatus(false));
}}
/>
{yaml.zowe?.externalDomains.length > 1 && <IconButton
sx={{ml: '16px'}}
size='small'
onClick={(e) => {
let domains = [...yaml.zowe?.externalDomains];
domains.splice(index, 1);
const newYaml = {...yaml, zowe: {...yaml.zowe, externalDomains: domains}};
window.electron.ipcRenderer.setConfig(newYaml )
dispatch(setYaml(newYaml))
dispatch(setNetworkingStatus(false));
}}>
<DeleteIcon/>
</IconButton>}
{yaml.zowe?.externalDomains.length > 1 && <IconButton
sx={{ml: '8px'}}
size='small'
onClick={(e) => {
let domains = [...yaml.zowe?.externalDomains];
domains.splice(index, 1);
const newYaml = {...yaml, zowe: {...yaml.zowe, externalDomains: domains}};
window.electron.ipcRenderer.setConfig(newYaml )
dispatch(setYaml(newYaml))
dispatch(setNetworkingStatus(false));
}}>
<DeleteForever/>
</IconButton>}
</Box>)}
<br />
<TextField
label={"External Port"}
variant="standard"
type="number"
sx={{mb: '24px'}}
helperText={schema.properties.zowe.properties.externalPort.description}
value={yaml.zowe.externalPort}
onChange={async (e) => {
Expand All @@ -246,43 +310,46 @@ const Networking = () => {
// // dispatch(setYaml(newYaml));
}}
/>
<Accordion sx={{mt: '24px'}}>
<Typography variant="caption" sx={{opacity: '0.8'}}>You can configure which port will be used by every component.<br></br>
Note that the gateway port should match the External Port in most cases.
In some use cases, like containerization, this port could be different.</Typography>
<br></br>
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
sx={{mt: '12px'}}
expandIcon={<ExpandMore/>}
aria-controls="components-content"
id="components-header"
>
<Typography>Components</Typography>
<Typography>Component ports configuration</Typography>
</AccordionSummary>
<AccordionDetails>
{Object.keys(yaml.components).filter(component => yaml.components[component].hasOwnProperty('port')).map(component => { // yaml.components[component].hasOwnProperty('enabled') &&
{Object.keys(yaml.components).filter(component => yaml.components[component].hasOwnProperty('port')).map(component => {
return <div key={`div-${component}`} style={{display: 'flex', alignItems: 'center'}}>
<FormControlLabel
sx={{width: '200px'}}
label={component}
key={`toggle-${component}`}
control={<Checkbox checked={yaml.components[component].enabled} onChange={async (e) => {
const newYaml = {...yaml, components: {...yaml.components, [component]: {...yaml.components[component], enabled: !yaml.components[component].enabled}}};
console.log(JSON.stringify(newYaml))
await window.electron.ipcRenderer.setConfigByKeyAndValidate(`components.${component}.enabled`, !yaml.components[component].enabled);
dispatch(setYaml(newYaml));
dispatch(setNetworkingStatus(false));
}}/>}
/>
<TextField
// label="Port"
// error={!yaml.components[component].port || yaml.components[component].port > 65535}
error={!yaml.components[component].port || yaml.components[component].port > 65535}
variant="standard"
size="small"
required={yaml.components[component].enabled}
type="number"
key={`port-input-${component}`}
value={yaml.components[component].port}
InputProps={{
style: {minWidth: '100px', width: '100px'}
}}
onChange={async (e) => {
if (!Number.isNaN(Number(e.target.value))) { // REVIEW: Use schema for validation
if (!Number.isNaN(Number(e.target.value))) {
const newYaml = {...yaml, components: {...yaml.components, [component]: {...yaml.components[component], port: Number(e.target.value)}}};
await window.electron.ipcRenderer.setConfigByKeyAndValidate(`components.${component}.port`, Number(e.target.value))
dispatch(setYaml(newYaml));
Expand All @@ -294,7 +361,14 @@ const Networking = () => {
})}
</AccordionDetails>
</Accordion>
<JsonForm schema={schema.properties.zowe.properties.network} onChange={(wut: any) => {console.log(wut)}} formData={yaml.zowe.network}/>
{(networkSchema && yaml.zowe?.network) ?
<Fragment>
<Typography variant="h6" sx={{mt: '36px'}}>Server TLS configuration</Typography>
<JsonForm schema={networkSchema.properties.server.properties.tls} onChange={(data: any) => handleNetworkTLSChange(data, 'server')} formData={yaml.zowe?.network.server.tls}/>
<Typography variant="h6" sx={{mt: '36px'}}>Client TLS configuration</Typography>
<JsonForm schema={networkSchema.properties.client.properties.tls} onChange={(data: any) => handleNetworkTLSChange(data, 'client')} formData={yaml.zowe?.network.client.tls}/>
</Fragment> : null
}
<Button id="reinstall-button" sx={{boxShadow: 'none', mr: '12px', mt: '24px'}} type="submit" variant="text" onClick={e => onSaveYaml(e)}>{'Save YAML to z/OS'}</Button>
</Box>
</ContainerCard>
Expand Down

0 comments on commit e501e5f

Please sign in to comment.