summaryrefslogtreecommitdiffstats
path: root/src/tools/emcoui/src
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/emcoui/src')
-rw-r--r--src/tools/emcoui/src/App.js4
-rw-r--r--src/tools/emcoui/src/admin/AdminNavigator.js12
-rw-r--r--src/tools/emcoui/src/admin/clusterProvider/ClusterProviderForm.jsx250
-rw-r--r--src/tools/emcoui/src/admin/clusterProvider/ClusterProvidersAccordian.jsx97
-rw-r--r--src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterForm.jsx6
-rw-r--r--src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterTable.jsx785
-rw-r--r--src/tools/emcoui/src/admin/controllers/Controllers.jsx7
-rw-r--r--src/tools/emcoui/src/admin/projects/ProjectForm.jsx249
-rw-r--r--src/tools/emcoui/src/admin/projects/ProjectsTable.jsx218
-rw-r--r--src/tools/emcoui/src/appbase/AppBase.js27
-rw-r--r--src/tools/emcoui/src/appbase/Content.js2
-rw-r--r--src/tools/emcoui/src/appbase/Header.js16
-rw-r--r--src/tools/emcoui/src/appbase/Navigator.js64
-rw-r--r--src/tools/emcoui/src/assets/icons/empty.svg1
-rw-r--r--src/tools/emcoui/src/common/ExpandableCard.jsx94
-rw-r--r--src/tools/emcoui/src/common/FileUpload.jsx94
-rw-r--r--src/tools/emcoui/src/common/Form.jsx245
-rw-r--r--src/tools/emcoui/src/compositeApps/CompositeApp.jsx30
-rw-r--r--src/tools/emcoui/src/compositeApps/CompositeAppTable.jsx143
-rw-r--r--src/tools/emcoui/src/compositeApps/CompositeApps.jsx88
-rw-r--r--src/tools/emcoui/src/compositeApps/apps/Apps.jsx11
-rw-r--r--src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfileCard.jsx23
-rw-r--r--src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfiles.jsx7
-rw-r--r--src/tools/emcoui/src/compositeApps/dialogs/AppForm.jsx149
-rw-r--r--src/tools/emcoui/src/compositeApps/dialogs/AppFormGeneral.jsx129
-rw-r--r--src/tools/emcoui/src/compositeApps/dialogs/AppFormPlacement.jsx83
-rw-r--r--src/tools/emcoui/src/compositeApps/dialogs/AppNetworkForm.jsx524
-rw-r--r--src/tools/emcoui/src/compositeApps/dialogs/CompositeAppForm.jsx441
-rw-r--r--src/tools/emcoui/src/compositeApps/dialogs/SortableTable.jsx410
-rw-r--r--src/tools/emcoui/src/compositeApps/intents/AppPlacementIntentTable.jsx55
-rw-r--r--src/tools/emcoui/src/compositeApps/intents/GenericPlacementIntentCard.jsx9
-rw-r--r--src/tools/emcoui/src/deploymentIntentGroups/DIGform.jsx307
-rw-r--r--src/tools/emcoui/src/deploymentIntentGroups/DIGtable.jsx167
-rw-r--r--src/tools/emcoui/src/deploymentIntentGroups/DeploymentIntentGroups.jsx174
-rw-r--r--src/tools/emcoui/src/deploymentIntentGroups/DigFormApp.jsx201
-rw-r--r--src/tools/emcoui/src/deploymentIntentGroups/DigFormGeneral.jsx265
-rw-r--r--src/tools/emcoui/src/deploymentIntentGroups/DigFormIntents.jsx160
-rw-r--r--src/tools/emcoui/src/deploymentIntentGroups/Stepper.jsx118
-rw-r--r--src/tools/emcoui/src/networkIntents/NetworkIntentCard.jsx8
-rw-r--r--src/tools/emcoui/src/networkIntents/WorkloadIntentTable.jsx419
-rw-r--r--src/tools/emcoui/src/services/apiService.js31
41 files changed, 4341 insertions, 1782 deletions
diff --git a/src/tools/emcoui/src/App.js b/src/tools/emcoui/src/App.js
index 2613ecfd..3a2c5ffc 100644
--- a/src/tools/emcoui/src/App.js
+++ b/src/tools/emcoui/src/App.js
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React from "react";
import {
BrowserRouter as Router,
@@ -53,7 +53,7 @@ function App() {
<Redirect
exact
from={`${match.path}`}
- to={`${match.path}/composite-apps`}
+ to={`${match.path}/services`}
/>
<Route
path={`${match.path}`}
diff --git a/src/tools/emcoui/src/admin/AdminNavigator.js b/src/tools/emcoui/src/admin/AdminNavigator.js
index be07cba0..9cee73b1 100644
--- a/src/tools/emcoui/src/admin/AdminNavigator.js
+++ b/src/tools/emcoui/src/admin/AdminNavigator.js
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React, { useState } from "react";
import PropTypes from "prop-types";
import clsx from "clsx";
@@ -37,15 +37,15 @@ const categories = [
url: "/projects",
},
{
- id: "Clusters",
- icon: <DnsRoundedIcon />,
- url: "/clusters",
- },
- {
id: "Controllers",
icon: <SettingsIcon />,
url: "/controllers",
},
+ {
+ id: "Clusters",
+ icon: <DnsRoundedIcon />,
+ url: "/clusters",
+ },
],
},
];
diff --git a/src/tools/emcoui/src/admin/clusterProvider/ClusterProviderForm.jsx b/src/tools/emcoui/src/admin/clusterProvider/ClusterProviderForm.jsx
index 150a1912..57ee7557 100644
--- a/src/tools/emcoui/src/admin/clusterProvider/ClusterProviderForm.jsx
+++ b/src/tools/emcoui/src/admin/clusterProvider/ClusterProviderForm.jsx
@@ -11,147 +11,157 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
-import React from 'react';
-import PropTypes from 'prop-types';
-import { withStyles } from '@material-ui/core/styles';
-import Button from '@material-ui/core/Button';
+// ========================================================================
+import React from "react";
+import PropTypes from "prop-types";
+import { withStyles } from "@material-ui/core/styles";
+import Button from "@material-ui/core/Button";
-import Dialog from '@material-ui/core/Dialog';
-import MuiDialogTitle from '@material-ui/core/DialogTitle';
-import MuiDialogContent from '@material-ui/core/DialogContent';
-import MuiDialogActions from '@material-ui/core/DialogActions';
-import IconButton from '@material-ui/core/IconButton';
-import CloseIcon from '@material-ui/icons/Close';
-import Typography from '@material-ui/core/Typography';
-import { TextField } from '@material-ui/core';
+import Dialog from "@material-ui/core/Dialog";
+import MuiDialogTitle from "@material-ui/core/DialogTitle";
+import MuiDialogContent from "@material-ui/core/DialogContent";
+import MuiDialogActions from "@material-ui/core/DialogActions";
+import IconButton from "@material-ui/core/IconButton";
+import CloseIcon from "@material-ui/icons/Close";
+import Typography from "@material-ui/core/Typography";
+import { TextField } from "@material-ui/core";
import * as Yup from "yup";
-import { Formik } from 'formik';
+import { Formik } from "formik";
const styles = (theme) => ({
- root: {
- margin: 0,
- padding: theme.spacing(2),
- },
- closeButton: {
- position: 'absolute',
- right: theme.spacing(1),
- top: theme.spacing(1),
- color: theme.palette.grey[500],
- },
+ root: {
+ margin: 0,
+ padding: theme.spacing(2),
+ },
+ closeButton: {
+ position: "absolute",
+ right: theme.spacing(1),
+ top: theme.spacing(1),
+ color: theme.palette.grey[500],
+ },
});
const DialogTitle = withStyles(styles)((props) => {
- const { children, classes, onClose, ...other } = props;
- return (
- <MuiDialogTitle disableTypography className={classes.root} {...other}>
- <Typography variant="h6">{children}</Typography>
- {onClose ? (
- <IconButton className={classes.closeButton} onClick={onClose}>
- <CloseIcon />
- </IconButton>
- ) : null}
- </MuiDialogTitle>
- );
+ const { children, classes, onClose, ...other } = props;
+ return (
+ <MuiDialogTitle disableTypography className={classes.root} {...other}>
+ <Typography variant="h6">{children}</Typography>
+ {onClose ? (
+ <IconButton className={classes.closeButton} onClick={onClose}>
+ <CloseIcon />
+ </IconButton>
+ ) : null}
+ </MuiDialogTitle>
+ );
});
const DialogActions = withStyles((theme) => ({
- root: {
- margin: 0,
- padding: theme.spacing(1),
- },
+ root: {
+ margin: 0,
+ padding: theme.spacing(1),
+ },
}))(MuiDialogActions);
const DialogContent = withStyles((theme) => ({
- root: {
- padding: theme.spacing(2),
- }
+ root: {
+ padding: theme.spacing(2),
+ },
}))(MuiDialogContent);
-const schema = Yup.object(
- {
- name: Yup.string().required(),
- description: Yup.string(),
- })
+const schema = Yup.object({
+ name: Yup.string().required(),
+ description: Yup.string(),
+});
const ClusterProviderForm = (props) => {
- const { onClose, item, open, onSubmit } = props;
- const buttonLabel = item ? "OK" : "Create"
- const title = item ? "Edit Cluster Provider" : "Register Cluster Provider"
- const handleClose = () => {
- onClose();
- };
- let initialValues = item ? { name: item.metadata.name, description: item.metadata.description } : { name: "", description: "" }
+ const { onClose, item, open, onSubmit } = props;
+ const buttonLabel = item ? "OK" : "Create";
+ const title = item ? "Edit Cluster Provider" : "Register Cluster Provider";
+ const handleClose = () => {
+ onClose();
+ };
+ let initialValues = item
+ ? { name: item.metadata.name, description: item.metadata.description }
+ : { name: "", description: "" };
- return (
- <Dialog maxWidth={"xs"} onClose={handleClose} aria-labelledby="customized-dialog-title" open={open} disableBackdropClick>
- <DialogTitle id="simple-dialog-title">{title}</DialogTitle>
- <Formik
- initialValues={initialValues}
- onSubmit={async values => {
- onSubmit(values);
- }}
- validationSchema={schema}
- >
- {props => {
- const {
- values,
- touched,
- errors,
- isSubmitting,
- handleChange,
- handleBlur,
- handleSubmit
- } = props;
- return (
- <form noValidate onSubmit={handleSubmit}>
- <DialogContent dividers>
- <TextField
- style={{ width: "100%", marginBottom: "10px" }}
- id="name"
- label="Provider name"
- type="text"
- value={values.name}
- onChange={handleChange}
- onBlur={handleBlur}
- helperText={(errors.name && touched.name && (
- "Name is required"
- ))}
- required
- error={errors.name && touched.name}
- />
- <TextField
- style={{ width: "100%", marginBottom: "25px" }}
- name="description"
- value={values.description}
- onChange={handleChange}
- onBlur={handleBlur}
- id="description"
- label="Description"
- multiline
- rowsMax={4}
- />
- </DialogContent>
- <DialogActions>
- <Button autoFocus onClick={handleClose} color="secondary">
- Cancel
- </Button>
- <Button autoFocus type="submit" color="primary" disabled={isSubmitting}>
- {buttonLabel}
- </Button>
- </DialogActions>
- </form>
- );
- }}
- </Formik>
- </Dialog>
- );
+ return (
+ <Dialog
+ maxWidth={"xs"}
+ onClose={handleClose}
+ aria-labelledby="customized-dialog-title"
+ open={open}
+ disableBackdropClick
+ >
+ <DialogTitle id="simple-dialog-title">{title}</DialogTitle>
+ <Formik
+ initialValues={initialValues}
+ onSubmit={async (values) => {
+ onSubmit(values);
+ }}
+ validationSchema={schema}
+ >
+ {(props) => {
+ const {
+ values,
+ touched,
+ errors,
+ isSubmitting,
+ handleChange,
+ handleBlur,
+ handleSubmit,
+ } = props;
+ return (
+ <form noValidate onSubmit={handleSubmit}>
+ <DialogContent dividers>
+ <TextField
+ style={{ width: "100%", marginBottom: "10px" }}
+ id="name"
+ label="Provider name"
+ type="text"
+ value={values.name}
+ onChange={handleChange}
+ onBlur={handleBlur}
+ helperText={errors.name && touched.name && "Name is required"}
+ required
+ error={errors.name && touched.name}
+ />
+ <TextField
+ style={{ width: "100%", marginBottom: "25px" }}
+ name="description"
+ value={values.description}
+ onChange={handleChange}
+ onBlur={handleBlur}
+ id="description"
+ label="Description"
+ multiline
+ rowsMax={4}
+ />
+ </DialogContent>
+ <DialogActions>
+ <Button autoFocus onClick={handleClose} color="secondary">
+ Cancel
+ </Button>
+ <Button
+ autoFocus
+ type="submit"
+ color="primary"
+ disabled={isSubmitting}
+ >
+ {buttonLabel}
+ </Button>
+ </DialogActions>
+ </form>
+ );
+ }}
+ </Formik>
+ </Dialog>
+ );
};
ClusterProviderForm.propTypes = {
- onClose: PropTypes.func.isRequired,
- open: PropTypes.bool.isRequired,
- item: PropTypes.object
+ onClose: PropTypes.func.isRequired,
+ open: PropTypes.bool.isRequired,
+ item: PropTypes.object,
};
export default ClusterProviderForm;
diff --git a/src/tools/emcoui/src/admin/clusterProvider/ClusterProvidersAccordian.jsx b/src/tools/emcoui/src/admin/clusterProvider/ClusterProvidersAccordian.jsx
index 20317695..192992bc 100644
--- a/src/tools/emcoui/src/admin/clusterProvider/ClusterProvidersAccordian.jsx
+++ b/src/tools/emcoui/src/admin/clusterProvider/ClusterProvidersAccordian.jsx
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React, { useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Accordion from "@material-ui/core/Accordion";
@@ -22,11 +22,13 @@ import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import apiService from "../../services/apiService";
import { Button } from "@material-ui/core";
import DeleteIcon from "@material-ui/icons/Delete";
-import EditIcon from "@material-ui/icons/Edit";
+// import EditIcon from "@material-ui/icons/Edit";
import ClusterForm from "./clusters/ClusterForm";
import ClustersTable from "./clusters/ClusterTable";
import DeleteDialog from "../../common/Dialogue";
-import ClusterProviderForm from "../clusterProvider/ClusterProviderForm";
+import Notification from "../../common/Notification";
+
+//import ClusterProviderForm from "../clusterProvider/ClusterProviderForm";
const useStyles = makeStyles((theme) => ({
root: {
@@ -47,8 +49,9 @@ export default function ControlledAccordions({ data, setData, ...props }) {
const [expanded, setExpanded] = useState(false);
const [open, setOpen] = React.useState(false);
const [formOpen, setFormOpen] = useState(false);
- const [openProviderForm, setOpenProviderForm] = useState(false);
+ // const [openProviderForm, setOpenProviderForm] = useState(false);
const [selectedRowIndex, setSelectedRowIndex] = useState(0);
+ const [notificationDetails, setNotificationDetails] = useState({});
const handleAccordianOpen = (providerRow) => (event, isExpanded) => {
if (!isExpanded) {
setExpanded(isExpanded ? providerRow : false);
@@ -141,7 +144,7 @@ export default function ControlledAccordions({ data, setData, ...props }) {
setSelectedRowIndex(index);
setOpen(true);
};
- const handleSubmit = (values) => {
+ const handleSubmit = (values, setSubmitting) => {
let metadata = {};
if (values.userData) {
metadata = JSON.parse(values.userData);
@@ -150,7 +153,6 @@ export default function ControlledAccordions({ data, setData, ...props }) {
metadata.description = values.description;
const formData = new FormData();
formData.append("file", values.file);
- // `{"metadata":{ "name": "${values.name}", "description": "${values.description}" }}`
formData.append("metadata", `{"metadata":${JSON.stringify(metadata)}}`);
formData.append("providerName", data[selectedRowIndex].metadata.name);
apiService
@@ -161,12 +163,24 @@ export default function ControlledAccordions({ data, setData, ...props }) {
? (data[selectedRowIndex].clusters = [res])
: data[selectedRowIndex].clusters.push(res);
setData([...data]);
+ setFormOpen(false);
+ setNotificationDetails({
+ show: true,
+ message: `${values.name} cluster added`,
+ severity: "success",
+ });
})
.catch((err) => {
- console.log("error adding cluster : ", err);
- })
- .finally(() => {
- setFormOpen(false);
+ debugger;
+ if (err.response.status === 403) {
+ setNotificationDetails({
+ show: true,
+ message: `${err.response.data}`,
+ severity: "error",
+ });
+ setSubmitting(false);
+ }
+ console.log("error adding cluster : " + err);
});
};
const handleFormClose = () => {
@@ -198,35 +212,36 @@ export default function ControlledAccordions({ data, setData, ...props }) {
setOpen(false);
setSelectedRowIndex(0);
};
- const handleEdit = (index) => {
- setSelectedRowIndex(index);
- setOpenProviderForm(true);
- };
- const handleCloseProviderForm = () => {
- setOpenProviderForm(false);
- };
- const handleSubmitProviderForm = (values) => {
- let request = {
- payload: { metatada: values },
- providerName: data[selectedRowIndex].metadata.name,
- };
- apiService
- .updateClusterProvider(request)
- .then((res) => {
- setData((data) => {
- data[selectedRowIndex].metadata = res.metadata;
- return data;
- });
- })
- .catch((err) => {
- console.log("error updating cluster provider. " + err);
- })
- .finally(() => {
- setOpenProviderForm(false);
- });
- };
+ // const handleEdit = (index) => {
+ // setSelectedRowIndex(index);
+ // setOpenProviderForm(true);
+ // };
+ // const handleCloseProviderForm = () => {
+ // setOpenProviderForm(false);
+ // };
+ // const handleSubmitProviderForm = (values) => {
+ // let request = {
+ // payload: { metatada: values },
+ // providerName: data[selectedRowIndex].metadata.name,
+ // };
+ // apiService
+ // .updateClusterProvider(request)
+ // .then((res) => {
+ // setData((data) => {
+ // data[selectedRowIndex].metadata = res.metadata;
+ // return data;
+ // });
+ // })
+ // .catch((err) => {
+ // console.log("error updating cluster provider. " + err);
+ // })
+ // .finally(() => {
+ // setOpenProviderForm(false);
+ // });
+ // };
return (
<>
+ <Notification notificationDetails={notificationDetails} />
{data && data.length > 0 && (
<div className={classes.root}>
<ClusterForm
@@ -234,12 +249,12 @@ export default function ControlledAccordions({ data, setData, ...props }) {
onClose={handleFormClose}
onSubmit={handleSubmit}
/>
- <ClusterProviderForm
+ {/* <ClusterProviderForm
open={openProviderForm}
onClose={handleCloseProviderForm}
onSubmit={handleSubmitProviderForm}
item={data[selectedRowIndex]}
- />
+ /> */}
<DeleteDialog
open={open}
onClose={handleClose}
@@ -288,6 +303,8 @@ export default function ControlledAccordions({ data, setData, ...props }) {
>
Delete Provider
</Button>
+ {/*
+ //edit cluster provider is not supported by the api yet
<Button
variant="outlined"
size="small"
@@ -299,7 +316,7 @@ export default function ControlledAccordions({ data, setData, ...props }) {
}}
>
Edit Provider
- </Button>
+ </Button> */}
</div>
<AccordionDetails>
{item.clusters && (
diff --git a/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterForm.jsx b/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterForm.jsx
index 6d9fc83b..6c49cb85 100644
--- a/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterForm.jsx
+++ b/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterForm.jsx
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React from "react";
import PropTypes from "prop-types";
import { withStyles } from "@material-ui/core/styles";
@@ -113,8 +113,8 @@ const ClusterForm = (props) => {
<DialogTitle id="simple-dialog-title">{title}</DialogTitle>
<Formik
initialValues={initialValues}
- onSubmit={async (values) => {
- onSubmit(values);
+ onSubmit={(values, actions) => {
+ onSubmit(values, actions.setSubmitting);
}}
validationSchema={schema}
>
diff --git a/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterTable.jsx b/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterTable.jsx
index 1066d472..26bc1ca9 100644
--- a/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterTable.jsx
+++ b/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterTable.jsx
@@ -11,309 +11,528 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import AddIconOutline from '@material-ui/icons/AddCircleOutline';
-import AddIcon from '@material-ui/icons/Add';
-import Table from '@material-ui/core/Table';
-import TableBody from '@material-ui/core/TableBody';
-import TableCell from '@material-ui/core/TableCell';
-import TableContainer from '@material-ui/core/TableContainer';
-import TableHead from '@material-ui/core/TableHead';
-import TableRow from '@material-ui/core/TableRow';
-import IconButton from '@material-ui/core/IconButton';
-import EditIcon from '@material-ui/icons/Edit';
-import Chip from '@material-ui/core/Chip';
-import SettingsEthernetIcon from '@material-ui/icons/SettingsEthernet';
-import DeleteIcon from '@material-ui/icons/Delete';
-import { makeStyles, TextField, Button } from '@material-ui/core';
+// ========================================================================
+import React, { useState } from "react";
+import PropTypes from "prop-types";
+import AddIconOutline from "@material-ui/icons/AddCircleOutline";
+import AddIcon from "@material-ui/icons/Add";
+import Table from "@material-ui/core/Table";
+import TableBody from "@material-ui/core/TableBody";
+import TableCell from "@material-ui/core/TableCell";
+import TableContainer from "@material-ui/core/TableContainer";
+import TableHead from "@material-ui/core/TableHead";
+import TableRow from "@material-ui/core/TableRow";
+import IconButton from "@material-ui/core/IconButton";
+// import EditIcon from "@material-ui/icons/Edit";
+import Chip from "@material-ui/core/Chip";
+import SettingsEthernetIcon from "@material-ui/icons/SettingsEthernet";
+import DeleteIcon from "@material-ui/icons/Delete";
+import { makeStyles, TextField, Button } from "@material-ui/core";
import NetworkForm from "../networks/NetworkForm";
import apiService from "../../../services/apiService";
import DeleteDialog from "../../../common/Dialogue";
-import CancelOutlinedIcon from '@material-ui/icons/CancelOutlined';
-import CheckIcon from '@material-ui/icons/CheckCircleOutlineOutlined';
-import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
+import CancelOutlinedIcon from "@material-ui/icons/CancelOutlined";
+import CheckIcon from "@material-ui/icons/CheckCircleOutlineOutlined";
+import InfoOutlinedIcon from "@material-ui/icons/InfoOutlined";
import NetworkDetailsDialog from "../../../common/DetailsDialog";
-import DoneOutlineIcon from '@material-ui/icons/DoneOutline';
+import DoneOutlineIcon from "@material-ui/icons/DoneOutline";
import ClusterForm from "../clusters/ClusterForm";
+import Notification from "../../../common/Notification";
const useStyles = makeStyles((theme) => ({
- root: {
- width: '100%',
- },
- heading: {
- fontSize: theme.typography.pxToRem(15),
- flexBasis: '33.33%',
- flexShrink: 0,
- },
- secondaryHeading: {
- fontSize: theme.typography.pxToRem(15),
- color: theme.palette.text.secondary,
- },
+ root: {
+ width: "100%",
+ },
+ heading: {
+ fontSize: theme.typography.pxToRem(15),
+ flexBasis: "33.33%",
+ flexShrink: 0,
+ },
+ secondaryHeading: {
+ fontSize: theme.typography.pxToRem(15),
+ color: theme.palette.text.secondary,
+ },
}));
const ClusterTable = ({ clustersData, ...props }) => {
- const classes = useStyles();
- const [formOpen, setformOpen] = useState(false);
- const [networkDetailsOpen, setNetworkDetailsOpen] = useState(false);
- const [network, setNetwork] = useState({});
- const [activeRowIndex, setActiveRowIndex] = useState(0);
- const [activeNetwork, setActiveNetwork] = useState({});
- const [open, setOpen] = useState(false);
- const [openDeleteNetwork, setOpenDeleteNetwork] = useState(false);
- const [showAddLabel, setShowAddLabel] = useState(false);
- const [labelInput, setLabelInput] = React.useState("");
- const [clusterFormOpen, setClusterFormOpen] = useState(false);
- const handleFormClose = () => {
+ const classes = useStyles();
+ const [formOpen, setformOpen] = useState(false);
+ const [networkDetailsOpen, setNetworkDetailsOpen] = useState(false);
+ const [network, setNetwork] = useState({});
+ const [activeRowIndex, setActiveRowIndex] = useState(0);
+ const [activeNetwork, setActiveNetwork] = useState({});
+ const [open, setOpen] = useState(false);
+ const [openDeleteNetwork, setOpenDeleteNetwork] = useState(false);
+ const [showAddLabel, setShowAddLabel] = useState(false);
+ const [labelInput, setLabelInput] = useState("");
+ // const [clusterFormOpen, setClusterFormOpen] = useState(false);
+ const [notificationDetails, setNotificationDetails] = useState({});
+ const handleFormClose = () => {
+ setformOpen(false);
+ };
+ const handleSubmit = (data) => {
+ let networkSpec = JSON.parse(data.spec);
+ let payload = {
+ metadata: { name: data.name, description: data.description },
+ spec: networkSpec,
+ };
+ let request = {
+ providerName: props.providerName,
+ clusterName: clustersData[activeRowIndex].metadata.name,
+ networkType: data.type,
+ payload: payload,
+ };
+ apiService
+ .addNetwork(request)
+ .then((res) => {
+ let networkType =
+ data.type === "networks" ? "networks" : "providerNetworks";
+ !clustersData[activeRowIndex][networkType] ||
+ clustersData[activeRowIndex][networkType] === null
+ ? (clustersData[activeRowIndex][networkType] = [res])
+ : clustersData[activeRowIndex][networkType].push(res);
+ })
+ .catch((err) => {
+ console.log("error adding cluster network : ", err);
+ })
+ .finally(() => {
+ setActiveRowIndex(0);
setformOpen(false);
- }
- const handleSubmit = (data) => {
- let networkSpec = JSON.parse(data.spec);
- let payload = { metadata: { name: data.name, description: data.description }, spec: networkSpec };
- let request = { providerName: props.providerName, clusterName: clustersData[activeRowIndex].metadata.name, networkType: data.type, payload: payload };
- apiService.addNetwork(request).then(res => {
- let networkType = (data.type === "networks" ? "networks" : "providerNetworks");
- (!clustersData[activeRowIndex][networkType] || clustersData[activeRowIndex][networkType] === null) ? (clustersData[activeRowIndex][networkType] = [res]) : clustersData[activeRowIndex][networkType].push(res);
- }).catch(err => {
- console.log("error adding cluster network : ", err)
- }).finally(() => {
- setActiveRowIndex(0);
- setformOpen(false);
+ });
+ };
+ const handleAddNetwork = (index) => {
+ setActiveRowIndex(index);
+ setformOpen(true);
+ };
+ const handleDeleteLabel = (index, label, labelIndex) => {
+ let request = {
+ providerName: props.providerName,
+ clusterName: clustersData[index].metadata.name,
+ labelName: label,
+ };
+ apiService
+ .deleteClusterLabel(request)
+ .then((res) => {
+ console.log("label deleted");
+ clustersData[index].labels.splice(labelIndex, 1);
+ props.onUpdateCluster(props.parentIndex, clustersData);
+ })
+ .catch((err) => {
+ console.log("error deleting label : ", err);
+ });
+ };
+ const handleClose = (el) => {
+ if (el.target.innerText === "Delete") {
+ let request = {
+ providerName: props.providerName,
+ clusterName: clustersData[activeRowIndex].metadata.name,
+ };
+ apiService
+ .deleteCluster(request)
+ .then(() => {
+ console.log("cluster deleted");
+ props.onDeleteCluster(props.parentIndex, activeRowIndex);
+ })
+ .catch((err) => {
+ console.log("Error deleting cluster : ", +err);
+ setNotificationDetails({
+ show: true,
+ message: "Unable to remove cluster",
+ severity: "error",
+ });
});
}
- const handleAddNetwork = (index) => {
- setActiveRowIndex(index);
- setformOpen(true);
- }
- const handleDeleteLabel = (index, label, labelIndex) => {
- let request = { providerName: props.providerName, clusterName: clustersData[index].metadata.name, labelName: label }
- apiService.deleteClusterLabel(request).then(res => {
- console.log("label deleted");
- clustersData[index].labels.splice(labelIndex, 1);
- props.onUpdateCluster(props.parentIndex, clustersData);
- }).catch(err => { console.log("error deleting label : ", err) })
- }
- const handleClose = el => {
- if (el.target.innerText === "Delete") {
- let request = { providerName: props.providerName, clusterName: clustersData[activeRowIndex].metadata.name };
- apiService.deleteCluster(request).then(() => {
- console.log("cluster deleted");
- props.onDeleteCluster(props.parentIndex, activeRowIndex);
- }).catch(err => {
- console.log("Error deleting cluster : ", err)
- })
- }
- setOpen(false);
- setActiveRowIndex(0);
- };
+ setOpen(false);
+ setActiveRowIndex(0);
+ };
- const handleCloseDeleteNetwork = (el) => {
- if (el.target.innerText === "Delete") {
- let networkName = clustersData[activeRowIndex][activeNetwork.networkType][activeNetwork.networkIndex].metadata.name;
- let networkType = (activeNetwork.networkType === "providerNetworks" ? "provider-networks" : "networks");
- let request = { providerName: props.providerName, clusterName: clustersData[activeRowIndex].metadata.name, networkType: networkType, networkName: networkName };
- apiService.deleteClusterNetwork(request).then(() => {
- console.log("cluster network deleted");
- clustersData[activeRowIndex][activeNetwork.networkType].splice(activeNetwork.networkIndex, 1);
- }).catch(err => {
- console.log("Error deleting cluster network : ", err)
- }).finally(() => { setActiveRowIndex(0); setActiveNetwork({}); })
- }
- setOpenDeleteNetwork(false);
- }
- const handleDeleteCluster = (index) => {
- setActiveRowIndex(index);
- setOpen(true);
- }
- const handleAddLabel = (index) => {
- if (labelInput !== "") {
- let request = { providerName: props.providerName, clusterName: clustersData[activeRowIndex].metadata.name, payload: { "label-name": labelInput } };
- apiService.addClusterLabel(request)
- .then(res => {
- (!clustersData[index].labels || clustersData[index].labels === null) ? (clustersData[index].labels = [res]) : clustersData[index].labels.push(res);
- })
- .catch(err => { console.log("error adding label", err) })
- .finally(() => {
- setShowAddLabel(!showAddLabel);
- })
- }
+ const handleCloseDeleteNetwork = (el) => {
+ if (el.target.innerText === "Delete") {
+ let networkName =
+ clustersData[activeRowIndex][activeNetwork.networkType][
+ activeNetwork.networkIndex
+ ].metadata.name;
+ let networkType =
+ activeNetwork.networkType === "providerNetworks"
+ ? "provider-networks"
+ : "networks";
+ let request = {
+ providerName: props.providerName,
+ clusterName: clustersData[activeRowIndex].metadata.name,
+ networkType: networkType,
+ networkName: networkName,
+ };
+ apiService
+ .deleteClusterNetwork(request)
+ .then(() => {
+ console.log("cluster network deleted");
+ clustersData[activeRowIndex][activeNetwork.networkType].splice(
+ activeNetwork.networkIndex,
+ 1
+ );
+ })
+ .catch((err) => {
+ console.log("Error deleting cluster network : ", err);
+ })
+ .finally(() => {
+ setActiveRowIndex(0);
+ setActiveNetwork({});
+ });
}
-
- const handleToggleAddLabel = (index) => {
- setShowAddLabel(showAddLabel === index ? false : index);
- setActiveRowIndex(index);
- setLabelInput('');
+ setOpenDeleteNetwork(false);
+ };
+ const handleDeleteCluster = (index) => {
+ setActiveRowIndex(index);
+ setOpen(true);
+ };
+ const handleAddLabel = (index) => {
+ if (labelInput !== "") {
+ let request = {
+ providerName: props.providerName,
+ clusterName: clustersData[activeRowIndex].metadata.name,
+ payload: { "label-name": labelInput },
+ };
+ apiService
+ .addClusterLabel(request)
+ .then((res) => {
+ !clustersData[index].labels || clustersData[index].labels === null
+ ? (clustersData[index].labels = [res])
+ : clustersData[index].labels.push(res);
+ })
+ .catch((err) => {
+ console.log("error adding label", err);
+ })
+ .finally(() => {
+ setShowAddLabel(!showAddLabel);
+ });
}
- const handleLabelInputChange = (event) => {
- setLabelInput(event.target.value);
- };
+ };
- const handleNetworkDetailOpen = (network) => {
- setNetwork(network);
- setNetworkDetailsOpen(true);
- }
- const handleDeleteNetwork = (index, networkIndex, networkType, networkName) => {
- setActiveNetwork({ networkIndex: networkIndex, networkType: networkType, name: networkName });
- setActiveRowIndex(index);
- setOpenDeleteNetwork(true);
- }
- const applyNetworkConfig = (clusterName) => {
- let request = { providerName: props.providerName, clusterName: clusterName }
- apiService.applyNetworkConfig(request)
- .then(res => {
- console.log("Network config applied");
- })
- .catch(err => {
- console.log("Error applying network config : ", err);
- if (err.response)
- console.log("Network config applied" + err.response.data);
- else
- console.log("Network config applied" + err);
- });
- }
- const handleClusterFormClose = () => {
- setClusterFormOpen(false);
- }
- const handleClusterSubmit = (values) => {
- const formData = new FormData();
- if (values.file)
- formData.append('file', values.file);
- formData.append("metadata", `{"metadata":{ "name": "${values.name}", "description": "${values.description}" }}`);
- formData.append("providerName", props.providerName);
- apiService.updateCluster(formData)
- .then(res => {
- clustersData[activeRowIndex].metadata = res.metadata;
- props.onUpdateCluster(props.parentIndex, clustersData);
- })
- .catch(err => { console.log("error updating cluster : ", err) })
- .finally(() => { handleClusterFormClose() });
+ const handleToggleAddLabel = (index) => {
+ setShowAddLabel(showAddLabel === index ? false : index);
+ setActiveRowIndex(index);
+ setLabelInput("");
+ };
+ const handleLabelInputChange = (event) => {
+ setLabelInput(event.target.value);
+ };
- }
- const handleEditCluster = (index) => {
- setActiveRowIndex(index);
- setClusterFormOpen(true);
- }
- return (
+ const handleNetworkDetailOpen = (network) => {
+ setNetwork(network);
+ setNetworkDetailsOpen(true);
+ };
+ const handleDeleteNetwork = (
+ index,
+ networkIndex,
+ networkType,
+ networkName
+ ) => {
+ setActiveNetwork({
+ networkIndex: networkIndex,
+ networkType: networkType,
+ name: networkName,
+ });
+ setActiveRowIndex(index);
+ setOpenDeleteNetwork(true);
+ };
+ const applyNetworkConfig = (clusterName) => {
+ let request = {
+ providerName: props.providerName,
+ clusterName: clusterName,
+ };
+ apiService
+ .applyNetworkConfig(request)
+ .then((res) => {
+ setNotificationDetails({
+ show: true,
+ message: "Network configuration applied",
+ severity: "success",
+ });
+ console.log("Network config applied");
+ })
+ .catch((err) => {
+ setNotificationDetails({
+ show: true,
+ message: "Error applying network configuration",
+ severity: "error",
+ });
+ console.log("Error applying network config : ", err);
+ if (err.response)
+ console.log("Network config applied" + err.response.data);
+ else console.log("Network config applied" + err);
+ });
+ };
+ // const handleClusterFormClose = () => {
+ // setClusterFormOpen(false);
+ // };
+ // const handleClusterSubmit = (values) => {
+ // const formData = new FormData();
+ // if (values.file) formData.append("file", values.file);
+ // formData.append(
+ // "metadata",
+ // `{"metadata":{ "name": "${values.name}", "description": "${values.description}" }}`
+ // );
+ // formData.append("providerName", props.providerName);
+ // apiService
+ // .updateCluster(formData)
+ // .then((res) => {
+ // clustersData[activeRowIndex].metadata = res.metadata;
+ // props.onUpdateCluster(props.parentIndex, clustersData);
+ // })
+ // .catch((err) => {
+ // console.log("error updating cluster : ", err);
+ // })
+ // .finally(() => {
+ // handleClusterFormClose();
+ // });
+ // };
+ //disabling as edit is not supported yet by the api yet
+ // const handleEditCluster = (index) => {
+ // setActiveRowIndex(index);
+ // setClusterFormOpen(true);
+ // };
+ return (
+ <>
+ <Notification notificationDetails={notificationDetails} />
+ {clustersData && clustersData.length > 0 && (
<>
- {clustersData && (clustersData.length > 0) &&
- (<>
- <ClusterForm item={clustersData[activeRowIndex]} open={clusterFormOpen} onClose={handleClusterFormClose} onSubmit={handleClusterSubmit} />
- <NetworkDetailsDialog onClose={setNetworkDetailsOpen} open={networkDetailsOpen} item={network} type="Network" />
- <NetworkForm onClose={handleFormClose} onSubmit={handleSubmit} open={formOpen} />
- <DeleteDialog open={open} onClose={handleClose} title={"Delete Cluster"}
- content={`Are you sure you want to delete "${clustersData[activeRowIndex] ? clustersData[activeRowIndex].metadata.name : ""}" ?`} />
- <DeleteDialog open={openDeleteNetwork} onClose={handleCloseDeleteNetwork} title={"Delete Network"} content={`Are you sure you want to delete "${activeNetwork.name}" ?`} />
- <TableContainer >
- <Table className={classes.table}>
- <TableHead>
- <TableRow>
- <TableCell style={{ width: "10%" }}>Name</TableCell>
- <TableCell style={{ width: "15%" }}>Description</TableCell>
- <TableCell style={{ width: "20%" }}>Networks </TableCell>
- <TableCell style={{ width: "35%" }}>Labels </TableCell>
- <TableCell style={{ width: "20%" }}>Actions</TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {clustersData.map((row, index) => (
- <TableRow key={row.metadata.name + "" + index}>
- <TableCell >{row.metadata.name}</TableCell>
- <TableCell >{row.metadata.description}</TableCell>
- <TableCell>
- <div>
- {row.providerNetworks && (row.providerNetworks.length > 0) && row.providerNetworks.map((providerNetwork, providerNetworkIndex) =>
- (<Chip
- key={providerNetwork.metadata.name + "" + providerNetworkIndex}
- size="small"
- icon={<InfoOutlinedIcon onClick={() => { handleNetworkDetailOpen(providerNetwork) }} style={{ cursor: "pointer" }} />}
- onDelete={(e) => { handleDeleteNetwork(index, providerNetworkIndex, "providerNetworks", providerNetwork.metadata.name) }}
- label={providerNetwork.metadata.name}
- style={{ marginRight: "10px", marginBottom: "5px" }}
- />)
- )}
+ {/* <ClusterForm
+ item={clustersData[activeRowIndex]}
+ open={clusterFormOpen}
+ onClose={handleClusterFormClose}
+ onSubmit={handleClusterSubmit}
+ /> */}
+ <NetworkDetailsDialog
+ onClose={setNetworkDetailsOpen}
+ open={networkDetailsOpen}
+ item={network}
+ type="Network"
+ />
+ <NetworkForm
+ onClose={handleFormClose}
+ onSubmit={handleSubmit}
+ open={formOpen}
+ />
+ <DeleteDialog
+ open={open}
+ onClose={handleClose}
+ title={"Delete Cluster"}
+ content={`Are you sure you want to delete "${
+ clustersData[activeRowIndex]
+ ? clustersData[activeRowIndex].metadata.name
+ : ""
+ }" ?`}
+ />
+ <DeleteDialog
+ open={openDeleteNetwork}
+ onClose={handleCloseDeleteNetwork}
+ title={"Delete Network"}
+ content={`Are you sure you want to delete "${activeNetwork.name}" ?`}
+ />
+ <TableContainer>
+ <Table className={classes.table}>
+ <TableHead>
+ <TableRow>
+ <TableCell style={{ width: "10%" }}>Name</TableCell>
+ <TableCell style={{ width: "15%" }}>Description</TableCell>
+ <TableCell style={{ width: "20%" }}>Networks </TableCell>
+ <TableCell style={{ width: "35%" }}>Labels </TableCell>
+ <TableCell style={{ width: "20%" }}>Actions</TableCell>
+ </TableRow>
+ </TableHead>
+ <TableBody>
+ {clustersData.map((row, index) => (
+ <TableRow key={row.metadata.name + "" + index}>
+ <TableCell>{row.metadata.name}</TableCell>
+ <TableCell>{row.metadata.description}</TableCell>
+ <TableCell>
+ <div>
+ {row.providerNetworks &&
+ row.providerNetworks.length > 0 &&
+ row.providerNetworks.map(
+ (providerNetwork, providerNetworkIndex) => (
+ <Chip
+ key={
+ providerNetwork.metadata.name +
+ "" +
+ providerNetworkIndex
+ }
+ size="small"
+ icon={
+ <InfoOutlinedIcon
+ onClick={() => {
+ handleNetworkDetailOpen(providerNetwork);
+ }}
+ style={{ cursor: "pointer" }}
+ />
+ }
+ onDelete={(e) => {
+ handleDeleteNetwork(
+ index,
+ providerNetworkIndex,
+ "providerNetworks",
+ providerNetwork.metadata.name
+ );
+ }}
+ label={providerNetwork.metadata.name}
+ style={{
+ marginRight: "10px",
+ marginBottom: "5px",
+ }}
+ />
+ )
+ )}
- {row.networks && (row.networks.length > 0) && row.networks.map((network, networkIndex) =>
- (<Chip
- key={network.metadata.name + "" + networkIndex}
- size="small"
- icon={<InfoOutlinedIcon onClick={() => { handleNetworkDetailOpen(network) }} style={{ cursor: "pointer" }} />}
- onDelete={(e) => { handleDeleteNetwork(index, networkIndex, "networks", network.metadata.name) }}
- label={network.metadata.name}
- style={{ marginRight: "10px", marginBottom: "5px" }}
- color="secondary"
- />)
- )}
- </div>
- </TableCell>
- <TableCell>
- {row.labels && (row.labels.length > 0) && row.labels.map((label, labelIndex) =>
- (<Chip
- key={label["label-name"] + "" + labelIndex}
- size="small"
- icon={<SettingsEthernetIcon />}
- label={label["label-name"]}
- onDelete={(e) => { handleDeleteLabel(index, label["label-name"], labelIndex) }}
- color="primary"
- style={{ marginRight: "10px" }}
- />)
- )}
- {(showAddLabel === index) &&
- <TextField
- style={{ height: "24px" }}
- size="small"
- value={labelInput}
- onChange={handleLabelInputChange}
- id="outlined-basic" label="Add label" variant="outlined" />
- }
- {(showAddLabel === index) &&
- <IconButton color="primary" onClick={() => { handleAddLabel(index) }}>
- <CheckIcon />
- </IconButton>
- }
- <IconButton color="primary" onClick={() => { handleToggleAddLabel(index) }}>
- {!(showAddLabel === index) && <AddIconOutline />}
- {(showAddLabel === index) && <CancelOutlinedIcon color="secondary" />}
- </IconButton>
- </TableCell>
- <TableCell>
- <Button
- variant="outlined"
- startIcon={<AddIcon />}
- size="small"
- color="primary"
- title="Add Network"
- onClick={() => { handleAddNetwork(index) }}>
- Network
- </Button>
- <IconButton
- style={{ color: "green" }}
- onClick={() => { applyNetworkConfig(row.metadata.name) }}
- title="Apply Network Configuration">
- <DoneOutlineIcon />
- </IconButton>
- <IconButton
- title="Edit"
- onClick={() => { handleEditCluster(index) }}
- color="primary">
- <EditIcon />
- </IconButton>
- <IconButton
- title="Delete"
- color="secondary"
- onClick={() => { handleDeleteCluster(index) }}>
- <DeleteIcon />
- </IconButton>
- </TableCell>
- </TableRow>))}
- </TableBody>
- </Table>
- </TableContainer>
- </>)}
- {(!clustersData || (clustersData.length === 0)) && (<span>No Clusters</span>)}
- </>)
-}
+ {row.networks &&
+ row.networks.length > 0 &&
+ row.networks.map((network, networkIndex) => (
+ <Chip
+ key={network.metadata.name + "" + networkIndex}
+ size="small"
+ icon={
+ <InfoOutlinedIcon
+ onClick={() => {
+ handleNetworkDetailOpen(network);
+ }}
+ style={{ cursor: "pointer" }}
+ />
+ }
+ onDelete={(e) => {
+ handleDeleteNetwork(
+ index,
+ networkIndex,
+ "networks",
+ network.metadata.name
+ );
+ }}
+ label={network.metadata.name}
+ style={{
+ marginRight: "10px",
+ marginBottom: "5px",
+ }}
+ color="secondary"
+ />
+ ))}
+ </div>
+ </TableCell>
+ <TableCell>
+ {row.labels &&
+ row.labels.length > 0 &&
+ row.labels.map((label, labelIndex) => (
+ <Chip
+ key={label["label-name"] + "" + labelIndex}
+ size="small"
+ icon={<SettingsEthernetIcon />}
+ label={label["label-name"]}
+ onDelete={(e) => {
+ handleDeleteLabel(
+ index,
+ label["label-name"],
+ labelIndex
+ );
+ }}
+ color="primary"
+ style={{ marginRight: "10px" }}
+ />
+ ))}
+ {showAddLabel === index && (
+ <TextField
+ style={{ height: "24px" }}
+ size="small"
+ value={labelInput}
+ onChange={handleLabelInputChange}
+ id="outlined-basic"
+ label="Add label"
+ variant="outlined"
+ />
+ )}
+ {showAddLabel === index && (
+ <IconButton
+ color="primary"
+ onClick={() => {
+ handleAddLabel(index);
+ }}
+ >
+ <CheckIcon />
+ </IconButton>
+ )}
+ <IconButton
+ color="primary"
+ onClick={() => {
+ handleToggleAddLabel(index);
+ }}
+ >
+ {!(showAddLabel === index) && <AddIconOutline />}
+ {showAddLabel === index && (
+ <CancelOutlinedIcon color="secondary" />
+ )}
+ </IconButton>
+ </TableCell>
+ <TableCell>
+ <Button
+ variant="outlined"
+ startIcon={<AddIcon />}
+ size="small"
+ color="primary"
+ title="Add Network"
+ onClick={() => {
+ handleAddNetwork(index);
+ }}
+ >
+ Network
+ </Button>
+ <IconButton
+ color="primary"
+ disabled={
+ !(
+ (row.networks && row.networks.length > 0) ||
+ (row.providerNetworks &&
+ row.providerNetworks.length > 0)
+ )
+ }
+ onClick={() => {
+ applyNetworkConfig(row.metadata.name);
+ }}
+ title="Apply Network Configuration"
+ >
+ <DoneOutlineIcon />
+ </IconButton>
+ {/*
+ //disabling as edit is not supported yet by the api yet
+ <IconButton
+ title="Edit"
+ onClick={() => { handleEditCluster(index) }}
+ color="primary">
+ <EditIcon />
+ </IconButton> */}
+ <IconButton
+ title="Delete"
+ color="secondary"
+ disabled={
+ (row.networks && row.networks.length > 0) ||
+ (row.providerNetworks &&
+ row.providerNetworks.length > 0) ||
+ (row.labels && row.labels.length > 0)
+ }
+ onClick={() => {
+ handleDeleteCluster(index);
+ }}
+ >
+ <DeleteIcon />
+ </IconButton>
+ </TableCell>
+ </TableRow>
+ ))}
+ </TableBody>
+ </Table>
+ </TableContainer>
+ </>
+ )}
+ {(!clustersData || clustersData.length === 0) && <span>No Clusters</span>}
+ </>
+ );
+};
ClusterTable.propTypes = {
- clusters: PropTypes.arrayOf(PropTypes.object)
+ clusters: PropTypes.arrayOf(PropTypes.object),
};
export default ClusterTable;
diff --git a/src/tools/emcoui/src/admin/controllers/Controllers.jsx b/src/tools/emcoui/src/admin/controllers/Controllers.jsx
index 4a8a502c..4316f6ea 100644
--- a/src/tools/emcoui/src/admin/controllers/Controllers.jsx
+++ b/src/tools/emcoui/src/admin/controllers/Controllers.jsx
@@ -29,7 +29,8 @@ function Controllers() {
apiService
.getControllers()
.then((res) => {
- setControllersData(res);
+ if (res && res.length > 0) setControllersData(res);
+ else setControllersData([]);
})
.catch((err) => {
console.log("error getting controllers : " + err);
@@ -53,9 +54,7 @@ function Controllers() {
.addController(request)
.then((res) => {
setControllersData((controllersData) => {
- if (controllersData && controllersData.length > 0)
- return [...controllersData, res];
- else return [res];
+ return [...controllersData, res];
});
})
.catch((err) => {
diff --git a/src/tools/emcoui/src/admin/projects/ProjectForm.jsx b/src/tools/emcoui/src/admin/projects/ProjectForm.jsx
index 751de5d0..4ea87b2c 100644
--- a/src/tools/emcoui/src/admin/projects/ProjectForm.jsx
+++ b/src/tools/emcoui/src/admin/projects/ProjectForm.jsx
@@ -11,146 +11,157 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
-import React from 'react';
-import PropTypes from 'prop-types';
-import { withStyles } from '@material-ui/core/styles';
-import Button from '@material-ui/core/Button';
+// ========================================================================
+import React from "react";
+import PropTypes from "prop-types";
+import { withStyles } from "@material-ui/core/styles";
+import Button from "@material-ui/core/Button";
-import Dialog from '@material-ui/core/Dialog';
-import MuiDialogTitle from '@material-ui/core/DialogTitle';
-import MuiDialogContent from '@material-ui/core/DialogContent';
-import MuiDialogActions from '@material-ui/core/DialogActions';
-import IconButton from '@material-ui/core/IconButton';
-import CloseIcon from '@material-ui/icons/Close';
-import Typography from '@material-ui/core/Typography';
-import { TextField } from '@material-ui/core';
+import Dialog from "@material-ui/core/Dialog";
+import MuiDialogTitle from "@material-ui/core/DialogTitle";
+import MuiDialogContent from "@material-ui/core/DialogContent";
+import MuiDialogActions from "@material-ui/core/DialogActions";
+import IconButton from "@material-ui/core/IconButton";
+import CloseIcon from "@material-ui/icons/Close";
+import Typography from "@material-ui/core/Typography";
+import { TextField } from "@material-ui/core";
import * as Yup from "yup";
-import { Formik } from 'formik';
+import { Formik } from "formik";
const styles = (theme) => ({
- root: {
- margin: 0,
- padding: theme.spacing(2),
- },
- closeButton: {
- position: 'absolute',
- right: theme.spacing(1),
- top: theme.spacing(1),
- color: theme.palette.grey[500],
- },
+ root: {
+ margin: 0,
+ padding: theme.spacing(2),
+ },
+ closeButton: {
+ position: "absolute",
+ right: theme.spacing(1),
+ top: theme.spacing(1),
+ color: theme.palette.grey[500],
+ },
});
const DialogTitle = withStyles(styles)((props) => {
- const { children, classes, onClose, ...other } = props;
- return (
- <MuiDialogTitle disableTypography className={classes.root} {...other}>
- <Typography variant="h6">{children}</Typography>
- {onClose ? (
- <IconButton className={classes.closeButton} onClick={onClose}>
- <CloseIcon />
- </IconButton>
- ) : null}
- </MuiDialogTitle>
- );
+ const { children, classes, onClose, ...other } = props;
+ return (
+ <MuiDialogTitle disableTypography className={classes.root} {...other}>
+ <Typography variant="h6">{children}</Typography>
+ {onClose ? (
+ <IconButton className={classes.closeButton} onClick={onClose}>
+ <CloseIcon />
+ </IconButton>
+ ) : null}
+ </MuiDialogTitle>
+ );
});
const DialogActions = withStyles((theme) => ({
- root: {
- margin: 0,
- padding: theme.spacing(1),
- },
+ root: {
+ margin: 0,
+ padding: theme.spacing(1),
+ },
}))(MuiDialogActions);
const DialogContent = withStyles((theme) => ({
- root: {
- padding: theme.spacing(2),
- }
+ root: {
+ padding: theme.spacing(2),
+ },
}))(MuiDialogContent);
-const schema = Yup.object(
- {
- name: Yup.string().required(),
- description: Yup.string(),
- })
+const schema = Yup.object({
+ name: Yup.string().required(),
+ description: Yup.string(),
+});
const ProjectFormFunc = (props) => {
- const { onClose, item, open, onSubmit } = props;
- const buttonLabel = item ? "OK" : "Create"
- const title = item ? "Edit Project" : "Create Project"
- const handleClose = () => {
- onClose();
- };
- let initialValues = item ? { name: item.metadata.name, description: item.metadata.description } : { name: "", description: "" }
+ const { onClose, item, open, onSubmit } = props;
+ const buttonLabel = item ? "OK" : "Create";
+ const title = item ? "Edit Project" : "Create Project";
+ const handleClose = () => {
+ onClose();
+ };
+ let initialValues = item
+ ? { name: item.metadata.name, description: item.metadata.description }
+ : { name: "", description: "" };
- return (
- <Dialog maxWidth={"xs"} onClose={handleClose} aria-labelledby="customized-dialog-title" open={open} disableBackdropClick>
- <DialogTitle id="simple-dialog-title">{title}</DialogTitle>
- <Formik
- initialValues={initialValues}
- onSubmit={async values => {
- onSubmit(values);
- }}
- validationSchema={schema}
- >
- {props => {
- const {
- values,
- touched,
- errors,
- isSubmitting,
- handleChange,
- handleBlur,
- handleSubmit
- } = props;
- return (
- <form noValidate onSubmit={handleSubmit}>
- <DialogContent dividers>
- <TextField
- style={{ width: "100%", marginBottom: "10px" }}
- id="name"
- label="Project name"
- type="text"
- value={values.name}
- onChange={handleChange}
- onBlur={handleBlur}
- helperText={(errors.name && touched.name && (
- "Name is required"
- ))}
- required
- error={errors.name && touched.name}
- />
- <TextField
- style={{ width: "100%", marginBottom: "25px" }}
- name="description"
- value={values.description}
- onChange={handleChange}
- onBlur={handleBlur}
- id="description"
- label="Description"
- multiline
- rowsMax={4}
- />
- </DialogContent>
- <DialogActions>
- <Button autoFocus onClick={handleClose} color="secondary">
- Cancel
- </Button>
- <Button autoFocus type="submit" color="primary" disabled={isSubmitting}>
- {buttonLabel}
- </Button>
- </DialogActions>
- </form>
- );
- }}
- </Formik>
- </Dialog>
- );
+ return (
+ <Dialog
+ maxWidth={"xs"}
+ onClose={handleClose}
+ aria-labelledby="customized-dialog-title"
+ open={open}
+ disableBackdropClick
+ >
+ <DialogTitle id="simple-dialog-title">{title}</DialogTitle>
+ <Formik
+ initialValues={initialValues}
+ onSubmit={async (values) => {
+ onSubmit(values);
+ }}
+ validationSchema={schema}
+ >
+ {(props) => {
+ const {
+ values,
+ touched,
+ errors,
+ isSubmitting,
+ handleChange,
+ handleBlur,
+ handleSubmit,
+ } = props;
+ return (
+ <form noValidate onSubmit={handleSubmit}>
+ <DialogContent dividers>
+ <TextField
+ style={{ width: "100%", marginBottom: "10px" }}
+ id="name"
+ label="Project name"
+ type="text"
+ value={values.name}
+ onChange={handleChange}
+ onBlur={handleBlur}
+ helperText={errors.name && touched.name && "Name is required"}
+ required
+ disabled={item}
+ error={errors.name && touched.name}
+ />
+ <TextField
+ style={{ width: "100%", marginBottom: "25px" }}
+ name="description"
+ value={values.description}
+ onChange={handleChange}
+ onBlur={handleBlur}
+ id="description"
+ label="Description"
+ multiline
+ rowsMax={4}
+ />
+ </DialogContent>
+ <DialogActions>
+ <Button autoFocus onClick={handleClose} color="secondary">
+ Cancel
+ </Button>
+ <Button
+ autoFocus
+ type="submit"
+ color="primary"
+ disabled={isSubmitting}
+ >
+ {buttonLabel}
+ </Button>
+ </DialogActions>
+ </form>
+ );
+ }}
+ </Formik>
+ </Dialog>
+ );
};
ProjectFormFunc.propTypes = {
- onClose: PropTypes.func.isRequired,
- open: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired,
+ open: PropTypes.bool.isRequired,
};
export default ProjectFormFunc;
diff --git a/src/tools/emcoui/src/admin/projects/ProjectsTable.jsx b/src/tools/emcoui/src/admin/projects/ProjectsTable.jsx
index d96d44fa..fb03155d 100644
--- a/src/tools/emcoui/src/admin/projects/ProjectsTable.jsx
+++ b/src/tools/emcoui/src/admin/projects/ProjectsTable.jsx
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React from "react";
import { withStyles, makeStyles } from "@material-ui/core/styles";
import Table from "@material-ui/core/Table";
@@ -24,118 +24,142 @@ import Paper from "@material-ui/core/Paper";
import { Link } from "react-router-dom";
import IconButton from "@material-ui/core/IconButton";
import EditIcon from "@material-ui/icons/Edit";
-import DeleteDialog from "../../common/Dialogue"
+import DeleteDialog from "../../common/Dialogue";
import DeleteIcon from "@material-ui/icons/Delete";
import ProjectForm from "./ProjectForm";
import apiService from "../../services/apiService";
const StyledTableCell = withStyles((theme) => ({
- body: {
- fontSize: 14,
- },
+ body: {
+ fontSize: 14,
+ },
}))(TableCell);
const StyledTableRow = withStyles((theme) => ({
- root: {
- "&:nth-of-type(odd)": {
- backgroundColor: theme.palette.action.hover,
- },
+ root: {
+ "&:nth-of-type(odd)": {
+ backgroundColor: theme.palette.action.hover,
},
+ },
}))(TableRow);
const useStyles = makeStyles({
- table: {
- minWidth: 350,
- },
- cell: {
- color: "grey",
- },
+ table: {
+ minWidth: 350,
+ },
+ cell: {
+ color: "grey",
+ },
});
export default function ProjectsTable(props) {
- const classes = useStyles();
- const [open, setOpen] = React.useState(false);
- const [openForm, setOpenForm] = React.useState(false);
- const [index, setIndex] = React.useState(0);
+ const classes = useStyles();
+ const [open, setOpen] = React.useState(false);
+ const [openForm, setOpenForm] = React.useState(false);
+ const [index, setIndex] = React.useState(0);
- let handleEdit = index => {
- setIndex(index);
- setOpenForm(true);
- }
- const handleClose = el => {
- if (el.target.innerText === "Delete") {
- apiService.deleteProject(props.data[index].metadata.name).then(() => {
- console.log("project deleted");
- props.data.splice(index, 1);
- props.setProjectsData([...props.data]);
- }).catch(err => {
- console.log("Error deleting project : ", err)
- })
- }
- setOpen(false);
- setIndex(0);
- };
- const handleFormClose = () => {
- setIndex(0);
- setOpenForm(false);
- };
- const handleDelete = (index) => {
- setIndex(index);
- setOpen(true);
- }
- const handleSubmit = (data) => {
- let payload = { "metadata": data }
- apiService.updateProject(payload).then(res => {
- props.data[index] = res;
- props.setProjectsData([...props.data]);
- }).catch(err => {
- console.log("Error updating project : ", err);
+ let handleEdit = (index) => {
+ setIndex(index);
+ setOpenForm(true);
+ };
+ const handleClose = (el) => {
+ if (el.target.innerText === "Delete") {
+ apiService
+ .deleteProject(props.data[index].metadata.name)
+ .then(() => {
+ console.log("project deleted");
+ props.data.splice(index, 1);
+ props.setProjectsData([...props.data]);
})
- setOpenForm(false);
- };
-
- return (
- <React.Fragment>
- {(props.data && props.data.length > 0) &&
- <>
- <ProjectForm open={openForm} onClose={handleFormClose} item={props.data[index]} onSubmit={handleSubmit} />
- <DeleteDialog open={open} onClose={handleClose} title={"Delete Project"}
- content={`Are you sure you want to delete "${props.data[index] ? props.data[index].metadata.name : ""}" ?`} />
- <TableContainer component={Paper}>
- <Table className={classes.table} size="small">
- <TableHead>
- <TableRow>
- <StyledTableCell>Name</StyledTableCell>
- <StyledTableCell>Description</StyledTableCell>
- <StyledTableCell>Actions</StyledTableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {props.data.map((row, index) => (
- <StyledTableRow key={row.metadata.name + "" + index}>
- <StyledTableCell>
- {" "}
- <Link to={`/app/projects/${row.metadata.name}`}>{row.metadata.name}</Link>
- </StyledTableCell>
- <StyledTableCell className={classes.cell}>
- {row.metadata.description}
- </StyledTableCell>
- <StyledTableCell className={classes.cell}>
- <IconButton onClick={(e) => handleEdit(index)} title="Edit" >
- <EditIcon color="primary" />
- </IconButton>
- <IconButton onClick={(e) => handleDelete(index)} title="Delete" >
- <DeleteIcon color="secondary" />
- </IconButton>
- </StyledTableCell>
- </StyledTableRow>
- ))}
- </TableBody>
- </Table>
- </TableContainer>
- </>
- }
+ .catch((err) => {
+ console.log("Error deleting project : ", err);
+ });
+ }
+ setOpen(false);
+ setIndex(0);
+ };
+ const handleFormClose = () => {
+ setIndex(0);
+ setOpenForm(false);
+ };
+ const handleDelete = (index) => {
+ setIndex(index);
+ setOpen(true);
+ };
+ const handleSubmit = (data) => {
+ let payload = { metadata: data };
+ apiService
+ .updateProject(payload)
+ .then((res) => {
+ props.data[index] = res;
+ props.setProjectsData([...props.data]);
+ })
+ .catch((err) => {
+ console.log("Error updating project : ", err);
+ });
+ setOpenForm(false);
+ };
- </React.Fragment>
- );
+ return (
+ <React.Fragment>
+ {props.data && props.data.length > 0 && (
+ <>
+ <ProjectForm
+ open={openForm}
+ onClose={handleFormClose}
+ item={props.data[index]}
+ onSubmit={handleSubmit}
+ />
+ <DeleteDialog
+ open={open}
+ onClose={handleClose}
+ title={"Delete Project"}
+ content={`Are you sure you want to delete "${
+ props.data[index] ? props.data[index].metadata.name : ""
+ }" ?`}
+ />
+ <TableContainer component={Paper}>
+ <Table className={classes.table} size="small">
+ <TableHead>
+ <TableRow>
+ <StyledTableCell>Name</StyledTableCell>
+ <StyledTableCell>Description</StyledTableCell>
+ <StyledTableCell>Actions</StyledTableCell>
+ </TableRow>
+ </TableHead>
+ <TableBody>
+ {props.data.map((row, index) => (
+ <StyledTableRow key={row.metadata.name + "" + index}>
+ <StyledTableCell>
+ {" "}
+ <Link to={`/app/projects/${row.metadata.name}`}>
+ {row.metadata.name}
+ </Link>
+ </StyledTableCell>
+ <StyledTableCell className={classes.cell}>
+ {row.metadata.description}
+ </StyledTableCell>
+ <StyledTableCell className={classes.cell}>
+ <IconButton
+ onClick={(e) => handleEdit(index)}
+ title="Edit"
+ >
+ <EditIcon color="primary" />
+ </IconButton>
+ <IconButton
+ onClick={(e) => handleDelete(index)}
+ title="Delete"
+ >
+ <DeleteIcon color="secondary" />
+ </IconButton>
+ </StyledTableCell>
+ </StyledTableRow>
+ ))}
+ </TableBody>
+ </Table>
+ </TableContainer>
+ </>
+ )}
+ </React.Fragment>
+ );
}
diff --git a/src/tools/emcoui/src/appbase/AppBase.js b/src/tools/emcoui/src/appbase/AppBase.js
index 5dd3b53b..76dc4d8e 100644
--- a/src/tools/emcoui/src/appbase/AppBase.js
+++ b/src/tools/emcoui/src/appbase/AppBase.js
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React from "react";
import PropTypes from "prop-types";
import { ThemeProvider, withStyles } from "@material-ui/core/styles";
@@ -25,6 +25,7 @@ import theme from "../theme/Theme";
import apiService from "../services/apiService";
import DeploymentIntentGroups from "../deploymentIntentGroups/DeploymentIntentGroups";
import { Switch, Route, Link } from "react-router-dom";
+// import Dashboard from "../dashboard/DashboardView";
const drawerWidth = 256;
const styles = {
@@ -45,11 +46,10 @@ const styles = {
},
main: {
flex: 1,
- padding: theme.spacing(6, 4),
+ padding: theme.spacing(3, 4, 6, 4),
background: "#eaeff1",
},
footer: {
- // padding: theme.spacing(2),
background: "#eaeff1",
},
};
@@ -63,17 +63,6 @@ class AppBase extends React.Component {
};
}
- componentDidMount() {
- apiService
- .getCompositeApps({ projectName: this.state.projectName })
- .then((response) => {
- this.setState({ data: response });
- })
- .catch((err) => {
- console.log("Unable to get composite apps");
- })
- .finally();
- }
setMobileOpen = (mobileOpen) => {
this.setState({ mobileOpen });
};
@@ -114,15 +103,15 @@ class AppBase extends React.Component {
path={`${this.props.match.url}/404`}
component={() => <div>Page Not found</div>}
/>
- <Route
- exact
- path={`${this.props.match.url}/composite-apps`}
- >
+ {/* <Route exact path={`${this.props.match.url}/dashboard`}>
+ <Dashboard projectName={this.state.projectName} />
+ </Route> */}
+ <Route exact path={`${this.props.match.url}/services`}>
<CompositeApps projectName={this.state.projectName} />
</Route>
<Route
exact
- path={`${this.props.match.url}/composite-apps/:appname/:version`}
+ path={`${this.props.match.url}/services/:appname/:version`}
>
<CompositeApp projectName={this.state.projectName} />
</Route>
diff --git a/src/tools/emcoui/src/appbase/Content.js b/src/tools/emcoui/src/appbase/Content.js
index 2c907acb..eff9a561 100644
--- a/src/tools/emcoui/src/appbase/Content.js
+++ b/src/tools/emcoui/src/appbase/Content.js
@@ -95,7 +95,7 @@ function Content(props) {
</AppBar>
<div className={classes.contentWrapper}>
<Typography color="textSecondary" align="center">
- No composite apps for this project yet
+ No services for this project yet
</Typography>
</div>
</Paper>
diff --git a/src/tools/emcoui/src/appbase/Header.js b/src/tools/emcoui/src/appbase/Header.js
index 0222151f..19f148a4 100644
--- a/src/tools/emcoui/src/appbase/Header.js
+++ b/src/tools/emcoui/src/appbase/Header.js
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React from "react";
import PropTypes from "prop-types";
import AppBar from "@material-ui/core/AppBar";
@@ -57,18 +57,18 @@ function Header(props) {
let headerName = "";
let getHeaderName = () => {
- if (location.pathname === `${props.match.url}/composite-apps`) {
- headerName = "Composite Apps";
+ if (location.pathname === `${props.match.url}/dashboard`) {
+ headerName = "Dashboard";
+ } else if (location.pathname === `${props.match.url}/services`) {
+ headerName = "Services";
} else if (
location.pathname === `${props.match.url}/deployment-intent-group`
) {
headerName = "Deployment Intent Groups";
- } else if (location.pathname.includes("composite-apps")) {
+ } else if (location.pathname.includes("services")) {
headerName =
- "Composite Apps / " +
- location.pathname
- .slice(location.pathname.indexOf("composite-apps"))
- .slice(15);
+ "services / " +
+ location.pathname.slice(location.pathname.indexOf("services")).slice(9);
} else if (location.pathname === `${props.match.url}/projects`) {
headerName = "Projects";
} else if (location.pathname === `${props.match.url}/clusters`) {
diff --git a/src/tools/emcoui/src/appbase/Navigator.js b/src/tools/emcoui/src/appbase/Navigator.js
index 2df2c009..e8f16367 100644
--- a/src/tools/emcoui/src/appbase/Navigator.js
+++ b/src/tools/emcoui/src/appbase/Navigator.js
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React, { useState } from "react";
import PropTypes from "prop-types";
import clsx from "clsx";
@@ -23,7 +23,7 @@ import ListItem from "@material-ui/core/ListItem";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import HomeIcon from "@material-ui/icons/Home";
-import AppsIcon from "@material-ui/icons/Apps";
+import DeviceHubIcon from "@material-ui/icons/DeviceHub";
import DnsRoundedIcon from "@material-ui/icons/DnsRounded";
import { withRouter, Link } from "react-router-dom";
@@ -32,9 +32,9 @@ const categories = [
id: "1",
children: [
{
- id: "Composite Apps",
- icon: <AppsIcon />,
- url: "/composite-apps",
+ id: "Services",
+ icon: <DeviceHubIcon />,
+ url: "/services",
},
{
id: "Deployment Intent Groups",
@@ -85,8 +85,8 @@ const styles = (theme) => ({
marginTop: theme.spacing(2),
},
version: {
- fontSize: "15px"
- }
+ fontSize: "15px",
+ },
});
function Navigator(props) {
@@ -99,11 +99,20 @@ function Navigator(props) {
setActiveTab(location.pathname);
}
return (
- <Drawer PaperProps={props.PaperProps} variant={props.variant} open={props.open} onClose={props.onClose}>
+ <Drawer
+ PaperProps={props.PaperProps}
+ variant={props.variant}
+ open={props.open}
+ onClose={props.onClose}
+ >
<List disablePadding>
- <Link style={{ textDecoration: "none" }} to='/'>
+ <Link style={{ textDecoration: "none" }} to="/">
<ListItem
- className={clsx(classes.firebase, classes.item, classes.itemCategory)}
+ className={clsx(
+ classes.firebase,
+ classes.item,
+ classes.itemCategory
+ )}
>
<ListItemText
classes={{
@@ -111,14 +120,29 @@ function Navigator(props) {
}}
>
ONAP4K8s
- </ListItemText>
- <span
- className={clsx(classes.version)}
- >{process.env.REACT_APP_VERSION}</span>
+ </ListItemText>
+ <span className={clsx(classes.version)}>
+ {process.env.REACT_APP_VERSION}
+ </span>
</ListItem>
</Link>
- <ListItem className={clsx(classes.item, classes.itemCategory)}>
+ {/* <Link
+ style={{ textDecoration: "none" }}
+ to={{
+ pathname: `${props.match.url}/dashboard`,
+ activeItem: "childId",
+ }}
+ key={"childId"}
+ > */}
+ <ListItem
+ button
+ className={clsx(
+ classes.item,
+ classes.itemCategory,
+ activeItem.includes("dashboard") && classes.itemActiveItem
+ )}
+ >
<ListItemIcon className={classes.itemIcon}>
<HomeIcon />
</ListItemIcon>
@@ -130,10 +154,18 @@ function Navigator(props) {
Dashboard
</ListItemText>
</ListItem>
+ {/* </Link> */}
{categories.map(({ id, children }) => (
<React.Fragment key={id}>
{children.map(({ id: childId, icon, url }) => (
- <Link style={{ textDecoration: "none" }} to={{ pathname: `${props.match.url}${url}`, activeItem: childId }} key={childId}>
+ <Link
+ style={{ textDecoration: "none" }}
+ to={{
+ pathname: `${props.match.url}${url}`,
+ activeItem: childId,
+ }}
+ key={childId}
+ >
<ListItem
button
className={clsx(
diff --git a/src/tools/emcoui/src/assets/icons/empty.svg b/src/tools/emcoui/src/assets/icons/empty.svg
new file mode 100644
index 00000000..f4e020ed
--- /dev/null
+++ b/src/tools/emcoui/src/assets/icons/empty.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 24 24" height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m21.5 22h-19c-1.378 0-2.5-1.121-2.5-2.5v-7c0-.07.015-.141.044-.205l3.969-8.82c.404-.896 1.299-1.475 2.28-1.475h11.414c.981 0 1.876.579 2.28 1.475l3.969 8.82c.029.064.044.135.044.205v7c0 1.379-1.122 2.5-2.5 2.5zm-20.5-9.393v6.893c0 .827.673 1.5 1.5 1.5h19c.827 0 1.5-.673 1.5-1.5v-6.893l-3.925-8.723c-.242-.536-.779-.884-1.368-.884h-11.414c-.589 0-1.126.348-1.368.885z"/><path d="m16.807 17h-9.614c-.622 0-1.186-.391-1.404-.973l-1.014-2.703c-.072-.194-.26-.324-.468-.324h-3.557c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h3.557c.622 0 1.186.391 1.405.973l1.013 2.703c.073.194.261.324.468.324h9.613c.208 0 .396-.13.468-.324l1.013-2.703c.22-.582.784-.973 1.406-.973h3.807c.276 0 .5.224.5.5s-.224.5-.5.5h-3.807c-.208 0-.396.13-.468.324l-1.013 2.703c-.219.582-.784.973-1.405.973z"/></svg> \ No newline at end of file
diff --git a/src/tools/emcoui/src/common/ExpandableCard.jsx b/src/tools/emcoui/src/common/ExpandableCard.jsx
new file mode 100644
index 00000000..1d2ea9e6
--- /dev/null
+++ b/src/tools/emcoui/src/common/ExpandableCard.jsx
@@ -0,0 +1,94 @@
+//=======================================================================
+// Copyright (c) 2017-2020 Aarna Networks, Inc.
+// All rights reserved.
+// ======================================================================
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================================================================
+
+import React, { useState } from "react";
+import { makeStyles } from "@material-ui/core/styles";
+import clsx from "clsx";
+import Card from "@material-ui/core/Card";
+import CardHeader from "@material-ui/core/CardHeader";
+import CardContent from "@material-ui/core/CardContent";
+import Collapse from "@material-ui/core/Collapse";
+import IconButton from "@material-ui/core/IconButton";
+import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
+import StorageIcon from "@material-ui/icons/Storage";
+import ErrorIcon from "@material-ui/icons/Error";
+
+const useStyles = makeStyles((theme) => ({
+ root: {
+ width: "100%",
+ },
+ expand: {
+ transform: "rotate(0deg)",
+ marginLeft: "auto",
+ transition: theme.transitions.create("transform", {
+ duration: theme.transitions.duration.shortest,
+ }),
+ },
+ expandOpen: {
+ transform: "rotate(180deg)",
+ },
+}));
+const ExpandableCard = (props) => {
+ const classes = useStyles();
+ const [expanded, setExpanded] = useState(false);
+
+ const handleExpandClick = () => {
+ if (!expanded) {
+ setExpanded(!expanded);
+ } else {
+ setExpanded(!expanded);
+ }
+ };
+
+ return (
+ <>
+ <Card className={classes.root}>
+ <CardHeader
+ onClick={handleExpandClick}
+ avatar={
+ <>
+ <StorageIcon fontSize="large" />
+ </>
+ }
+ action={
+ <>
+ {props.error && (
+ <ErrorIcon color="error" style={{ verticalAlign: "middle" }} />
+ )}
+ <IconButton
+ className={clsx(classes.expand, {
+ [classes.expandOpen]: expanded,
+ })}
+ onClick={handleExpandClick}
+ aria-expanded={expanded}
+ >
+ <ExpandMoreIcon />
+ </IconButton>
+ </>
+ }
+ title={props.title}
+ subheader={props.description}
+ />
+ <Collapse in={expanded} timeout="auto" unmountOnExit>
+ <CardContent>{props.content}</CardContent>
+ </Collapse>
+ </Card>
+ </>
+ );
+};
+
+ExpandableCard.propTypes = {};
+
+export default ExpandableCard;
diff --git a/src/tools/emcoui/src/common/FileUpload.jsx b/src/tools/emcoui/src/common/FileUpload.jsx
index 847951e9..97d34bc2 100644
--- a/src/tools/emcoui/src/common/FileUpload.jsx
+++ b/src/tools/emcoui/src/common/FileUpload.jsx
@@ -11,58 +11,64 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
-import React from 'react';
-import PropTypes from 'prop-types';
+// ========================================================================
+import React from "react";
+import PropTypes from "prop-types";
import FileCopyIcon from "@material-ui/icons/FileCopy";
import CloudUploadIcon from "@material-ui/icons/CloudUpload";
-import './fileUpload.css'
+import "./fileUpload.css";
const FileUpload = (props) => {
- return (
- <>
- <div className="file-upload">
- <div
- className="file-upload-wrap"
- style={{
- border: props.file && props.file.name && "2px dashed rgba(0, 131, 143, 1)"
- }}
- >
- <input
- required
- className="file-upload-input"
- type="file"
- accept={props.accept ? props.accept : "*"}
- name="file"
- onBlur={props.handleBlur ? props.handleBlur : null}
- onChange={(event) => {
- props.setFieldValue("file", event.currentTarget.files[0]);
- }}
- />
+ return (
+ <>
+ <div className="file-upload">
+ <div
+ className="file-upload-wrap"
+ style={{
+ border:
+ props.file &&
+ props.file.name &&
+ "2px dashed rgba(0, 131, 143, 1)",
+ }}
+ >
+ <input
+ required
+ className="file-upload-input"
+ type="file"
+ accept={props.accept ? props.accept : "*"}
+ name="file"
+ onBlur={props.handleBlur ? props.handleBlur : null}
+ onChange={(event) => {
+ props.setFieldValue(props.name, event.currentTarget.files[0]);
+ }}
+ />
- <div className="file-upload-text">
- {(props.file && props.file.name) ? (<>
- <span>
- <FileCopyIcon color="primary" />
- </span>
- <span style={{ fontWeight: 600 }}>{props.file.name}</span>
- </>) : (<>
- <span>
- <CloudUploadIcon />
- </span>
- <span>
- Drag And Drop or Click To Upload
- </span>
- </>)}
- </div>
- </div>
- </div>
- </>);
+ <div className="file-upload-text">
+ {props.file && props.file.name ? (
+ <>
+ <span>
+ <FileCopyIcon color="primary" />
+ </span>
+ <span style={{ fontWeight: 600 }}>{props.file.name}</span>
+ </>
+ ) : (
+ <>
+ <span>
+ <CloudUploadIcon />
+ </span>
+ <span>Drag And Drop or Click To Upload</span>
+ </>
+ )}
+ </div>
+ </div>
+ </div>
+ </>
+ );
};
FileUpload.propTypes = {
- handleBlur: PropTypes.func,
- setFieldValue: PropTypes.func.isRequired,
+ handleBlur: PropTypes.func,
+ setFieldValue: PropTypes.func.isRequired,
};
export default FileUpload;
diff --git a/src/tools/emcoui/src/common/Form.jsx b/src/tools/emcoui/src/common/Form.jsx
index e9fe3a2d..6e8eee2e 100644
--- a/src/tools/emcoui/src/common/Form.jsx
+++ b/src/tools/emcoui/src/common/Form.jsx
@@ -11,144 +11,155 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
-import React from 'react';
-import PropTypes from 'prop-types';
-import { withStyles } from '@material-ui/core/styles';
-import Button from '@material-ui/core/Button';
+// ========================================================================
+import React from "react";
+import PropTypes from "prop-types";
+import { withStyles } from "@material-ui/core/styles";
+import Button from "@material-ui/core/Button";
-import Dialog from '@material-ui/core/Dialog';
-import MuiDialogTitle from '@material-ui/core/DialogTitle';
-import MuiDialogContent from '@material-ui/core/DialogContent';
-import MuiDialogActions from '@material-ui/core/DialogActions';
-import IconButton from '@material-ui/core/IconButton';
-import CloseIcon from '@material-ui/icons/Close';
-import Typography from '@material-ui/core/Typography';
-import { TextField } from '@material-ui/core';
+import Dialog from "@material-ui/core/Dialog";
+import MuiDialogTitle from "@material-ui/core/DialogTitle";
+import MuiDialogContent from "@material-ui/core/DialogContent";
+import MuiDialogActions from "@material-ui/core/DialogActions";
+import IconButton from "@material-ui/core/IconButton";
+import CloseIcon from "@material-ui/icons/Close";
+import Typography from "@material-ui/core/Typography";
+import { TextField } from "@material-ui/core";
import * as Yup from "yup";
-import { Formik } from 'formik';
+import { Formik } from "formik";
const styles = (theme) => ({
- root: {
- margin: 0,
- padding: theme.spacing(2),
- },
- closeButton: {
- position: 'absolute',
- right: theme.spacing(1),
- top: theme.spacing(1),
- color: theme.palette.grey[500],
- },
+ root: {
+ margin: 0,
+ padding: theme.spacing(2),
+ },
+ closeButton: {
+ position: "absolute",
+ right: theme.spacing(1),
+ top: theme.spacing(1),
+ color: theme.palette.grey[500],
+ },
});
const DialogTitle = withStyles(styles)((props) => {
- const { children, classes, onClose, ...other } = props;
- return (
- <MuiDialogTitle disableTypography className={classes.root} {...other}>
- <Typography variant="h6">{children}</Typography>
- {onClose ? (
- <IconButton className={classes.closeButton} onClick={onClose}>
- <CloseIcon />
- </IconButton>
- ) : null}
- </MuiDialogTitle>
- );
+ const { children, classes, onClose, ...other } = props;
+ return (
+ <MuiDialogTitle disableTypography className={classes.root} {...other}>
+ <Typography variant="h6">{children}</Typography>
+ {onClose ? (
+ <IconButton className={classes.closeButton} onClick={onClose}>
+ <CloseIcon />
+ </IconButton>
+ ) : null}
+ </MuiDialogTitle>
+ );
});
const DialogActions = withStyles((theme) => ({
- root: {
- margin: 0,
- padding: theme.spacing(1),
- },
+ root: {
+ margin: 0,
+ padding: theme.spacing(1),
+ },
}))(MuiDialogActions);
const DialogContent = withStyles((theme) => ({
- root: {
- padding: theme.spacing(2),
- }
+ root: {
+ padding: theme.spacing(2),
+ },
}))(MuiDialogContent);
-const schema = Yup.object(
- {
- name: Yup.string().required(),
- description: Yup.string(),
- })
+const schema = Yup.object({
+ name: Yup.string().required(),
+ description: Yup.string(),
+});
const CreateForm = (props) => {
- const { onClose, item, open, onSubmit } = props;
- const buttonLabel = item ? "OK" : "Create"
- const title = item ? "Edit" : "Create"
- const handleClose = () => {
- onClose();
- };
- let initialValues = item ? { name: item.metadata.name, description: item.metadata.description } : { name: "", description: "" }
+ const { onClose, item, open, onSubmit } = props;
+ const buttonLabel = item ? "OK" : "Create";
+ const title = item ? "Edit" : "Create";
+ const handleClose = () => {
+ onClose();
+ };
+ let initialValues = item
+ ? { name: item.metadata.name, description: item.metadata.description }
+ : { name: "", description: "" };
- return (
- <Dialog maxWidth={"xs"} onClose={handleClose} aria-labelledby="customized-dialog-title" open={open} disableBackdropClick>
- <DialogTitle id="simple-dialog-title">{title}</DialogTitle>
- <Formik
- initialValues={initialValues}
- onSubmit={async values => {
- onSubmit(values);
- }}
- validationSchema={schema}
- >
- {props => {
- const {
- touched,
- errors,
- isSubmitting,
- handleChange,
- handleBlur,
- handleSubmit
- } = props;
- return (
- <form noValidate onSubmit={handleSubmit}>
- <DialogContent dividers>
- <TextField
- style={{ width: "100%", marginBottom: "10px" }}
- id="name"
- label="Name"
- name="name"
- type="text"
- onChange={handleChange}
- onBlur={handleBlur}
- helperText={(errors.name && touched.name && (
- "Name is required"
- ))}
- required
- error={errors.name && touched.name}
- />
- <TextField
- style={{ width: "100%", marginBottom: "25px" }}
- name="description"
- onChange={handleChange}
- onBlur={handleBlur}
- id="description"
- label="Description"
- multiline
- rowsMax={4}
- />
- </DialogContent>
- <DialogActions>
- <Button autoFocus onClick={handleClose} color="secondary">
- Cancel
- </Button>
- <Button autoFocus type="submit" color="primary" disabled={isSubmitting}>
- {buttonLabel}
- </Button>
- </DialogActions>
- </form>
- );
- }}
- </Formik>
- </Dialog>
- );
+ return (
+ <Dialog
+ maxWidth={"xs"}
+ onClose={handleClose}
+ aria-labelledby="customized-dialog-title"
+ open={open}
+ disableBackdropClick
+ >
+ <DialogTitle id="simple-dialog-title">{title}</DialogTitle>
+ <Formik
+ initialValues={initialValues}
+ onSubmit={async (values) => {
+ onSubmit(values);
+ }}
+ validationSchema={schema}
+ >
+ {(props) => {
+ const {
+ touched,
+ errors,
+ isSubmitting,
+ handleChange,
+ handleBlur,
+ handleSubmit,
+ submitCount,
+ } = props;
+ return (
+ <form noValidate onSubmit={handleSubmit}>
+ <DialogContent dividers>
+ <TextField
+ style={{ width: "100%", marginBottom: "10px" }}
+ id="name"
+ label="Name"
+ name="name"
+ type="text"
+ onChange={handleChange}
+ onBlur={handleBlur}
+ helperText={errors.name && touched.name && "Name is required"}
+ required
+ error={errors.name && touched.name}
+ />
+ <TextField
+ style={{ width: "100%", marginBottom: "25px" }}
+ name="description"
+ onChange={handleChange}
+ onBlur={handleBlur}
+ id="description"
+ label="Description"
+ multiline
+ rowsMax={4}
+ />
+ </DialogContent>
+ <DialogActions>
+ <Button autoFocus onClick={handleClose} color="secondary">
+ Cancel
+ </Button>
+ <Button
+ autoFocus
+ type="submit"
+ color="primary"
+ disabled={isSubmitting || submitCount > 0}
+ >
+ {buttonLabel}
+ </Button>
+ </DialogActions>
+ </form>
+ );
+ }}
+ </Formik>
+ </Dialog>
+ );
};
CreateForm.propTypes = {
- onClose: PropTypes.func.isRequired,
- open: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired,
+ open: PropTypes.bool.isRequired,
};
export default CreateForm;
diff --git a/src/tools/emcoui/src/compositeApps/CompositeApp.jsx b/src/tools/emcoui/src/compositeApps/CompositeApp.jsx
index 34d07fbc..8b2c2b10 100644
--- a/src/tools/emcoui/src/compositeApps/CompositeApp.jsx
+++ b/src/tools/emcoui/src/compositeApps/CompositeApp.jsx
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React from "react";
import Tab from "@material-ui/core/Tab";
import Tabs from "@material-ui/core/Tabs";
@@ -19,15 +19,15 @@ import Paper from "@material-ui/core/Paper";
import { withStyles } from "@material-ui/core/styles";
import Box from "@material-ui/core/Box";
import PropTypes from "prop-types";
-import Apps from "../compositeApps/apps/Apps";
-import CompositeProfiles from "../compositeApps/compositeProfiles/CompositeProfiles";
-import Intents from "../compositeApps/intents/GenericPlacementIntents";
import BackIcon from "@material-ui/icons/ArrowBack";
import { withRouter } from "react-router-dom";
import { IconButton } from "@material-ui/core";
import apiService from "../services/apiService";
import Spinner from "../common/Spinner";
-import NetworkIntent from "../networkIntents/NetworkIntents";
+import Apps from "../compositeApps/apps/Apps";
+import CompositeProfiles from "../compositeApps/compositeProfiles/CompositeProfiles";
+// import Intents from "../compositeApps/intents/GenericPlacementIntents";
+// import NetworkIntent from "../networkIntents/NetworkIntents";
const lightColor = "rgba(255, 255, 255, 0.7)";
@@ -134,8 +134,6 @@ class CompositeApp extends React.Component {
>
<Tab label="Apps" />
<Tab label="Composite Profiles" />
- <Tab label="Generic Placement Intents" />
- <Tab label="Network Controller Intents" />
</Tabs>
{this.state.isLoading && <Spinner />}
@@ -158,22 +156,6 @@ class CompositeApp extends React.Component {
appsData={this.state.appsData}
/>
</TabPanel>
- <TabPanel value={this.state.activeTab} index={2}>
- <Intents
- projectName={this.props.projectName}
- compositeAppName={this.state.compositeAppName}
- compositeAppVersion={this.state.compositeAppVersion}
- appsData={this.state.appsData}
- />
- </TabPanel>
- <TabPanel value={this.state.activeTab} index={3}>
- <NetworkIntent
- projectName={this.props.projectName}
- compositeAppName={this.state.compositeAppName}
- compositeAppVersion={this.state.compositeAppVersion}
- appsData={this.state.appsData}
- />
- </TabPanel>
</>
)}
</Paper>
@@ -181,7 +163,5 @@ class CompositeApp extends React.Component {
);
}
}
-
CompositeApp.propTypes = {};
-
export default withStyles(styles)(withRouter(CompositeApp));
diff --git a/src/tools/emcoui/src/compositeApps/CompositeAppTable.jsx b/src/tools/emcoui/src/compositeApps/CompositeAppTable.jsx
index 220d7df8..926ab545 100644
--- a/src/tools/emcoui/src/compositeApps/CompositeAppTable.jsx
+++ b/src/tools/emcoui/src/compositeApps/CompositeAppTable.jsx
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React, { useState } from "react";
import { withStyles, makeStyles } from "@material-ui/core/styles";
import Table from "@material-ui/core/Table";
@@ -21,13 +21,15 @@ import TableContainer from "@material-ui/core/TableContainer";
import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import Paper from "@material-ui/core/Paper";
-import { Link } from "react-router-dom";
+// import { Link } from "react-router-dom";
import IconButton from "@material-ui/core/IconButton";
import EditIcon from "@material-ui/icons/Edit";
-import DeleteIcon from '@material-ui/icons/Delete';
+import DeleteIcon from "@material-ui/icons/Delete";
import CreateCompositeAppForm from "./dialogs/CompositeAppForm";
import apiService from "../services/apiService";
import DeleteDialog from "../common/Dialogue";
+import { Link, withRouter } from "react-router-dom";
+import Notification from "../common/Notification";
const StyledTableCell = withStyles((theme) => ({
body: {
@@ -52,50 +54,80 @@ const useStyles = makeStyles({
},
});
-export default function CustomizedTables({ data, ...props }) {
+function CustomizedTables({ data, ...props }) {
const classes = useStyles();
const [openForm, setOpenForm] = useState(false);
const [activeRowIndex, setActiveRowIndex] = useState(0);
const [row, setRow] = useState({});
const [open, setOpen] = useState(false);
+ const [notificationDetails, setNotificationDetails] = useState({});
let onEditCompositeApp = (row, index) => {
setActiveRowIndex(index);
setRow(row);
setOpenForm(true);
- }
+ // props.history.push(`services/${row.metadata.name}/${row.spec.version}`);
+ };
const handleCloseForm = (fields) => {
if (fields) {
- let request = { payload: { name: fields.name, description: fields.description, spec: { version: fields.version } }, projectName: props.projectName, compositeAppVersion: row.spec.version };
- apiService.updateCompositeApp(request).then(res => {
- let updatedData = data.slice();
- updatedData.splice(activeRowIndex, 1);
- updatedData.push(res);
- props.handleUpdateState(updatedData);
- }).catch(err => {
- console.log("error creating composite app : ", err)
- }).finally(() => {
- setOpenForm(false);
- });
- }
- else {
+ let request = {
+ payload: {
+ name: fields.name,
+ description: fields.description,
+ spec: { version: fields.version },
+ },
+ projectName: props.projectName,
+ compositeAppVersion: row.spec.version,
+ };
+ apiService
+ .updateCompositeApp(request)
+ .then((res) => {
+ let updatedData = data.slice();
+ updatedData.splice(activeRowIndex, 1);
+ updatedData.push(res);
+ props.handleUpdateState(updatedData);
+ })
+ .catch((err) => {
+ console.log("error creating composite app : ", err);
+ })
+ .finally(() => {
+ setOpenForm(false);
+ });
+ } else {
setOpenForm(false);
}
};
const handleDeleteCompositeApp = (index) => {
setActiveRowIndex(index);
setOpen(true);
- }
- const handleClose = el => {
+ };
+ const handleClose = (el) => {
if (el.target.innerText === "Delete") {
- let request = { projectName: props.projectName, compositeAppName: data[activeRowIndex].metadata.name, compositeAppVersion: data[activeRowIndex].spec.version };
- apiService.deleteCompositeApp(request).then(() => {
- console.log("cluster deleted");
- data.splice(activeRowIndex, 1);
- let updatedData = data.slice();
- props.handleUpdateState(updatedData);
- }).catch(err => {
- console.log("Error deleting cluster : ", err)
- })
+ let request = {
+ projectName: props.projectName,
+ compositeAppName: data[activeRowIndex].metadata.name,
+ compositeAppVersion: data[activeRowIndex].spec.version,
+ };
+ apiService
+ .deleteCompositeApp(request)
+ .then(() => {
+ console.log("cluster deleted");
+ data.splice(activeRowIndex, 1);
+ let updatedData = data.slice();
+ props.handleUpdateState(updatedData);
+ })
+ .catch((err) => {
+ console.log("Error deleting cluster : ", err);
+ let message = "Error deleting service";
+ if (err.response.data.includes("Non emtpy DIG in service")) {
+ message =
+ "Error deleting service : please delete deployment intent group first";
+ }
+ setNotificationDetails({
+ show: true,
+ message: message,
+ severity: "error",
+ });
+ });
}
setOpen(false);
setActiveRowIndex(0);
@@ -103,11 +135,22 @@ export default function CustomizedTables({ data, ...props }) {
return (
<>
- {data && (data.length > 0) &&
- (<>
- <CreateCompositeAppForm open={openForm} handleClose={handleCloseForm} item={row} />
- <DeleteDialog open={open} onClose={handleClose} title={"Delete Cluster"}
- content={`Are you sure you want to delete "${data[activeRowIndex] ? data[activeRowIndex].metadata.name : ""}" ?`} />
+ <Notification notificationDetails={notificationDetails} />
+ {data && data.length > 0 && (
+ <>
+ <CreateCompositeAppForm
+ open={openForm}
+ handleClose={handleCloseForm}
+ item={row}
+ />
+ <DeleteDialog
+ open={open}
+ onClose={handleClose}
+ title={"Delete Service"}
+ content={`Are you sure you want to delete "${
+ data[activeRowIndex] ? data[activeRowIndex].metadata.name : ""
+ }" ?`}
+ />
<TableContainer component={Paper}>
<Table className={classes.table} size="small">
<TableHead>
@@ -122,8 +165,12 @@ export default function CustomizedTables({ data, ...props }) {
{data.map((row, index) => (
<StyledTableRow key={row.metadata.name}>
<StyledTableCell>
- {" "}
- <Link to={`composite-apps/${row.metadata.name}/${row.spec.version}`}>{row.metadata.name}</Link>
+ <Link
+ to={`services/${row.metadata.name}/${row.spec.version}`}
+ >
+ {row.metadata.name}
+ </Link>
+ {/* {row.metadata.name} */}
</StyledTableCell>
<StyledTableCell className={classes.cell}>
{row.metadata.description}
@@ -132,10 +179,18 @@ export default function CustomizedTables({ data, ...props }) {
{row.spec.version}
</StyledTableCell>
<StyledTableCell className={classes.cell}>
- <IconButton onClick={(e) => onEditCompositeApp(row, index)} title="Edit">
+ {/* <IconButton
+ onClick={(e) => onEditCompositeApp(row, index)}
+ title="Edit"
+ >
<EditIcon color="primary" />
- </IconButton>
- <IconButton color="secondary" onClick={() => { handleDeleteCompositeApp(index) }}>
+ </IconButton> */}
+ <IconButton
+ color="secondary"
+ onClick={() => {
+ handleDeleteCompositeApp(index);
+ }}
+ >
<DeleteIcon />
</IconButton>
</StyledTableCell>
@@ -144,7 +199,11 @@ export default function CustomizedTables({ data, ...props }) {
</TableBody>
</Table>
</TableContainer>
- </>)}
- {(!data || (data.length === 0)) && (<span>No Clusters</span>)}
- </>)
+ </>
+ )}
+ {(!data || data.length === 0) && <span>No Composite Apps</span>}
+ </>
+ );
}
+
+export default withRouter(CustomizedTables);
diff --git a/src/tools/emcoui/src/compositeApps/CompositeApps.jsx b/src/tools/emcoui/src/compositeApps/CompositeApps.jsx
index e7901ff9..5c540039 100644
--- a/src/tools/emcoui/src/compositeApps/CompositeApps.jsx
+++ b/src/tools/emcoui/src/compositeApps/CompositeApps.jsx
@@ -11,14 +11,15 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React from "react";
import CompositeAppTable from "./CompositeAppTable";
-import { withStyles, Button, Grid } from "@material-ui/core";
+import { withStyles, Button, Grid, Typography } from "@material-ui/core";
import CreateCompositeAppForm from "./dialogs/CompositeAppForm";
import AddIcon from "@material-ui/icons/Add";
import apiService from "../services/apiService";
import Spinner from "../common/Spinner";
+import { ReactComponent as EmptyIcon } from "../assets/icons/empty.svg";
const styles = {
root: {
display: "flex",
@@ -57,22 +58,43 @@ class CompositeApps extends React.Component {
handleClose = (fields) => {
if (fields) {
- let request = {
- payload: {
- metadata: { name: fields.name, description: fields.description },
- spec: { version: fields.version },
- },
- projectName: this.props.projectName,
+ const formData = new FormData();
+ let appsData = [];
+ fields.apps.forEach((app) => {
+ //add files for each app
+ formData.append(`${app.appName}_file`, app.file);
+ formData.append(`${app.appName}_profile`, app.profilePackageFile);
+ appsData.push({
+ metadata: {
+ name: app.appName,
+ description: app.description ? app.description : "na",
+ filename: app.file.name,
+ },
+ profileMetadata: {
+ name: `${app.appName}_profile`,
+ filename: app.profilePackageFile.name,
+ },
+ clusters: app.clusters,
+ });
+ });
+
+ let servicePayload = {
+ name: fields.name,
+ description: fields.description,
+ spec: { projectName: this.props.projectName, appsData },
};
+ formData.append("servicePayload", JSON.stringify(servicePayload));
+ let request = { projectName: this.props.projectName, payload: formData };
apiService
- .createCompositeApp(request)
+ .addService(request)
.then((res) => {
+ console.log("create service response : " + res);
if (this.state.data && this.state.data.length > 0)
this.setState({ data: [...this.state.data, res] });
else this.setState({ data: [res] });
})
.catch((err) => {
- console.log("error creating composite app : ", err);
+ console.log("error adding app : ", err);
});
}
this.setState({ open: false });
@@ -88,32 +110,48 @@ class CompositeApps extends React.Component {
{this.state.isLoading && <Spinner />}
{!this.state.isLoading && (
<>
- <Button
- variant="outlined"
- color="primary"
- startIcon={<AddIcon />}
- onClick={this.handleCreateCompositeApp}
- >
- Create Composite App
- </Button>
<CreateCompositeAppForm
open={this.state.open}
handleClose={this.handleClose}
/>
<Grid container spacing={2} alignItems="center">
- <Grid item xs style={{ marginTop: "20px" }}>
- {this.state.data && this.state.data.length > 0 && (
+ <Grid item xs={12}>
+ <Button
+ variant="outlined"
+ color="primary"
+ startIcon={<AddIcon />}
+ onClick={this.handleCreateCompositeApp}
+ >
+ Add service
+ </Button>
+ </Grid>
+ {this.state.data && this.state.data.length > 0 && (
+ <Grid item xs={12}>
<CompositeAppTable
data={this.state.data}
projectName={this.props.projectName}
handleUpdateState={this.handleUpdateState}
/>
- )}
- {(!this.state.data || this.state.data.length === 0) && (
- <span>No Composite Apps</span>
- )}
- </Grid>
+ </Grid>
+ )}
</Grid>
+ {(!this.state.data || this.state.data.length === 0) && (
+ <Grid
+ container
+ spacing={2}
+ direction="column"
+ alignItems="center"
+ >
+ <Grid style={{ marginTop: "60px" }} item xs={6}>
+ <EmptyIcon style={{ height: "100px", width: "100px" }} />
+ </Grid>
+ <Grid item xs={12}>
+ <Typography variant="h6">
+ No service found, start by adding a service
+ </Typography>
+ </Grid>
+ </Grid>
+ )}
</>
)}
</>
diff --git a/src/tools/emcoui/src/compositeApps/apps/Apps.jsx b/src/tools/emcoui/src/compositeApps/apps/Apps.jsx
index 14be60c2..0e3638b3 100644
--- a/src/tools/emcoui/src/compositeApps/apps/Apps.jsx
+++ b/src/tools/emcoui/src/compositeApps/apps/Apps.jsx
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React, { useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import Card from "@material-ui/core/Card";
@@ -19,7 +19,7 @@ import CardContent from "@material-ui/core/CardContent";
import IconButton from "@material-ui/core/IconButton";
import Typography from "@material-ui/core/Typography";
import DeleteIcon from "@material-ui/icons/Delete";
-import EditIcon from "@material-ui/icons/Edit";
+// import EditIcon from "@material-ui/icons/Edit";
import { Grid, Button, Tooltip } from "@material-ui/core";
import AddIcon from "@material-ui/icons/Add";
import apiService from "../../services/apiService";
@@ -144,7 +144,7 @@ const Apps = ({ data, onStateChange, ...props }) => {
};
return (
<>
- <Button
+ {/* <Button
variant="outlined"
color="primary"
startIcon={<AddIcon />}
@@ -152,7 +152,7 @@ const Apps = ({ data, onStateChange, ...props }) => {
size="small"
>
Add App
- </Button>
+ </Button> */}
<AppForm
open={formOpen}
onClose={handleFormClose}
@@ -200,6 +200,7 @@ const Apps = ({ data, onStateChange, ...props }) => {
</Typography>
</CardContent>
<div className={classes.controls}>
+ {/* //edit app api is not implemented yet
<IconButton
onClick={handleEditApp.bind(this, value)}
color="primary"
@@ -212,7 +213,7 @@ const Apps = ({ data, onStateChange, ...props }) => {
onClick={() => handleDeleteApp(index)}
>
<DeleteIcon />
- </IconButton>
+ </IconButton> */}
</div>
</div>
</Card>
diff --git a/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfileCard.jsx b/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfileCard.jsx
index 58161e8e..d565eba7 100644
--- a/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfileCard.jsx
+++ b/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfileCard.jsx
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React, { useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import clsx from "clsx";
@@ -35,7 +35,7 @@ import TableCell from "@material-ui/core/TableCell";
import Paper from "@material-ui/core/Paper";
import TableBody from "@material-ui/core/TableBody";
import apiService from "../../services/apiService";
-import EditIcon from "@material-ui/icons/Edit";
+// import EditIcon from "@material-ui/icons/Edit";
import DeleteIcon from "@material-ui/icons/Delete";
import ProfileForm from "./ProfileForm";
import DeleteDialog from "../../common/Dialogue";
@@ -196,7 +196,7 @@ export default function RecipeReviewCard(props) {
/>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent>
- <Button
+ {/* <Button
disabled={!(props.appsData && props.appsData.length > 0)}
variant="outlined"
size="small"
@@ -208,11 +208,12 @@ export default function RecipeReviewCard(props) {
}}
>
Add Profile
- </Button>
- <Button
+ </Button> */}
+ {/* <Button
variant="outlined"
size="small"
color="secondary"
+ disabled={data && data.length > 0}
style={{ float: "right" }}
startIcon={<DeleteIcon />}
onClick={() => {
@@ -220,7 +221,7 @@ export default function RecipeReviewCard(props) {
}}
>
Delete Composite Profile
- </Button>
+ </Button> */}
{data && data.length > 0 && (
<>
<DeleteDialog
@@ -238,7 +239,7 @@ export default function RecipeReviewCard(props) {
<StyledTableCell>Name</StyledTableCell>
<StyledTableCell>Description</StyledTableCell>
<StyledTableCell>App</StyledTableCell>
- <StyledTableCell>Actions</StyledTableCell>
+ {/* <StyledTableCell>Actions</StyledTableCell> */}
</TableRow>
</TableHead>
<TableBody>
@@ -253,7 +254,9 @@ export default function RecipeReviewCard(props) {
<StyledTableCell>
{profile.spec["app-name"]}
</StyledTableCell>
- <StyledTableCell>
+ {/* <StyledTableCell>
+
+ //edit profile api is not implemented yet
<IconButton
onClick={(e) => handleEdit(index)}
title="Edit"
@@ -266,7 +269,7 @@ export default function RecipeReviewCard(props) {
>
<DeleteIcon color="secondary" />
</IconButton>
- </StyledTableCell>
+ </StyledTableCell> */}
</StyledTableRow>
))}
</TableBody>
@@ -275,7 +278,7 @@ export default function RecipeReviewCard(props) {
</>
)}
{!(props.appsData && props.appsData.length > 0) && (
- <div>No apps found for adding profile</div>
+ <div>No app found for adding profile</div>
)}
</CardContent>
</Collapse>
diff --git a/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfiles.jsx b/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfiles.jsx
index 25ecaaee..26629e03 100644
--- a/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfiles.jsx
+++ b/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfiles.jsx
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React, { useState, useEffect } from "react";
import Card from "./CompositeProfileCard";
import { Button, Grid } from "@material-ui/core";
@@ -82,7 +82,6 @@ const CompositeProfiles = (props) => {
compositeAppVersion: props.compositeAppVersion,
compositeProfileName: data[index].metadata.name,
};
- console.log(request);
apiService
.deleteCompositeProfile(request)
.then(() => {
@@ -115,7 +114,7 @@ const CompositeProfiles = (props) => {
}"`}
/>
- <Button
+ {/* <Button
disabled={isLoading}
variant="outlined"
color="primary"
@@ -123,7 +122,7 @@ const CompositeProfiles = (props) => {
onClick={handleAddCompositeProfile}
>
Add Composite Profile
- </Button>
+ </Button> */}
<Form onClose={handleCloseForm} open={openForm} onSubmit={handleSubmit} />
<Grid
container
diff --git a/src/tools/emcoui/src/compositeApps/dialogs/AppForm.jsx b/src/tools/emcoui/src/compositeApps/dialogs/AppForm.jsx
new file mode 100644
index 00000000..12dd7dd8
--- /dev/null
+++ b/src/tools/emcoui/src/compositeApps/dialogs/AppForm.jsx
@@ -0,0 +1,149 @@
+//=======================================================================
+// Copyright (c) 2017-2020 Aarna Networks, Inc.
+// All rights reserved.
+// ======================================================================
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================================================================
+import React from "react";
+import ExpandableCard from "../../common/ExpandableCard";
+import { Grid, Paper, TextField } from "@material-ui/core";
+import FileUpload from "../../common/FileUpload";
+
+function AppDetailsForm({ formikProps, ...props }) {
+ return (
+ <>
+ <Paper
+ style={{
+ width: "100%",
+ padding: "20px",
+ maxHeight: "395px",
+ overflowY: "auto",
+ scrollbarWidth: "thin",
+ }}
+ >
+ <Grid container spacing={3}>
+ <Grid item xs={6}>
+ <TextField
+ fullWidth
+ value={formikProps.values.apps[props.index].appName}
+ name={`apps[${props.index}].appName`}
+ id="app-name"
+ label="App name"
+ size="small"
+ onChange={formikProps.handleChange}
+ onBlur={formikProps.handleBlur}
+ required
+ helperText={
+ formikProps.errors.apps &&
+ formikProps.errors.apps[props.index] &&
+ formikProps.errors.apps[props.index].appName
+ }
+ error={
+ formikProps.errors.apps &&
+ formikProps.errors.apps[props.index] &&
+ formikProps.errors.apps[props.index].appName &&
+ true
+ }
+ />
+ </Grid>
+ <Grid item xs={6}>
+ <TextField
+ fullWidth
+ value={formikProps.values.apps[props.index].description}
+ name={`apps[${props.index}].description`}
+ id="app-description"
+ label="Description"
+ multiline
+ onChange={formikProps.handleChange}
+ onBlur={formikProps.handleBlur}
+ rowsMax={4}
+ />
+ </Grid>
+ <Grid item xs={6}>
+ <label
+ style={{ marginTop: "20px" }}
+ className="MuiFormLabel-root MuiInputLabel-root"
+ htmlFor="file"
+ id="file-label"
+ >
+ App tgz file
+ <span className="MuiFormLabel-asterisk MuiInputLabel-asterisk">
+  *
+ </span>
+ </label>
+ <FileUpload
+ setFieldValue={formikProps.setFieldValue}
+ file={formikProps.values.apps[props.index].file}
+ onBlur={formikProps.handleBlur}
+ name={`apps[${props.index}].file`}
+ accept={".tgz"}
+ />
+ {formikProps.errors.apps &&
+ formikProps.errors.apps[props.index] &&
+ formikProps.errors.apps[props.index].file && (
+ <p style={{ color: "#f44336" }}>
+ {formikProps.errors.apps[props.index].file}
+ </p>
+ )}
+ </Grid>
+ <Grid item xs={6}>
+ <label
+ style={{ marginTop: "20px" }}
+ className="MuiFormLabel-root MuiInputLabel-root"
+ htmlFor="file"
+ id="file-label"
+ >
+ Profile tar file
+ <span className="MuiFormLabel-asterisk MuiInputLabel-asterisk">
+  *
+ </span>
+ </label>
+ <FileUpload
+ setFieldValue={formikProps.setFieldValue}
+ file={formikProps.values.apps[props.index].profilePackageFile}
+ onBlur={formikProps.handleBlur}
+ name={`apps[${props.index}].profilePackageFile`}
+ accept={".tar.gz, .tar"}
+ />
+ {formikProps.errors.apps &&
+ formikProps.errors.apps[props.index] &&
+ formikProps.errors.apps[props.index].profilePackageFile && (
+ <p style={{ color: "#f44336" }}>
+ {formikProps.errors.apps[props.index].profilePackageFile}
+ </p>
+ )}
+ </Grid>
+ </Grid>
+ </Paper>
+ </>
+ );
+}
+
+const AppForm = (props) => {
+ return (
+ <ExpandableCard
+ error={
+ props.formikProps.errors.apps &&
+ props.formikProps.errors.apps[props.index]
+ }
+ title={props.name}
+ description={props.description}
+ content={
+ <AppDetailsForm
+ formikProps={props.formikProps}
+ name={props.name}
+ index={props.index}
+ />
+ }
+ />
+ );
+};
+export default AppForm;
diff --git a/src/tools/emcoui/src/compositeApps/dialogs/AppFormGeneral.jsx b/src/tools/emcoui/src/compositeApps/dialogs/AppFormGeneral.jsx
new file mode 100644
index 00000000..e2272ae8
--- /dev/null
+++ b/src/tools/emcoui/src/compositeApps/dialogs/AppFormGeneral.jsx
@@ -0,0 +1,129 @@
+//=======================================================================
+// Copyright (c) 2017-2020 Aarna Networks, Inc.
+// All rights reserved.
+// ======================================================================
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================================================================
+import { Grid, Paper, TextField } from "@material-ui/core";
+import FileUpload from "../../common/FileUpload";
+import React from "react";
+
+function AppFormGeneral({ formikProps, ...props }) {
+ return (
+ <>
+ <Paper
+ style={{
+ width: "100%",
+ padding: "20px",
+ maxHeight: "395px",
+ overflowY: "auto",
+ scrollbarWidth: "thin",
+ }}
+ >
+ <Grid container spacing={3}>
+ <Grid item xs={6}>
+ <TextField
+ fullWidth
+ value={formikProps.values.apps[props.index].appName}
+ name={`apps[${props.index}].appName`}
+ id="app-name"
+ label="App name"
+ size="small"
+ onChange={formikProps.handleChange}
+ onBlur={formikProps.handleBlur}
+ required
+ helperText={
+ formikProps.errors.apps &&
+ formikProps.errors.apps[props.index] &&
+ formikProps.errors.apps[props.index].appName
+ }
+ error={
+ formikProps.errors.apps &&
+ formikProps.errors.apps[props.index] &&
+ formikProps.errors.apps[props.index].appName &&
+ true
+ }
+ />
+ </Grid>
+ <Grid item xs={6}>
+ <TextField
+ fullWidth
+ value={formikProps.values.apps[props.index].description}
+ name={`apps[${props.index}].description`}
+ id="app-description"
+ label="Description"
+ multiline
+ onChange={formikProps.handleChange}
+ onBlur={formikProps.handleBlur}
+ rowsMax={4}
+ />
+ </Grid>
+ <Grid item xs={6}>
+ <label
+ style={{ marginTop: "20px" }}
+ className="MuiFormLabel-root MuiInputLabel-root"
+ htmlFor="file"
+ id="file-label"
+ >
+ App tgz file
+ <span className="MuiFormLabel-asterisk MuiInputLabel-asterisk">
+  *
+ </span>
+ </label>
+ <FileUpload
+ setFieldValue={formikProps.setFieldValue}
+ file={formikProps.values.apps[props.index].file}
+ onBlur={formikProps.handleBlur}
+ name={`apps[${props.index}].file`}
+ accept={".tgz"}
+ />
+ {formikProps.errors.apps &&
+ formikProps.errors.apps[props.index] &&
+ formikProps.errors.apps[props.index].file && (
+ <p style={{ color: "#f44336" }}>
+ {formikProps.errors.apps[props.index].file}
+ </p>
+ )}
+ </Grid>
+ <Grid item xs={6}>
+ <label
+ style={{ marginTop: "20px" }}
+ className="MuiFormLabel-root MuiInputLabel-root"
+ htmlFor="file"
+ id="file-label"
+ >
+ Profile tar file
+ <span className="MuiFormLabel-asterisk MuiInputLabel-asterisk">
+  *
+ </span>
+ </label>
+ <FileUpload
+ setFieldValue={formikProps.setFieldValue}
+ file={formikProps.values.apps[props.index].profilePackageFile}
+ onBlur={formikProps.handleBlur}
+ name={`apps[${props.index}].profilePackageFile`}
+ accept={".tar.gz, .tar"}
+ />
+ {formikProps.errors.apps &&
+ formikProps.errors.apps[props.index] &&
+ formikProps.errors.apps[props.index].profilePackageFile && (
+ <p style={{ color: "#f44336" }}>
+ {formikProps.errors.apps[props.index].profilePackageFile}
+ </p>
+ )}
+ </Grid>
+ </Grid>
+ </Paper>
+ </>
+ );
+}
+
+export default AppFormGeneral;
diff --git a/src/tools/emcoui/src/compositeApps/dialogs/AppFormPlacement.jsx b/src/tools/emcoui/src/compositeApps/dialogs/AppFormPlacement.jsx
new file mode 100644
index 00000000..c52c2b42
--- /dev/null
+++ b/src/tools/emcoui/src/compositeApps/dialogs/AppFormPlacement.jsx
@@ -0,0 +1,83 @@
+//=======================================================================
+// Copyright (c) 2017-2020 Aarna Networks, Inc.
+// All rights reserved.
+// ======================================================================
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================================================================
+import React from "react";
+import PropTypes from "prop-types";
+import { Grid, Paper, Typography } from "@material-ui/core";
+import EnhancedTable from "./SortableTable";
+
+function AppFormPlacement({
+ formikProps,
+ index,
+ clusterProviders,
+ handleRowSelect,
+ ...props
+}) {
+ return (
+ <>
+ <Typography variant="subtitle1" style={{ float: "left" }}>
+ Select Clusters
+ <span className="MuiFormLabel-asterisk MuiInputLabel-asterisk"> *</span>
+ </Typography>
+ {formikProps.errors.apps &&
+ formikProps.errors.apps[index] &&
+ formikProps.errors.apps[index].clusters && (
+ <span
+ style={{
+ color: "#f44336",
+ marginRight: "35px",
+ float: "right",
+ }}
+ >
+ {typeof formikProps.errors.apps[index].clusters === "string" &&
+ formikProps.errors.apps[index].clusters}
+ </span>
+ )}
+ <Grid
+ container
+ spacing={3}
+ style={{
+ height: "400px",
+ overflowY: "auto",
+ width: "100%",
+ scrollbarWidth: "thin",
+ }}
+ >
+ {clusterProviders &&
+ clusterProviders.length > 0 &&
+ clusterProviders.map((clusterProvider) => (
+ <Grid key={clusterProvider.name} item xs={12}>
+ <Paper>
+ <EnhancedTable
+ key={clusterProvider.name}
+ tableName={clusterProvider.name}
+ clusters={clusterProvider.clusters}
+ formikValues={formikProps.values.apps[index].clusters}
+ onRowSelect={handleRowSelect}
+ />
+ </Paper>
+ </Grid>
+ ))}
+ </Grid>
+ </>
+ );
+}
+
+AppFormPlacement.propTypes = {
+ formikProps: PropTypes.object,
+ index: PropTypes.number,
+ handleRowSelect: PropTypes.func,
+};
+
+export default AppFormPlacement;
diff --git a/src/tools/emcoui/src/compositeApps/dialogs/AppNetworkForm.jsx b/src/tools/emcoui/src/compositeApps/dialogs/AppNetworkForm.jsx
new file mode 100644
index 00000000..055dc3e6
--- /dev/null
+++ b/src/tools/emcoui/src/compositeApps/dialogs/AppNetworkForm.jsx
@@ -0,0 +1,524 @@
+//=======================================================================
+// Copyright (c) 2017-2020 Aarna Networks, Inc.
+// All rights reserved.
+// ======================================================================
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================================================================
+import React, { useState } from "react";
+import { makeStyles } from "@material-ui/core/styles";
+import Button from "@material-ui/core/Button";
+import Typography from "@material-ui/core/Typography";
+import { Grid, IconButton } from "@material-ui/core";
+import { TextField, Select, MenuItem, InputLabel } from "@material-ui/core";
+import AddIcon from "@material-ui/icons/Add";
+import CardContent from "@material-ui/core/CardContent";
+import Card from "@material-ui/core/Card";
+import apiService from "../../services/apiService";
+import DeleteIcon from "@material-ui/icons/Delete";
+import { Formik } from "formik";
+import Notification from "../../common/Notification";
+
+function NetworkForm({ formikProps, ...props }) {
+ const [clusters, setClusters] = useState(props.clusters);
+ const [notificationDetails, setNotificationDetails] = useState({});
+ const useStyles = makeStyles({
+ root: {
+ minWidth: 275,
+ },
+ title: {
+ fontSize: 14,
+ },
+ pos: {
+ marginBottom: 12,
+ },
+ });
+
+ const handleAddNetworkInterface = (providerIndex, clusterIndex, values) => {
+ let updatedFields = [];
+ if (
+ values.apps[props.index].clusters[providerIndex].selectedClusters[
+ clusterIndex
+ ].interfaces
+ ) {
+ updatedFields = [
+ ...values.apps[props.index].clusters[providerIndex].selectedClusters[
+ clusterIndex
+ ].interfaces,
+ {
+ networkName: "",
+ ip: "",
+ subnet: "",
+ },
+ ];
+ } else {
+ updatedFields = [
+ {
+ networkName: "",
+ ip: "",
+ subnet: "",
+ },
+ ];
+ }
+
+ let request = {
+ providerName: values.apps[props.index].clusters[providerIndex].provider,
+ clusterName:
+ values.apps[props.index].clusters[providerIndex].selectedClusters[
+ clusterIndex
+ ].name,
+ };
+ apiService
+ .getClusterProviderNetworks(request)
+ .then((networks) => {
+ let networkData = [];
+ if (networks && networks.length > 0) {
+ networks.forEach((network) => {
+ networkData.push({
+ name: network.metadata.name,
+ subnets: network.spec.ipv4Subnets,
+ });
+ });
+ }
+
+ apiService
+ .getClusterNetworks(request)
+ .then((clusterNetworks) => {
+ if (clusterNetworks && clusterNetworks.length > 0) {
+ clusterNetworks.forEach((clusterNetwork) => {
+ networkData.push({
+ name: clusterNetwork.metadata.name,
+ subnets: clusterNetwork.spec.ipv4Subnets,
+ });
+ });
+ }
+ //add interface entry onyl of there is atlease one available network
+ if (networkData.length > 0) {
+ setClusters((clusters) => {
+ clusters[providerIndex].selectedClusters[
+ clusterIndex
+ ].interfaces = updatedFields;
+ clusters[providerIndex].selectedClusters[
+ clusterIndex
+ ].networks = networkData;
+ clusters[providerIndex].selectedClusters[
+ clusterIndex
+ ].availableNetworks = getAvailableNetworks(
+ clusters[providerIndex].selectedClusters[clusterIndex]
+ );
+ return clusters;
+ });
+ formikProps.setFieldValue(
+ `apps[${props.index}].clusters[${providerIndex}].selectedClusters[${clusterIndex}].interfaces`,
+ updatedFields
+ );
+ } else {
+ setNotificationDetails({
+ show: true,
+ message: `No network available for this cluster`,
+ severity: "warning",
+ });
+ }
+ })
+ .catch((err) => {
+ console.log("error getting cluster networks : ", err);
+ });
+ })
+ .catch((err) => {
+ console.log("error getting cluster provider networks : ", err);
+ })
+ .finally(() => {
+ return updatedFields;
+ });
+ };
+
+ const handleSelectNetowrk = (
+ e,
+ providerIndex,
+ clusterIndex,
+ interfaceIndex
+ ) => {
+ setClusters((clusters) => {
+ clusters[providerIndex].selectedClusters[clusterIndex].interfaces[
+ interfaceIndex
+ ] = {
+ networkName: e.target.value,
+ ip: "",
+ subnet: "",
+ };
+ clusters[providerIndex].selectedClusters[
+ clusterIndex
+ ].availableNetworks = getAvailableNetworks(
+ clusters[providerIndex].selectedClusters[clusterIndex],
+ "handleAddNetworkInterface"
+ );
+ return clusters;
+ });
+ formikProps.handleChange(e);
+ };
+ const handleRemoveNetwork = (providerIndex, clusterIndex, interfaceIndex) => {
+ setClusters((clusters) => {
+ clusters[providerIndex].selectedClusters[clusterIndex].interfaces.splice(
+ interfaceIndex,
+ 1
+ );
+ clusters[providerIndex].selectedClusters[
+ clusterIndex
+ ].availableNetworks = getAvailableNetworks(
+ clusters[providerIndex].selectedClusters[clusterIndex]
+ );
+ return clusters;
+ });
+ formikProps.setFieldValue(
+ `apps[${props.index}].clusters[${providerIndex}].selectedClusters[${clusterIndex}].interfaces`,
+ clusters[providerIndex].selectedClusters[clusterIndex].interfaces
+ );
+ };
+ const getAvailableNetworks = (cluster) => {
+ let availableNetworks = [];
+ cluster.networks.forEach((network) => {
+ let match = false;
+ cluster.interfaces.forEach((networkInterface) => {
+ if (network.name === networkInterface.networkName) {
+ match = true;
+ return;
+ }
+ });
+ if (!match) availableNetworks.push(network);
+ });
+ return availableNetworks;
+ };
+
+ const classes = useStyles();
+ return (
+ <>
+ <Notification notificationDetails={notificationDetails} />
+ <Grid
+ key="networkForm"
+ container
+ spacing={3}
+ style={{
+ height: "400px",
+ overflowY: "auto",
+ width: "100%",
+ scrollbarWidth: "thin",
+ }}
+ >
+ {(!clusters || clusters.length < 1) && (
+ <Grid item xs={12}>
+ <Typography variant="h6">No clusters selected</Typography>
+ </Grid>
+ )}
+ {clusters &&
+ clusters.map((cluster, providerIndex) => (
+ <Grid key={cluster.provider + providerIndex} item xs={12}>
+ <Card className={classes.root}>
+ <CardContent>
+ <Grid container spacing={2}>
+ <Grid item xs={12}>
+ <Typography
+ className={classes.title}
+ color="textSecondary"
+ gutterBottom
+ >
+ {cluster.provider}
+ </Typography>
+ </Grid>
+ {cluster.selectedClusters.map(
+ (selectedCluster, clusterIndex) => (
+ <React.Fragment key={selectedCluster.name}>
+ <Grid item xs={12}>
+ <Typography>{selectedCluster.name}</Typography>
+ </Grid>
+ <Formik>
+ {() => {
+ const {
+ values,
+ errors,
+ handleChange,
+ handleBlur,
+ } = formikProps;
+ return (
+ <>
+ {selectedCluster.interfaces &&
+ selectedCluster.interfaces.length > 0
+ ? selectedCluster.interfaces.map(
+ (networkInterface, interfaceIndex) => (
+ <Grid
+ spacing={1}
+ container
+ item
+ key={interfaceIndex}
+ xs={12}
+ >
+ <Grid item xs={4}>
+ <InputLabel id="network-select-label">
+ Network
+ </InputLabel>
+ <Select
+ fullWidth
+ labelId="network-select-label"
+ id="network-select"
+ name={`apps[${props.index}].clusters[${providerIndex}].selectedClusters[${clusterIndex}].interfaces[${interfaceIndex}].networkName`}
+ value={
+ values.apps[props.index]
+ .clusters[providerIndex]
+ .selectedClusters[
+ clusterIndex
+ ].interfaces[interfaceIndex]
+ .networkName
+ }
+ onChange={(e) => {
+ handleSelectNetowrk(
+ e,
+ providerIndex,
+ clusterIndex,
+ interfaceIndex
+ );
+ }}
+ >
+ {values.apps[props.index]
+ .clusters[providerIndex]
+ .selectedClusters[
+ clusterIndex
+ ].interfaces[interfaceIndex]
+ .networkName && (
+ <MenuItem
+ key={
+ values.apps[props.index]
+ .clusters[providerIndex]
+ .selectedClusters[
+ clusterIndex
+ ].interfaces[
+ interfaceIndex
+ ].networkName
+ }
+ value={
+ values.apps[props.index]
+ .clusters[providerIndex]
+ .selectedClusters[
+ clusterIndex
+ ].interfaces[
+ interfaceIndex
+ ].networkName
+ }
+ >
+ {
+ values.apps[props.index]
+ .clusters[providerIndex]
+ .selectedClusters[
+ clusterIndex
+ ].interfaces[
+ interfaceIndex
+ ].networkName
+ }
+ </MenuItem>
+ )}
+ {selectedCluster.availableNetworks &&
+ selectedCluster.availableNetworks.map(
+ (network) => (
+ <MenuItem
+ key={network.name}
+ value={network.name}
+ >
+ {network.name}
+ </MenuItem>
+ )
+ )}
+ </Select>
+ </Grid>
+
+ <Grid item xs={4}>
+ <InputLabel id="subnet-select-label">
+ Subnet
+ </InputLabel>
+ <Select
+ fullWidth
+ labelId="subnet-select-label"
+ id="subnet-select-label"
+ name={`apps[${props.index}].clusters[${providerIndex}].selectedClusters[${clusterIndex}].interfaces[${interfaceIndex}].subnet`}
+ value={
+ values.apps[props.index]
+ .clusters[providerIndex]
+ .selectedClusters[
+ clusterIndex
+ ].interfaces[interfaceIndex]
+ .subnet
+ }
+ onChange={handleChange}
+ >
+ {values.apps[props.index]
+ .clusters[providerIndex]
+ .selectedClusters[
+ clusterIndex
+ ].interfaces[interfaceIndex]
+ .networkName === ""
+ ? null
+ : selectedCluster.networks
+ .filter(
+ (network) =>
+ network.name ===
+ values.apps[
+ props.index
+ ].clusters[
+ providerIndex
+ ].selectedClusters[
+ clusterIndex
+ ].interfaces[
+ interfaceIndex
+ ].networkName
+ )[0]
+ .subnets.map((subnet) => (
+ <MenuItem
+ key={subnet.name}
+ value={subnet.name}
+ >
+ {subnet.name}(
+ {subnet.subnet})
+ </MenuItem>
+ ))}
+ </Select>
+ </Grid>
+ <Grid item xs={3}>
+ <TextField
+ width={"65%"}
+ name={`apps[${props.index}].clusters[${providerIndex}].selectedClusters[${clusterIndex}].interfaces[${interfaceIndex}].ip`}
+ onBlur={handleBlur}
+ id="ip"
+ label="IP Address"
+ value={
+ values.apps[props.index]
+ .clusters[providerIndex]
+ .selectedClusters[
+ clusterIndex
+ ].interfaces[interfaceIndex]
+ .ip
+ }
+ onChange={handleChange}
+ helperText={
+ (errors.apps &&
+ errors.apps[props.index] &&
+ errors.apps[props.index]
+ .clusters &&
+ errors.apps[props.index]
+ .clusters[clusterIndex] &&
+ errors.apps[props.index]
+ .clusters[clusterIndex]
+ .selectedClusters[
+ clusterIndex
+ ] &&
+ errors.apps[props.index]
+ .clusters[clusterIndex]
+ .selectedClusters[
+ clusterIndex
+ ].interfaces[
+ interfaceIndex
+ ] &&
+ errors.apps[props.index]
+ .clusters[clusterIndex]
+ .selectedClusters[
+ clusterIndex
+ ].interfaces[interfaceIndex]
+ .ip) ||
+ "blank for auto assign"
+ }
+ error={
+ errors.apps &&
+ errors.apps[props.index] &&
+ errors.apps[props.index]
+ .clusters &&
+ errors.apps[props.index]
+ .clusters[clusterIndex] &&
+ errors.apps[props.index]
+ .clusters[clusterIndex]
+ .selectedClusters[
+ clusterIndex
+ ] &&
+ errors.apps[props.index]
+ .clusters[clusterIndex]
+ .selectedClusters[
+ clusterIndex
+ ].interfaces[
+ interfaceIndex
+ ] &&
+ errors.apps[props.index]
+ .clusters[clusterIndex]
+ .selectedClusters[
+ clusterIndex
+ ].interfaces[interfaceIndex]
+ .ip &&
+ true
+ }
+ />
+ </Grid>
+ <Grid item xs={1}>
+ <IconButton
+ color="secondary"
+ onClick={() => {
+ handleRemoveNetwork(
+ providerIndex,
+ clusterIndex,
+ interfaceIndex
+ );
+ }}
+ >
+ <DeleteIcon fontSize="small" />
+ </IconButton>
+ </Grid>
+ </Grid>
+ )
+ )
+ : null}
+ <Grid
+ key={selectedCluster.name + "addButton"}
+ item
+ xs={12}
+ >
+ <Button
+ variant="outlined"
+ size="small"
+ fullWidth
+ color="primary"
+ disabled={
+ selectedCluster.interfaces &&
+ selectedCluster.interfaces.length > 0 &&
+ selectedCluster.networks.length ===
+ selectedCluster.interfaces.length
+ }
+ onClick={() => {
+ handleAddNetworkInterface(
+ providerIndex,
+ clusterIndex,
+ values
+ );
+ }}
+ startIcon={<AddIcon />}
+ >
+ Add Network Interface
+ </Button>
+ </Grid>
+ </>
+ );
+ }}
+ </Formik>
+ </React.Fragment>
+ )
+ )}
+ </Grid>
+ </CardContent>
+ </Card>
+ </Grid>
+ ))}
+ </Grid>
+ </>
+ );
+}
+
+export default NetworkForm;
diff --git a/src/tools/emcoui/src/compositeApps/dialogs/CompositeAppForm.jsx b/src/tools/emcoui/src/compositeApps/dialogs/CompositeAppForm.jsx
index 29e17cd7..751ea8eb 100644
--- a/src/tools/emcoui/src/compositeApps/dialogs/CompositeAppForm.jsx
+++ b/src/tools/emcoui/src/compositeApps/dialogs/CompositeAppForm.jsx
@@ -11,185 +11,298 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
-import React from "react";
-import { withStyles } from "@material-ui/core/styles";
+// ========================================================================
+import React, { useState } from "react";
import Button from "@material-ui/core/Button";
import Dialog from "@material-ui/core/Dialog";
-import MuiDialogTitle from "@material-ui/core/DialogTitle";
-import MuiDialogContent from "@material-ui/core/DialogContent";
-import MuiDialogActions from "@material-ui/core/DialogActions";
+import AppBar from "@material-ui/core/AppBar";
+import Toolbar from "@material-ui/core/Toolbar";
+import IconButton from "@material-ui/core/IconButton";
import Typography from "@material-ui/core/Typography";
-import { TextField } from '@material-ui/core';
-const styles = (theme) => ({
- root: {
- margin: 0,
- padding: theme.spacing(2),
+import CloseIcon from "@material-ui/icons/Close";
+import Slide from "@material-ui/core/Slide";
+import { Grid } from "@material-ui/core";
+import { TextField } from "@material-ui/core";
+import { makeStyles } from "@material-ui/core/styles";
+import AddIcon from "@material-ui/icons/Add";
+import NewAppForm from "../../common/Form";
+import AppForm from "./AppForm";
+import { Formik, FieldArray } from "formik";
+import * as Yup from "yup";
+
+const Transition = React.forwardRef(function Transition(props, ref) {
+ return <Slide direction="up" ref={ref} {...props} />;
+});
+
+const useStyles = makeStyles((theme) => ({
+ tableRoot: {
+ width: "100%",
},
- closeButton: {
+ paper: {
+ width: "100%",
+ marginBottom: theme.spacing(2),
+ },
+ table: {
+ minWidth: 550,
+ },
+ visuallyHidden: {
+ border: 0,
+ clip: "rect(0 0 0 0)",
+ height: 1,
+ margin: -1,
+ overflow: "hidden",
+ padding: 0,
position: "absolute",
- right: theme.spacing(1),
- top: theme.spacing(1),
- color: theme.palette.grey[500],
+ top: 20,
+ width: 1,
},
-});
-
-
-const DialogContent = withStyles((theme) => ({
- root: {
- padding: theme.spacing(2),
+ appBar: {
+ position: "relative",
+ },
+ title: {
+ marginLeft: theme.spacing(2),
+ flex: 1,
+ },
+ demo: {
+ backgroundColor: theme.palette.background.paper,
},
-}))(MuiDialogContent);
-
-const DialogActions = withStyles((theme) => ({
root: {
- margin: 0,
- padding: theme.spacing(1),
+ flexGrow: 1,
+ backgroundColor: theme.palette.background.paper,
+ display: "flex",
+ height: 424,
},
-}))(MuiDialogActions);
-
+ tabs: {
+ borderRight: `1px solid ${theme.palette.divider}`,
+ },
+}));
-class CreateCompositeAppForm extends React.Component {
- constructor(props) {
- super(props)
- this.state = {
- fields: { name: "", version: "", description: "" },
- errors: {}
- }
- this.handleChange = this.handleChange.bind(this);
- this.submituserRegistrationForm = this.submituserRegistrationForm.bind(this);
- }
+const PROFILE_SUPPORTED_FORMATS = [
+ ".tgz",
+ ".tar.gz",
+ ".tar",
+ "application/x-tar",
+ "application/x-tgz",
+ "application/x-compressed",
+ "application/x-gzip",
+ "application/x-compressed-tar",
+ "application/gzip",
+];
+const APP_PACKAGE_SUPPORTED_FORMATS = [
+ ".tgz",
+ ".tar.gz",
+ ".tar",
+ "application/x-tar",
+ "application/x-tgz",
+ "application/x-compressed",
+ "application/x-gzip",
+ "application/x-compressed-tar",
+];
+const serviceBasicValidationSchema = Yup.object({
+ name: Yup.string().required(),
+ description: Yup.string(),
+ apps: Yup.array()
+ .of(
+ Yup.object({
+ appName: Yup.string().required("App name is required"),
+ file: Yup.mixed()
+ .required("An app package file is required")
+ .test(
+ "fileFormat",
+ "Unsupported file format",
+ (value) =>
+ value && APP_PACKAGE_SUPPORTED_FORMATS.includes(value.type)
+ ),
+ profilePackageFile: Yup.mixed()
+ .required("A profile package file is required")
+ .test(
+ "fileFormat",
+ "Unsupported file format",
+ (value) => value && PROFILE_SUPPORTED_FORMATS.includes(value.type)
+ ),
+ })
+ )
+ .required("At least one app is required"),
+});
- componentDidMount = () => {
- if (this.props.item) {
- this.title = "Edit Composite App";
- this.buttonLabel = "Update";
- this.isEdit = true;
- }
- else {
- this.title = "New Composite App";
- this.buttonLabel = "Create";
- this.isEdit = false;
- }
+const CreateCompositeAppForm = ({ open, handleClose }) => {
+ const classes = useStyles();
+ const [openForm, setOpenForm] = useState(false);
+ const handleCloseForm = () => {
+ setOpenForm(false);
};
-
- componentDidUpdate = (prevProps, prevState) => {
- if (this.props.item && ((prevProps.item !== this.props.item))) {
- this.setState({ fields: { ...this.props.item.metadata, version: this.props.item.spec.version } });
- }
- }
-
- resetFields = () => {
- if (!this.isEdit) {
- this.setState({
- fields: { name: "", version: "", description: "" },
- errors: {}
- });
- }
- else {
- this.setState({ fields: { ...this.props.item.metadata, version: this.props.item.spec.version } });
- }
- }
-
- handleClose = () => {
- this.resetFields();
- this.props.handleClose();
+ const handleAddApp = () => {
+ setOpenForm(true);
};
-
- submituserRegistrationForm(e) {
- e.preventDefault();
- if (this.validateForm()) {
- this.resetFields();
- this.props.handleClose(this.state.fields);
- }
- }
-
- validateForm() {
- let fields = this.state.fields;
- let errors = {};
- let formIsValid = true;
-
- if (!fields["name"]) {
- formIsValid = false;
- errors["name"] = "*Please enter your username.";
- }
-
- if (typeof fields["name"] !== "string") {
- if (!fields["name"].match(/^[a-zA-Z ]*$/)) {
- formIsValid = false;
- errors["name"] = "*Please enter alphabet characters only.";
- }
- }
- this.setState({
- errors: errors
- });
- return formIsValid;
- }
-
- handleChange = (e) => {
- this.setState({ fields: { ...this.state.fields, [e.target.name]: e.target.value } });
- }
-
- render = () => {
- const { classes } = this.props;
- return (
- <>
+ let initialValues = { name: "", description: "", apps: [] };
+ return (
+ <>
+ {open && (
<Dialog
- maxWidth={"xs"}
- onClose={this.handleClose}
- aria-labelledby="customized-dialog-title"
- open={this.props.open}
- disableBackdropClick
+ open={open}
+ onClose={() => {
+ handleClose();
+ }}
+ fullScreen
+ TransitionComponent={Transition}
>
- <MuiDialogTitle disableTypography className={classes.root} >
- <Typography variant="h6">{this.title}</Typography>
- </MuiDialogTitle>
+ <Formik
+ initialValues={initialValues}
+ onSubmit={(values, { setSubmitting }) => {
+ setSubmitting(false);
+ handleClose(values);
+ }}
+ validationSchema={serviceBasicValidationSchema}
+ >
+ {(props) => {
+ const {
+ values,
+ touched,
+ errors,
+ isSubmitting,
+ handleChange,
+ handleBlur,
+ handleSubmit,
+ } = props;
+ return (
+ <>
+ <form noValidate onSubmit={handleSubmit}>
+ <AppBar className={classes.appBar}>
+ <Toolbar>
+ <IconButton
+ edge="start"
+ color="inherit"
+ onClick={() => {
+ handleClose();
+ }}
+ aria-label="close"
+ >
+ <CloseIcon />
+ </IconButton>
+ <Typography variant="h6" className={classes.title}>
+ Add Service
+ </Typography>
+ <Button
+ type="submit"
+ autoFocus
+ variant="contained"
+ disabled={isSubmitting}
+ >
+ SUBMIT
+ </Button>
+ </Toolbar>
+ </AppBar>
+ <div style={{ padding: "12px" }}>
+ <Grid
+ container
+ direction="row"
+ justify="center"
+ alignItems="center"
+ style={{ marginTop: "40px" }}
+ spacing={3}
+ >
+ <Grid item xs={6}>
+ <Grid container spacing={3}>
+ {errors.apps &&
+ touched.apps &&
+ typeof errors.apps !== "object" && (
+ <Grid item xs={12} sm={12}>
+ <Typography>{errors.apps}</Typography>
+ </Grid>
+ )}
+
+ <Grid item xs={12} sm={6}>
+ <TextField
+ fullWidth
+ name="name"
+ id="input-name"
+ label="Name"
+ variant="outlined"
+ size="small"
+ value={values.name}
+ onChange={handleChange}
+ onBlur={handleBlur}
+ required
+ helperText={
+ errors.name &&
+ touched.name &&
+ "Name is required"
+ }
+ error={errors.name && touched.name}
+ />
+ </Grid>
+ <Grid item xs={12} sm={6}>
+ <TextField
+ fullWidth
+ name="description"
+ id="input-description"
+ label="Description"
+ variant="outlined"
+ size="small"
+ value={values.description}
+ onChange={handleChange}
+ onBlur={handleBlur}
+ />
+ </Grid>
- <form onSubmit={this.submituserRegistrationForm}>
- <DialogContent dividers>
- <TextField
- style={{ width: "40%", marginBottom: "10px" }}
- name="name"
- value={this.state.fields.name}
- id="input-name"
- label="Name"
- helperText="Name should be unique"
- onChange={this.handleChange}
- required
- />
- <TextField
- style={{ width: "40%", marginBottom: "20px", float: "right" }}
- name="version"
- value={this.state.fields.version}
- onChange={this.handleChange}
- id="input-version"
- label="Version"
- required
- />
- <TextField
- style={{ width: "100%", marginBottom: "25px" }}
- name="description"
- value={this.state.fields.description}
- onChange={this.handleChange}
- id="input-description"
- label="Description"
- multiline
- rowsMax={4}
- />
- </DialogContent>
- <DialogActions>
- <Button autoFocus onClick={this.handleClose} color="secondary">
- Cancel
- </Button>
- <Button autoFocus type="submit" color="primary">
- {this.buttonLabel}
- </Button>
- </DialogActions>
- </form>
+ <FieldArray
+ name="apps"
+ render={(arrayHelpers) => (
+ <>
+ <NewAppForm
+ open={openForm}
+ onClose={handleCloseForm}
+ onSubmit={(values) => {
+ arrayHelpers.push({
+ appName: values.name,
+ description: values.description,
+ });
+ setOpenForm(false);
+ }}
+ />
+ {values.apps &&
+ values.apps.length > 0 &&
+ values.apps.map((app, index) => (
+ <Grid key={index} item sm={12} xs={12}>
+ <AppForm
+ formikProps={props}
+ name={app.appName}
+ description={app.description}
+ index={index}
+ initialValues={values}
+ />
+ </Grid>
+ ))}
+ </>
+ )}
+ />
+ <Grid item xs={12}>
+ <Button
+ variant="outlined"
+ size="small"
+ fullWidth
+ color="primary"
+ onClick={() => {
+ handleAddApp();
+ }}
+ startIcon={<AddIcon />}
+ >
+ Add App
+ </Button>
+ </Grid>
+ </Grid>
+ </Grid>
+ </Grid>
+ </div>
+ </form>
+ </>
+ );
+ }}
+ </Formik>
</Dialog>
- </>
- );
- }
-}
-export default withStyles(styles)(CreateCompositeAppForm)
+ )}
+ </>
+ );
+};
+export default CreateCompositeAppForm;
diff --git a/src/tools/emcoui/src/compositeApps/dialogs/SortableTable.jsx b/src/tools/emcoui/src/compositeApps/dialogs/SortableTable.jsx
new file mode 100644
index 00000000..f1a6ac2d
--- /dev/null
+++ b/src/tools/emcoui/src/compositeApps/dialogs/SortableTable.jsx
@@ -0,0 +1,410 @@
+//=======================================================================
+// Copyright (c) 2017-2020 Aarna Networks, Inc.
+// All rights reserved.
+// ======================================================================
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================================================================
+import Table from "@material-ui/core/Table";
+import TableBody from "@material-ui/core/TableBody";
+import TableCell from "@material-ui/core/TableCell";
+import TableContainer from "@material-ui/core/TableContainer";
+import TableHead from "@material-ui/core/TableHead";
+import TableRow from "@material-ui/core/TableRow";
+import TableSortLabel from "@material-ui/core/TableSortLabel";
+import { makeStyles } from "@material-ui/core/styles";
+import React, { useEffect, useState } from "react";
+import Checkbox from "@material-ui/core/Checkbox";
+import PropTypes from "prop-types";
+import Typography from "@material-ui/core/Typography";
+import Toolbar from "@material-ui/core/Toolbar";
+
+import clsx from "clsx";
+import TablePagination from "@material-ui/core/TablePagination";
+import { lighten } from "@material-ui/core/styles";
+
+function descendingComparator(a, b, orderBy) {
+ if (b[orderBy] < a[orderBy]) {
+ return -1;
+ }
+ if (b[orderBy] > a[orderBy]) {
+ return 1;
+ }
+ return 0;
+}
+
+function getComparator(order, orderBy) {
+ return order === "desc"
+ ? (a, b) => descendingComparator(a, b, orderBy)
+ : (a, b) => -descendingComparator(a, b, orderBy);
+}
+
+function stableSort(array, comparator) {
+ const stabilizedThis = array.map((el, index) => [el, index]);
+ stabilizedThis.sort((a, b) => {
+ const order = comparator(a[0], b[0]);
+ if (order !== 0) return order;
+ return a[1] - b[1];
+ });
+ return stabilizedThis.map((el) => el[0]);
+}
+
+const headCells = [
+ {
+ id: "name",
+ numeric: false,
+ sortable: true,
+ disablePadding: true,
+ label: "Cluster",
+ },
+ {
+ id: "description",
+ numeric: true,
+ sortable: false,
+ disablePadding: false,
+ label: "Description",
+ },
+];
+
+function EnhancedTableHead(props) {
+ const {
+ classes,
+ onSelectAllClick,
+ order,
+ orderBy,
+ numSelected,
+ rowCount,
+ onRequestSort,
+ } = props;
+ const createSortHandler = (property) => (event) => {
+ onRequestSort(event, property);
+ };
+
+ return (
+ <TableHead>
+ <TableRow>
+ <TableCell padding="checkbox">
+ <Checkbox
+ indeterminate={numSelected > 0 && numSelected < rowCount}
+ checked={rowCount > 0 && numSelected === rowCount}
+ onChange={onSelectAllClick}
+ />
+ </TableCell>
+ {headCells.map((headCell) =>
+ headCell.sortable ? (
+ <TableCell
+ style={{ fontWeight: "520" }}
+ key={headCell.id}
+ align={headCell.numeric ? "right" : "left"}
+ padding={headCell.disablePadding ? "none" : "default"}
+ sortDirection={orderBy === headCell.id ? order : false}
+ >
+ <TableSortLabel
+ active={orderBy === headCell.id}
+ direction={orderBy === headCell.id ? order : "asc"}
+ onClick={createSortHandler(headCell.id)}
+ >
+ {headCell.label}
+ {orderBy === headCell.id ? (
+ <span className={classes.visuallyHidden}>
+ {order === "desc"
+ ? "sorted descending"
+ : "sorted ascending"}
+ </span>
+ ) : null}
+ </TableSortLabel>
+ </TableCell>
+ ) : (
+ <TableCell
+ key={headCell.id}
+ style={{ fontWeight: "520" }}
+ padding={headCell.disablePadding ? "none" : "default"}
+ align={headCell.numeric ? "right" : "left"}
+ >
+ {headCell.label}
+ </TableCell>
+ )
+ )}
+ </TableRow>
+ </TableHead>
+ );
+}
+
+EnhancedTableHead.propTypes = {
+ classes: PropTypes.object.isRequired,
+ numSelected: PropTypes.number.isRequired,
+ onRequestSort: PropTypes.func.isRequired,
+ onSelectAllClick: PropTypes.func.isRequired,
+ order: PropTypes.oneOf(["asc", "desc"]).isRequired,
+ orderBy: PropTypes.string.isRequired,
+ rowCount: PropTypes.number.isRequired,
+};
+
+const useToolbarStyles = makeStyles((theme) => ({
+ root: {
+ paddingLeft: theme.spacing(2),
+ paddingRight: theme.spacing(1),
+ },
+ highlight:
+ theme.palette.type === "light"
+ ? {
+ color: theme.palette.primary.main,
+ backgroundColor: lighten(theme.palette.primary.light, 0.85),
+ }
+ : {
+ color: theme.palette.text.primary,
+ backgroundColor: theme.palette.primary.dark,
+ },
+ title: {
+ flex: "1 1 100%",
+ },
+}));
+
+const EnhancedTableToolbar = (props) => {
+ const classes = useToolbarStyles();
+ const { numSelected } = props;
+
+ return (
+ <Toolbar
+ className={clsx(classes.root, {
+ [classes.highlight]: numSelected > 0,
+ })}
+ >
+ <Typography
+ className={classes.title}
+ variant="h6"
+ id="tableTitle"
+ component="div"
+ >
+ {props.tableName}
+ </Typography>
+
+ <Typography
+ className={classes.title}
+ style={{ textAlign: "right" }}
+ color="inherit"
+ variant="subtitle1"
+ component="div"
+ >
+ {numSelected} selected
+ </Typography>
+ </Toolbar>
+ );
+};
+
+EnhancedTableToolbar.propTypes = {
+ numSelected: PropTypes.number.isRequired,
+};
+
+const useStyles = makeStyles((theme) => ({
+ tableRoot: {
+ width: "100%",
+ },
+ paper: {
+ width: "100%",
+ marginBottom: theme.spacing(2),
+ },
+ table: {
+ minWidth: 550,
+ },
+ visuallyHidden: {
+ border: 0,
+ clip: "rect(0 0 0 0)",
+ height: 1,
+ margin: -1,
+ overflow: "hidden",
+ padding: 0,
+ position: "absolute",
+ top: 20,
+ width: 1,
+ },
+ appBar: {
+ position: "relative",
+ },
+ title: {
+ marginLeft: theme.spacing(2),
+ flex: 1,
+ },
+ demo: {
+ backgroundColor: theme.palette.background.paper,
+ },
+ root: {
+ flexGrow: 1,
+ backgroundColor: theme.palette.background.paper,
+ display: "flex",
+ height: 424,
+ },
+ tabs: {
+ borderRight: `1px solid ${theme.palette.divider}`,
+ },
+}));
+
+function EnhancedTable({
+ clusters,
+ formikValues,
+ tableName,
+ onRowSelect,
+ ...props
+}) {
+ const classes = useStyles();
+ const [order, setOrder] = useState("asc");
+ const [orderBy, setOrderBy] = useState("name");
+ const [selected, setSelected] = useState([]);
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const [rows, setRows] = useState([]);
+
+ const handleRequestSort = (event, property) => {
+ const isAsc = orderBy === property && order === "asc";
+ setOrder(isAsc ? "desc" : "asc");
+ setOrderBy(property);
+ };
+
+ useEffect(() => {
+ if (formikValues) {
+ let formikClusterData = formikValues.filter(
+ (cluster) => cluster.provider === tableName
+ );
+ if (formikClusterData && formikClusterData.length > 0) {
+ let data = [];
+ formikClusterData[0].selectedClusters.forEach((selectedCluster) => {
+ data.push(selectedCluster.name);
+ });
+ setSelected(data);
+ }
+ }
+ setRows(clusters);
+ }, []);
+
+ useEffect(() => {
+ onRowSelect(tableName, selected);
+ }, [selected]);
+
+ const handleSelectAllClick = (event) => {
+ if (event.target.checked) {
+ const newSelecteds = rows.map((n) => n.name);
+ setSelected(newSelecteds);
+ return;
+ }
+ setSelected([]);
+ };
+
+ const handleClick = (event, name) => {
+ const selectedIndex = selected.indexOf(name);
+ let newSelected = [];
+
+ if (selectedIndex === -1) {
+ newSelected = newSelected.concat(selected, name);
+ } else if (selectedIndex === 0) {
+ newSelected = newSelected.concat(selected.slice(1));
+ } else if (selectedIndex === selected.length - 1) {
+ newSelected = newSelected.concat(selected.slice(0, -1));
+ } else if (selectedIndex > 0) {
+ newSelected = newSelected.concat(
+ selected.slice(0, selectedIndex),
+ selected.slice(selectedIndex + 1)
+ );
+ }
+ setSelected(newSelected);
+ };
+
+ const handleChangePage = (event, newPage) => {
+ setPage(newPage);
+ };
+
+ const handleChangeRowsPerPage = (event) => {
+ setRowsPerPage(parseInt(event.target.value, 10));
+ setPage(0);
+ };
+
+ const isSelected = (name) => selected.indexOf(name) !== -1;
+
+ const emptyRows =
+ rowsPerPage - Math.min(rowsPerPage, rows.length - page * rowsPerPage);
+
+ return (
+ <div className={classes.tableRoot}>
+ <EnhancedTableToolbar
+ tableName={tableName}
+ numSelected={selected.length}
+ />
+ <TableContainer>
+ <Table
+ className={classes.table}
+ aria-labelledby="tableTitle"
+ size={"small"}
+ aria-label="enhanced table"
+ >
+ <EnhancedTableHead
+ classes={classes}
+ numSelected={selected.length}
+ order={order}
+ orderBy={orderBy}
+ onSelectAllClick={handleSelectAllClick}
+ onRequestSort={handleRequestSort}
+ rowCount={rows.length}
+ />
+ <TableBody>
+ {stableSort(rows, getComparator(order, orderBy))
+ .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
+ .map((row, index) => {
+ const isItemSelected = isSelected(row.name);
+ const labelId = `enhanced-table-checkbox-${index}`;
+
+ return (
+ <TableRow
+ hover
+ onClick={(event) => handleClick(event, row.name)}
+ role="checkbox"
+ aria-checked={isItemSelected}
+ tabIndex={-1}
+ key={row.name}
+ selected={isItemSelected}
+ >
+ <TableCell padding="checkbox">
+ <Checkbox
+ checked={isItemSelected}
+ inputProps={{ "aria-labelledby": labelId }}
+ />
+ </TableCell>
+ <TableCell
+ component="th"
+ id={labelId}
+ scope="row"
+ padding="none"
+ >
+ {row.name}
+ </TableCell>
+ <TableCell align="right">{row.description}</TableCell>
+ </TableRow>
+ );
+ })}
+ {emptyRows > 0 && (
+ <TableRow style={{ height: 33 * emptyRows }}>
+ <TableCell colSpan={6} />
+ </TableRow>
+ )}
+ </TableBody>
+ </Table>
+ </TableContainer>
+ <TablePagination
+ rowsPerPageOptions={[5, 10, 25]}
+ component="div"
+ count={rows.length}
+ rowsPerPage={rowsPerPage}
+ page={page}
+ onChangePage={handleChangePage}
+ onChangeRowsPerPage={handleChangeRowsPerPage}
+ />
+ </div>
+ );
+}
+
+export default EnhancedTable;
diff --git a/src/tools/emcoui/src/compositeApps/intents/AppPlacementIntentTable.jsx b/src/tools/emcoui/src/compositeApps/intents/AppPlacementIntentTable.jsx
index 6dfb8279..09f70c99 100644
--- a/src/tools/emcoui/src/compositeApps/intents/AppPlacementIntentTable.jsx
+++ b/src/tools/emcoui/src/compositeApps/intents/AppPlacementIntentTable.jsx
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React, { useState } from "react";
import {
TableContainer,
@@ -24,7 +24,7 @@ import {
} from "@material-ui/core";
import Paper from "@material-ui/core/Paper";
import TableBody from "@material-ui/core/TableBody";
-import EditIcon from "@material-ui/icons/Edit";
+// import EditIcon from "@material-ui/icons/Edit";
import DeleteIcon from "@material-ui/icons/Delete";
import PropTypes from "prop-types";
import apiService from "../../services/apiService";
@@ -106,31 +106,42 @@ const AppPlacementIntentTable = ({ data, setData, ...props }) => {
<StyledTableCell>{entry.name}</StyledTableCell>
<StyledTableCell>{entry.description}</StyledTableCell>
<StyledTableCell>
- {entry.allOf.map((intent, index) => (
- <Paper
- key={index}
- style={{ width: "max-content" }}
- variant="outlined"
- >
- <label>Cluster Provider :&nbsp;</label>
- <label style={{ fontWeight: "bold" }}>
- {intent["provider-name"]}, &nbsp;
- </label>
- <label>Labels : </label>
- <Chip
- style={{ marginRight: "10px" }}
- size="small"
- label={intent["cluster-label-name"]}
- color="primary"
+ {entry.allOf &&
+ entry.allOf.map((intent, index) => (
+ <Paper
+ key={index}
+ style={{ width: "max-content" }}
variant="outlined"
- />
- </Paper>
- ))}
+ >
+ <label>Cluster Provider :&nbsp;</label>
+ <label style={{ fontWeight: "bold" }}>
+ {intent["provider-name"]}
+ </label>
+ <label>, &nbsp; Cluster :&nbsp;</label>
+ <label style={{ fontWeight: "bold" }}>
+ {intent["cluster-name"]}
+ </label>
+ {intent["cluster-label-name"] && (
+ <>
+ <label>, &nbsp; Labels : </label>
+ <Chip
+ style={{ marginRight: "10px" }}
+ size="small"
+ label={intent["cluster-label-name"]}
+ color="primary"
+ variant="outlined"
+ />
+ </>
+ )}
+ </Paper>
+ ))}
</StyledTableCell>
<StyledTableCell>
+ {/*
+ //edit app placement api has not been implemented yet
<IconButton onClick={(e) => handleEdit(index)} title="Edit">
<EditIcon color="primary" />
- </IconButton>
+ </IconButton> */}
<IconButton
onClick={(e) => handleDelete(index)}
title="Delete"
diff --git a/src/tools/emcoui/src/compositeApps/intents/GenericPlacementIntentCard.jsx b/src/tools/emcoui/src/compositeApps/intents/GenericPlacementIntentCard.jsx
index bb43972c..9946c92d 100644
--- a/src/tools/emcoui/src/compositeApps/intents/GenericPlacementIntentCard.jsx
+++ b/src/tools/emcoui/src/compositeApps/intents/GenericPlacementIntentCard.jsx
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React, { useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import clsx from "clsx";
@@ -168,6 +168,10 @@ const GenericPlacementIntentCard = (props) => {
variant="outlined"
size="small"
color="secondary"
+ disabled={
+ appPlacementIntentData.applications &&
+ appPlacementIntentData.applications.length > 0
+ }
style={{ float: "right" }}
startIcon={<DeleteIcon />}
onClick={() => {
@@ -190,6 +194,9 @@ const GenericPlacementIntentCard = (props) => {
}
/>
)}
+ {!(props.appsData && props.appsData.length > 0) && (
+ <div>No app found for adding app placement intent</div>
+ )}
</CardContent>
</Collapse>
</Card>
diff --git a/src/tools/emcoui/src/deploymentIntentGroups/DIGform.jsx b/src/tools/emcoui/src/deploymentIntentGroups/DIGform.jsx
index f0cf1e1d..ee1fec74 100644
--- a/src/tools/emcoui/src/deploymentIntentGroups/DIGform.jsx
+++ b/src/tools/emcoui/src/deploymentIntentGroups/DIGform.jsx
@@ -11,245 +11,112 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
-import React, { useState, useEffect } from 'react';
-import PropTypes from 'prop-types';
-import { withStyles } from '@material-ui/core/styles';
-import Button from '@material-ui/core/Button';
-import Dialog from '@material-ui/core/Dialog';
-import MuiDialogTitle from '@material-ui/core/DialogTitle';
-import MuiDialogContent from '@material-ui/core/DialogContent';
-import MuiDialogActions from '@material-ui/core/DialogActions';
-import IconButton from '@material-ui/core/IconButton';
-import CloseIcon from '@material-ui/icons/Close';
-import Typography from '@material-ui/core/Typography';
-import { TextField, InputLabel, NativeSelect, FormControl, FormHelperText } from '@material-ui/core';
-import * as Yup from "yup";
-import { Formik } from 'formik';
+// ========================================================================
+import React, { useState, useEffect } from "react";
+import PropTypes from "prop-types";
+import { withStyles } from "@material-ui/core/styles";
+import Button from "@material-ui/core/Button";
+import Dialog from "@material-ui/core/Dialog";
+import MuiDialogTitle from "@material-ui/core/DialogTitle";
+import MuiDialogContent from "@material-ui/core/DialogContent";
+import MuiDialogActions from "@material-ui/core/DialogActions";
+import IconButton from "@material-ui/core/IconButton";
+import CloseIcon from "@material-ui/icons/Close";
+import Typography from "@material-ui/core/Typography";
+import Stepper from "./Stepper";
import apiService from "../services/apiService";
const styles = (theme) => ({
- root: {
- margin: 0,
- padding: theme.spacing(2),
- },
- closeButton: {
- position: 'absolute',
- right: theme.spacing(1),
- top: theme.spacing(1),
- color: theme.palette.grey[500],
- },
+ root: {
+ margin: 0,
+ padding: theme.spacing(2),
+ },
+ closeButton: {
+ position: "absolute",
+ right: theme.spacing(1),
+ top: theme.spacing(1),
+ color: theme.palette.grey[500],
+ },
});
const DialogTitle = withStyles(styles)((props) => {
- const { children, classes, onClose, ...other } = props;
- return (
- <MuiDialogTitle disableTypography className={classes.root} {...other}>
- <Typography variant="h6">{children}</Typography>
- {onClose ? (
- <IconButton className={classes.closeButton} onClick={onClose}>
- <CloseIcon />
- </IconButton>
- ) : null}
- </MuiDialogTitle>
- );
+ const { children, classes, onClose, ...other } = props;
+ return (
+ <MuiDialogTitle disableTypography className={classes.root} {...other}>
+ <Typography variant="h6">{children}</Typography>
+ {onClose ? (
+ <IconButton className={classes.closeButton} onClick={onClose}>
+ <CloseIcon />
+ </IconButton>
+ ) : null}
+ </MuiDialogTitle>
+ );
});
const DialogActions = withStyles((theme) => ({
- root: {
- margin: 0,
- padding: theme.spacing(1),
- },
+ root: {
+ margin: 0,
+ padding: theme.spacing(1),
+ },
}))(MuiDialogActions);
const DialogContent = withStyles((theme) => ({
- root: {
- padding: theme.spacing(2),
- }
+ root: {
+ padding: theme.spacing(2),
+ },
}))(MuiDialogContent);
-const schema = Yup.object(
- {
- name: Yup.string().required(),
- description: Yup.string(),
- version: Yup.string().required(),
- compositeProfile: Yup.string().required(),
- overrideValues: Yup.array().of(Yup.object()).typeError("Invalid override values, expected array"),
- })
-
const DIGform = (props) => {
- const { onClose, item, open, onSubmit } = props;
- const buttonLabel = item ? "OK" : "Create";
- const title = item ? "Edit Deployment Intent Group" : "Create Deployment Intent Group";
- const [selectedAppIndex, setSelectedAppIndex] = useState(0);
- const handleClose = () => {
- onClose();
- };
- useEffect(() => {
- props.data.compositeApps.forEach(compositeApp => {
- let request = { projectName: props.projectName, compositeAppName: compositeApp.metadata.name, compositeAppVersion: compositeApp.spec.version }
- apiService.getCompositeProfiles(request).then(res => {
- compositeApp.profiles = res;
- }).catch(error => {
- console.log("error getting cluster providers : ", error)
- }).finally(() => {
- })
+ const { onClose, item, open, onSubmit } = props;
+ const title = item
+ ? "Edit Deployment Intent Group"
+ : "Create Deployment Intent Group";
+ const handleClose = () => {
+ onClose();
+ };
+ useEffect(() => {
+ props.data.compositeApps.forEach((compositeApp) => {
+ let request = {
+ projectName: props.projectName,
+ compositeAppName: compositeApp.metadata.name,
+ compositeAppVersion: compositeApp.spec.version,
+ };
+ apiService
+ .getCompositeProfiles(request)
+ .then((res) => {
+ compositeApp.profiles = res;
})
- }, [props.data.compositeApps, props.projectName]);
- let initialValues = item ?
- { name: item.metadata.name, description: item.metadata.description, overrideValues: JSON.stringify(item.spec["override-values"]), compositeApp: item.compositeAppName, compositeProfile: item.spec.profile, version: item.spec.version } :
- { name: "", description: "", overrideValues: undefined, compositeApp: props.data.compositeApps[0].metadata.name, compositeProfile: "", version: "" }
-
- const handleSetCompositeApp = (val) => {
- props.data.compositeApps.forEach((ca, index) => {
- if (ca.metadata.name === val)
- setSelectedAppIndex(index);
- });
- }
-
- return (
- <Dialog maxWidth={"xs"} onClose={handleClose} aria-labelledby="customized-dialog-title" open={open} disableBackdropClick>
- <DialogTitle id="simple-dialog-title">{title}</DialogTitle>
- <Formik
- initialValues={initialValues}
- onSubmit={async values => {
- values.compositeAppVersion = props.data.compositeApps[selectedAppIndex].spec.version;
- onSubmit(values);
- }}
- validationSchema={schema}
- >
- {formicProps => {
- const {
- values,
- touched,
- errors,
- isSubmitting,
- handleChange,
- handleBlur,
- handleSubmit
- } = formicProps;
- return (
- <form noValidate onSubmit={handleSubmit} onChange={handleChange}>
- <DialogContent dividers>
- <div style={{ width: "45%", float: "left" }}>
- <InputLabel shrink htmlFor="compositeApp-label-placeholder">
- Composite App
- </InputLabel>
- <NativeSelect
- name="compositeApp"
- onChange={(e) => { handleChange(e); handleSetCompositeApp(e.target.value) }}
- onBlur={handleBlur}
- disabled={item ? true : false}
- inputProps={{
- name: 'compositeApp',
- id: 'compositeApps-label-placeholder',
- }}
- >
- {item && (<option >{values.compositeApp}</option>)}
- {props.data && props.data.compositeApps.map(compositeApp =>
- (<option value={compositeApp.metadata.name} key={compositeApp.metadata.name} >{compositeApp.metadata.name}</option>)
- )}
- </NativeSelect>
- </div>
-
- <FormControl style={{ width: "45%", float: "right" }} required error={errors.compositeProfile && touched.compositeProfile}>
- <InputLabel htmlFor="compositeProfile-label-placeholder">
- Composite Profile
- </InputLabel>
- <NativeSelect
- name="compositeProfile"
- onChange={handleChange}
- onBlur={handleBlur}
- disabled={item ? true : false}
- required
- inputProps={{
- name: 'compositeProfile',
- id: 'compositeProfile-label-placeholder',
- }}
- >
- <option value="" />
- {props.data.compositeApps[selectedAppIndex].profiles && props.data.compositeApps[selectedAppIndex].profiles.map(compositeProfile =>
- (<option value={compositeProfile.metadata.name} key={compositeProfile.metadata.name} >{compositeProfile.metadata.name}</option>)
- )}
- </NativeSelect>
- {errors.compositeProfile && touched.compositeProfile && <FormHelperText>Required</FormHelperText>}
- </FormControl>
- <TextField
- style={{ width: "45%", float: "left", marginTop: "10px" }}
- id="name"
- label="Name"
- type="text"
- value={values.name}
- onChange={handleChange}
- onBlur={handleBlur}
- helperText={(errors.name && touched.name && (
- "Name is required"
- ))}
- required
- error={errors.name && touched.name}
- />
- <TextField
- style={{ width: "45%", float: "right", marginTop: "10px" }}
- id="version"
- label="Version"
- type="text"
- name="version"
- onChange={handleChange}
- onBlur={handleBlur}
- helperText={(errors.version && touched.version && (
- "Version is required"
- ))}
- required
- error={errors.version && touched.version}
- />
- <TextField
- style={{ width: "100%", marginTop: "20px" }}
- id="overrideValues"
- label="Override Values"
- type="text"
- value={values.overrideValues}
- onChange={handleChange}
- onBlur={handleBlur}
- required
- multiline
- rows={4}
- variant="outlined"
- error={errors.overrideValues && touched.overrideValues}
- helperText={(errors.overrideValues && touched.overrideValues && (
- (errors["overrideValues"])
- ))}
- />
- <TextField
- style={{ width: "100%", marginBottom: "25px", marginTop: "10px" }}
- name="description"
- value={values.description}
- onChange={handleChange}
- onBlur={handleBlur}
- id="description"
- label="Description"
- multiline
- rowsMax={4}
- />
- </DialogContent>
- <DialogActions>
- <Button autoFocus onClick={handleClose} color="secondary">
- Cancel
- </Button>
- <Button autoFocus type="submit" color="primary" disabled={isSubmitting}>
- {buttonLabel}
- </Button>
- </DialogActions>
- </form>
- );
- }}
- </Formik>
- </Dialog>
- );
+ .catch((error) => {
+ console.log("error getting cluster providers : ", error);
+ })
+ .finally(() => {});
+ });
+ }, [props.data.compositeApps, props.projectName]);
+ return (
+ <Dialog
+ maxWidth={"md"}
+ fullWidth={true}
+ onClose={handleClose}
+ open={open}
+ disableBackdropClick
+ >
+ <DialogTitle id="customized-dialog-title" onClose={handleClose}>
+ {title}
+ </DialogTitle>
+ <DialogContent dividers>
+ <Stepper
+ data={props.data}
+ projectName={props.projectName}
+ onSubmit={onSubmit}
+ />
+ </DialogContent>
+ </Dialog>
+ );
};
DIGform.propTypes = {
- onClose: PropTypes.func.isRequired,
- open: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired,
+ open: PropTypes.bool.isRequired,
};
export default DIGform;
diff --git a/src/tools/emcoui/src/deploymentIntentGroups/DIGtable.jsx b/src/tools/emcoui/src/deploymentIntentGroups/DIGtable.jsx
index 5710b52b..3e22dc4c 100644
--- a/src/tools/emcoui/src/deploymentIntentGroups/DIGtable.jsx
+++ b/src/tools/emcoui/src/deploymentIntentGroups/DIGtable.jsx
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React, { useState } from "react";
import { withStyles, makeStyles } from "@material-ui/core/styles";
import Table from "@material-ui/core/Table";
@@ -22,14 +22,14 @@ import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow";
import Paper from "@material-ui/core/Paper";
import IconButton from "@material-ui/core/IconButton";
-import EditIcon from "@material-ui/icons/Edit";
+// import EditIcon from "@material-ui/icons/Edit";
import DeleteDialog from "../common/Dialogue";
-import AddIcon from "@material-ui/icons/Add";
+// import AddIcon from "@material-ui/icons/Add";
import DeleteIcon from "@material-ui/icons/Delete";
import GetAppIcon from "@material-ui/icons/GetApp";
import apiService from "../services/apiService";
-import { Button } from "@material-ui/core";
-import IntentsForm from "./IntentsForm";
+// import { Button } from "@material-ui/core";
+// import IntentsForm from "./IntentsForm";
import Notification from "../common/Notification";
const StyledTableCell = withStyles((theme) => ({
@@ -58,20 +58,15 @@ const useStyles = makeStyles({
export default function DIGtable({ data, setData, ...props }) {
const classes = useStyles();
const [open, setOpen] = useState(false);
- // const [openForm, setOpenForm] = useState(false);
const [index, setIndex] = useState(0);
- const [openIntentsForm, setOpenIntentsForm] = useState(false);
+ // const [openIntentsForm, setOpenIntentsForm] = useState(false);
const [notificationDetails, setNotificationDetails] = useState({});
- let handleEdit = (index) => {
- // setIndex(index);
- // setOpenForm(true);
- };
const handleClose = (el) => {
if (el.target.innerText === "Delete") {
let request = {
projectName: props.projectName,
- compositeAppName: data[index].compositeAppName,
- compositeAppVersion: data[index].compositeAppVersion,
+ compositeAppName: data[index].metadata.compositeAppName,
+ compositeAppVersion: data[index].metadata.compositeAppVersion,
deploymentIntentGroupName: data[index].metadata.name,
};
apiService
@@ -92,50 +87,46 @@ export default function DIGtable({ data, setData, ...props }) {
setIndex(index);
setOpen(true);
};
- const handleAddIntent = (index) => {
- setIndex(index);
- setOpenIntentsForm(true);
- };
- const handleCloseIntentsForm = () => {
- setOpenIntentsForm(false);
- };
- const handleSubmitIntentForm = (values) => {
- setOpenIntentsForm(false);
- let request = {
- projectName: props.projectName,
- compositeAppName: values.compositeAppName,
- compositeAppVersion: values.compositeAppVersion,
- deploymentIntentGroupName: values.deploymentIntentGroupName,
- payload: {
- metadata: { name: values.name, description: values.description },
- spec: {
- intent: {
- genericPlacementIntent: values.genericPlacementIntent,
- },
- },
- },
- };
- if (values.networkControllerIntent && values.networkControllerIntent !== "")
- request.payload.spec.intent.ovnaction = values.networkControllerIntent;
- apiService
- .addIntentsToDeploymentIntentGroup(request)
- .then((res) => {
- if (data[index].intent) {
- data[index].intent.push(res.spec.intent);
- } else {
- data[index].intent = [res.spec.intent];
- }
- setData([...data]);
- })
- .catch((err) => {
- console.log("error adding intent to deployment intent group");
- });
- };
+ // const handleCloseIntentsForm = () => {
+ // setOpenIntentsForm(false);
+ // };
+ // const handleSubmitIntentForm = (values) => {
+ // setOpenIntentsForm(false);
+ // let request = {
+ // projectName: props.projectName,
+ // compositeAppName: values.compositeAppName,
+ // compositeAppVersion: values.compositeAppVersion,
+ // deploymentIntentGroupName: values.deploymentIntentGroupName,
+ // payload: {
+ // metadata: { name: values.name, description: values.description },
+ // spec: {
+ // intent: {
+ // genericPlacementIntent: values.genericPlacementIntent,
+ // },
+ // },
+ // },
+ // };
+ // if (values.networkControllerIntent && values.networkControllerIntent !== "")
+ // request.payload.spec.intent.ovnaction = values.networkControllerIntent;
+ // apiService
+ // .addIntentsToDeploymentIntentGroup(request)
+ // .then((res) => {
+ // if (data[index].intent) {
+ // data[index].intent.push(res.spec.intent);
+ // } else {
+ // data[index].intent = [res.spec.intent];
+ // }
+ // setData([...data]);
+ // })
+ // .catch((err) => {
+ // console.log("error adding intent to deployment intent group");
+ // });
+ // };
const handleInstantiate = (index) => {
let request = {
projectName: props.projectName,
- compositeAppName: data[index].compositeAppName,
- compositeAppVersion: data[index].compositeAppVersion,
+ compositeAppName: data[index].metadata.compositeAppName,
+ compositeAppVersion: data[index].metadata.compositeAppVersion,
deploymentIntentGroupName: data[index].metadata.name,
};
apiService
@@ -184,13 +175,6 @@ export default function DIGtable({ data, setData, ...props }) {
<Notification notificationDetails={notificationDetails} />
{data && data.length > 0 && (
<>
- <IntentsForm
- projectName={props.projectName}
- open={openIntentsForm}
- onClose={handleCloseIntentsForm}
- onSubmit={handleSubmitIntentForm}
- data={data[index]}
- />
<DeleteDialog
open={open}
onClose={handleClose}
@@ -207,7 +191,7 @@ export default function DIGtable({ data, setData, ...props }) {
<StyledTableCell>Version</StyledTableCell>
<StyledTableCell>Profile</StyledTableCell>
<StyledTableCell>Composite App</StyledTableCell>
- <StyledTableCell>Intents</StyledTableCell>
+ {/* <StyledTableCell>Intents</StyledTableCell> */}
<StyledTableCell>Description</StyledTableCell>
<StyledTableCell style={{ width: "15%" }}>
Actions
@@ -225,54 +209,39 @@ export default function DIGtable({ data, setData, ...props }) {
{row.spec.profile}
</StyledTableCell>
<StyledTableCell className={classes.cell}>
- {row.compositeAppName}
+ {row.metadata.compositeAppName}
</StyledTableCell>
- {
+ {/* {
<StyledTableCell className={classes.cell}>
- {row.intent
- ? row.intent.map((intentEntry) => {
- return Object.keys(intentEntry)
- .map(function (k) {
- return intentEntry[k];
- })
- .join(" | ");
- })
- : ""}
+ {Object.keys(row.spec.deployedIntents[0]).map(function (
+ key,
+ index
+ ) {
+ if (
+ index === 0 ||
+ row.spec.deployedIntents[0][key] === ""
+ )
+ return row.spec.deployedIntents[0][key];
+ else return ", " + row.spec.deployedIntents[0][key];
+ })}
</StyledTableCell>
- }
+ } */}
<StyledTableCell className={classes.cell}>
{row.metadata.description}
</StyledTableCell>
<StyledTableCell className={classes.cell}>
- <Button
- variant="outlined"
- color="primary"
- size="small"
- onClick={() => {
- handleAddIntent(index);
- }}
- startIcon={<AddIcon />}
- >
- Intents
- </Button>
<IconButton
- disabled={!(row.intent && row.intent.length > 0)}
+ color={"primary"}
+ // disabled={
+ // !(
+ // row.spec.deployedIntents &&
+ // row.spec.deployedIntents.length > 0
+ // )
+ // }
title="Instantiate"
onClick={(e) => handleInstantiate(index)}
>
- <GetAppIcon
- color={
- !(row.intent && row.intent.length > 0)
- ? ""
- : "primary"
- }
- />
- </IconButton>
- <IconButton
- onClick={(e) => handleEdit(index)}
- title="Edit"
- >
- <EditIcon color="primary" />
+ <GetAppIcon />
</IconButton>
<IconButton
onClick={(e) => handleDelete(index)}
diff --git a/src/tools/emcoui/src/deploymentIntentGroups/DeploymentIntentGroups.jsx b/src/tools/emcoui/src/deploymentIntentGroups/DeploymentIntentGroups.jsx
index c1f73bb7..132b9fc3 100644
--- a/src/tools/emcoui/src/deploymentIntentGroups/DeploymentIntentGroups.jsx
+++ b/src/tools/emcoui/src/deploymentIntentGroups/DeploymentIntentGroups.jsx
@@ -11,14 +11,15 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React, { useEffect, useState } from "react";
import DIGtable from "./DIGtable";
-import { withStyles, Button, Grid } from "@material-ui/core";
+import { withStyles, Button, Grid, Typography } from "@material-ui/core";
import AddIcon from "@material-ui/icons/Add";
import apiService from "../services/apiService";
import Spinner from "../common/Spinner";
import DIGform from "./DIGform";
+import { ReactComponent as EmptyIcon } from "../assets/icons/empty.svg";
const styles = {
root: {
@@ -44,84 +45,59 @@ const DeploymentIntentGroups = (props) => {
setOpen(true);
};
const handleSubmit = (inputFields) => {
- let payload = {
- metadata: {
- name: inputFields.name,
- description: inputFields.description,
- },
- spec: {
- profile: inputFields.compositeProfile,
- version: inputFields.version,
- },
- projectName: props.projectName,
- compositeAppName: inputFields.compositeApp,
- compositeAppVersion: inputFields.compositeAppVersion,
- };
- if (inputFields.overrideValues && inputFields.overrideValues !== "") {
- payload.spec["override-values"] = JSON.parse(inputFields.overrideValues);
+ try {
+ let payload = {
+ spec: {
+ projectName: props.projectName,
+ appsData: inputFields.intents.apps,
+ },
+ };
+ if (inputFields.overrideValues && inputFields.overrideValues !== "") {
+ payload.spec["override-values"] = JSON.parse(
+ inputFields.overrideValues
+ );
+ }
+ payload = { ...payload, ...inputFields.general };
+ apiService
+ .createDeploymentIntentGroup(payload)
+ .then((response) => {
+ response.metadata.compositeAppName = inputFields.general.compositeApp;
+ response.metadata.compositeAppVersion =
+ inputFields.general.compositeAppVersion;
+ data && data.length > 0
+ ? setData([...data, response])
+ : setData([response]);
+ })
+ .catch((error) => {
+ console.log("error creating DIG : ", error);
+ })
+ .finally(() => {
+ setIsloading(false);
+ setOpen(false);
+ });
+ } catch (error) {
+ console.error(error);
}
- apiService
- .createDeploymentIntentGroup(payload)
- .then((response) => {
- response.compositeAppName = inputFields.compositeApp;
- response.compositeAppVersion = inputFields.compositeAppVersion;
- data && data.length > 0
- ? setData([...data, response])
- : setData([response]);
- })
- .catch((error) => {
- console.log("error creating DIG : ", error);
- })
- .finally(() => {
- setIsloading(false);
- setOpen(false);
- });
};
useEffect(() => {
+ let getDigs = () => {
+ apiService
+ .getDeploymentIntentGroups({ projectName: props.projectName })
+ .then((res) => {
+ setData(res);
+ })
+ .catch((err) => {
+ console.log("error getting deplotment intent groups : " + err);
+ })
+ .finally(() => setIsloading(false));
+ };
+
apiService
.getCompositeApps({ projectName: props.projectName })
.then((response) => {
- const getDigIntents = (input) => {
- let request = {
- projectName: props.projectName,
- compositeAppName: input.compositeAppName,
- compositeAppVersion: input.compositeAppVersion,
- deploymentIntentGroupName: input.metadata.name,
- };
- apiService
- .getDeploymentIntentGroupIntents(request)
- .then((res) => {
- input.intent = res.intent;
- })
- .catch((err) => {})
- .finally(() => {
- setData((data) => [...data, input]);
- });
- };
- response.forEach((compositeApp) => {
- let request = {
- projectName: props.projectName,
- compositeAppName: compositeApp.metadata.name,
- compositeAppVersion: compositeApp.spec.version,
- };
- apiService
- .getDeploymentIntentGroups(request)
- .then((digResponse) => {
- digResponse.forEach((res) => {
- res.compositeAppName = compositeApp.metadata.name;
- res.compositeAppVersion = compositeApp.spec.version;
- getDigIntents(res);
- });
- })
- .catch((error) => {
- console.log("unable to get deployment intent groups", error);
- })
- .finally(() => {
- setCompositeApps(response);
- setIsloading(false);
- });
- });
+ setCompositeApps(response);
+ getDigs();
})
.catch((err) => {
console.log("Unable to get composite apps : ", err);
@@ -131,16 +107,8 @@ const DeploymentIntentGroups = (props) => {
return (
<>
{isLoading && <Spinner />}
- {!isLoading && compositeApps && compositeApps.length > 0 && (
+ {!isLoading && compositeApps && (
<>
- <Button
- variant="outlined"
- color="primary"
- startIcon={<AddIcon />}
- onClick={onCreateDIG}
- >
- Create Deployment Intent Group
- </Button>
<DIGform
projectName={props.projectName}
open={open}
@@ -148,15 +116,41 @@ const DeploymentIntentGroups = (props) => {
onSubmit={handleSubmit}
data={{ compositeApps: compositeApps }}
/>
- <Grid container spacing={2} alignItems="center">
- <Grid item xs style={{ marginTop: "20px" }}>
- <DIGtable
- data={data}
- setData={setData}
- projectName={props.projectName}
- />
- </Grid>
+ <Grid item xs={12}>
+ <Button
+ variant="outlined"
+ color="primary"
+ startIcon={<AddIcon />}
+ onClick={onCreateDIG}
+ >
+ Create Deployment Intent Group
+ </Button>
</Grid>
+
+ {data && data.length > 0 && (
+ <Grid container spacing={2} alignItems="center">
+ <Grid item xs style={{ marginTop: "20px" }}>
+ <DIGtable
+ data={data}
+ setData={setData}
+ projectName={props.projectName}
+ />
+ </Grid>
+ </Grid>
+ )}
+
+ {(data === null || (data && data.length < 1)) && (
+ <Grid container spacing={2} direction="column" alignItems="center">
+ <Grid style={{ marginTop: "60px" }} item xs={6}>
+ <EmptyIcon style={{ height: "100px", width: "100px" }} />
+ </Grid>
+ <Grid item xs={12}>
+ <Typography variant="h6">
+ No deployment group found, start by adding a deployment group
+ </Typography>
+ </Grid>
+ </Grid>
+ )}
</>
)}
</>
diff --git a/src/tools/emcoui/src/deploymentIntentGroups/DigFormApp.jsx b/src/tools/emcoui/src/deploymentIntentGroups/DigFormApp.jsx
new file mode 100644
index 00000000..e0662455
--- /dev/null
+++ b/src/tools/emcoui/src/deploymentIntentGroups/DigFormApp.jsx
@@ -0,0 +1,201 @@
+//=======================================================================
+// Copyright (c) 2017-2020 Aarna Networks, Inc.
+// All rights reserved.
+// ======================================================================
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================================================================
+import { makeStyles } from "@material-ui/core/styles";
+import PropTypes from "prop-types";
+import Tabs from "@material-ui/core/Tabs";
+import Tab from "@material-ui/core/Tab";
+import Box from "@material-ui/core/Box";
+import React, { useState } from "react";
+import Typography from "@material-ui/core/Typography";
+import { Formik } from "formik";
+import ExpandableCard from "../common/ExpandableCard";
+import AppPlacementForm from "../compositeApps/dialogs/AppFormPlacement";
+import NetworkForm from "../compositeApps/dialogs/AppNetworkForm";
+
+const useStyles = makeStyles((theme) => ({
+ tableRoot: {
+ width: "100%",
+ },
+ paper: {
+ width: "100%",
+ marginBottom: theme.spacing(2),
+ },
+ table: {
+ minWidth: 550,
+ },
+ visuallyHidden: {
+ border: 0,
+ clip: "rect(0 0 0 0)",
+ height: 1,
+ margin: -1,
+ overflow: "hidden",
+ padding: 0,
+ position: "absolute",
+ top: 20,
+ width: 1,
+ },
+ appBar: {
+ position: "relative",
+ },
+ title: {
+ marginLeft: theme.spacing(2),
+ flex: 1,
+ },
+ demo: {
+ backgroundColor: theme.palette.background.paper,
+ },
+ root: {
+ flexGrow: 1,
+ backgroundColor: theme.palette.background.paper,
+ display: "flex",
+ height: 424,
+ },
+ tabs: {
+ borderRight: `1px solid ${theme.palette.divider}`,
+ },
+}));
+function TabPanel(props) {
+ const { children, value, index, ...other } = props;
+ return (
+ <div
+ role="tabpanel"
+ hidden={value !== index}
+ id={`vertical-tabpanel-${index}`}
+ aria-labelledby={`vertical-tab-${index}`}
+ {...other}
+ >
+ {value === index && <Box style={{ padding: "0 24px" }}>{children}</Box>}
+ </div>
+ );
+}
+
+function AppDetailsForm({ formikProps, ...props }) {
+ const classes = useStyles();
+ const [value, setValue] = useState(0);
+ const handleChange = (event, newValue) => {
+ setValue(newValue);
+ };
+ const handleRowSelect = (clusterProvider, selectedClusters) => {
+ if (
+ !formikProps.values.apps[props.index].clusters ||
+ formikProps.values.apps[props.index].clusters === undefined
+ ) {
+ if (selectedClusters.length > 0) {
+ let selectedClusterData = [];
+ selectedClusters.forEach((selectedCluster) => {
+ selectedClusterData.push({ name: selectedCluster, interfaces: [] });
+ });
+ formikProps.setFieldValue(`apps[${props.index}].clusters`, [
+ {
+ provider: clusterProvider,
+ selectedClusters: selectedClusterData,
+ },
+ ]);
+ }
+ } else {
+ let selectedClusterData = [];
+ //filter out the value of cluster provider so that it can be completely replaced by the new values
+ let updatedClusterValues = formikProps.values.apps[
+ props.index
+ ].clusters.filter((cluster) => cluster.provider !== clusterProvider);
+ selectedClusters.forEach((selectedCluster) => {
+ selectedClusterData.push({ name: selectedCluster, interfaces: [] });
+ });
+ if (selectedClusters.length > 0)
+ updatedClusterValues.push({
+ provider: clusterProvider,
+ selectedClusters: selectedClusterData,
+ });
+ formikProps.setFieldValue(
+ `apps[${props.index}].clusters`,
+ updatedClusterValues
+ );
+ }
+ };
+ return (
+ <div className={classes.root}>
+ <Formik>
+ {() => {
+ return (
+ <>
+ <Tabs
+ orientation="vertical"
+ variant="scrollable"
+ value={value}
+ onChange={handleChange}
+ aria-label="Vertical tabs example"
+ className={classes.tabs}
+ >
+ <Tab label="Placement" {...a11yProps(1)} />
+ <Tab label="Network" {...a11yProps(2)} />
+ </Tabs>
+ <TabPanel style={{ width: "85%" }} value={value} index={0}>
+ <AppPlacementForm
+ formikProps={formikProps}
+ index={props.index}
+ clusterProviders={props.clusterProviders}
+ handleRowSelect={handleRowSelect}
+ />
+ </TabPanel>
+ <TabPanel style={{ width: "85%" }} value={value} index={1}>
+ <Typography variant="subtitle1">Select Network</Typography>
+ <NetworkForm
+ clusters={formikProps.values.apps[props.index].clusters}
+ formikProps={formikProps}
+ index={props.index}
+ />
+ </TabPanel>
+ </>
+ );
+ }}
+ </Formik>
+ </div>
+ );
+}
+
+TabPanel.propTypes = {
+ children: PropTypes.node,
+ index: PropTypes.any.isRequired,
+ value: PropTypes.any.isRequired,
+};
+
+function a11yProps(index) {
+ return {
+ id: `vertical-tab-${index}`,
+ "aria-controls": `vertical-tabpanel-${index}`,
+ };
+}
+
+const AppForm2 = (props) => {
+ return (
+ <ExpandableCard
+ error={
+ props.formikProps.errors.apps &&
+ props.formikProps.errors.apps[props.index]
+ }
+ title={props.name}
+ description={props.description}
+ content={
+ <AppDetailsForm
+ formikProps={props.formikProps}
+ name={props.name}
+ index={props.index}
+ clusterProviders={props.clusterProviders}
+ />
+ }
+ />
+ );
+};
+export default AppForm2;
diff --git a/src/tools/emcoui/src/deploymentIntentGroups/DigFormGeneral.jsx b/src/tools/emcoui/src/deploymentIntentGroups/DigFormGeneral.jsx
new file mode 100644
index 00000000..5b5c4191
--- /dev/null
+++ b/src/tools/emcoui/src/deploymentIntentGroups/DigFormGeneral.jsx
@@ -0,0 +1,265 @@
+//=======================================================================
+// Copyright (c) 2017-2020 Aarna Networks, Inc.
+// All rights reserved.
+// ======================================================================
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================================================================
+import React, { useEffect, useState } from "react";
+import { Formik } from "formik";
+import * as Yup from "yup";
+
+import {
+ Button,
+ DialogActions,
+ FormControl,
+ FormHelperText,
+ Grid,
+ InputLabel,
+ MenuItem,
+ Select,
+ TextField,
+} from "@material-ui/core";
+
+const schema = Yup.object({
+ name: Yup.string().required(),
+ description: Yup.string(),
+ version: Yup.string()
+ .matches(/^[A-Za-z0-9\\s]+$/, "Special characters and space not allowed")
+ .required("Version is required"),
+ compositeProfile: Yup.string().required(),
+ overrideValues: Yup.array()
+ .of(Yup.object())
+ .typeError("Invalid override values, expected array"),
+});
+
+function DigFormGeneral(props) {
+ const { item, onSubmit } = props;
+ const [selectedAppIndex, setSelectedAppIndex] = useState(0); //let the first composite app as default selection
+ useEffect(() => {
+ if (item) {
+ props.data.compositeApps.forEach((ca, index) => {
+ if (ca.metadata.name === item.compositeApp) {
+ setSelectedAppIndex(index);
+ }
+ });
+ }
+ }, []);
+
+ let initialValues = item
+ ? {
+ ...item,
+ }
+ : {
+ name: "",
+ description: "",
+ overrideValues: undefined,
+ compositeApp: props.data.compositeApps[selectedAppIndex].metadata.name,
+ compositeProfile: "",
+ version: "",
+ };
+
+ const handleSetCompositeApp = (val) => {
+ props.data.compositeApps.forEach((ca, index) => {
+ if (ca.metadata.name === val) setSelectedAppIndex(index);
+ });
+ };
+ return (
+ <Formik
+ initialValues={initialValues}
+ onSubmit={(values) => {
+ values.compositeAppVersion =
+ props.data.compositeApps[selectedAppIndex].spec.version;
+ onSubmit(values);
+ }}
+ validationSchema={schema}
+ >
+ {(formicProps) => {
+ const {
+ values,
+ touched,
+ errors,
+ isSubmitting,
+ handleChange,
+ handleBlur,
+ handleSubmit,
+ } = formicProps;
+ return (
+ <form noValidate onSubmit={handleSubmit} onChange={handleChange}>
+ <Grid container spacing={4} justify="center">
+ <Grid container item xs={12} spacing={8}>
+ <Grid item xs={12} md={6}>
+ <TextField
+ fullWidth
+ id="name"
+ label="Name"
+ type="text"
+ value={values.name}
+ onChange={handleChange}
+ onBlur={handleBlur}
+ helperText={
+ errors.name && touched.name && "Name is required"
+ }
+ required
+ error={errors.name && touched.name}
+ />
+ </Grid>
+ <Grid item xs={12} md={6}>
+ <TextField
+ fullWidth
+ id="version"
+ label="Version"
+ type="text"
+ name="version"
+ value={values.version}
+ onChange={handleChange}
+ onBlur={handleBlur}
+ helperText={
+ errors.version && touched.version && errors["version"]
+ }
+ required
+ error={errors.version && touched.version}
+ />
+ </Grid>
+ </Grid>
+
+ <Grid item container xs={12} spacing={8}>
+ <Grid item xs={12} md={6}>
+ <InputLabel shrink htmlFor="compositeApp-label-placeholder">
+ Composite App
+ </InputLabel>
+ <Select
+ fullWidth
+ name="compositeApp"
+ value={values.compositeApp}
+ onChange={(e) => {
+ handleChange(e);
+ handleSetCompositeApp(e.target.value);
+ }}
+ onBlur={handleBlur}
+ inputProps={{
+ name: "compositeApp",
+ id: "compositeApps-label-placeholder",
+ }}
+ >
+ {props.data &&
+ props.data.compositeApps.map((compositeApp) => (
+ <MenuItem
+ value={compositeApp.metadata.name}
+ key={compositeApp.metadata.name}
+ >
+ {compositeApp.metadata.name}
+ </MenuItem>
+ ))}
+ </Select>
+ </Grid>
+ <Grid item xs={12} md={6}>
+ <FormControl
+ fullWidth
+ required
+ error={errors.compositeProfile && touched.compositeProfile}
+ >
+ <InputLabel htmlFor="compositeProfile-label-placeholder">
+ Composite Profile
+ </InputLabel>
+ <Select
+ name="compositeProfile"
+ onChange={handleChange}
+ onBlur={handleBlur}
+ required
+ value={values.compositeProfile}
+ inputProps={{
+ name: "compositeProfile",
+ id: "compositeProfile-label-placeholder",
+ }}
+ >
+ {props.data.compositeApps[selectedAppIndex].profiles &&
+ props.data.compositeApps[selectedAppIndex].profiles.map(
+ (compositeProfile) => (
+ <MenuItem
+ value={compositeProfile.metadata.name}
+ key={compositeProfile.metadata.name}
+ >
+ {compositeProfile.metadata.name}
+ </MenuItem>
+ )
+ )}
+ </Select>
+ {errors.compositeProfile && touched.compositeProfile && (
+ <FormHelperText>Required</FormHelperText>
+ )}
+ </FormControl>
+ </Grid>
+ </Grid>
+
+ <Grid item container xs={12} spacing={8}>
+ <Grid item xs={12} md={6}>
+ <TextField
+ fullWidth
+ name="description"
+ value={values.description}
+ onChange={handleChange}
+ onBlur={handleBlur}
+ id="description"
+ label="Description"
+ multiline
+ rowsMax={4}
+ />
+ </Grid>
+ <Grid item xs={12} md={6}>
+ <TextField
+ fullWidth
+ id="overrideValues"
+ label="Override Values"
+ type="text"
+ value={values.overrideValues}
+ onChange={handleChange}
+ onBlur={handleBlur}
+ multiline
+ rows={4}
+ variant="outlined"
+ error={errors.overrideValues && touched.overrideValues}
+ helperText={
+ errors.overrideValues &&
+ touched.overrideValues &&
+ errors["overrideValues"]
+ }
+ />
+ </Grid>
+ </Grid>
+ <Grid item xs={12}>
+ <DialogActions>
+ <Button
+ autoFocus
+ disabled
+ onClick={props.onClickBack}
+ color="secondary"
+ >
+ Back
+ </Button>
+ <Button
+ autoFocus
+ type="submit"
+ color="primary"
+ disabled={isSubmitting}
+ >
+ Next
+ </Button>
+ </DialogActions>
+ </Grid>
+ </Grid>
+ </form>
+ );
+ }}
+ </Formik>
+ );
+}
+
+export default DigFormGeneral;
diff --git a/src/tools/emcoui/src/deploymentIntentGroups/DigFormIntents.jsx b/src/tools/emcoui/src/deploymentIntentGroups/DigFormIntents.jsx
new file mode 100644
index 00000000..580044ac
--- /dev/null
+++ b/src/tools/emcoui/src/deploymentIntentGroups/DigFormIntents.jsx
@@ -0,0 +1,160 @@
+//=======================================================================
+// Copyright (c) 2017-2020 Aarna Networks, Inc.
+// All rights reserved.
+// ======================================================================
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================================================================
+import React, { useEffect, useState } from "react";
+import { Formik } from "formik";
+import * as Yup from "yup";
+import AppForm from "./DigFormApp";
+import apiService from "../services/apiService";
+
+import { Button, DialogActions, Grid } from "@material-ui/core";
+
+DigFormIntents.propTypes = {};
+const schema = Yup.object({
+ apps: Yup.array()
+ .of(
+ Yup.object({
+ clusters: Yup.array()
+ .of(
+ Yup.object({
+ provider: Yup.string(),
+ selectedClusters: Yup.array().of(
+ Yup.object({
+ name: Yup.string(),
+ interfaces: Yup.array().of(
+ Yup.object({
+ networkName: Yup.string().required(),
+ subnet: Yup.string().required(),
+ ip: Yup.string().matches(
+ /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
+ "invalid ip address"
+ ),
+ })
+ ),
+ })
+ ),
+ })
+ )
+ .required("Select at least one cluster"),
+ })
+ )
+ .required("At least one app is required"),
+});
+
+function DigFormIntents(props) {
+ const { onSubmit, appsData } = props;
+ const [isLoading, setIsloading] = useState(true);
+ const [clusterProviders, setClusterProviders] = useState([]);
+ let initialValues = { apps: appsData };
+ useEffect(() => {
+ let clusterProviderData = [];
+ apiService
+ .getClusterProviders()
+ .then((res) => {
+ res.forEach((clusterProvider, providerIndex) => {
+ clusterProviderData.push({
+ name: clusterProvider.metadata.name,
+ clusters: [],
+ });
+ apiService
+ .getClusters(clusterProvider.metadata.name)
+ .then((clusters) => {
+ clusters.forEach((cluster) => {
+ clusterProviderData[providerIndex].clusters.push({
+ name: cluster.metadata.name,
+ description: cluster.metadata.description,
+ });
+ });
+ if (providerIndex + 1 === res.length) {
+ setClusterProviders(clusterProviderData);
+ setIsloading(false);
+ }
+ })
+ .catch((err) => {
+ console.log(
+ `error getting clusters for ${clusterProvider.metadata.name} : ` +
+ err
+ );
+ });
+ });
+ })
+ .catch((err) => {
+ console.log("error getting cluster providers : " + err);
+ });
+ }, []);
+ useEffect(() => {}, []);
+
+ return (
+ <Formik
+ initialValues={initialValues}
+ onSubmit={(values) => {
+ values.compositeAppVersion = onSubmit(values);
+ }}
+ validationSchema={schema}
+ >
+ {(formikProps) => {
+ const {
+ values,
+ isSubmitting,
+ handleChange,
+ handleSubmit,
+ } = formikProps;
+ return (
+ !isLoading && (
+ <form noValidate onSubmit={handleSubmit} onChange={handleChange}>
+ <Grid container spacing={4} justify="center">
+ {initialValues.apps &&
+ initialValues.apps.length > 0 &&
+ initialValues.apps.map((app, index) => (
+ <Grid key={index} item sm={12} xs={12}>
+ <AppForm
+ clusterProviders={clusterProviders}
+ formikProps={formikProps}
+ name={app.metadata.name}
+ description={app.metadata.description}
+ index={index}
+ initialValues={values}
+ />
+ </Grid>
+ ))}
+
+ <Grid item xs={12}>
+ <DialogActions>
+ <Button
+ autoFocus
+ onClick={props.onClickBack}
+ color="secondary"
+ >
+ Back
+ </Button>
+ <Button
+ autoFocus
+ type="submit"
+ color="primary"
+ disabled={isSubmitting}
+ >
+ Submit
+ </Button>
+ </DialogActions>
+ </Grid>
+ </Grid>
+ </form>
+ )
+ );
+ }}
+ </Formik>
+ );
+}
+
+export default DigFormIntents;
diff --git a/src/tools/emcoui/src/deploymentIntentGroups/Stepper.jsx b/src/tools/emcoui/src/deploymentIntentGroups/Stepper.jsx
new file mode 100644
index 00000000..746f49e3
--- /dev/null
+++ b/src/tools/emcoui/src/deploymentIntentGroups/Stepper.jsx
@@ -0,0 +1,118 @@
+//=======================================================================
+// Copyright (c) 2017-2020 Aarna Networks, Inc.
+// All rights reserved.
+// ======================================================================
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ========================================================================
+import React, { useState } from "react";
+import { makeStyles } from "@material-ui/core/styles";
+import Stepper from "@material-ui/core/Stepper";
+import Step from "@material-ui/core/Step";
+import StepLabel from "@material-ui/core/StepLabel";
+import DigFormGeneral from "./DigFormGeneral";
+import DigFormIntents from "./DigFormIntents";
+import apiService from "../services/apiService";
+
+const useStyles = makeStyles((theme) => ({
+ root: {
+ width: "100%",
+ },
+ backButton: {
+ marginRight: theme.spacing(1),
+ },
+ instructions: {
+ marginTop: theme.spacing(1),
+ marginBottom: theme.spacing(1),
+ },
+}));
+
+function getSteps() {
+ return ["General", "Intents"];
+}
+
+export default function HorizontalStepper(props) {
+ const classes = useStyles();
+ const [activeStep, setActiveStep] = useState(0);
+ const [generalData, setGeneralData] = useState(null);
+ const [intentsData, setIntentsData] = useState(null);
+ const [appsData, setAppsData] = useState([]);
+
+ const steps = getSteps();
+
+ function getStepContent(stepIndex) {
+ switch (stepIndex) {
+ case 0:
+ return (
+ <DigFormGeneral
+ data={props.data}
+ onSubmit={handleGeneralFormSubmit}
+ item={generalData}
+ />
+ );
+ case 1:
+ return (
+ <DigFormIntents
+ appsData={appsData}
+ onSubmit={handleIntentsFormSubmit}
+ onClickBack={handleBack}
+ item={intentsData}
+ />
+ );
+ default:
+ return "Unknown stepIndex";
+ }
+ }
+
+ const handleNext = () => {
+ setActiveStep((prevActiveStep) => prevActiveStep + 1);
+ };
+
+ const handleBack = () => {
+ setActiveStep((prevActiveStep) => prevActiveStep - 1);
+ };
+ const handleGeneralFormSubmit = (values) => {
+ setGeneralData(values);
+ let request = {
+ projectName: props.projectName,
+ compositeAppName: values.compositeApp,
+ compositeAppVersion: values.compositeAppVersion,
+ };
+ apiService
+ .getApps(request)
+ .then((res) => {
+ setAppsData(res);
+ handleNext((prevActiveStep) => prevActiveStep + 1);
+ })
+ .catch((err) => {
+ console.log("Error getting apps : " + err);
+ });
+ };
+
+ const handleIntentsFormSubmit = (values) => {
+ setIntentsData(values);
+ let digPayload = { general: generalData, intents: values };
+ props.onSubmit(digPayload);
+ };
+ return (
+ <div className={classes.root}>
+ <Stepper activeStep={activeStep} alternativeLabel>
+ {steps.map((label) => (
+ <Step key={label}>
+ <StepLabel>{label}</StepLabel>
+ </Step>
+ ))}
+ </Stepper>
+ <div>
+ <div>{getStepContent(activeStep)}</div>
+ </div>
+ </div>
+ );
+}
diff --git a/src/tools/emcoui/src/networkIntents/NetworkIntentCard.jsx b/src/tools/emcoui/src/networkIntents/NetworkIntentCard.jsx
index b641737b..ed93bd0f 100644
--- a/src/tools/emcoui/src/networkIntents/NetworkIntentCard.jsx
+++ b/src/tools/emcoui/src/networkIntents/NetworkIntentCard.jsx
@@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import React, { useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import clsx from "clsx";
@@ -54,7 +54,7 @@ const NetworkIntentCard = (props) => {
const [expanded, setExpanded] = useState(false);
const [workloadData, setWorkloadData] = useState([]);
const handleExpandClick = () => {
- if (!expanded && workloadData.length < 1) {
+ if (!expanded && workloadData && workloadData.length < 1) {
let request = {
projectName: props.projectName,
compositeAppName: props.compositeAppName,
@@ -189,6 +189,7 @@ const NetworkIntentCard = (props) => {
variant="outlined"
size="small"
color="secondary"
+ disabled={workloadData && workloadData.length > 0}
style={{ float: "right" }}
startIcon={<DeleteIcon />}
onClick={props.onDeleteNetworkControllerIntent.bind(
@@ -210,6 +211,9 @@ const NetworkIntentCard = (props) => {
}
/>
)}
+ {!(props.appsData && props.appsData.length > 0) && (
+ <div>No app found for adding workload intent</div>
+ )}
</CardContent>
</Collapse>
</Card>
diff --git a/src/tools/emcoui/src/networkIntents/WorkloadIntentTable.jsx b/src/tools/emcoui/src/networkIntents/WorkloadIntentTable.jsx
index 5e196ed9..540de8a0 100644
--- a/src/tools/emcoui/src/networkIntents/WorkloadIntentTable.jsx
+++ b/src/tools/emcoui/src/networkIntents/WorkloadIntentTable.jsx
@@ -11,207 +11,268 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
-import React, { useState } from 'react';
-import { TableContainer, Table, TableRow, TableHead, withStyles, Chip, TableCell } from '@material-ui/core';
+// ========================================================================
+import React, { useState } from "react";
+import {
+ TableContainer,
+ Table,
+ TableRow,
+ TableHead,
+ withStyles,
+ Chip,
+ TableCell,
+} from "@material-ui/core";
import Paper from "@material-ui/core/Paper";
import TableBody from "@material-ui/core/TableBody";
import EditIcon from "@material-ui/icons/Edit";
import DeleteIcon from "@material-ui/icons/Delete";
-import PropTypes from 'prop-types';
+import PropTypes from "prop-types";
import apiService from "../services/apiService";
import DeleteDialog from "../common/Dialogue";
-import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
-import IconButton from '@material-ui/core/IconButton';
-import AddIconOutline from '@material-ui/icons/AddCircleOutline';
+import InfoOutlinedIcon from "@material-ui/icons/InfoOutlined";
+import IconButton from "@material-ui/core/IconButton";
+import AddIconOutline from "@material-ui/icons/AddCircleOutline";
import Form from "./InterfaceForm";
import InterfaceDetailsDialog from "../common/DetailsDialog";
-
const StyledTableCell = withStyles((theme) => ({
- body: {
- fontSize: 14,
- },
+ body: {
+ fontSize: 14,
+ },
}))(TableCell);
const StyledTableRow = withStyles((theme) => ({
- root: {
- "&:nth-of-type(odd)": {
- backgroundColor: theme.palette.action.hover,
- },
+ root: {
+ "&:nth-of-type(odd)": {
+ backgroundColor: theme.palette.action.hover,
},
+ },
}))(TableRow);
const WokloadIntentTable = ({ data, setData, ...props }) => {
- const [formOpen, setFormOpen] = useState(false);
- const [index, setIndex] = useState(0);
- const [openDialog, setOpenDialog] = useState(false);
- const [openInterfaceDetails, setOpenInterfaceDetails] = useState(false);
- const [selectedInterface, setSelectedInterface] = useState({});
- const [openInterfaceDialog, setOpenInterfaceDialog] = useState(false);
- const handleDelete = (index) => {
- setIndex(index);
- setOpenDialog(true);
- }
- const handleEdit = () => {
+ const [formOpen, setFormOpen] = useState(false);
+ const [index, setIndex] = useState(0);
+ const [openDialog, setOpenDialog] = useState(false);
+ const [openInterfaceDetails, setOpenInterfaceDetails] = useState(false);
+ const [selectedInterface, setSelectedInterface] = useState({});
+ const [openInterfaceDialog, setOpenInterfaceDialog] = useState(false);
+ const handleDelete = (index) => {
+ setIndex(index);
+ setOpenDialog(true);
+ };
+ const handleEdit = () => {};
+ const handleInterfaceDetailOpen = (entry) => {
+ setSelectedInterface(entry);
+ setOpenInterfaceDetails(true);
+ };
+ const handleDeleteInterface = (index, entry) => {
+ setIndex(index);
+ setSelectedInterface(entry);
+ setOpenInterfaceDialog(true);
+ };
+ const handleAddInterface = (index) => {
+ setIndex(index);
+ setFormOpen(true);
+ };
+ const handleCloseForm = () => {
+ setFormOpen(false);
+ };
+ const handleCloseInterfaceDialog = (el) => {
+ if (el.target.innerText === "Delete") {
+ let request = {
+ projectName: props.projectName,
+ compositeAppName: props.compositeAppName,
+ compositeAppVersion: props.compositeAppVersion,
+ networkControllerIntentName: props.networkControllerIntentName,
+ workloadIntentName: data[index].metadata.name,
+ interfaceName: selectedInterface.metadata.name,
+ };
+ apiService
+ .deleteInterface(request)
+ .then(() => {
+ console.log("Interface deleted");
+ let updatedInterfaceData = data[index].interfaces.filter(function (
+ obj
+ ) {
+ return obj.metadata.name !== selectedInterface.metadata.name;
+ });
+ data[index].interfaces = updatedInterfaceData;
+ setData([...data]);
+ })
+ .catch((err) => {
+ console.log("Error deleting interface : ", err);
+ })
+ .finally(() => {
+ setIndex(0);
+ setSelectedInterface({});
+ });
}
- const handleInterfaceDetailOpen = (entry) => {
- setSelectedInterface(entry);
- setOpenInterfaceDetails(true);
+ setOpenInterfaceDialog(false);
+ };
+ const handleCloseDialog = (el) => {
+ if (el.target.innerText === "Delete") {
+ let request = {
+ projectName: props.projectName,
+ compositeAppName: props.compositeAppName,
+ compositeAppVersion: props.compositeAppVersion,
+ networkControllerIntentName: props.networkControllerIntentName,
+ workloadIntentName: data[index].metadata.name,
+ };
+ apiService
+ .deleteWorkloadIntent(request)
+ .then(() => {
+ console.log("workload intent deleted");
+ data.splice(index, 1);
+ setData([...data]);
+ })
+ .catch((err) => {
+ console.log("Error deleting workload intent : ", err);
+ })
+ .finally(() => {
+ setIndex(0);
+ });
}
-
- const handleDeleteInterface = (index, entry) => {
- setIndex(index);
- setSelectedInterface(entry);
- setOpenInterfaceDialog(true);
- }
- const handleAddInterface = (index) => {
- setIndex(index);
- setFormOpen(true);
- }
- const handleCloseForm = () => {
- setFormOpen(false);
- }
- const handleCloseInterfaceDialog = (el) => {
- if (el.target.innerText === "Delete") {
- let request = {
- projectName: props.projectName,
- compositeAppName: props.compositeAppName,
- compositeAppVersion: props.compositeAppVersion,
- networkControllerIntentName: props.networkControllerIntentName,
- workloadIntentName: data[index].metadata.name,
- interfaceName: selectedInterface.metadata.name
- }
- apiService.deleteInterface(request).then(() => {
- console.log("Interface deleted");
- let updatedInterfaceData = data[index].interfaces.filter(function (obj) {
- return obj.metadata.name !== selectedInterface.metadata.name;
- });
- data[index].interfaces = updatedInterfaceData;
- setData([...data]);
- }).catch(err => {
- console.log("Error deleting interface : ", err)
- }).finally(() => {
- setIndex(0);
- setSelectedInterface({});
- })
- }
- setOpenInterfaceDialog(false);
- }
- const handleCloseDialog = (el) => {
- if (el.target.innerText === "Delete") {
- let request = {
- projectName: props.projectName,
- compositeAppName: props.compositeAppName,
- compositeAppVersion: props.compositeAppVersion,
- networkControllerIntentName: props.networkControllerIntentName,
- workloadIntentName: data[index].metadata.name
- }
- apiService.deleteWorkloadIntent(request).then(() => {
- console.log("workload intent deleted");
- data.splice(index, 1);
- setData([...data]);
- }).catch(err => {
- console.log("Error deleting workload intent : ", err)
- }).finally(() => {
- setIndex(0);
- })
+ setOpenDialog(false);
+ };
+ const handleSubmit = (values) => {
+ let spec = values.spec ? JSON.parse(values.spec) : "";
+ let request = {
+ payload: {
+ metadata: { name: values.name, description: values.description },
+ spec: spec,
+ },
+ projectName: props.projectName,
+ compositeAppName: props.compositeAppName,
+ compositeAppVersion: props.compositeAppVersion,
+ networkControllerIntentName: props.networkControllerIntentName,
+ workloadIntentName: data[index].metadata.name,
+ };
+ apiService
+ .addInterface(request)
+ .then((res) => {
+ if (data[index].interfaces && data[index].interfaces.length > 0) {
+ data[index].interfaces.push(res);
+ } else {
+ data[index].interfaces = [res];
}
- setOpenDialog(false);
- }
- const handleSubmit = (values) => {
- let spec = values.spec ? JSON.parse(values.spec) : "";
- let request = {
- payload: { metadata: { name: values.name, description: values.description }, spec: spec },
- projectName: props.projectName,
- compositeAppName: props.compositeAppName,
- compositeAppVersion: props.compositeAppVersion,
- networkControllerIntentName: props.networkControllerIntentName,
- workloadIntentName: data[index].metadata.name
- };
- apiService.addInterface(request)
- .then(res => {
- if (data[index].interfaces && data[index].interfaces.length > 0) {
- data[index].interfaces.push(res);
- }
- else {
- data[index].interfaces = [res];
- }
- setData([...data]);
- })
- .catch(err => {
- console.log("error creating composite profile : ", err);
- })
- .finally(() => { setFormOpen(false); })
- }
- return (
- <>
- <InterfaceDetailsDialog open={openInterfaceDetails} onClose={setOpenInterfaceDetails} item={selectedInterface} type="Interface" />
- <Form open={formOpen} onClose={handleCloseForm} onSubmit={handleSubmit} />
- <DeleteDialog open={openDialog} onClose={handleCloseDialog} title={"Delete Profile"}
- content={`Are you sure you want to delete "${data && data[index] ? data[index].metadata.name : ""}"`} />
- <DeleteDialog open={openInterfaceDialog} onClose={handleCloseInterfaceDialog} title={"Delete Interface"}
- content={`Are you sure you want to delete "${selectedInterface.metadata ? selectedInterface.metadata.name : ""}"`} />
- <TableContainer component={Paper}>
- <Table>
- <TableHead>
- <TableRow>
- <StyledTableCell>Name</StyledTableCell>
- <StyledTableCell>Description</StyledTableCell>
- <StyledTableCell>App</StyledTableCell>
- <StyledTableCell>Workload Resource</StyledTableCell>
- <StyledTableCell style={{ width: "27%" }}>Interfaces</StyledTableCell>
- <StyledTableCell>Actions</StyledTableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {data.map((entry, index) =>
- <StyledTableRow key={entry.metadata.name + index}>
- <StyledTableCell>
- {entry.metadata.name}
- </StyledTableCell>
- <StyledTableCell >
- {entry.metadata.description}
- </StyledTableCell>
- <StyledTableCell >
- {entry.spec["application-name"]}
- </StyledTableCell>
- <StyledTableCell>
- {entry.spec["workload-resource"]}
- </StyledTableCell>
- <StyledTableCell>
- {entry.interfaces && (entry.interfaces.length > 0) && entry.interfaces.map((interfaceEntry, interfacekIndex) =>
- (<Chip
- key={interfaceEntry.metadata.name + "" + interfacekIndex}
- size="small"
- icon={<InfoOutlinedIcon onClick={() => { handleInterfaceDetailOpen(interfaceEntry) }} style={{ cursor: "pointer" }} />}
- onDelete={(e) => { handleDeleteInterface(index, interfaceEntry) }}
- label={interfaceEntry.spec.ipAddress}
- style={{ marginRight: "10px", marginBottom: "5px" }}
- />)
- )}
- <IconButton color="primary" onClick={() => { handleAddInterface(index) }}>
- <AddIconOutline />
- </IconButton>
- </StyledTableCell>
- <StyledTableCell >
- <IconButton onClick={(e) => handleEdit(index)} title="Edit" >
- <EditIcon color="primary" />
- </IconButton>
- <IconButton onClick={(e) => handleDelete(index)} title="Delete" >
- <DeleteIcon color="secondary" />
- </IconButton>
- </StyledTableCell>
- </StyledTableRow>
- )}
- </TableBody>
- </Table>
- </TableContainer></>
- );
+ setData([...data]);
+ })
+ .catch((err) => {
+ console.log("error creating composite profile : ", err);
+ })
+ .finally(() => {
+ setFormOpen(false);
+ });
+ };
+ return (
+ <>
+ <InterfaceDetailsDialog
+ open={openInterfaceDetails}
+ onClose={setOpenInterfaceDetails}
+ item={selectedInterface}
+ type="Interface"
+ />
+ <Form open={formOpen} onClose={handleCloseForm} onSubmit={handleSubmit} />
+ <DeleteDialog
+ open={openDialog}
+ onClose={handleCloseDialog}
+ title={"Delete Profile"}
+ content={`Are you sure you want to delete "${
+ data && data[index] ? data[index].metadata.name : ""
+ }"`}
+ />
+ <DeleteDialog
+ open={openInterfaceDialog}
+ onClose={handleCloseInterfaceDialog}
+ title={"Delete Interface"}
+ content={`Are you sure you want to delete "${
+ selectedInterface.metadata ? selectedInterface.metadata.name : ""
+ }"`}
+ />
+ <TableContainer component={Paper}>
+ <Table>
+ <TableHead>
+ <TableRow>
+ <StyledTableCell>Name</StyledTableCell>
+ <StyledTableCell>Description</StyledTableCell>
+ <StyledTableCell>App</StyledTableCell>
+ <StyledTableCell>Workload Resource</StyledTableCell>
+ <StyledTableCell style={{ width: "27%" }}>
+ Interfaces
+ </StyledTableCell>
+ <StyledTableCell>Actions</StyledTableCell>
+ </TableRow>
+ </TableHead>
+ <TableBody>
+ {data.map((entry, index) => (
+ <StyledTableRow key={entry.metadata.name + index}>
+ <StyledTableCell>{entry.metadata.name}</StyledTableCell>
+ <StyledTableCell>{entry.metadata.description}</StyledTableCell>
+ <StyledTableCell>
+ {entry.spec["application-name"]}
+ </StyledTableCell>
+ <StyledTableCell>
+ {entry.spec["workload-resource"]}
+ </StyledTableCell>
+ <StyledTableCell>
+ {entry.interfaces &&
+ entry.interfaces.length > 0 &&
+ entry.interfaces.map((interfaceEntry, interfacekIndex) => (
+ <Chip
+ key={
+ interfaceEntry.metadata.name + "" + interfacekIndex
+ }
+ size="small"
+ icon={
+ <InfoOutlinedIcon
+ onClick={() => {
+ handleInterfaceDetailOpen(interfaceEntry);
+ }}
+ style={{ cursor: "pointer" }}
+ />
+ }
+ onDelete={(e) => {
+ handleDeleteInterface(index, interfaceEntry);
+ }}
+ label={interfaceEntry.spec.ipAddress}
+ style={{ marginRight: "10px", marginBottom: "5px" }}
+ />
+ ))}
+ <IconButton
+ color="primary"
+ onClick={() => {
+ handleAddInterface(index);
+ }}
+ >
+ <AddIconOutline />
+ </IconButton>
+ </StyledTableCell>
+ <StyledTableCell>
+ {/*
+ //edit workload intent api has not been added yet
+ <IconButton onClick={(e) => handleEdit(index)} title="Edit" >
+ <EditIcon color="primary" />
+ </IconButton> */}
+ <IconButton
+ color="secondary"
+ disabled={entry.interfaces && entry.interfaces.length > 0}
+ onClick={(e) => handleDelete(index)}
+ title="Delete"
+ >
+ <DeleteIcon />
+ </IconButton>
+ </StyledTableCell>
+ </StyledTableRow>
+ ))}
+ </TableBody>
+ </Table>
+ </TableContainer>
+ </>
+ );
};
WokloadIntentTable.propTypes = {
- data: PropTypes.arrayOf(PropTypes.object).isRequired,
- setData: PropTypes.func.isRequired
+ data: PropTypes.arrayOf(PropTypes.object).isRequired,
+ setData: PropTypes.func.isRequired,
};
export default WokloadIntentTable;
diff --git a/src/tools/emcoui/src/services/apiService.js b/src/tools/emcoui/src/services/apiService.js
index 4bff9305..0c830768 100644
--- a/src/tools/emcoui/src/services/apiService.js
+++ b/src/tools/emcoui/src/services/apiService.js
@@ -11,10 +11,9 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-// ========================================================================
+// ========================================================================
import axios from "axios";
axios.defaults.baseURL = process.env.REACT_APP_BACKEND || "";
-
//orchestrator
//projects
const createProject = (request) => {
@@ -45,6 +44,14 @@ const getCompositeApps = (request) => {
return res.data;
});
};
+const addService = ({ projectName, ...request }) => {
+ return axios
+ .post(`/middleend/projects/${projectName}/composite-apps`, request.payload)
+ .then((res) => {
+ return res.data;
+ });
+};
+
const createCompositeApp = ({ projectName, ...request }) => {
return axios
.post(`/v2/projects/${projectName}/composite-apps`, request.payload)
@@ -65,7 +72,7 @@ const updateCompositeApp = (request) => {
const deleteCompositeApp = (request) => {
return axios
.delete(
- `/v2/projects/${request.projectName}/composite-apps/${request.compositeAppName}/${request.compositeAppVersion}`
+ `/middleend/projects/${request.projectName}/composite-apps/${request.compositeAppName}/${request.compositeAppVersion}`
)
.then((res) => {
return res.data;
@@ -302,15 +309,10 @@ const deleteInterface = (request) => {
};
//deployment intent group
-const createDeploymentIntentGroup = ({
- projectName,
- compositeAppName,
- compositeAppVersion,
- ...request
-}) => {
+const createDeploymentIntentGroup = (request) => {
return axios
.post(
- `/v2/projects/${projectName}/composite-apps/${compositeAppName}/${compositeAppVersion}/deployment-intent-groups`,
+ `/middleend/projects/${request.spec.projectName}/composite-apps/${request.compositeApp}/${request.compositeAppVersion}/deployment-intent-groups`,
{ ...request }
)
.then((res) => {
@@ -329,9 +331,7 @@ const addIntentsToDeploymentIntentGroup = (request) => {
};
const getDeploymentIntentGroups = (request) => {
return axios
- .get(
- `/v2/projects/${request.projectName}/composite-apps/${request.compositeAppName}/${request.compositeAppVersion}/deployment-intent-groups`
- )
+ .get(`/middleend/projects/${request.projectName}/deployment-intent-groups`)
.then((res) => {
return res.data;
});
@@ -349,7 +349,7 @@ const editDeploymentIntentGroup = (request) => {
const deleteDeploymentIntentGroup = (request) => {
return axios
.delete(
- `/v2/projects/${request.projectName}/composite-apps/${request.compositeAppName}/${request.compositeAppVersion}/deployment-intent-groups/${request.deploymentIntentGroupName}`
+ `/middleend/projects/${request.projectName}/composite-apps/${request.compositeAppName}/${request.compositeAppVersion}/deployment-intent-groups/${request.deploymentIntentGroupName}`
)
.then((res) => {
return res.data;
@@ -421,7 +421,7 @@ const updateClusterProvider = (request) => {
const addCluster = (request) => {
return axios
.post(
- `/v2/cluster-providers/${request.get("providerName")}/clusters`,
+ `/middleend/clusterproviders/${request.get("providerName")}/clusters`,
request
)
.then((res) => {
@@ -577,6 +577,7 @@ const vimService = {
getCompositeApps,
getProfiles,
createCompositeApp,
+ addService,
updateCompositeApp,
deleteCompositeApp,
getApps,