diff --git a/install.sh b/install.sh index 9cb875469c..97318df647 100755 --- a/install.sh +++ b/install.sh @@ -2269,6 +2269,7 @@ ONEVMDUMP_LIB_RESTORERS_FILES="src/onevmdump/lib/restorers/base.rb" ETC_FILES="share/etc/oned.conf \ share/etc/defaultrc \ + share/etc/guacd \ src/tm_mad/tmrc \ src/scheduler/etc/sched.conf \ src/monitor/etc/monitord.conf " diff --git a/share/doc/xsd/host.xsd b/share/doc/xsd/host.xsd index 2b68aed002..cc25cdf7f2 100644 --- a/share/doc/xsd/host.xsd +++ b/share/doc/xsd/host.xsd @@ -65,15 +65,14 @@ + + - - - diff --git a/share/etc/guacd b/share/etc/guacd new file mode 100644 index 0000000000..a7c2b247fe --- /dev/null +++ b/share/etc/guacd @@ -0,0 +1,17 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2022, 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. # +#--------------------------------------------------------------------------- # + +OPTS="-b 0.0.0.0" \ No newline at end of file diff --git a/share/linters/.rubocop.yml b/share/linters/.rubocop.yml index 6d3d7ce91c..4f3e1c9a4b 100644 --- a/share/linters/.rubocop.yml +++ b/share/linters/.rubocop.yml @@ -559,6 +559,12 @@ Layout/MultilineOperationIndentation: Layout/LineEndStringConcatenationIndentation: Enabled: false +Layout/LineContinuationSpacing: + Enabled: false + +Layout/LineContinuationLeadingSpace: + Enabled: false + ####### # STYLE ####### @@ -914,6 +920,9 @@ Lint/UnmodifiedReduceAccumulator: Lint/AmbiguousOperatorPrecedence: Enabled: false +Lint/NonAtomicFileOperation: + Enabled: false + ######### # METRICS ######## diff --git a/src/fireedge/etc/sunstone/admin/marketplace-app-tab.yaml b/src/fireedge/etc/sunstone/admin/marketplace-app-tab.yaml index 38ff64cfea..19a7c19090 100644 --- a/src/fireedge/etc/sunstone/admin/marketplace-app-tab.yaml +++ b/src/fireedge/etc/sunstone/admin/marketplace-app-tab.yaml @@ -33,6 +33,8 @@ actions: disable: true delete: true edit_labels: true + lock: true + unlock: true # Filters - List of criteria to filter the resources diff --git a/src/fireedge/src/client/components/Tables/MarketplaceApps/actions.js b/src/fireedge/src/client/components/Tables/MarketplaceApps/actions.js index 726cbd2b87..61d2573d2d 100644 --- a/src/fireedge/src/client/components/Tables/MarketplaceApps/actions.js +++ b/src/fireedge/src/client/components/Tables/MarketplaceApps/actions.js @@ -19,7 +19,12 @@ import { AddCircledOutline, CloudDownload, DownloadCircledOutline, + Lock, + MoreVert, + Group, + Trash, } from 'iconoir-react' +import PropTypes from 'prop-types' import { useViews } from 'client/features/Auth' import { useGeneralApi } from 'client/features/General' @@ -27,6 +32,12 @@ import { Translate } from 'client/components/HOC' import { useExportAppMutation, useDownloadAppMutation, + useLockAppMutation, + useUnlockAppMutation, + useEnableAppMutation, + useDisableAppMutation, + useChangeAppOwnershipMutation, + useDeleteAppMutation, } from 'client/features/OneApi/marketplaceApp' import { ExportForm } from 'client/components/Forms/MarketplaceApp' @@ -37,22 +48,37 @@ import { import { PATH } from 'client/apps/sunstone/routesOne' import { T, RESOURCE_NAMES, MARKETPLACE_APP_ACTIONS } from 'client/constants' +import { ChangeGroupForm, ChangeUserForm } from 'client/components/Forms/Vm' +import { Typography } from '@mui/material' -const MessageToConfirmAction = (rows) => { +const ListAppNames = ({ rows = [] }) => { const names = rows?.map?.(({ original }) => original?.NAME) return ( - <> -

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

-

- -

