diff --git a/README.md b/README.md
index 1c577a4d9..c1cdcef06 100644
--- a/README.md
+++ b/README.md
@@ -208,6 +208,29 @@ see the [osbuild-getting-started project](https://github.com/osbuild/osbuild-get
## File Structure
+### OnPremise Development - Cockpit Build and Install
+
+## Overview
+
+The following scripts are used to build the frontend with Webpack and install it into the Cockpit directories. These scripts streamline the development process by automating build and installation steps.
+
+### Scripts
+
+#### 1. Build the Cockpit Frontend
+
+Runs Webpack with the specified configuration (cockpit/webpack.config.ts) to build the frontend assets.
+Use this command whenever you need to compile the latest changes in your frontend code.
+
+Creates the necessary directory in the user's local Cockpit share (~/.local/share/cockpit/).
+Creates a symbolic link (image-builder-frontend) pointing to the built frontend assets (cockpit/public).
+Use this command after building the frontend to install it locally for development purposes.
+The symbolic link allows Cockpit to serve the frontend assets from your local development environment, making it easier to test changes in real-time without deploying to a remote server.
+```bash
+npm run cockpit:build
+
+```bash
+npm run cockpit:install
+
### Quick Reference
| Directory | Description |
| --------- | ----------- |
diff --git a/cockpit/public/index.html b/cockpit/public/index.html
new file mode 100644
index 000000000..66c226296
--- /dev/null
+++ b/cockpit/public/index.html
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/src/Components/LandingPage/LandingPage.tsx b/src/Components/LandingPage/LandingPage.tsx
index 7b94951f7..1086bd660 100644
--- a/src/Components/LandingPage/LandingPage.tsx
+++ b/src/Components/LandingPage/LandingPage.tsx
@@ -18,7 +18,9 @@ import {
ToolbarContent,
} from '@patternfly/react-core';
import { ExternalLinkAltIcon, HelpIcon } from '@patternfly/react-icons';
-import { useFlag } from '@unleash/proxy-client-react';
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+import { useGetFeatureFlag } from 'getFeatureFlag';
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
import './LandingPage.scss';
@@ -55,7 +57,7 @@ export const LandingPage = () => {
setActiveTabKey(tabIndex);
};
- const edgeParityFlag = useFlag('edgeParity.image-list');
+ const edgeParityFlag = useGetFeatureFlag('edgeParity.image-list');
const imageList = (
<>
diff --git a/src/Components/sharedComponents/ImageBuilderHeader.tsx b/src/Components/sharedComponents/ImageBuilderHeader.tsx
index 711bc693e..f8d90eac3 100644
--- a/src/Components/sharedComponents/ImageBuilderHeader.tsx
+++ b/src/Components/sharedComponents/ImageBuilderHeader.tsx
@@ -15,6 +15,12 @@ import {
PageHeader,
PageHeaderTitle,
} from '@redhat-cloud-services/frontend-components';
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+import { useGetFeatureFlag } from 'getFeatureFlag';
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+import { resolveRelPath } from 'pathRes';
import { useNavigate } from 'react-router-dom';
import BetaLabel from './BetaLabel';
@@ -27,9 +33,7 @@ import {
import { useAppSelector } from '../../store/hooks';
import { imageBuilderApi } from '../../store/imageBuilderApi';
import { selectDistribution } from '../../store/wizardSlice';
-import { resolveRelPath } from '../../Utilities/path';
import './ImageBuilderHeader.scss';
-import { useFlagWithEphemDefault } from '../../Utilities/useGetEnvironment';
import { ImportBlueprintModal } from '../Blueprints/ImportBlueprintModal';
type ImageBuilderHeaderPropTypes = {
@@ -97,13 +101,10 @@ export const ImageBuilderHeader = ({
inWizard,
}: ImageBuilderHeaderPropTypes) => {
const navigate = useNavigate();
-
const distribution = useAppSelector(selectDistribution);
const prefetchTargets = imageBuilderApi.usePrefetch('getArchitectures');
- const importExportFlag = useFlagWithEphemDefault(
- 'image-builder.import.enabled'
- );
+ const importExportFlag = useGetFeatureFlag('image-builder.import.enabled');
const [showImportModal, setShowImportModal] = useState(false);
const isOnBlueprintsTab = activeTab === 0;
return (
diff --git a/src/Router.tsx b/src/Router.tsx
index 4f3239145..60ec756ff 100644
--- a/src/Router.tsx
+++ b/src/Router.tsx
@@ -1,12 +1,13 @@
import React, { lazy, Suspense } from 'react';
-import { useFlag } from '@unleash/proxy-client-react';
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+import { useGetFeatureFlag } from 'getFeatureFlag';
import { Route, Routes } from 'react-router-dom';
import EdgeImageDetail from './Components/edge/ImageDetails';
import ShareImageModal from './Components/ShareImageModal/ShareImageModal';
import { manageEdgeImagesUrlName } from './Utilities/edge';
-import { useFlagWithEphemDefault } from './Utilities/useGetEnvironment';
const LandingPage = lazy(() => import('./Components/LandingPage/LandingPage'));
const ImportImageWizard = lazy(
@@ -15,10 +16,8 @@ const ImportImageWizard = lazy(
const CreateImageWizard = lazy(() => import('./Components/CreateImageWizard'));
export const Router = () => {
- const edgeParityFlag = useFlag('edgeParity.image-list');
- const importExportFlag = useFlagWithEphemDefault(
- 'image-builder.import.enabled'
- );
+ const edgeParityFlag = useGetFeatureFlag('edgeParity.image-list');
+ const importExportFlag = useGetFeatureFlag('image-builder.import.enabled');
return (
0 ? `/${path}` : ''}`;
+}
+
+export { resolveRelPath };
diff --git a/src/Utilities/useGetEnvironment.ts b/src/Utilities/useGetEnvironment.ts
index 9c6603838..9b83b1d8d 100644
--- a/src/Utilities/useGetEnvironment.ts
+++ b/src/Utilities/useGetEnvironment.ts
@@ -1,6 +1,8 @@
import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome';
import { useFlag } from '@unleash/proxy-client-react';
+import { isOnPremise } from '../constants';
+
export const useGetEnvironment = () => {
const { isBeta, isProd, getEnvironment } = useChrome();
// Expose beta features in the ephemeral environment
@@ -26,3 +28,23 @@ export const useFlagWithEphemDefault = (
const { getEnvironment } = useChrome();
return (getEnvironment() === 'qa' && ephemDefault) || getFlag;
};
+
+export const useGetFeatureFlag = (flagName: string) => {
+ // If it's the Cockpit app (on-premise), always return false
+ if (isOnPremise) {
+ return false;
+ }
+ if (
+ flagName === 'edgeParity.image-list' ||
+ flagName === 'image-builder.snapshots.enabled' ||
+ flagName === 'image-builder.firstboot.enabled' ||
+ flagName === 'image-builder.wsl.enabled' ||
+ flagName === 'image-builder.compliance.enabled'
+ ) {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ return useFlag(flagName);
+ } else if (flagName === 'image-builder.import.enabled') {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ return useFlagWithEphemDefault(flagName);
+ }
+};
diff --git a/src/constants.ts b/src/constants.ts
index c18830420..4a6a0c994 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -8,7 +8,7 @@ export const PROVISIONING_API = '/api/provisioning/v1';
export const COMPLIANCE_API = '/api/compliance/v2';
export const CREATE_BLUEPRINT = `${IMAGE_BUILDER_API}/blueprints`;
export const EDIT_BLUEPRINT = `${IMAGE_BUILDER_API}/blueprints`;
-
+export const isOnPremise = process.env.IS_ON_PREMISE;
export const CDN_PROD_URL = 'https://cdn.redhat.com/';
export const CDN_STAGE_URL = 'https://cdn.stage.redhat.com/';
export const CONTENT_URL = '/insights/content/repositories';
diff --git a/src/store/cockpitApi.ts b/src/store/cockpitApi.ts
new file mode 100644
index 000000000..ebcfd7dbb
--- /dev/null
+++ b/src/store/cockpitApi.ts
@@ -0,0 +1,40 @@
+import { emptyCockpitApi as api } from './emptyCockpitApi';
+import {
+ GetArchitecturesApiArg,
+ GetArchitecturesApiResponse,
+ GetBlueprintsApiArg,
+ GetBlueprintsApiResponse,
+} from './imageBuilderApi';
+
+// Injects API endpoints into the service for querying blueprints
+export const blueprintsReducer = api.injectEndpoints({
+ endpoints: (build) => ({
+ getArchitectures: build.query<
+ GetArchitecturesApiResponse,
+ GetArchitecturesApiArg
+ >({
+ query: (queryArg) => ({ url: `/architectures/${queryArg.distribution}` }),
+ }),
+ getBlueprints: build.query({
+ queryFn: async () => {
+ try {
+ const response: GetBlueprintsApiResponse = {
+ meta: { count: 0 },
+ links: {
+ first: 'first',
+ last: 'last',
+ },
+ data: [],
+ };
+
+ return { data: response };
+ } catch (error) {
+ return { error: error.message || 'Unknown error occurred' };
+ }
+ },
+ }),
+ }),
+});
+
+export const { useGetBlueprintsQuery, useGetArchitecturesQuery } =
+ blueprintsReducer;
diff --git a/src/store/emptyCockpitApi.ts b/src/store/emptyCockpitApi.ts
new file mode 100644
index 000000000..7ef6fb76f
--- /dev/null
+++ b/src/store/emptyCockpitApi.ts
@@ -0,0 +1,8 @@
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
+
+// initialize an empty api service that we'll inject endpoints into later as needed
+export const emptyCockpitApi = createApi({
+ reducerPath: 'cockpitApi',
+ baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
+ endpoints: () => ({}),
+});
diff --git a/src/store/index.ts b/src/store/index.ts
index d0b1dcbea..be95c409c 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -3,6 +3,7 @@ import { combineReducers, configureStore } from '@reduxjs/toolkit';
import promiseMiddleware from 'redux-promise-middleware';
import { blueprintsSlice } from './BlueprintSlice';
+import { blueprintsReducer } from './cockpitApi';
import { complianceApi } from './complianceApi';
import { contentSourcesApi } from './contentSourcesApi';
import { edgeApi } from './edgeApi';
@@ -19,13 +20,26 @@ import wizardSlice, {
selectImageTypes,
} from './wizardSlice';
-export const reducer = combineReducers({
+import { isOnPremise } from '../constants';
+
+export const serviceReducer = combineReducers({
[contentSourcesApi.reducerPath]: contentSourcesApi.reducer,
[edgeApi.reducerPath]: edgeApi.reducer,
[imageBuilderApi.reducerPath]: imageBuilderApi.reducer,
[rhsmApi.reducerPath]: rhsmApi.reducer,
[provisioningApi.reducerPath]: provisioningApi.reducer,
[complianceApi.reducerPath]: complianceApi.reducer,
+ [blueprintsReducer.reducerPath]: blueprintsReducer.reducer,
+ notifications: notificationsReducer,
+ wizard: wizardSlice,
+ blueprints: blueprintsSlice.reducer,
+});
+
+export const onPremReducer = combineReducers({
+ [contentSourcesApi.reducerPath]: contentSourcesApi.reducer,
+ [imageBuilderApi.reducerPath]: imageBuilderApi.reducer,
+ [rhsmApi.reducerPath]: rhsmApi.reducer,
+ [blueprintsReducer.reducerPath]: blueprintsReducer.reducer,
notifications: notificationsReducer,
wizard: wizardSlice,
blueprints: blueprintsSlice.reducer,
@@ -88,7 +102,7 @@ startAppListening({
// Listener middleware must be prepended according to RTK docs:
// https://redux-toolkit.js.org/api/createListenerMiddleware#basic-usage
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
-export const middleware = (getDefaultMiddleware: Function) =>
+export const serviceMiddleware = (getDefaultMiddleware: Function) =>
getDefaultMiddleware()
.prepend(listenerMiddleware.middleware)
.concat(
@@ -97,10 +111,29 @@ export const middleware = (getDefaultMiddleware: Function) =>
imageBuilderApi.middleware,
rhsmApi.middleware,
provisioningApi.middleware,
- complianceApi.middleware
+ complianceApi.middleware,
+ blueprintsReducer.middleware
);
-export const store = configureStore({ reducer, middleware });
+// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
+export const onPremMiddleware = (getDefaultMiddleware: Function) =>
+ getDefaultMiddleware()
+ .prepend(listenerMiddleware.middleware)
+ .concat(
+ promiseMiddleware,
+ contentSourcesApi.middleware,
+ imageBuilderApi.middleware,
+ rhsmApi.middleware,
+ blueprintsReducer.middleware
+ );
+
+const rootReducer = isOnPremise ? onPremReducer : serviceReducer;
+const rootMiddleware = isOnPremise ? onPremMiddleware : serviceMiddleware;
+
+export const store = configureStore({
+ reducer: rootReducer,
+ middleware: rootMiddleware,
+});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType;
diff --git a/tsconfig.json b/tsconfig.json
index 1334a92ca..8d4c6d5c0 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -10,6 +10,8 @@
"moduleResolution": "node",
"types": ["vitest/globals", "@testing-library/jest-dom"],
"strictNullChecks": true,
- "allowSyntheticDefaultImports": true
- }
+ "allowSyntheticDefaultImports": true,
+ "declaration": true,
+ "skipLibCheck": true
+ },
}