diff --git a/src/fireedge/src/client/components/DebugLog/ansiHtml.js b/src/fireedge/src/client/components/DebugLog/ansiHtml.js
index 8b3cc9856a..1a72f8855d 100644
--- a/src/fireedge/src/client/components/DebugLog/ansiHtml.js
+++ b/src/fireedge/src/client/components/DebugLog/ansiHtml.js
@@ -80,7 +80,6 @@ export default function ansiHTML(text) {
if (ot) {
// If current sequence has been opened, close it.
if (~ansiCodes.indexOf(seq)) {
- // eslint-disable-line no-extra-boolean-cast
ansiCodes.pop()
return ''
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/index.js
index 3c1e80e385..2de22e3a98 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/index.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/index.js
@@ -87,7 +87,6 @@ const Content = ({ data, setFormData }) => {
...section,
name,
label: ,
- // eslint-disable-next-line react/display-name
renderContent: () => (
),
diff --git a/src/fireedge/src/client/components/HOC/AuthLayout.js b/src/fireedge/src/client/components/HOC/AuthLayout.js
index 4d8464bc7c..f84ee88ac8 100644
--- a/src/fireedge/src/client/components/HOC/AuthLayout.js
+++ b/src/fireedge/src/client/components/HOC/AuthLayout.js
@@ -51,6 +51,7 @@ const AuthLayout = ({ subscriptions = [], children }) => {
return () => {
endpoints.forEach((endpoint) => {
endpoint.unsubscribe()
+ endpoint.abort()
})
}
}, [dispatch, jwt])
diff --git a/src/fireedge/src/client/components/Header/index.js b/src/fireedge/src/client/components/Header/index.js
index 3b7cb51dc1..82c97119ed 100644
--- a/src/fireedge/src/client/components/Header/index.js
+++ b/src/fireedge/src/client/components/Header/index.js
@@ -39,7 +39,7 @@ import { sentenceCase } from 'client/utils'
const Header = () => {
const { isOneAdmin } = useAuth()
const { fixMenu } = useGeneralApi()
- const { appTitle, title, isBeta } = useGeneral()
+ const { appTitle, title, isBeta, withGroupSwitcher } = useGeneral()
const appAsSentence = useMemo(() => sentenceCase(appTitle), [appTitle])
return (
@@ -104,7 +104,7 @@ const Header = () => {
>
- {!isOneAdmin && }
+ {!isOneAdmin && withGroupSwitcher && }
diff --git a/src/fireedge/src/client/constants/index.js b/src/fireedge/src/client/constants/index.js
index 3a674cbe3a..0e696b9879 100644
--- a/src/fireedge/src/client/constants/index.js
+++ b/src/fireedge/src/client/constants/index.js
@@ -30,6 +30,7 @@ export const BY = {
export const _APPS = defaultApps
export const APPS = Object.keys(defaultApps)
export const APPS_IN_BETA = [_APPS.sunstone.name]
+export const APPS_WITH_SWITCHER = [_APPS.sunstone.name]
export const APP_URL = defaultAppName ? `/${defaultAppName}` : ''
export const WEBSOCKET_URL = `${APP_URL}/websockets`
export const STATIC_FILES_URL = `${APP_URL}/client/assets`
@@ -52,6 +53,7 @@ export const LANGUAGES_URL = `${STATIC_FILES_URL}/languages`
export const ONEADMIN_ID = '0'
export const SERVERADMIN_ID = '1'
+export const ONEADMIN_GROUP_ID = '0'
export const FILTER_POOL = {
PRIMARY_GROUP_RESOURCES: '-4',
diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js
index adf6d03f55..7ae685c3f3 100644
--- a/src/fireedge/src/client/constants/translates.js
+++ b/src/fireedge/src/client/constants/translates.js
@@ -197,6 +197,8 @@ module.exports = {
/* errors */
SessionExpired: 'Sorry, your session has expired',
+ OnlyForOneadminGroup:
+ 'Only members of the oneadmin group can access OneProvision functionality',
SomethingWrong: 'Something go wrong',
CannotConnectOneFlow: 'Cannot connect to OneFlow server',
CannotConnectOneProvision: 'Cannot connect to OneProvision server',
diff --git a/src/fireedge/src/client/containers/Login/index.js b/src/fireedge/src/client/containers/Login/index.js
index 22ed1bd469..309357725b 100644
--- a/src/fireedge/src/client/containers/Login/index.js
+++ b/src/fireedge/src/client/containers/Login/index.js
@@ -44,9 +44,9 @@ function Login() {
const classes = loginStyles()
const isMobile = useMediaQuery((theme) => theme.breakpoints.only('xs'))
+ const { logout } = useAuthApi()
const { error: otherError, isLoginInProgress: needGroupToContinue } =
useAuth()
- const { logout } = useAuthApi()
const [changeAuthGroup, changeAuthGroupState] = useChangeAuthGroupMutation()
const [login, loginState] = useLoginMutation()
diff --git a/src/fireedge/src/client/dev/_app.js b/src/fireedge/src/client/dev/_app.js
deleted file mode 100644
index e42daeeb5c..0000000000
--- a/src/fireedge/src/client/dev/_app.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/* ------------------------------------------------------------------------- *
- * 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. *
- * ------------------------------------------------------------------------- */
-import { ReactElement } from 'react'
-
-import SunstoneApp from 'client/apps/sunstone'
-import ProvisionApp from 'client/apps/provision'
-import LoadingScreen from 'client/components/LoadingScreen'
-
-import { isDevelopment, isBackend } from 'client/utils'
-import { _APPS, APPS } from 'client/constants'
-
-/**
- * Render App by url: http:///fireedge/.
- *
- * @param {object} props - Props from server
- * @returns {ReactElement} Returns App
- */
-const DevelopmentApp = (props) => {
- let appName = ''
-
- if (isDevelopment() && !isBackend()) {
- appName = window.location.pathname
- .split(/\//gi)
- .filter((sub) => sub?.length > 0)
- .find((resource) => APPS.includes(resource))
- }
-
- return (
- {
- [_APPS.provision.name]: ,
- [_APPS.sunstone.name]: ,
- }[appName] ??
- )
-}
-
-DevelopmentApp.displayName = 'DevelopmentApp'
-
-export default DevelopmentApp
diff --git a/src/fireedge/src/client/dev/index.js b/src/fireedge/src/client/dev/index.js
deleted file mode 100644
index 0df11b1e93..0000000000
--- a/src/fireedge/src/client/dev/index.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* ------------------------------------------------------------------------- *
- * 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. *
- * ------------------------------------------------------------------------- */
-import { render } from 'react-dom'
-
-import { createStore } from 'client/store'
-import App from 'client/dev/_app'
-
-const { store } = createStore({ initState: window.__PRELOADED_STATE__ })
-
-render(, document.getElementById('root'))
diff --git a/src/fireedge/src/client/features/AuthApi/index.js b/src/fireedge/src/client/features/AuthApi/index.js
index 61d59013a5..f8482065cb 100644
--- a/src/fireedge/src/client/features/AuthApi/index.js
+++ b/src/fireedge/src/client/features/AuthApi/index.js
@@ -13,39 +13,45 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
+import { createApi } from '@reduxjs/toolkit/query/react'
+import { Actions, Commands } from 'server/routes/api/auth/routes'
import { dismissSnackbar } from 'client/features/General/actions'
import { actions } from 'client/features/Auth/slice'
import userApi from 'client/features/OneApi/user'
-import { storage } from 'client/utils'
-import { APP_URL, JWT_NAME, FILTER_POOL, ONEADMIN_ID } from 'client/constants'
+import http from 'client/utils/rest'
+import { requestConfig, storage } from 'client/utils'
+import { JWT_NAME, FILTER_POOL, ONEADMIN_ID } from 'client/constants'
const { ALL_RESOURCES, PRIMARY_GROUP_RESOURCES } = FILTER_POOL
const authApi = createApi({
reducerPath: 'authApi',
- baseQuery: fetchBaseQuery({
- baseUrl: `${APP_URL}/api/`,
- prepareHeaders: (headers, { getState }) => {
- const token = getState().auth.jwt
+ baseQuery: async ({ params, command, needState }, { getState, signal }) => {
+ try {
+ const config = requestConfig(params, command)
+ const response = await http.request({ ...config, signal })
+ const state = needState ? getState() : {}
- // If we have a token set in state,
- // let's assume that we should be passing it.
- token && headers.set('authorization', `Bearer ${token}`)
+ return { data: response.data ?? {}, meta: { state } }
+ } catch (axiosError) {
+ const { message, data = {}, status, statusText } = axiosError
+ const { message: messageFromServer, data: errorFromOned } = data
- return headers
- },
- }),
+ const error = message ?? errorFromOned ?? messageFromServer ?? statusText
+
+ return { error: { status: status, data: error } }
+ }
+ },
endpoints: (builder) => ({
getAuthUser: builder.query({
/**
* @returns {object} Information about authenticated user
* @throws Fails when response isn't code 200
*/
- query: () => ({ url: 'user/info' }),
- transformResponse: (response) => response?.data?.USER,
+ query: () => ({ command: { path: '/user/info' } }),
+ transformResponse: (response) => response?.USER,
async onQueryStarted(_, { queryFulfilled, dispatch }) {
try {
const { data: user } = await queryFulfilled
@@ -55,23 +61,31 @@ const authApi = createApi({
}),
login: builder.mutation({
/**
- * @param {object} data - User credentials
- * @param {string} data.user - Username
- * @param {string} data.token - Password
- * @param {boolean} [data.remember] - Remember session
- * @param {string} [data.token2fa] - Token for Two factor authentication
+ * Login in the interface.
+ *
+ * @param {object} params - User credentials
+ * @param {string} params.user - Username
+ * @param {string} params.token - Password
+ * @param {boolean} [params.remember] - Remember session
+ * @param {string} [params.token2fa] - Token for Two factor authentication
* @returns {object} Response data from request
* @throws Fails when response isn't code 200
*/
- query: (data) => ({ url: 'auth', method: 'POST', body: data }),
- transformResponse: (response) => {
- const { id, token } = response?.data
+ query: (params) => {
+ const name = Actions.AUTHENTICATION
+ const command = { name, ...Commands[name] }
+
+ return { params, command, needState: true }
+ },
+ transformResponse: (response, meta) => {
+ const { id, token } = response
+ const { withGroupSwitcher } = meta?.state?.general ?? {}
const isOneAdmin = id === ONEADMIN_ID
return {
jwt: token,
user: { ID: id },
- isLoginInProgress: !!token && !isOneAdmin,
+ isLoginInProgress: withGroupSwitcher && !!token && !isOneAdmin,
}
},
async onQueryStarted({ remember }, { queryFulfilled, dispatch }) {
diff --git a/src/fireedge/src/client/features/General/slice.js b/src/fireedge/src/client/features/General/slice.js
index f9c20023bb..bb15ef2bb4 100644
--- a/src/fireedge/src/client/features/General/slice.js
+++ b/src/fireedge/src/client/features/General/slice.js
@@ -18,13 +18,14 @@ import { createSlice } from '@reduxjs/toolkit'
import { actions as authActions } from 'client/features/Auth/slice'
import * as actions from 'client/features/General/actions'
import { generateKey } from 'client/utils'
-import { APPS_IN_BETA } from 'client/constants'
+import { APPS_IN_BETA, APPS_WITH_SWITCHER } from 'client/constants'
const initial = {
zone: 0,
title: null,
appTitle: null,
isBeta: false,
+ withGroupSwitcher: false,
isLoading: false,
isFixMenu: false,
@@ -56,10 +57,12 @@ const { name, reducer } = createSlice({
...state,
title: payload,
}))
- .addCase(actions.changeAppTitle, (state, { payload }) => {
- const isBeta = APPS_IN_BETA?.includes(String(payload).toLowerCase())
+ .addCase(actions.changeAppTitle, (state, { payload: appTitle }) => {
+ const lowerAppTitle = String(appTitle).toLowerCase()
+ const isBeta = APPS_IN_BETA?.includes(lowerAppTitle)
+ const withGroupSwitcher = APPS_WITH_SWITCHER?.includes(lowerAppTitle)
- return { ...state, appTitle: payload, isBeta }
+ return { ...state, appTitle, isBeta, withGroupSwitcher }
})
.addCase(actions.changeZone, (state, { payload }) => ({
...state,
diff --git a/src/fireedge/src/client/features/OneApi/index.js b/src/fireedge/src/client/features/OneApi/index.js
index 5ddaba709c..0e6c69e26d 100644
--- a/src/fireedge/src/client/features/OneApi/index.js
+++ b/src/fireedge/src/client/features/OneApi/index.js
@@ -99,12 +99,7 @@ const oneApi = createApi({
})
)
- return {
- error: {
- status: status,
- data: message ?? data?.data ?? statusText,
- },
- }
+ return { error: { status: status, data: error } }
}
},
refetchOnMountOrArgChange: 30,
diff --git a/src/fireedge/src/client/features/middleware.js b/src/fireedge/src/client/features/middleware.js
index d3b1cb330c..1c2fd202a5 100644
--- a/src/fireedge/src/client/features/middleware.js
+++ b/src/fireedge/src/client/features/middleware.js
@@ -14,8 +14,9 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { isRejectedWithValue, Middleware, Dispatch } from '@reduxjs/toolkit'
-import { actions } from 'client/features/Auth/slice'
-import { T } from 'client/constants'
+
+import * as Auth from 'client/features/Auth/slice'
+import { T, ONEADMIN_GROUP_ID } from 'client/constants'
/**
* @param {{ dispatch: Dispatch }} params - Redux parameters
@@ -26,7 +27,27 @@ export const unauthenticatedMiddleware =
(next) =>
(action) => {
if (isRejectedWithValue(action) && action.payload.status === 401) {
- dispatch(actions.logout(T.SessionExpired))
+ dispatch(Auth.actions.logout(T.SessionExpired))
+ }
+
+ return next(action)
+ }
+
+/**
+ * @param {{ dispatch: Dispatch, getState: function():object }} params - Redux parameters
+ * @returns {Middleware} - Middleware to logout when user isn't in oneadmin group
+ */
+export const onlyForOneadminMiddleware =
+ ({ dispatch, getState }) =>
+ (next) =>
+ (action) => {
+ const groups = getState()?.[Auth.name]?.user?.GROUPS?.ID
+
+ if (!Auth.actions.logout.match(action) && groups) {
+ const ensuredGroups = [groups].flat()
+
+ !ensuredGroups.includes(ONEADMIN_GROUP_ID) &&
+ dispatch(Auth.actions.logout(T.OnlyForOneadminGroup))
}
return next(action)
diff --git a/src/fireedge/src/client/provision.js b/src/fireedge/src/client/provision.js
index d62708eacd..7b188f2a16 100644
--- a/src/fireedge/src/client/provision.js
+++ b/src/fireedge/src/client/provision.js
@@ -17,8 +17,12 @@ import { hydrate, render } from 'react-dom'
import { createStore } from 'client/store'
import App from 'client/apps/provision'
+import { onlyForOneadminMiddleware } from 'client/features/middleware'
-const { store } = createStore({ initState: window.__PRELOADED_STATE__ })
+const { store } = createStore({
+ initState: window.__PRELOADED_STATE__,
+ extraMiddleware: [onlyForOneadminMiddleware],
+})
const rootHTML = document.getElementById('root')?.innerHTML
const renderMethod = rootHTML !== '' ? hydrate : render
diff --git a/src/fireedge/src/client/store/index.js b/src/fireedge/src/client/store/index.js
index decb2bae74..becbe1df83 100644
--- a/src/fireedge/src/client/store/index.js
+++ b/src/fireedge/src/client/store/index.js
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-import { configureStore, EnhancedStore } from '@reduxjs/toolkit'
+import { configureStore, Middleware, EnhancedStore } from '@reduxjs/toolkit'
import { setupListeners } from '@reduxjs/toolkit/query/react'
import { isDevelopment } from 'client/utils'
@@ -27,9 +27,10 @@ import { unauthenticatedMiddleware } from 'client/features/middleware'
/**
* @param {object} props - Props
* @param {object} props.initState - Initial state
+ * @param {Middleware[]} props.extraMiddleware - Extra middleware to apply on store
* @returns {{ store: EnhancedStore }} Configured Redux Store
*/
-export const createStore = ({ initState = {} }) => {
+export const createStore = ({ initState = {}, extraMiddleware = [] }) => {
const store = configureStore({
reducer: {
[Auth.name]: Auth.reducer,
@@ -42,6 +43,7 @@ export const createStore = ({ initState = {} }) => {
getDefaultMiddleware({
immutableCheck: true,
}).concat([
+ ...extraMiddleware,
unauthenticatedMiddleware,
authApi.middleware,
oneApi.middleware,
diff --git a/src/fireedge/src/server/routes/api/auth/routes.js b/src/fireedge/src/server/routes/api/auth/routes.js
index bb5c9224ad..327ade9b68 100644
--- a/src/fireedge/src/server/routes/api/auth/routes.js
+++ b/src/fireedge/src/server/routes/api/auth/routes.js
@@ -17,10 +17,11 @@
const {
httpMethod,
from: fromData,
-} = require('server/utils/constants/defaults')
+} = require('../../../utils/constants/defaults')
const { POST } = httpMethod
const { postBody } = fromData
+
const basepath = '/auth'
const AUTHENTICATION = 'authentication'
diff --git a/src/fireedge/src/server/routes/entrypoints/App.js b/src/fireedge/src/server/routes/entrypoints/App.js
index cae0d2b8b1..f5d6b6fdb9 100644
--- a/src/fireedge/src/server/routes/entrypoints/App.js
+++ b/src/fireedge/src/server/routes/entrypoints/App.js
@@ -45,32 +45,24 @@ languages.map((language) =>
const router = Router()
router.get('*', (req, res) => {
- let app = 'dev'
- let title = 'FireEdge'
+ const defaultTitle = 'FireEdge'
const context = {}
let store = ''
let component = ''
let css = ''
let storeRender = ''
- // production
- if (
- env &&
- (!env.NODE_ENV || (env.NODE_ENV && env.NODE_ENV !== defaultWebpackMode))
- ) {
- const apps = Object.keys(defaultApps)
- const parseUrl = req.url
- .split(/\//gi)
- .filter((sub) => sub && sub.length > 0)
+ const isProduction =
+ !env?.NODE_ENV || (env?.NODE_ENV && env?.NODE_ENV !== defaultWebpackMode)
- parseUrl.forEach((element) => {
- if (element && apps.includes(element)) {
- app = element
- title = element
- }
- })
+ const apps = Object.keys(defaultApps)
+ const appName = req.url
+ .split(/\//gi)
+ .filter((sub) => sub?.length > 0)
+ .find((resource) => apps.includes(resource))
- const App = require(`../../../client/apps/${app}/index.js`).default
+ if (isProduction) {
+ const App = require(`../../../client/apps/${appName}/index.js`).default
const sheets = new ServerStyleSheets()
const composeEnhancer =
(root && root.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose
@@ -92,11 +84,11 @@ router.get('*', (req, res) => {
- ${upperCaseFirst(title)} by OpenNebula
-
-
-
-
+ ${upperCaseFirst(appName ?? defaultTitle)} by OpenNebula
+
+
+
+
@@ -106,7 +98,11 @@ router.get('*', (req, res) => {
${component}
${storeRender}
-
+ ${
+ isProduction
+ ? ``
+ : ``
+ }
`
diff --git a/src/fireedge/webpack.config.dev.client.js b/src/fireedge/webpack.config.dev.client.js
index 1c3ea720c4..99f40376bf 100644
--- a/src/fireedge/webpack.config.dev.client.js
+++ b/src/fireedge/webpack.config.dev.client.js
@@ -14,6 +14,12 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
+const {
+ defaultWebpackMode,
+ defaultApps,
+ defaultAppName,
+} = require('./src/server/utils/constants/defaults')
+
const getDevConfiguration = () => {
try {
const path = require('path')
@@ -21,11 +27,6 @@ const getDevConfiguration = () => {
const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
const TimeFixPlugin = require('time-fix-plugin')
- const {
- defaultWebpackMode,
- defaultAppName,
- } = require('./src/server/utils/constants/defaults')
-
const appName = defaultAppName ? `/${defaultAppName}` : ''
/** @type {webpack.Configuration} */
@@ -33,7 +34,9 @@ const getDevConfiguration = () => {
mode: defaultWebpackMode,
entry: [
'webpack-hot-middleware/client',
- path.resolve(__dirname, 'src/client/dev/index.js'),
+ ...Object.keys(defaultApps).map((app) =>
+ path.resolve(__dirname, `src/client/${app}.js`)
+ ),
],
output: {
filename: 'bundle.dev.js',