diff --git a/src/fireedge/package.json b/src/fireedge/package.json
index d0cb278fc9..898b77ecc3 100644
--- a/src/fireedge/package.json
+++ b/src/fireedge/package.json
@@ -43,9 +43,11 @@
"express": "^4.17.1",
"fireedge-genpotfile": "^1.0.0",
"fs-extra": "^9.0.1",
+ "fuse.js": "^6.4.1",
"helmet": "^3.23.3",
"http": "0.0.1-security",
"immutable": "^4.0.0-rc.12",
+ "intersection-observer": "^0.11.0",
"jsonschema": "^1.2.6",
"jsonwebtoken": "^8.5.1",
"jwt-simple": "^0.5.6",
diff --git a/src/fireedge/src/public/components/Cards/SelectCard.js b/src/fireedge/src/public/components/Cards/SelectCard.js
new file mode 100644
index 0000000000..45e70e6617
--- /dev/null
+++ b/src/fireedge/src/public/components/Cards/SelectCard.js
@@ -0,0 +1,73 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import clsx from 'clsx';
+
+import { Card, CardActionArea, Fade, makeStyles } from '@material-ui/core';
+import { Skeleton } from '@material-ui/lab';
+
+import useNearScreen from 'client/hooks/useNearScreen';
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ height: '100%'
+ },
+ selected: {
+ color: theme.palette.primary.contrastText,
+ backgroundColor: theme.palette.primary.main
+ },
+ actionArea: {
+ height: '100%',
+ minHeight: 140,
+ padding: theme.spacing(1)
+ }
+}));
+
+const SelectCard = React.memo(
+ ({ isSelected, handleSelect, handleUnselect, ID, NAME }) => {
+ const classes = useStyles();
+ const { isNearScreen, fromRef } = useNearScreen({
+ distance: '100px'
+ });
+
+ return (
+
+ {isNearScreen ? (
+
+
+
+ isSelected ? handleUnselect(ID) : handleSelect(ID)
+ }
+ >
+ {`📦 ${NAME}`}
+
+
+
+ ) : (
+
+ )}
+
+ );
+ }
+);
+
+SelectCard.propTypes = {
+ isSelected: PropTypes.bool,
+ handleSelect: PropTypes.func,
+ handleUnselect: PropTypes.func,
+ ID: PropTypes.string,
+ NAME: PropTypes.string
+};
+
+SelectCard.defaultProps = {
+ isSelected: false,
+ handleSelect: () => undefined,
+ handleUnselect: () => undefined,
+ ID: undefined,
+ NAME: undefined
+};
+
+export default SelectCard;
diff --git a/src/fireedge/src/public/components/Cards/index.js b/src/fireedge/src/public/components/Cards/index.js
index cc45f28981..45d96cd233 100644
--- a/src/fireedge/src/public/components/Cards/index.js
+++ b/src/fireedge/src/public/components/Cards/index.js
@@ -1,5 +1,6 @@
import ClusterCard from 'client/components/Cards/ClusterCard';
import NetworkCard from 'client/components/Cards/NetworkCard';
import RoleCard from 'client/components/Cards/RoleCard';
+import SelectCard from 'client/components/Cards/SelectCard';
-export { ClusterCard, NetworkCard, RoleCard };
+export { ClusterCard, NetworkCard, RoleCard, SelectCard };
diff --git a/src/fireedge/src/public/components/Dialogs/DialogForm.js b/src/fireedge/src/public/components/Dialogs/DialogForm.js
index e1088e0642..3ac71a6306 100644
--- a/src/fireedge/src/public/components/Dialogs/DialogForm.js
+++ b/src/fireedge/src/public/components/Dialogs/DialogForm.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { memo } from 'react';
import PropTypes from 'prop-types';
import {
@@ -14,7 +14,7 @@ import { yupResolver } from '@hookform/resolvers';
import { Tr } from 'client/components/HOC';
-const DialogForm = React.memo(
+const DialogForm = memo(
({ open, title, values, resolver, onSubmit, onCancel, children }) => {
const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'));
@@ -30,25 +30,33 @@ const DialogForm = React.memo(
open={open}
maxWidth="lg"
scroll="paper"
- PaperProps={{ style: { height: '80%', minWidth: '80%' } }}
+ PaperProps={{
+ style: { height: '100%', minHeight: '80%', minWidth: '80%' }
+ }}
>
{title}
{children}
-
-
-
-
+ {(onCancel || onSubmit) && (
+
+ {onCancel && (
+
+ )}
+ {onSubmit && (
+
+ )}
+
+ )}
);
}
@@ -75,8 +83,8 @@ DialogForm.defaultProps = {
title: 'Title dialog form',
values: {},
resolver: {},
- onSubmit: () => undefined,
- onCancel: () => undefined,
+ onSubmit: undefined,
+ onCancel: undefined,
children: null
};
diff --git a/src/fireedge/src/public/components/Footer/styles.js b/src/fireedge/src/public/components/Footer/styles.js
index a647b591c5..fa755c7196 100644
--- a/src/fireedge/src/public/components/Footer/styles.js
+++ b/src/fireedge/src/public/components/Footer/styles.js
@@ -18,6 +18,7 @@ export default makeStyles(theme => ({
color: theme.palette.error.dark
},
link: {
- color: theme.palette.primary.light
+ color: theme.palette.primary.light,
+ marginLeft: theme.spacing(1)
}
}));
diff --git a/src/fireedge/src/public/components/FormStepper/FormList.js b/src/fireedge/src/public/components/FormStepper/FormList.js
index 1ac76f28b1..64cb392dda 100644
--- a/src/fireedge/src/public/components/FormStepper/FormList.js
+++ b/src/fireedge/src/public/components/FormStepper/FormList.js
@@ -1,7 +1,6 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, useMemo } from 'react';
import PropTypes from 'prop-types';
-import { Box } from '@material-ui/core';
import { useFormContext } from 'react-hook-form';
import ErrorHelper from 'client/components/FormControl/ErrorHelper';
@@ -14,7 +13,7 @@ function FormList({ step, data, setFormData }) {
const { id, preRender, ListComponent, DialogComponent, DEFAULT_DATA } = step;
useEffect(() => {
- preRender && preRender();
+ if (preRender) preRender();
}, []);
const handleSubmit = values => {
@@ -55,19 +54,24 @@ function FormList({ step, data, setFormData }) {
const handleClose = () => setShowDialog(false);
return (
-
+ <>
{typeof errors[id]?.message === 'string' && (
)}
- handleOpen()}
- itemsProps={({ index }) => ({
- handleEdit: () => handleOpen(index),
- handleClone: () => handleClone(index),
- handleRemove: () => handleRemove(index)
- })}
- />
+ {useMemo(
+ () => (
+ handleOpen()}
+ itemsProps={({ index }) => ({
+ handleEdit: () => handleOpen(index),
+ handleClone: () => handleClone(index),
+ handleRemove: () => handleRemove(index)
+ })}
+ />
+ ),
+ [data, handleOpen, handleClone, handleRemove]
+ )}
{showDialog && DialogComponent && (
)}
-
+ >
);
}
diff --git a/src/fireedge/src/public/components/FormStepper/FormListSelect.js b/src/fireedge/src/public/components/FormStepper/FormListSelect.js
index 99c8195625..214a9b0d4b 100644
--- a/src/fireedge/src/public/components/FormStepper/FormListSelect.js
+++ b/src/fireedge/src/public/components/FormStepper/FormListSelect.js
@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
-import { Box, Grid } from '@material-ui/core';
+import { Grid } from '@material-ui/core';
import { useFormContext } from 'react-hook-form';
import ErrorHelper from '../FormControl/ErrorHelper';
@@ -11,7 +11,7 @@ function FormListSelect({ step, data, setFormData }) {
const { id, onlyOneSelect, preRender, list, ItemComponent } = step;
useEffect(() => {
- preRender && preRender();
+ if (preRender) preRender();
}, []);
const handleSelect = index =>
@@ -27,26 +27,24 @@ function FormListSelect({ step, data, setFormData }) {
}));
return (
-
-
- {typeof errors[id]?.message === 'string' && (
-
-
+
+ {typeof errors[id]?.message === 'string' && (
+
+
+
+ )}
+ {Array.isArray(list) &&
+ list?.map((info, index) => (
+
+ selected === info?.ID)}
+ handleSelect={handleSelect}
+ handleUnselect={handleUnselect}
+ />
- )}
- {Array.isArray(list) &&
- list?.map((info, index) => (
-
- selected === info?.ID)}
- handleSelect={handleSelect}
- handleUnselect={handleUnselect}
- />
-
- ))}
-
-
+ ))}
+
);
}
diff --git a/src/fireedge/src/public/components/FormStepper/FormStep.js b/src/fireedge/src/public/components/FormStepper/FormStep.js
index d6aa1ca80d..54011c5a68 100644
--- a/src/fireedge/src/public/components/FormStepper/FormStep.js
+++ b/src/fireedge/src/public/components/FormStepper/FormStep.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, useMemo } from 'react';
import PropTypes from 'prop-types';
import { useFormContext } from 'react-hook-form';
@@ -11,29 +11,32 @@ function FormStep({ step, data, setFormData }) {
const { id, preRender, FormComponent, DialogComponent } = step;
useEffect(() => {
- preRender && preRender();
+ if (preRender) preRender();
}, []);
const handleOpen = () => setShowDialog(true);
const handleClose = () => setShowDialog(false);
- const handleSubmit = d => console.log(d);
return (
<>
{typeof errors[id]?.message === 'string' && (
)}
- {React.useMemo(
+ {useMemo(
() => (
-
+
),
- [id, handleOpen]
+ [id, handleOpen, setFormData, data]
)}
{showDialog && DialogComponent && (
)}
@@ -43,7 +46,11 @@ function FormStep({ step, data, setFormData }) {
FormStep.propTypes = {
step: PropTypes.objectOf(PropTypes.any).isRequired,
- data: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired,
+ data: PropTypes.oneOfType([
+ PropTypes.array,
+ PropTypes.object,
+ PropTypes.string
+ ]).isRequired,
setFormData: PropTypes.func.isRequired
};
diff --git a/src/fireedge/src/public/components/Header/Group.js b/src/fireedge/src/public/components/Header/Group.js
index ad4b933f96..848cd94a60 100644
--- a/src/fireedge/src/public/components/Header/Group.js
+++ b/src/fireedge/src/public/components/Header/Group.js
@@ -23,9 +23,6 @@ const Group = () => {
group && setPrimaryGroup({ group });
};
- const filterSearch = ({ NAME }, search) =>
- NAME?.toLowerCase().includes(search);
-
const renderResult = ({ ID, NAME }, handleClose) => {
const isSelected =
(filterPool === ALL_RESOURCES && ALL_RESOURCES === ID) ||
@@ -47,16 +44,18 @@ const Group = () => {
);
};
+ const sortGroupAsMainFirst = (a, b) => {
+ if (a.ID === authUser?.GUID) {
+ return -1;
+ } else if (b.ID === authUser?.GUID) {
+ return 1;
+ }
+ return 0;
+ };
+
const sortMainGroupFirst = groups
?.concat({ ID: ALL_RESOURCES, NAME: 'Show All' })
- ?.sort((a, b) => {
- if (a.ID === authUser?.GUID) {
- return -1;
- } else if (b.ID === authUser?.GUID) {
- return 1;
- }
- return 0;
- });
+ ?.sort(sortGroupAsMainFirst);
return (
{
{({ handleClose }) => (
renderResult(group, handleClose)}
/>
)}
diff --git a/src/fireedge/src/public/components/List/ListCards.js b/src/fireedge/src/public/components/List/ListCards.js
index 1405409723..3cc3b8cec1 100644
--- a/src/fireedge/src/public/components/List/ListCards.js
+++ b/src/fireedge/src/public/components/List/ListCards.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import {
@@ -19,17 +19,17 @@ const useStyles = makeStyles(() => ({
}
}));
-function ListCards({ addCardClick, list, CardComponent, cardsProps }) {
+function ListCards({ handleCreate, list, CardComponent, cardsProps }) {
const classes = useStyles();
return (
- {addCardClick &&
- React.useMemo(
+ {handleCreate &&
+ useMemo(
() => (
-
+
@@ -37,7 +37,7 @@ function ListCards({ addCardClick, list, CardComponent, cardsProps }) {
),
- [addCardClick, classes]
+ [handleCreate, classes]
)}
{Array.isArray(list) &&
list?.map((value, index) => (
@@ -51,7 +51,7 @@ function ListCards({ addCardClick, list, CardComponent, cardsProps }) {
ListCards.propTypes = {
list: PropTypes.arrayOf(PropTypes.any).isRequired,
- addCardClick: PropTypes.func,
+ handleCreate: PropTypes.func,
CardComponent: PropTypes.oneOfType([
PropTypes.node,
PropTypes.object,
@@ -62,7 +62,7 @@ ListCards.propTypes = {
ListCards.defaultProps = {
list: [],
- addCardClick: [],
+ handleCreate: undefined,
CardComponent: null,
cardsProps: () => undefined
};
diff --git a/src/fireedge/src/public/components/List/ListInfiniteScroll.js b/src/fireedge/src/public/components/List/ListInfiniteScroll.js
new file mode 100644
index 0000000000..1ece6695f6
--- /dev/null
+++ b/src/fireedge/src/public/components/List/ListInfiniteScroll.js
@@ -0,0 +1,71 @@
+import React, { useRef, useEffect, useCallback, createRef } from 'react';
+import PropTypes from 'prop-types';
+
+import { debounce, LinearProgress } from '@material-ui/core';
+
+import useNearScreen from 'client/hooks/useNearScreen';
+import useList from 'client/hooks/useList';
+
+const ListInfiniteScroll = ({ list, renderResult }) => {
+ const gridRef = createRef();
+ const { loading, shortList, finish, reset, setLength } = useList({
+ list,
+ initLength: 50
+ });
+
+ const loaderRef = useRef();
+ const { isNearScreen } = useNearScreen({
+ distance: '100px',
+ externalRef: loading ? null : loaderRef,
+ once: false
+ });
+
+ useEffect(() => {
+ reset(list);
+ gridRef.current.scrollIntoView({ block: 'start' });
+ }, [list]);
+
+ const debounceHandleNextPage = useCallback(
+ debounce(() => {
+ setLength(prevLength => prevLength + 20);
+ }, 200),
+ [setLength]
+ );
+
+ useEffect(() => {
+ if (isNearScreen && !finish) debounceHandleNextPage();
+ }, [isNearScreen, finish, debounceHandleNextPage]);
+
+ return (
+
+
+ {shortList?.map(renderResult)}
+
+ {!finish && (
+
+ )}
+
+ );
+};
+
+ListInfiniteScroll.propTypes = {
+ list: PropTypes.arrayOf(PropTypes.any),
+ renderResult: PropTypes.func
+};
+
+ListInfiniteScroll.defaultProps = {
+ list: [],
+ renderResult: () => null
+};
+
+export default ListInfiniteScroll;
diff --git a/src/fireedge/src/public/components/ProcessScreen/index.js b/src/fireedge/src/public/components/ProcessScreen/index.js
new file mode 100644
index 0000000000..91608c4fa3
--- /dev/null
+++ b/src/fireedge/src/public/components/ProcessScreen/index.js
@@ -0,0 +1,86 @@
+import React, { useState, useEffect, createElement } from 'react';
+import PropTypes from 'prop-types';
+
+import { Fade, Button, IconButton } from '@material-ui/core';
+import BackIcon from '@material-ui/icons/ArrowBackIosOutlined';
+
+function ProcessScreen({ screens, id, values, setFormData }) {
+ const [process, setProcess] = useState(undefined);
+
+ useEffect(() => {
+ const keyValues = Object.keys(values);
+
+ if (keyValues.length > 0) {
+ const currentScreen = keyValues[0];
+ const index = screens.findIndex(scr => scr.id === currentScreen);
+ if (index !== -1) setProcess(index);
+ }
+ }, []);
+
+ const handleSetData = data =>
+ setFormData(prevData => ({
+ ...prevData,
+ [id]: data ? { [screens[process]?.id]: data } : undefined
+ }));
+
+ const handleBack = () => {
+ setProcess(undefined);
+ handleSetData();
+ };
+
+ return (
+ <>
+ {process !== undefined ? (
+ createElement(screens[process]?.screen, {
+ backButton: (
+
+
+
+ ),
+ handleSetData,
+ currentValue: values[screens[process]?.id]
+ })
+ ) : (
+
+
+ {screens?.map(({ id, button }, index) => (
+
+
+
+ ))}
+
+
+ )}
+ >
+ );
+}
+
+ProcessScreen.propTypes = {
+ screens: PropTypes.arrayOf(
+ PropTypes.shape({
+ id: PropTypes.string,
+ button: PropTypes.element,
+ screen: PropTypes.func
+ })
+ )
+};
+
+ProcessScreen.defaultProps = {
+ screens: []
+};
+
+export default ProcessScreen;
diff --git a/src/fireedge/src/public/components/Search/index.js b/src/fireedge/src/public/components/Search/index.js
index 760ae9c96a..60d152eac2 100644
--- a/src/fireedge/src/public/components/Search/index.js
+++ b/src/fireedge/src/public/components/Search/index.js
@@ -1,54 +1,81 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useMemo } from 'react';
import PropTypes from 'prop-types';
-import { TextField, Box } from '@material-ui/core';
+import Fuse from 'fuse.js';
+import { TextField, Box, debounce } from '@material-ui/core';
+
+import ListInfiniteScroll from 'client/components/List/ListInfiniteScroll';
const Search = ({
list,
- maxResults,
- filterSearch,
+ listOptions,
renderResult,
- ResultBoxProps
+ startAdornment,
+ searchBoxProps
}) => {
- const [search, setSearch] = useState('');
- const [result, setResult] = useState(list);
+ const [query, setQuery] = useState('');
+ const [result, setResult] = useState(undefined);
+ const listFuse = useMemo(
+ () => new Fuse(list, listOptions, Fuse.createIndex(listOptions.keys, list)),
+ [list, listOptions]
+ );
- useEffect(() => {
- setResult(list?.filter(item => filterSearch(item, search)));
- }, [search, list]);
+ const debounceResult = React.useCallback(
+ debounce(value => {
+ const search = listFuse.search(value)?.map(({ item }) => item);
- const handleChange = event => setSearch(event.target.value);
+ setResult(value ? search : undefined);
+ }, 1000),
+ [list]
+ );
+
+ const handleChange = event => {
+ const { value: nextValue } = event?.target;
+
+ setQuery(nextValue);
+ debounceResult(nextValue);
+ };
return (
<>
-
-
- {result?.slice(0, maxResults).map(renderResult)}
+
+ {startAdornment && startAdornment}
+
+ {result?.length === 0 ? (
+ {`Your search did not match`}
+ ) : (
+
+ )}
>
);
};
Search.propTypes = {
list: PropTypes.arrayOf(PropTypes.object).isRequired,
- maxResults: PropTypes.number,
- filterSearch: PropTypes.func,
+ listOptions: PropTypes.shape({
+ isCaseSensitive: PropTypes.bool,
+ shouldSort: PropTypes.bool,
+ sortFn: PropTypes.func,
+ keys: PropTypes.arrayOf(PropTypes.string)
+ }),
renderResult: PropTypes.func,
- ResultBoxProps: PropTypes.objectOf(PropTypes.object)
+ startAdornment: PropTypes.objectOf(PropTypes.any),
+ searchBoxProps: PropTypes.objectOf(PropTypes.any)
};
Search.defaultProps = {
- list: [],
- maxResults: undefined,
- filterSearch: (item, search) => item.toLowerCase().includes(search),
+ fullList: [],
+ listOptions: {},
renderResult: item => item,
- ResultBoxProps: {}
+ startAdornment: undefined,
+ searchBoxProps: {}
};
export default Search;
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Networks/index.js b/src/fireedge/src/public/containers/Application/Create/Steps/Networks/index.js
index 28c923bcc1..e30e074850 100644
--- a/src/fireedge/src/public/containers/Application/Create/Steps/Networks/index.js
+++ b/src/fireedge/src/public/containers/Application/Create/Steps/Networks/index.js
@@ -26,10 +26,10 @@ const Networks = () => {
},
resolver: STEP_FORM_SCHEMA,
DEFAULT_DATA: NETWORK_FORM_SCHEMA.default(),
- ListComponent: ({ list, addCardClick, itemsProps }) => (
+ ListComponent: ({ list, handleCreate, itemsProps }) => (
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Policies/index.js b/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Policies/index.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/Docker.js b/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/Docker.js
new file mode 100644
index 0000000000..a85d8b1cb3
--- /dev/null
+++ b/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/Docker.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+function ImportDockerFile({ backButton }) {
+ return (
+
+ {backButton}
+
Docker file
+
+ );
+}
+
+ImportDockerFile.propTypes = {
+ backButton: PropTypes.node
+};
+
+ImportDockerFile.defaultProps = {
+ backButton: null
+};
+
+export default ImportDockerFile;
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/MarketApps.js b/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/MarketApps.js
new file mode 100644
index 0000000000..b95f7ba388
--- /dev/null
+++ b/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/MarketApps.js
@@ -0,0 +1,59 @@
+import React, { useEffect } from 'react';
+import PropTypes from 'prop-types';
+
+import useOpennebula from 'client/hooks/useOpennebula';
+import Search from 'client/components/Search';
+import { SelectCard } from 'client/components/Cards';
+
+const sortByID = (a, b) => a.ID - b.ID;
+
+function ListMarketApp({ backButton, currentValue, handleSetData }) {
+ const { apps, getMarketApps } = useOpennebula();
+
+ useEffect(() => {
+ getMarketApps();
+ }, []);
+
+ const handleSelect = index => handleSetData(index);
+ const handleUnselect = () => handleSetData();
+
+ const renderApp = app => (
+
+ );
+
+ return (
+
+ );
+}
+
+ListMarketApp.propTypes = {
+ backButton: PropTypes.node,
+ currentValue: PropTypes.string,
+ handleSetData: PropTypes.func
+};
+
+ListMarketApp.defaultProps = {
+ backButton: null,
+ currentValue: undefined,
+ handleSetData: () => undefined
+};
+
+export default ListMarketApp;
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/Templates.js b/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/Templates.js
new file mode 100644
index 0000000000..079d561209
--- /dev/null
+++ b/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/List/Templates.js
@@ -0,0 +1,59 @@
+import React, { useEffect } from 'react';
+import PropTypes from 'prop-types';
+
+import useOpennebula from 'client/hooks/useOpennebula';
+import Search from 'client/components/Search';
+import { SelectCard } from 'client/components/Cards';
+
+const sortByID = (a, b) => a.ID - b.ID;
+
+function ListTemplates({ backButton, currentValue, handleSetData }) {
+ const { templates, getTemplates } = useOpennebula();
+
+ useEffect(() => {
+ getTemplates();
+ }, []);
+
+ const handleSelect = index => handleSetData(index);
+ const handleUnselect = () => handleSetData();
+
+ const renderTemplate = tmp => (
+
+ );
+
+ return (
+
+ );
+}
+
+ListTemplates.propTypes = {
+ backButton: PropTypes.node,
+ currentValue: PropTypes.string,
+ handleSetData: PropTypes.func
+};
+
+ListTemplates.defaultProps = {
+ backButton: null,
+ currentValue: undefined,
+ handleSetData: () => undefined
+};
+
+export default ListTemplates;
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/index.js b/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/index.js
index 6102f59090..be1f85a028 100644
--- a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/index.js
+++ b/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/index.js
@@ -1,18 +1,43 @@
import React from 'react';
+import TemplateIcon from '@material-ui/icons/InsertDriveFileOutlined';
+import MarketplaceIcon from '@material-ui/icons/ShoppingCartOutlined';
+import DockerLogo from 'client/icons/docker';
+
+import ProcessScreen from 'client/components/ProcessScreen';
import FormStep from 'client/components/FormStepper/FormStep';
+import ListTemplates from './List/Templates';
+import ListMarketApps from './List/MarketApps';
+import DockerFile from './List/Docker';
import { STEP_FORM_SCHEMA } from './schema';
const Template = () => {
const STEP_ID = 'template';
+ const SCREENS = [
+ {
+ id: 'template',
+ button: ,
+ screen: ListTemplates
+ },
+ {
+ id: 'app',
+ button: ,
+ screen: ListMarketApps
+ },
+ {
+ id: 'docker',
+ button: ,
+ screen: DockerFile
+ }
+ ];
return {
id: STEP_ID,
label: 'Template VM',
content: FormStep,
resolver: STEP_FORM_SCHEMA,
- FormComponent: () => Screen with options
+ FormComponent: props => ProcessScreen({ screens: SCREENS, ...props })
};
};
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/schema.js b/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/schema.js
index fbb86b9a11..2a8f7e0503 100644
--- a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/schema.js
+++ b/src/fireedge/src/public/containers/Application/Create/Steps/Roles/Steps/Template/schema.js
@@ -1,21 +1,22 @@
import * as yup from 'yup';
-import { TYPE_INPUT } from 'client/constants';
import { getValidationFromFields } from 'client/utils/helpers';
export const FORM_FIELDS = [
{
name: 'template',
- label: 'Template VM',
- type: TYPE_INPUT.TEXT,
- validation: yup
- .string()
- .min(1)
- .trim()
- .required('Template field is required')
- .default('0')
+ validation: yup.string().trim()
+ },
+ {
+ name: 'app',
+ validation: yup.string().trim()
+ },
+ {
+ name: 'docker',
+ validation: yup.string().trim()
}
];
-export const STEP_FORM_SCHEMA = yup.object(
- getValidationFromFields(FORM_FIELDS)
-);
+export const STEP_FORM_SCHEMA = yup
+ .object(getValidationFromFields(FORM_FIELDS))
+ .required('Template is required')
+ .default(undefined);
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/index.js b/src/fireedge/src/public/containers/Application/Create/Steps/Roles/index.js
index f2ff3efebd..8e1e91bfae 100644
--- a/src/fireedge/src/public/containers/Application/Create/Steps/Roles/index.js
+++ b/src/fireedge/src/public/containers/Application/Create/Steps/Roles/index.js
@@ -1,12 +1,9 @@
import React, { useMemo } from 'react';
import * as yup from 'yup';
-import { useForm, FormProvider } from 'react-hook-form';
-import { yupResolver } from '@hookform/resolvers';
-
import FormStepper from 'client/components/FormStepper';
import { DialogForm } from 'client/components/Dialogs';
-import FormStep from 'client/components/FormStepper/FormStep';
+import FormList from 'client/components/FormStepper/FormList';
import FlowWithFAB from 'client/components/Flows/FlowWithFAB';
import Steps from './Steps';
@@ -15,37 +12,47 @@ const Roles = () => {
const STEP_ID = 'roles';
const { steps, defaultValues, resolvers } = Steps();
- const methods = useForm({
- mode: 'onBlur',
- defaultValues,
- resolver: yupResolver(resolvers)
- });
-
- const onSubmit = d => console.log('role data form', d);
-
return useMemo(
() => ({
id: STEP_ID,
label: 'Defining each role',
- content: FormStep,
+ content: FormList,
+ DEFAULT_DATA: defaultValues,
resolver: yup
.array()
.of(resolvers)
.min(1)
.required()
.default([]),
- DialogComponent: props => (
-
-
+ ListComponent: ({ list, handleCreate }) => (
+
+
+
{JSON.stringify(list)}
+
+ ),
+ DialogComponent: ({ values, onSubmit, onCancel, ...props }) => (
+
+
-
+
- ),
- FormComponent: FlowWithFAB
+ )
}),
[]
);
diff --git a/src/fireedge/src/public/containers/Application/Create/Steps/index.js b/src/fireedge/src/public/containers/Application/Create/Steps/index.js
index 7a6146c789..2e6b6464b6 100644
--- a/src/fireedge/src/public/containers/Application/Create/Steps/index.js
+++ b/src/fireedge/src/public/containers/Application/Create/Steps/index.js
@@ -1,23 +1,23 @@
import * as yup from 'yup';
import BasicConfiguration from './BasicConfiguration';
+import Clusters from './Clusters';
import Networks from './Networks';
import Roles from './Roles';
-import Clusters from './Clusters';
const Steps = () => {
const basic = BasicConfiguration();
+ const clusters = Clusters();
const networks = Networks();
const roles = Roles();
- const clusters = Clusters();
- const steps = [basic, networks, roles, clusters];
+ const steps = [basic, clusters, networks, roles];
const resolvers = yup.object({
[basic.id]: basic.resolver,
+ [clusters.id]: clusters.resolver,
[networks.id]: networks.resolver,
- [roles.id]: roles.resolver,
- [clusters.id]: clusters.resolver
+ [roles.id]: roles.resolver
});
const defaultValues = resolvers.default();
diff --git a/src/fireedge/src/public/hooks/useList.js b/src/fireedge/src/public/hooks/useList.js
new file mode 100644
index 0000000000..7e9bb0963f
--- /dev/null
+++ b/src/fireedge/src/public/hooks/useList.js
@@ -0,0 +1,64 @@
+import { useState, useEffect } from 'react';
+import PropTypes from 'prop-types';
+
+import { fakeDelay } from 'client/utils/helpers';
+
+function useList({ list, initLength }) {
+ const [fullList, setFullList] = useState([]);
+ const [shortList, setShortList] = useState([]);
+ const [finish, setFinish] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [loadingNextPage, setLoadingNextPage] = useState(false);
+ const [length, setLength] = useState(initLength);
+
+ useEffect(() => {
+ /* FIRST TIME */
+ if (list?.length === 0 || shortList.length !== 0) return;
+
+ setLoading(true);
+
+ fakeDelay(200)
+ .then(() => setFullList(list))
+ .then(() => setShortList(list.slice(0, initLength)))
+ .then(() => setLoading(false));
+ }, [list]);
+
+ useEffect(() => {
+ /* SHOW NEXT PAGE */
+ if (finish) return;
+ if (length === initLength) return;
+
+ setLoadingNextPage(true);
+
+ fakeDelay(500)
+ .then(() =>
+ setShortList(prev => prev.concat(fullList.slice(prev.length, length)))
+ )
+ .then(() => setLoadingNextPage(false))
+ .then(() => setFinish(shortList.length >= fullList.length));
+ }, [length, setLength]);
+
+ const reset = newList => {
+ /* RESET VALUES */
+ setLength(initLength);
+ setFullList(newList);
+ setShortList(newList.slice(0, initLength));
+ setFinish(newList.length < initLength);
+ };
+
+ return { loading, loadingNextPage, shortList, finish, reset, setLength };
+}
+
+useList.propTypes = {
+ list: PropTypes.arrayOf(PropTypes.object).isRequired,
+ fetchList: PropTypes.func.isRequired,
+ initLength: PropTypes.string
+};
+
+useList.defaultProps = {
+ list: [],
+ fetchList: () => undefined,
+ initLength: 50
+};
+
+export default useList;
diff --git a/src/fireedge/src/public/hooks/useNearScreen.js b/src/fireedge/src/public/hooks/useNearScreen.js
new file mode 100644
index 0000000000..71526344d2
--- /dev/null
+++ b/src/fireedge/src/public/hooks/useNearScreen.js
@@ -0,0 +1,46 @@
+import { useEffect, useState, useRef } from 'react';
+import PropTypes from 'prop-types';
+
+const useNearScreen = ({ externalRef, distance, once = true } = {}) => {
+ const [isNearScreen, setShow] = useState(false);
+ const fromRef = useRef();
+
+ useEffect(() => {
+ let observer;
+ const element = externalRef ? externalRef.current : fromRef.current;
+
+ const onChange = entries => {
+ entries.forEach(({ isIntersecting }) => {
+ if (isIntersecting) {
+ setShow(true);
+ once && observer.disconnect();
+ } else {
+ !once && setShow(false);
+ }
+ });
+ };
+
+ Promise.resolve(
+ typeof IntersectionObserver !== 'undefined'
+ ? IntersectionObserver
+ : import('intersection-observer')
+ ).then(() => {
+ observer = new IntersectionObserver(onChange, {
+ // root: listRef.current
+ rootMargin: distance
+ });
+
+ if (element) observer.observe(element);
+ });
+
+ return () => observer && observer.disconnect();
+ });
+
+ return { isNearScreen, fromRef };
+};
+
+useNearScreen.propTypes = {};
+
+useNearScreen.defaultProps = {};
+
+export default useNearScreen;
diff --git a/src/fireedge/src/public/hooks/useOpennebula.js b/src/fireedge/src/public/hooks/useOpennebula.js
index 88dc892962..b0b51df7b7 100644
--- a/src/fireedge/src/public/hooks/useOpennebula.js
+++ b/src/fireedge/src/public/hooks/useOpennebula.js
@@ -7,6 +7,7 @@ import actions, {
} from 'client/actions/pool';
import * as servicePool from 'client/services/pool';
+import { filterBy } from 'client/utils/helpers';
export default function useOpennebula() {
const dispatch = useDispatch();
@@ -59,21 +60,31 @@ export default function useOpennebula() {
.catch(err => dispatch(failureOneRequest({ error: err })));
}, [dispatch, filter]);
- const getTemplates = useCallback(() => {
- dispatch(startOneRequest());
- return servicePool
- .getTemplates({ filter })
- .then(data => dispatch(actions.setTemplates(data)))
- .catch(err => dispatch(failureOneRequest({ error: err })));
- }, [dispatch, filter]);
+ const getTemplates = useCallback(
+ ({ end, start } = { end: -1, start: -1 }) => {
+ dispatch(startOneRequest());
+ return servicePool
+ .getTemplates({ filter, end, start })
+ .then(data =>
+ dispatch(actions.setTemplates(filterBy(templates.concat(data), 'ID')))
+ )
+ .catch(err => dispatch(failureOneRequest({ error: err })));
+ },
+ [dispatch, filter, templates]
+ );
- const getMarketApps = useCallback(() => {
- dispatch(startOneRequest());
- return servicePool
- .getMarketApps({ filter })
- .then(data => dispatch(actions.setApps(data)))
- .catch(err => dispatch(failureOneRequest({ error: err })));
- }, [dispatch, filter]);
+ const getMarketApps = useCallback(
+ ({ end, start } = { end: -1, start: -1 }) => {
+ dispatch(startOneRequest());
+ return servicePool
+ .getMarketApps({ filter, end, start })
+ .then(data =>
+ dispatch(actions.setApps(filterBy(apps.concat(data), 'ID')))
+ )
+ .catch(err => dispatch(failureOneRequest({ error: err })));
+ },
+ [dispatch, filter, apps]
+ );
const getClusters = useCallback(() => {
dispatch(startOneRequest());
diff --git a/src/fireedge/src/public/icons/docker.js b/src/fireedge/src/public/icons/docker.js
new file mode 100644
index 0000000000..5c51f962ae
--- /dev/null
+++ b/src/fireedge/src/public/icons/docker.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import { number, string, oneOfType } from 'prop-types';
+
+function DockerLogo({ viewBox, width, height, color, ...props }) {
+ return (
+
+ );
+}
+
+DockerLogo.propTypes = {
+ width: oneOfType([number, string]).isRequired,
+ height: oneOfType([number, string]).isRequired,
+ viewBox: string,
+ color: string
+};
+
+DockerLogo.defaultProps = {
+ width: 360,
+ height: 360,
+ viewBox: '0 0 512 512',
+ color: '#066da5'
+};
+
+export default DockerLogo;
diff --git a/src/fireedge/src/public/icons/logo.js b/src/fireedge/src/public/icons/logo.js
index 8832d94981..d2d65a16da 100644
--- a/src/fireedge/src/public/icons/logo.js
+++ b/src/fireedge/src/public/icons/logo.js
@@ -87,7 +87,7 @@ const Logo = ({ width, height, spinner, withText, viewBox, ...props }) => {
Logo.propTypes = {
width: oneOfType([number, string]).isRequired,
- height: number.isRequired,
+ height: oneOfType([number, string]).isRequired,
viewBox: string,
spinner: bool,
withText: bool
@@ -100,4 +100,5 @@ Logo.defaultProps = {
spinner: false,
withText: false
};
+
export default Logo;
diff --git a/src/fireedge/src/public/services/pool.js b/src/fireedge/src/public/services/pool.js
index fab53b93a5..c277d56bd2 100644
--- a/src/fireedge/src/public/services/pool.js
+++ b/src/fireedge/src/public/services/pool.js
@@ -65,10 +65,10 @@ export const getVNetworksTemplates = ({ filter }) => {
});
};
-export const getTemplates = ({ filter }) => {
+export const getTemplates = ({ filter, end, start }) => {
const name = Template.Actions.TEMPLATE_POOL_INFO;
const { url, options } = requestParams(
- { filter },
+ { filter, end, start },
{ name, ...Template.Commands[name] }
);
@@ -79,10 +79,10 @@ export const getTemplates = ({ filter }) => {
});
};
-export const getMarketApps = ({ filter }) => {
+export const getMarketApps = ({ filter, end, start }) => {
const name = MarketApp.Actions.MARKETAPP_POOL_INFO;
const { url, options } = requestParams(
- { filter },
+ { filter, end, start },
{ name, ...MarketApp.Commands[name] }
);
diff --git a/src/fireedge/src/public/utils/helpers.js b/src/fireedge/src/public/utils/helpers.js
index 9c925ff21b..f848e7feaa 100644
--- a/src/fireedge/src/public/utils/helpers.js
+++ b/src/fireedge/src/public/utils/helpers.js
@@ -8,3 +8,20 @@ export const getValidationFromFields = schema =>
}),
{}
);
+
+export const filterBy = (arr, predicate) => {
+ const callback =
+ typeof predicate === 'function' ? predicate : output => output[predicate];
+
+ return [
+ ...arr
+ .reduce((map, item) => {
+ const key = item === null || item === undefined ? item : callback(item);
+
+ map.has(key) || map.set(key, item);
+
+ return map;
+ }, new Map())
+ .values()
+ ];
+};
diff --git a/src/fireedge/src/public/utils/request.js b/src/fireedge/src/public/utils/request.js
index 1f54915034..03877c3f77 100644
--- a/src/fireedge/src/public/utils/request.js
+++ b/src/fireedge/src/public/utils/request.js
@@ -4,8 +4,9 @@ import { from as resourceFrom } from 'server/utils/constants/defaults';
export const getQueries = params =>
Object.entries(params)
- ?.filter(([, { from, value }]) =>
- Boolean(from === resourceFrom.query && value)
+ ?.filter(
+ ([, { from, value }]) =>
+ from === resourceFrom.query && value !== undefined
)
?.map(([name, { value }]) => `${name}=${encodeURI(value)}`)
?.join('&');