Skip to content

Commit

Permalink
F #5422: Add actions & tabs to apps (#1589)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergio Betanzos committed Nov 18, 2021
1 parent 083f86a commit e84d1d2
Show file tree
Hide file tree
Showing 10 changed files with 507 additions and 18 deletions.
@@ -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 (
<>
<p>
<Translate word={T.Apps} />
{`: ${names.join(', ')}`}
</p>
<p>
<Translate word={T.DoYouWantProceed} />
</p>
</>
)
}

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
Expand Up @@ -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
Expand All @@ -47,6 +52,7 @@ const MarketplaceAppsTable = () => {
isLoading={loading || reloading}
getRowId={row => String(row.ID)}
RowComponent={MarketplaceAppRow}
{...props}
/>
)
}
Expand Down
134 changes: 134 additions & 0 deletions 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 (
<div style={{
display: 'grid',
gap: '1em',
gridTemplateColumns: 'repeat(auto-fit, minmax(480px, 1fr))',
padding: '0.8em'
}}>
{informationPanel?.enabled && (
<Information
actions={getActions(informationPanel?.actions)}
handleRename={handleRename}
marketplaceApp={marketplaceApp}
/>
)}
{permissionsPanel?.enabled && (
<Permissions
actions={getActions(permissionsPanel?.actions)}
ownerUse={PERMISSIONS.OWNER_U}
ownerManage={PERMISSIONS.OWNER_M}
ownerAdmin={PERMISSIONS.OWNER_A}
groupUse={PERMISSIONS.GROUP_U}
groupManage={PERMISSIONS.GROUP_M}
groupAdmin={PERMISSIONS.GROUP_A}
otherUse={PERMISSIONS.OTHER_U}
otherManage={PERMISSIONS.OTHER_M}
otherAdmin={PERMISSIONS.OTHER_A}
handleEdit={handleChangePermission}
/>
)}
{ownershipPanel?.enabled && (
<Ownership
actions={getActions(ownershipPanel?.actions)}
userId={UID}
userName={UNAME}
groupId={GID}
groupName={GNAME}
handleEdit={handleChangeOwnership}
/>
)}
{attributesPanel?.enabled && attributes && (
<AttributePanel
attributes={attributes}
actions={getActions(attributesPanel?.actions)}
title={Tr(T.Attributes)}
handleAdd={handleAttributeInXml}
handleEdit={handleAttributeInXml}
handleDelete={handleAttributeInXml}
/>
)}
</div>
)
}

MarketplaceAppInfoTab.propTypes = {
tabProps: PropTypes.object
}

MarketplaceAppInfoTab.displayName = 'MarketplaceAppInfoTab'

export default MarketplaceAppInfoTab
@@ -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: <StatusChip text={stateName} stateColor={stateColor} />
},
{ name: T.Locked, value: levelLockToString(LOCK?.LOCKED) },
{ name: T.Format, value: FORMAT },
{ name: T.Version, value: VERSION }
]

return (
<List
title={T.Information}
list={info}
containerProps={{ style: { gridRow: 'span 3' } }}
/>
)
}

InformationPanel.displayName = 'InformationPanel'

InformationPanel.propTypes = {
actions: PropTypes.arrayOf(PropTypes.string),
handleRename: PropTypes.func,
marketplaceApp: PropTypes.object
}

export default InformationPanel
70 changes: 70 additions & 0 deletions 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 (
<>
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Translate word={T.AppTemplate} />
</AccordionSummary>
<AccordionDetails>
<pre>
<code style={{ whiteSpace: 'break-spaces', wordBreak: 'break-all' }}>
{appTemplate}
</code>
</pre>
</AccordionDetails>
</Accordion>
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Translate word={T.VMTemplate} />
</AccordionSummary>
<AccordionDetails>
<pre>
<code style={{ whiteSpace: 'break-spaces', wordBreak: 'break-all' }}>
{vmTemplate}
</code>
</pre>
</AccordionDetails>
</Accordion>
</>
)
}

AppTemplateTab.displayName = 'AppTemplateTab'

export default AppTemplateTab

0 comments on commit e84d1d2

Please sign in to comment.