diff --git a/app/electron/main.ts b/app/electron/main.ts index 678f7de933e..68beaba0e01 100644 --- a/app/electron/main.ts +++ b/app/electron/main.ts @@ -128,9 +128,6 @@ const defaultPort = 4466; const useExternalServer = process.env.EXTERNAL_SERVER || false; const shouldCheckForUpdates = process.env.HEADLAMP_CHECK_FOR_UPDATES !== 'false'; -const manifestDir = isDev ? path.resolve('./') : process.resourcesPath; -const manifestFile = path.join(manifestDir, 'app-build-manifest.json'); -const buildManifest = fs.existsSync(manifestFile) ? require(manifestFile) : {}; // make it global so that it doesn't get garbage collected let mainWindow: BrowserWindow | null; @@ -585,6 +582,18 @@ async function startServer(flags: string[] = []): Promise = {}; + try { + const manifestContent = await fsPromises.readFile(manifestFile, 'utf8'); + buildManifest = JSON.parse(manifestContent); + } catch (err) { + // If the manifest doesn't exist or can't be read, fall back to empty object + buildManifest = {}; + } + const proxyUrls = !!buildManifest && buildManifest['proxy-urls']; if (!!proxyUrls && proxyUrls.length > 0) { serverArgs = serverArgs.concat(['--proxy-urls', proxyUrls.join(',')]); @@ -603,8 +612,16 @@ async function startServer(flags: string[] = []): Promise { + // Cheap platform check first to avoid reading /proc on non-Linux platforms. + if (platform() !== 'linux') { + return false; + } + try { - const data = fs.readFileSync('/proc/version', { + const data = await fsPromises.readFile('/proc/version', { encoding: 'utf8', - flag: 'r', }); - return data.indexOf('icrosoft') !== -1; + const lower = data.toLowerCase(); + return lower.includes('microsoft') || lower.includes('wsl'); } catch { return false; } @@ -1081,10 +1103,11 @@ function saveZoomFactor(factor: number) { } } -function loadZoomFactor() { +async function loadZoomFactor(): Promise { try { - const { zoomFactor = 1.0 } = JSON.parse(fs.readFileSync(ZOOM_FILE_PATH, 'utf-8')); - return zoomFactor; + const content = await fsPromises.readFile(ZOOM_FILE_PATH, 'utf-8'); + const { zoomFactor = 1.0 } = JSON.parse(content); + return typeof zoomFactor === 'number' ? zoomFactor : 1.0; } catch (err) { console.error('Failed to load zoom factor, defaulting to 1.0:', err); return 1.0; @@ -1119,7 +1142,7 @@ function startElecron() { console.log('Check for updates: ', shouldCheckForUpdates); - async function createWindow() { + async function startServerIfNeeded() { if (!useExternalServer) { serverProcess = await startServer(); attachServerEventHandlers(serverProcess); @@ -1207,9 +1230,11 @@ function startElecron() { } }); } + } + async function createWindow() { // WSL has a problem with full size window placement, so make it smaller. - const withMargin = isWSL(); + const withMargin = await isWSL(); const { width, height } = windowSize(screen.getPrimaryDisplay().workAreaSize, withMargin); mainWindow = new BrowserWindow({ @@ -1250,9 +1275,11 @@ function startElecron() { } }); - mainWindow.webContents.on('did-finish-load', () => { - const startZoom = loadZoomFactor(); - setZoom(startZoom); + mainWindow.webContents.on('did-finish-load', async () => { + const startZoom = await loadZoomFactor(); + if (startZoom !== 1.0) { + setZoom(startZoom); + } }); mainWindow.webContents.on('dom-ready', () => { @@ -1419,10 +1446,12 @@ function startElecron() { app.disableHardwareAcceleration(); } - app.on('ready', createWindow); - app.on('activate', function () { + app.on('ready', async () => { + await Promise.all([startServerIfNeeded(), createWindow()]); + }); + app.on('activate', async function () { if (mainWindow === null) { - createWindow(); + await Promise.all([startServerIfNeeded(), createWindow()]); } }); diff --git a/frontend/src/components/App/Home/ClusterTable.tsx b/frontend/src/components/App/Home/ClusterTable.tsx index 9ee29cfcf56..9998c23f796 100644 --- a/frontend/src/components/App/Home/ClusterTable.tsx +++ b/frontend/src/components/App/Home/ClusterTable.tsx @@ -28,6 +28,7 @@ import { ApiError } from '../../../lib/k8s/api/v2/ApiError'; import { Cluster } from '../../../lib/k8s/cluster'; import { getClusterPrefixedPath } from '../../../lib/util'; import { useTypedSelector } from '../../../redux/hooks'; +import { Loader } from '../../common'; import Link from '../../common/Link'; import Table from '../../common/Table'; import ClusterContextMenu from './ClusterContextMenu'; @@ -139,6 +140,11 @@ export default function ClusterTable({ } const viewClusters = t('View Clusters'); + const loading = clusters === null; + if (loading) { + return ; + } + return ( +
+
+
+
+
+
+
+
+

+ Home +

