diff --git a/src/fireedge/src/client/components/Tables/MarketplaceApps/actions.js b/src/fireedge/src/client/components/Tables/MarketplaceApps/actions.js new file mode 100644 index 0000000000..afc5b0e2d4 --- /dev/null +++ b/src/fireedge/src/client/components/Tables/MarketplaceApps/actions.js @@ -0,0 +1,78 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * 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. * + * ------------------------------------------------------------------------- */ +/* eslint-disable jsdoc/require-jsdoc */ +import { useMemo } from 'react' +// import { useHistory } from 'react-router-dom' +import { RefreshDouble } from 'iconoir-react' + +import { useAuth } from 'client/features/Auth' +import { useMarketplaceAppApi } from 'client/features/One' +import { Translate } from 'client/components/HOC' + +import { createActions } from 'client/components/Tables/Enhanced/Utils' +// import { PATH } from 'client/apps/sunstone/routesOne' +import { T, MARKETPLACE_APP_ACTIONS } from 'client/constants' + +const MessageToConfirmAction = rows => { + const names = rows?.map?.(({ original }) => original?.NAME) + + return ( + <> +

+ + {`: ${names.join(', ')}`} +

+

+ +

+ + ) +} + +MessageToConfirmAction.displayName = 'MessageToConfirmAction' + +const Actions = () => { + const { view, getResourceView } = useAuth() + const { getMarketplaceApps } = useMarketplaceAppApi() + + const marketplaceAppActions = useMemo(() => createActions({ + filters: getResourceView('MARKETPLACE-APP')?.actions, + actions: [ + { + accessor: MARKETPLACE_APP_ACTIONS.REFRESH, + tooltip: T.Refresh, + icon: RefreshDouble, + action: async () => { + await getMarketplaceApps() + } + } + /* { + accessor: MARKETPLACE_APP_ACTIONS.CREATE_DIALOG, + tooltip: T.CreateMarketApp, + icon: AddSquare, + action: () => { + const path = PATH.STORAGE.MARKETPLACE_APPS.CREATE + + history.push(path) + } + } */ + ] + }), [view]) + + return marketplaceAppActions +} + +export default Actions diff --git a/src/fireedge/src/client/components/Tables/MarketplaceApps/index.js b/src/fireedge/src/client/components/Tables/MarketplaceApps/index.js index 9a0831ce34..74331ba757 100644 --- a/src/fireedge/src/client/components/Tables/MarketplaceApps/index.js +++ b/src/fireedge/src/client/components/Tables/MarketplaceApps/index.js @@ -21,15 +21,20 @@ import { useFetch } from 'client/hooks' import { useMarketplaceApp, useMarketplaceAppApi } from 'client/features/One' import { SkeletonTable, EnhancedTable } from 'client/components/Tables' +import { createColumns } from 'client/components/Tables/Enhanced/Utils' import MarketplaceAppColumns from 'client/components/Tables/MarketplaceApps/columns' import MarketplaceAppRow from 'client/components/Tables/MarketplaceApps/row' -const MarketplaceAppsTable = () => { - const columns = useMemo(() => MarketplaceAppColumns, []) +const MarketplaceAppsTable = props => { + const { view, getResourceView, filterPool } = useAuth() + + const columns = useMemo(() => createColumns({ + filters: getResourceView('MARKETPLACE-APP')?.filters, + columns: MarketplaceAppColumns + }), [view]) const marketplaceApps = useMarketplaceApp() const { getMarketplaceApps } = useMarketplaceAppApi() - const { filterPool } = useAuth() const { status, fetchRequest, loading, reloading, STATUS } = useFetch(getMarketplaceApps) const { INIT, PENDING } = STATUS @@ -47,6 +52,7 @@ const MarketplaceAppsTable = () => { isLoading={loading || reloading} getRowId={row => String(row.ID)} RowComponent={MarketplaceAppRow} + {...props} /> ) } diff --git a/src/fireedge/src/client/components/Tabs/MarketplaceApp/Info/index.js b/src/fireedge/src/client/components/Tabs/MarketplaceApp/Info/index.js new file mode 100644 index 0000000000..cdd70bdb64 --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/MarketplaceApp/Info/index.js @@ -0,0 +1,134 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * 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. * + * ------------------------------------------------------------------------- */ +/* eslint-disable jsdoc/require-jsdoc */ +import { useContext } from 'react' +import PropTypes from 'prop-types' + +import { useMarketplaceAppApi } from 'client/features/One' +import { TabContext } from 'client/components/Tabs/TabProvider' +import { Permissions, Ownership, AttributePanel } from 'client/components/Tabs/Common' +import Information from 'client/components/Tabs/MarketplaceApp/Info/information' + +import { Tr } from 'client/components/HOC' +import { getActionsAvailable, filterAttributes, jsonToXml } from 'client/models/Helper' +import { cloneObject, set } from 'client/utils' +import { T } from 'client/constants' + +const HIDDEN_ATTRIBUTES_REG = /^(VMTEMPLATE64|APPTEMPLATE64)$/ + +const MarketplaceAppInfoTab = ({ tabProps = {} }) => { + const { + information_panel: informationPanel, + permissions_panel: permissionsPanel, + ownership_panel: ownershipPanel, + attributes_panel: attributesPanel + } = tabProps + + const { rename, changeOwnership, changePermissions, updateTemplate } = useMarketplaceAppApi() + const { handleRefetch, data: marketplaceApp = {} } = useContext(TabContext) + const { ID, UNAME, UID, GNAME, GID, PERMISSIONS, TEMPLATE } = marketplaceApp + + const handleChangeOwnership = async newOwnership => { + const response = await changeOwnership(ID, newOwnership) + String(response) === String(ID) && (await handleRefetch?.()) + } + + const handleChangePermission = async newPermission => { + const response = await changePermissions(ID, newPermission) + String(response) === String(ID) && (await handleRefetch?.()) + } + + const handleRename = async (_, newName) => { + const response = await rename(ID, newName) + String(response) === String(ID) && (await handleRefetch?.()) + } + + const handleAttributeInXml = async (path, newValue) => { + const newTemplate = cloneObject(TEMPLATE) + + set(newTemplate, path, newValue) + + const xml = jsonToXml(newTemplate) + + // 0: Replace the whole template + const response = await updateTemplate(ID, xml, 0) + String(response) === String(ID) && (await handleRefetch?.()) + } + + const getActions = actions => getActionsAvailable(actions) + + const { attributes } = filterAttributes(TEMPLATE, { hidden: HIDDEN_ATTRIBUTES_REG }) + + return ( +
+ {informationPanel?.enabled && ( + + )} + {permissionsPanel?.enabled && ( + + )} + {ownershipPanel?.enabled && ( + + )} + {attributesPanel?.enabled && attributes && ( + + )} +
+ ) +} + +MarketplaceAppInfoTab.propTypes = { + tabProps: PropTypes.object +} + +MarketplaceAppInfoTab.displayName = 'MarketplaceAppInfoTab' + +export default MarketplaceAppInfoTab diff --git a/src/fireedge/src/client/components/Tabs/MarketplaceApp/Info/information.js b/src/fireedge/src/client/components/Tabs/MarketplaceApp/Info/information.js new file mode 100644 index 0000000000..8c84be600b --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/MarketplaceApp/Info/information.js @@ -0,0 +1,80 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * 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. * + * ------------------------------------------------------------------------- */ +/* eslint-disable jsdoc/require-jsdoc */ +import PropTypes from 'prop-types' +import { generatePath } from 'react-router' + +import { StatusChip } from 'client/components/Status' +import { List } from 'client/components/Tabs/Common' + +import { getType, getState } from 'client/models/MarketplaceApp' +import { timeToString, levelLockToString } from 'client/models/Helper' +import { prettyBytes } from 'client/utils' +import { T, MARKETPLACE_APP_ACTIONS } from 'client/constants' +import { PATH } from 'client/apps/sunstone/routesOne' + +const InformationPanel = ({ marketplaceApp = {}, handleRename, actions }) => { + const { ID, NAME, REGTIME, LOCK, MARKETPLACE, MARKETPLACE_ID, SIZE, FORMAT, VERSION } = marketplaceApp + const typeName = getType(marketplaceApp) + const { name: stateName, color: stateColor } = getState(marketplaceApp) + + const info = [ + { name: T.ID, value: ID }, + { + name: T.Name, + value: NAME, + canEdit: actions?.includes?.(MARKETPLACE_APP_ACTIONS.RENAME), + handleEdit: handleRename + }, + { + name: T.Marketplace, + value: `#${MARKETPLACE_ID} ${MARKETPLACE}`, + link: !Number.isNaN(+MARKETPLACE_ID) && + generatePath(PATH.STORAGE.MARKETPLACES.DETAIL, { id: MARKETPLACE_ID }) + }, + { + name: T.StartTime, + value: timeToString(REGTIME) + }, + { name: T.Type, value: typeName }, + { name: T.Size, value: prettyBytes(SIZE, 'MB') }, + { + name: T.State, + value: + }, + { name: T.Locked, value: levelLockToString(LOCK?.LOCKED) }, + { name: T.Format, value: FORMAT }, + { name: T.Version, value: VERSION } + ] + + return ( + + ) +} + +InformationPanel.displayName = 'InformationPanel' + +InformationPanel.propTypes = { + actions: PropTypes.arrayOf(PropTypes.string), + handleRename: PropTypes.func, + marketplaceApp: PropTypes.object +} + +export default InformationPanel diff --git a/src/fireedge/src/client/components/Tabs/MarketplaceApp/Template.js b/src/fireedge/src/client/components/Tabs/MarketplaceApp/Template.js new file mode 100644 index 0000000000..c78d329c2d --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/MarketplaceApp/Template.js @@ -0,0 +1,70 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * 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. * + * ------------------------------------------------------------------------- */ +/* eslint-disable jsdoc/require-jsdoc */ +import { useContext, useMemo } from 'react' +import { Accordion, AccordionDetails, AccordionSummary } from '@mui/material' +import { NavArrowDown as ExpandMoreIcon } from 'iconoir-react' + +import { TabContext } from 'client/components/Tabs/TabProvider' +import { Translate } from 'client/components/HOC' +import { T } from 'client/constants' + +const parseTemplateInB64 = template => { + try { + return decodeURIComponent(escape(atob(template))) + } catch (e) { return {} } +} + +const AppTemplateTab = () => { + const { data: marketplaceApp = {} } = useContext(TabContext) + const { TEMPLATE: { APPTEMPLATE64, VMTEMPLATE64 } } = marketplaceApp + + const appTemplate = useMemo(() => parseTemplateInB64(APPTEMPLATE64), [APPTEMPLATE64]) + const vmTemplate = useMemo(() => parseTemplateInB64(VMTEMPLATE64), [VMTEMPLATE64]) + + return ( + <> + + }> + + + +
+            
+              {appTemplate}
+            
+          
+
+
+ + }> + + + +
+            
+              {vmTemplate}
+            
+          
+
+
+ + ) +} + +AppTemplateTab.displayName = 'AppTemplateTab' + +export default AppTemplateTab diff --git a/src/fireedge/src/client/components/Tabs/MarketplaceApp/index.js b/src/fireedge/src/client/components/Tabs/MarketplaceApp/index.js new file mode 100644 index 0000000000..aa74d67455 --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/MarketplaceApp/index.js @@ -0,0 +1,84 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * 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. * + * ------------------------------------------------------------------------- */ +/* eslint-disable jsdoc/require-jsdoc */ +import { memo, useEffect, useState } from 'react' +import PropTypes from 'prop-types' +import { LinearProgress } from '@mui/material' + +import { useFetch } from 'client/hooks' +import { useAuth } from 'client/features/Auth' +import { useMarketplaceAppApi } from 'client/features/One' + +import Tabs from 'client/components/Tabs' +import { camelCase } from 'client/utils' + +import TabProvider from 'client/components/Tabs/TabProvider' +import Info from 'client/components/Tabs/MarketplaceApp/Info' +import Template from 'client/components/Tabs/MarketplaceApp/Template' + +const getTabComponent = tabName => ({ + info: Info, + template: Template +}[tabName]) + +const MarketplaceAppTabs = memo(({ id }) => { + const { getMarketplaceApp } = useMarketplaceAppApi() + const { data, fetchRequest, loading, error } = useFetch(getMarketplaceApp) + + const handleRefetch = () => fetchRequest(id, { reload: true }) + + const [tabsAvailable, setTabs] = useState(() => []) + const { view, getResourceView } = useAuth() + + useEffect(() => { + fetchRequest(id) + }, [id]) + + useEffect(() => { + const infoTabs = getResourceView('MARKETPLACE-APP')?.['info-tabs'] ?? {} + + setTabs(() => Object.entries(infoTabs) + ?.filter(([_, { enabled } = {}]) => !!enabled) + ?.map(([tabName, tabProps]) => { + const camelName = camelCase(tabName) + const TabContent = getTabComponent(camelName) + + return TabContent && { + name: camelName, + renderContent: props => TabContent({ ...props, tabProps }) + } + }) + ?.filter(Boolean)) + }, [view]) + + if ((!data && !error) || loading) { + return + } + + return ( + + + + ) +}) + +MarketplaceAppTabs.propTypes = { + id: PropTypes.string.isRequired +} + +MarketplaceAppTabs.displayName = 'MarketplaceAppTabs' + +export default MarketplaceAppTabs diff --git a/src/fireedge/src/client/constants/marketplaceApp.js b/src/fireedge/src/client/constants/marketplaceApp.js index 5515fc8516..658d8c5331 100644 --- a/src/fireedge/src/client/constants/marketplaceApp.js +++ b/src/fireedge/src/client/constants/marketplaceApp.js @@ -15,5 +15,6 @@ * ------------------------------------------------------------------------- */ export const MARKETPLACE_APP_ACTIONS = { REFRESH: 'refresh', - CREATE_DIALOG: 'create_dialog' + CREATE_DIALOG: 'create_dialog', + RENAME: 'rename' } diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index 676009572f..2245893bbe 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -286,6 +286,7 @@ module.exports = { ID: 'ID', Name: 'Name', State: 'State', + Size: 'Size', Description: 'Description', RegistrationTime: 'Registration time', StartTime: 'Start time', @@ -295,6 +296,7 @@ module.exports = { Type: 'Type', Data: 'Data', Validate: 'Validate', + Format: 'Format', /* permissions */ Permissions: 'Permissions', @@ -578,6 +580,11 @@ module.exports = { ReservedMemory: 'Allocated Memory', ReservedCpu: 'Allocated CPU', + /* Marketplace App schema */ + /* Marketplace App - general */ + Version: 'Version', + AppTemplate: 'App Template', + /* User inputs */ UserInputs: 'User Inputs', UserInputsConcept: ` diff --git a/src/fireedge/src/client/containers/MarketplaceApps/index.js b/src/fireedge/src/client/containers/MarketplaceApps/index.js index 67d02e7fd4..c7d1c2e30e 100644 --- a/src/fireedge/src/client/containers/MarketplaceApps/index.js +++ b/src/fireedge/src/client/containers/MarketplaceApps/index.js @@ -13,24 +13,53 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ - -import { Container, Box } from '@mui/material' +import { useState, JSXElementConstructor } from 'react' +import { Container, Stack, Chip } from '@mui/material' import { MarketplaceAppsTable } from 'client/components/Tables' +import MarketplaceAppActions from 'client/components/Tables/MarketplaceApps/actions' +import MarketplaceAppsTabs from 'client/components/Tabs/MarketplaceApp' +import SplitPane from 'client/components/SplitPane' +import MultipleTags from 'client/components/MultipleTags' +/** + * Displays a Marketplace Apps list. + * + * @returns {JSXElementConstructor} List of Marketplace Apps + */ function MarketplaceApps () { + const [selectedRows, onSelectedRowsChange] = useState(() => []) + const actions = MarketplaceAppActions() + return ( - - - + + + + + {selectedRows?.length > 0 && ( + + {selectedRows?.length === 1 + ? + : + ( + toggleRowSelected(false)} + /> + ))} + /> + + } + + )} + + ) } diff --git a/src/fireedge/src/client/features/One/marketplaceApp/services.js b/src/fireedge/src/client/features/One/marketplaceApp/services.js index b5b8a91c37..37ef4d5567 100644 --- a/src/fireedge/src/client/features/One/marketplaceApp/services.js +++ b/src/fireedge/src/client/features/One/marketplaceApp/services.js @@ -35,7 +35,7 @@ export const marketplaceAppService = ({ if (!res?.id || res?.id !== httpCodes.ok.id) throw res - return res?.data?.MARKETAPP ?? {} + return res?.data?.MARKETPLACEAPP ?? {} }, /**