- + + + {`: ${names.join(', ')}`} + ) } +ListAppNames.propTypes = { + rows: PropTypes.arrayOf( + PropTypes.shape({ + original: PropTypes.shape({ + NAME: PropTypes.string, + }), + }) + ), +} + +const SubHeader = (rows) => + +const MessageToConfirmAction = (rows) => ( + <> + + + +) MessageToConfirmAction.displayName = 'MessageToConfirmAction' @@ -67,6 +93,12 @@ const Actions = () => { const { enqueueSuccess } = useGeneralApi() const [exportApp] = useExportAppMutation() const [downloadApp] = useDownloadAppMutation() + const [lock] = useLockAppMutation() + const [unlock] = useUnlockAppMutation() + const [enable] = useEnableAppMutation() + const [disable] = useDisableAppMutation() + const [changeOwnership] = useChangeAppOwnershipMutation() + const [deleteApp] = useDeleteAppMutation() const marketplaceAppActions = useMemo( () => @@ -117,6 +149,142 @@ const Actions = () => { urls.forEach((url) => window.open(url, '_blank')) }, }, + { + tooltip: T.Lock, + icon: Lock, + selected: true, + color: 'secondary', + dataCy: 'marketapp-lock', + options: [ + { + accessor: MARKETPLACE_APP_ACTIONS.LOCK, + name: T.Lock, + isConfirmDialog: true, + dialogProps: { + title: T.Lock, + children: MessageToConfirmAction, + dataCy: `modal-${MARKETPLACE_APP_ACTIONS.LOCK}`, + }, + onSubmit: (rows) => async () => { + const ids = rows?.map?.(({ original }) => original?.ID) + await Promise.all(ids.map((id) => lock({ id }))) + }, + }, + { + accessor: MARKETPLACE_APP_ACTIONS.UNLOCK, + name: T.Unlock, + isConfirmDialog: true, + dialogProps: { + title: T.Unlock, + children: MessageToConfirmAction, + dataCy: `modal-${MARKETPLACE_APP_ACTIONS.UNLOCK}`, + }, + onSubmit: (rows) => async () => { + const ids = rows?.map?.(({ original }) => original?.ID) + await Promise.all(ids.map((id) => unlock({ id }))) + }, + }, + ], + }, + { + tooltip: T.Enable, + icon: MoreVert, + selected: true, + color: 'secondary', + dataCy: 'marketapp-enable', + options: [ + { + accessor: MARKETPLACE_APP_ACTIONS.ENABLE, + name: T.Enable, + isConfirmDialog: true, + dialogProps: { + title: T.Enable, + children: MessageToConfirmAction, + dataCy: `modal-${MARKETPLACE_APP_ACTIONS.ENABLE}`, + }, + onSubmit: (rows) => async () => { + const ids = rows?.map?.(({ original }) => original?.ID) + await Promise.all(ids.map((id) => enable(id))) + }, + }, + { + accessor: MARKETPLACE_APP_ACTIONS.DISABLE, + name: T.Disable, + isConfirmDialog: true, + dialogProps: { + title: T.Disable, + children: MessageToConfirmAction, + dataCy: `modal-${MARKETPLACE_APP_ACTIONS.DISABLE}`, + }, + onSubmit: (rows) => async () => { + const ids = rows?.map?.(({ original }) => original?.ID) + await Promise.all(ids.map((id) => disable(id))) + }, + }, + ], + }, + { + tooltip: T.Ownership, + icon: Group, + selected: true, + color: 'secondary', + dataCy: 'vm-ownership', + options: [ + { + accessor: MARKETPLACE_APP_ACTIONS.CHANGE_OWNER, + name: T.ChangeOwner, + dialogProps: { + title: T.ChangeOwner, + subheader: SubHeader, + dataCy: `modal-${MARKETPLACE_APP_ACTIONS.CHANGE_OWNER}`, + }, + form: ChangeUserForm, + onSubmit: (rows) => async (newOwnership) => { + const ids = rows?.map?.(({ original }) => original?.ID) + await Promise.all( + ids.map((id) => changeOwnership({ id, ...newOwnership })) + ) + }, + }, + { + accessor: MARKETPLACE_APP_ACTIONS.CHANGE_GROUP, + name: T.ChangeGroup, + dialogProps: { + title: T.ChangeGroup, + subheader: SubHeader, + dataCy: `modal-${MARKETPLACE_APP_ACTIONS.CHANGE_GROUP}`, + }, + form: ChangeGroupForm, + onSubmit: (rows) => async (newOwnership) => { + const ids = rows?.map?.(({ original }) => original?.ID) + await Promise.all( + ids.map((id) => changeOwnership({ id, ...newOwnership })) + ) + }, + }, + ], + }, + { + accessor: MARKETPLACE_APP_ACTIONS.DELETE, + tooltip: T.Delete, + icon: Trash, + color: 'error', + selected: { min: 1 }, + options: [ + { + isConfirmDialog: true, + dialogProps: { + title: T.Delete, + dataCy: `modal-${MARKETPLACE_APP_ACTIONS.DELETE}`, + children: MessageToConfirmAction, + }, + onSubmit: (rows) => async () => { + const ids = rows?.map?.(({ original }) => original?.ID) + await Promise.all(ids.map((id) => deleteApp({ id }))) + }, + }, + ], + }, ], }), [view] diff --git a/src/fireedge/src/client/components/Tables/Vms/actions.js b/src/fireedge/src/client/components/Tables/Vms/actions.js index ece85dc1de..b33284ed88 100644 --- a/src/fireedge/src/client/components/Tables/Vms/actions.js +++ b/src/fireedge/src/client/components/Tables/Vms/actions.js @@ -374,7 +374,6 @@ const Actions = () => { form: MigrateForm, dialogProps: { title: T.Deploy, - subheader: SubHeader, dataCy: `modal-${VM_ACTIONS.DEPLOY}`, }, onSubmit: (rows) => async (formData) => { diff --git a/src/fireedge/src/client/components/Tabs/Vm/Configuration.js b/src/fireedge/src/client/components/Tabs/Vm/Configuration.js index a3ccd220e2..0674419441 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Configuration.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Configuration.js @@ -68,7 +68,13 @@ const VmConfigurationTab = ({ tabProps: { actions } = {}, id }) => { if (isSupported && hasValue) { const name = idx ? `${idx}.${key}` : key - sectionAttributes.push({ name, value, dataCy: name }) + sectionAttributes.push({ + name, + value, + dataCy: name, + canCopy: true, + showActionsOnHover: true, + }) } } diff --git a/src/fireedge/src/client/components/Tabs/index.js b/src/fireedge/src/client/components/Tabs/index.js index 6bd4302751..6e232e6893 100644 --- a/src/fireedge/src/client/components/Tabs/index.js +++ b/src/fireedge/src/client/components/Tabs/index.js @@ -49,6 +49,8 @@ const Content = ({ renderContent: RenderContent, hidden, addBorder = false, + setTab, + logTabId, }) => ( {typeof RenderContent === 'function' ? ( - + ) : ( RenderContent )} @@ -129,6 +131,12 @@ const Tabs = ({ [tabSelected] ) + const logTabId = tabs + .map(function (tabProps) { + return tabProps.name + }) + .indexOf('log') + return ( @@ -139,7 +147,9 @@ const Tabs = ({ ) : ( (value ?? idx) === tabSelected)} + logTabId={logTabId} /> )} @@ -162,6 +172,8 @@ Content.propTypes = { renderContent: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), hidden: PropTypes.bool, addBorder: PropTypes.bool, + setTab: PropTypes.func, + logTabId: PropTypes.number, } export default Tabs diff --git a/src/fireedge/src/client/constants/marketplaceApp.js b/src/fireedge/src/client/constants/marketplaceApp.js index aa8d8e7ee7..d78e312d93 100644 --- a/src/fireedge/src/client/constants/marketplaceApp.js +++ b/src/fireedge/src/client/constants/marketplaceApp.js @@ -56,6 +56,8 @@ export const MARKETPLACE_APP_ACTIONS = { DISABLE: 'disable', DELETE: 'delete', EDIT_LABELS: 'edit_labels', + LOCK: 'lock', + UNLOCK: 'unlock', // INFORMATION RENAME: ACTIONS.RENAME, diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index 06485b55c8..7fd13ca96a 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -305,6 +305,7 @@ module.exports = { LabelAlreadyExists: 'Label already exists', PressToCreateLabel: 'Press enter to create a new label', SavesInTheUserTemplate: "Saved in the User's template", + NoLabelsOnList: 'You have not defined any labels, list is empty', /* sections - system */ User: 'User', diff --git a/src/fireedge/src/client/containers/Provisions/DialogInfo/hosts.js b/src/fireedge/src/client/containers/Provisions/DialogInfo/hosts.js index edcb983406..c73fff4696 100644 --- a/src/fireedge/src/client/containers/Provisions/DialogInfo/hosts.js +++ b/src/fireedge/src/client/containers/Provisions/DialogInfo/hosts.js @@ -38,7 +38,7 @@ import { SubmitButton } from 'client/components/FormControl' import { Translate } from 'client/components/HOC' import { T } from 'client/constants' -const Hosts = memo(({ id }) => { +const Hosts = memo(({ id, setTab, logTabId }) => { const [amount, setAmount] = useState(() => 1) const { enqueueInfo } = useGeneralApi() @@ -113,6 +113,7 @@ const Hosts = memo(({ id }) => { onClick={async () => { configureHost({ provision: id, id: host.ID }) enqueueInfo(`Configuring host - ID: ${host.ID}`) + setTab(logTabId) }} /> { ) }) -Hosts.propTypes = { id: PropTypes.string.isRequired } +Hosts.propTypes = { + id: PropTypes.string.isRequired, + setTab: PropTypes.func, + logTabId: PropTypes.number, +} Hosts.displayName = 'Hosts' export default Hosts diff --git a/src/fireedge/src/client/containers/Provisions/DialogInfo/index.js b/src/fireedge/src/client/containers/Provisions/DialogInfo/index.js index 3d187478ee..e79dc8bcaa 100644 --- a/src/fireedge/src/client/containers/Provisions/DialogInfo/index.js +++ b/src/fireedge/src/client/containers/Provisions/DialogInfo/index.js @@ -63,7 +63,9 @@ const DialogInfo = ({ id }) => { name: 'hosts', label: T.Hosts, icon: HostIcon, - renderContent: () => , + renderContent: ({ setTab, logTabId }) => ( + + ), }, { name: 'log', diff --git a/src/fireedge/src/client/containers/Settings/LabelsSection/index.js b/src/fireedge/src/client/containers/Settings/LabelsSection/index.js index 279daab505..3e33b550cb 100644 --- a/src/fireedge/src/client/containers/Settings/LabelsSection/index.js +++ b/src/fireedge/src/client/containers/Settings/LabelsSection/index.js @@ -123,7 +123,7 @@ const Settings = () => { {labels.length === 0 && ( - + )} {result?.map((label) => ( diff --git a/src/fireedge/src/client/features/OneApi/marketplaceApp.js b/src/fireedge/src/client/features/OneApi/marketplaceApp.js index e6dc6341ad..6b076ff046 100644 --- a/src/fireedge/src/client/features/OneApi/marketplaceApp.js +++ b/src/fireedge/src/client/features/OneApi/marketplaceApp.js @@ -484,6 +484,23 @@ const marketAppApi = oneApi.injectEndpoints({ }, invalidatesTags: [APP_POOL], }), + deleteApp: builder.mutation({ + /** + * Delete Marketplaceapp. + * + * @param {object} params - Request parameters + * @param {string} params.id - Marketplaceapp ID + * @returns {number} Marketplace app id + * @throws Fails when response isn't code 200 + */ + query: (params) => { + const name = Actions.MARKETAPP_DELETE + const command = { name, ...Commands[name] } + + return { params, command } + }, + invalidatesTags: (_, __, { id }) => [{ type: APP, id }], + }), downloadApp: builder.mutation({ /** * Download a MarketPlaceApp. @@ -559,6 +576,7 @@ export const { useImportAppMutation, useExportAppMutation, useDownloadAppMutation, + useDeleteAppMutation, } = marketAppApi export default marketAppApi diff --git a/src/fireedge/src/client/models/Helper.js b/src/fireedge/src/client/models/Helper.js index 378a596575..97cde2a887 100644 --- a/src/fireedge/src/client/models/Helper.js +++ b/src/fireedge/src/client/models/Helper.js @@ -315,7 +315,7 @@ export const getAvailableInfoTabs = (infoTabs = {}, getTabComponent, id) => return ( TabContent && { - name: camelName, + label: camelName, id: tabName, renderContent: () => , } diff --git a/src/fireedge/src/server/routes/api/auth/utils.js b/src/fireedge/src/server/routes/api/auth/utils.js index 5541d4419a..02167c3277 100644 --- a/src/fireedge/src/server/routes/api/auth/utils.js +++ b/src/fireedge/src/server/routes/api/auth/utils.js @@ -45,10 +45,6 @@ const { const { ok, unauthorized, accepted, internalServerError } = httpCodes -const appConfig = getFireedgeConfig() - -const namespace = appConfig.namespace || defaultNamespace - const { GET } = httpMethod let user = '' @@ -200,6 +196,7 @@ const setRes = (newRes = {}) => { * Set dates. */ const setDates = () => { + const appConfig = getFireedgeConfig() limitToken = remember ? appConfig.session_remember_expiration || defaultRememberSessionExpiration : appConfig.session_expiration || defaultSessionExpiration @@ -314,13 +311,7 @@ const genJWT = (token, informationUser) => { * @returns {object} - user token */ const getCreatedTokenOpennebula = (username = '') => { - if ( - global && - global.users && - username && - global.users[username] && - global.users[username].tokens - ) { + if (username && global?.users?.[username]?.tokens) { let acc = { token: '', time: 0 } global.users[username].tokens.forEach((curr = {}, index = 0) => { const currentTime = parseInt(curr.time, 10) @@ -550,6 +541,8 @@ const getServerAdminAndWrapUser = (userData = {}) => { const login = (userData) => { let rtn = false if (userData) { + const appConfig = getFireedgeConfig() + const namespace = appConfig.namespace || defaultNamespace const findTextError = `[${namespace}.${ActionUsers.USER_INFO}]` if (userData.indexOf && userData.indexOf(findTextError) >= 0) { updaterResponse(httpResponse(unauthorized)) diff --git a/src/fireedge/src/server/routes/entrypoints/Api/middlawares.js b/src/fireedge/src/server/routes/entrypoints/Api/middlawares.js index f065636dd1..9741308a07 100644 --- a/src/fireedge/src/server/routes/entrypoints/Api/middlawares.js +++ b/src/fireedge/src/server/routes/entrypoints/Api/middlawares.js @@ -14,6 +14,7 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ const { env } = require('process') +const { DateTime } = require('luxon') const { httpCodes, defaults } = require('server/utils/constants') const { getFireedgeConfig } = require('server/utils/yml') const { defaultWebpackMode, defaultEmptyFunction, defaultOpennebulaZones } = @@ -33,16 +34,16 @@ let passOpennebula = '' * @returns {boolean} user valid data */ const userValidation = (user = '', token = '') => { + const nowUnix = DateTime.local().toSeconds() let rtn = false if ( user && token && - global && - global.users && - global.users[user] && - global.users[user].tokens && - Array.isArray(global.users[user].tokens) && - global.users[user].tokens.some((x) => x && x.token === token) + Array.isArray(global?.users?.[user]?.tokens) && + global?.users?.[user]?.tokens?.some?.( + ({ token: internalToken, time }) => + time > nowUnix && internalToken === token + ) ) { rtn = true } diff --git a/src/fireedge/src/server/utils/constants/commands/marketapp.js b/src/fireedge/src/server/utils/constants/commands/marketapp.js index 38c2bb385d..8378008663 100644 --- a/src/fireedge/src/server/utils/constants/commands/marketapp.js +++ b/src/fireedge/src/server/utils/constants/commands/marketapp.js @@ -158,11 +158,11 @@ module.exports = { from: resource, default: 0, }, - userId: { + user: { from: postBody, default: -1, }, - groupId: { + group: { from: postBody, default: -1, }, diff --git a/src/hm/HookStateVirtualNetwork.cc b/src/hm/HookStateVirtualNetwork.cc index d7d39b6db9..a11ea56380 100644 --- a/src/hm/HookStateVirtualNetwork.cc +++ b/src/hm/HookStateVirtualNetwork.cc @@ -38,7 +38,7 @@ string HookStateVirtualNetwork::format_message(VirtualNetwork * vn) oss << "" << "STATE" - << "VNET" + << "NET" << "" << VirtualNetwork::state_to_str(vn->get_state()) << "" << "" << vn->get_oid() << "" << vn->to_xml(vn_xml)