+
+
+
+
+
+
+
+
+
+ + +
+ +
+
+
+
+
+
+
+ +
+
+
+ +
+ +
+
+ + + +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ + + + + + + + cluster0 + + +

+ Unknown +

+
+
+
+

+ Active +

+
+
+
+ 0 + + ⋯ + + +
+
+ + + + + + + + cluster1 + + +

+ Unknown +

+
+
+
+

+ Active +

+
+
+
+ 0 + + ⋯ + + +
+
+ + + + + + + + cluster2 + + +

+ Unknown +

+
+
+
+

+ Active +

+
+
+
+ 0 + + ⋯ + + +
+
+
+
+ +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/frontend/src/components/App/Home/__snapshots__/index.LoadingClusters.stories.storyshot b/frontend/src/components/App/Home/__snapshots__/index.LoadingClusters.stories.storyshot new file mode 100644 index 00000000000..0bd3fb5830a --- /dev/null +++ b/frontend/src/components/App/Home/__snapshots__/index.LoadingClusters.stories.storyshot @@ -0,0 +1,122 @@ + +
+
+
+
+
+
+
+
+

+ Home +

+
+
+
+
+
+
+
+
+
+ + +
+ +
+
+
+
+ + + + + +
+
+
+
+
+
+
+ \ No newline at end of file diff --git a/frontend/src/components/App/Home/index.stories.tsx b/frontend/src/components/App/Home/index.stories.tsx index 44c4af0a6f3..f911099c902 100644 --- a/frontend/src/components/App/Home/index.stories.tsx +++ b/frontend/src/components/App/Home/index.stories.tsx @@ -42,6 +42,15 @@ const ourState = { resourceTable: { tableColumnsProcessors: [], }, + clusterProvider: { + dialogs: {}, + menuItems: [], + clusterProviders: [], + clusterStatuses: [], + }, + drawerMode: { + isDetailDrawerEnabled: false, + }, }; // @todo: Add a way for the results from useClustersVersion to be mocked, so not @@ -49,29 +58,50 @@ const ourState = { export default { title: 'Home/Home', component: Home, - decorators: [ - Story => { - return ( - - state, - preloadedState: ourState, - })} - > - - - - ); - }, - ], - parameters: { - storyshots: { - disable: true, - }, - }, } as Meta; const Template: StoryFn = () => ; export const Base = Template.bind({}); +Base.decorators = [ + Story => { + return ( + + state, + preloadedState: ourState, + })} + > + + + + ); + }, +]; + +const loadingState = { + ...ourState, + config: { + ...ourState.config, + clusters: null as any, + }, +}; + +export const LoadingClusters = Template.bind({}); +LoadingClusters.decorators = [ + Story => { + return ( + + state, + preloadedState: loadingState, + })} + > + + + + ); + }, +]; diff --git a/frontend/src/components/App/Home/index.tsx b/frontend/src/components/App/Home/index.tsx index 816cb5bba09..31198bec398 100644 --- a/frontend/src/components/App/Home/index.tsx +++ b/frontend/src/components/App/Home/index.tsx @@ -38,18 +38,23 @@ import RecentClusters from './RecentClusters'; export default function Home() { const history = useHistory(); - const clusters = useClustersConf() || {}; + const clusters = useClustersConf(); - if (!isElectron() && Object.keys(clusters).length === 1) { + if (!isElectron() && clusters && Object.keys(clusters).length === 1) { history.push(createRouteURL('cluster', { cluster: Object.keys(clusters)[0] })); return null; } - return ; + return ( + + ); } interface HomeComponentProps { - clusters: { [name: string]: Cluster }; + clusters: { [name: string]: Cluster } | null; } function useWarningSettingsPerCluster(clusterNames: string[]) { @@ -96,7 +101,7 @@ function HomeComponent(props: HomeComponentProps) { getCustomClusterNames(clusters) ); const { t } = useTranslation(['translation', 'glossary']); - const [versions, errors] = useClustersVersion(Object.values(clusters)); + const [versions, errors] = useClustersVersion(Object.values(clusters || {})); const warningLabels = useWarningSettingsPerCluster( Object.values(customNameClusters).map(c => c.name) ); diff --git a/frontend/src/components/App/Settings/__snapshots__/NodeShellSettings.Default.stories.storyshot b/frontend/src/components/App/Settings/__snapshots__/NodeShellSettings.Default.stories.storyshot index fc5a1a0682e..c4c1b96c52d 100644 --- a/frontend/src/components/App/Settings/__snapshots__/NodeShellSettings.Default.stories.storyshot +++ b/frontend/src/components/App/Settings/__snapshots__/NodeShellSettings.Default.stories.storyshot @@ -81,7 +81,7 @@ class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-formControl MuiInputBase-sizeSmall MuiInputBase-adornedEnd css-14f1a7d-MuiInputBase-root-MuiOutlinedInput-root" >
allClusters, [Object.keys(allClusters).join(',')]); + return useMemo( + () => (state.clusters === null ? null : allClusters), + [state.clusters === null, Object.keys(allClusters).join(',')] + ); } /**