From c47bbc0c55ae23063f44c68547255d03111c14d3 Mon Sep 17 00:00:00 2001
From: ashwani yadav <22ashwaniyadav@gmail.com>
Date: Fri, 12 Jun 2026 17:24:24 +0530
Subject: [PATCH 1/4] plugin: Add CRD detection guard for Agones sidebar views
Signed-off-by: ashwani yadav <22ashwaniyadav@gmail.com>
---
src/components/AgonesInstallCheck.tsx | 77 +++++++++++++++++++++++++++
src/hooks/useAgonesInstalled.tsx | 35 ++++++++++++
src/index.tsx | 43 ++++++++++++---
src/isAgonesInstalled.ts | 28 ++++++++++
4 files changed, 176 insertions(+), 7 deletions(-)
create mode 100644 src/components/AgonesInstallCheck.tsx
create mode 100644 src/hooks/useAgonesInstalled.tsx
create mode 100644 src/isAgonesInstalled.ts
diff --git a/src/components/AgonesInstallCheck.tsx b/src/components/AgonesInstallCheck.tsx
new file mode 100644
index 0000000..5251cd1
--- /dev/null
+++ b/src/components/AgonesInstallCheck.tsx
@@ -0,0 +1,77 @@
+/*
+ * Copyright Contributors to Agones a Series of LF Projects, LLC.
+ *
+ * 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 Box from '@mui/material/Box';
+import CircularProgress from '@mui/material/CircularProgress';
+import Grid from '@mui/material/Grid';
+import Link from '@mui/material/Link';
+import Typography from '@mui/material/Typography';
+import React from 'react';
+import { useAgonesInstalled } from '../hooks/useAgonesInstalled';
+
+interface NotInstalledBannerProps {
+ isLoading?: boolean;
+}
+
+function NotInstalledBanner({ isLoading = false }: NotInstalledBannerProps) {
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ Agones was not detected on your cluster. If you haven't already, please install it.
+
+
+
+
+ Learn how to{' '}
+
+ install
+ {' '}
+ Agones
+
+
+
+
+ );
+}
+
+interface AgonesInstallCheckProps {
+ children: React.ReactNode;
+ fallback?: React.ReactNode;
+}
+
+export function AgonesInstallCheck({ children, fallback }: AgonesInstallCheckProps) {
+ const { isAgonesInstalled, isAgonesCheckLoading } = useAgonesInstalled();
+
+ if (!isAgonesInstalled) {
+ return <>{fallback || }>;
+ }
+
+ return <>{children}>;
+}
diff --git a/src/hooks/useAgonesInstalled.tsx b/src/hooks/useAgonesInstalled.tsx
new file mode 100644
index 0000000..ec2d23f
--- /dev/null
+++ b/src/hooks/useAgonesInstalled.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright Contributors to Agones a Series of LF Projects, LLC.
+ *
+ * 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 { useEffect, useState } from 'react';
+import { isAgonesInstalled as checkAgonesInstallation } from '../isAgonesInstalled';
+
+export function useAgonesInstalled() {
+ const [isInstalled, setIsInstalled] = useState(null);
+
+ useEffect(() => {
+ async function checkInstalled() {
+ const installed = await checkAgonesInstallation();
+ setIsInstalled(!!installed);
+ }
+ checkInstalled();
+ }, []);
+
+ return {
+ isAgonesInstalled: isInstalled,
+ isAgonesCheckLoading: isInstalled === null,
+ };
+}
diff --git a/src/index.tsx b/src/index.tsx
index 6f9e55e..1ae3f0a 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -20,6 +20,7 @@ import {
registerSidebarEntry,
} from '@kinvolk/headlamp-plugin/lib';
import React from 'react';
+import { AgonesInstallCheck } from './components/AgonesInstallCheck';
import { agonesMapSource } from './mapView';
import { FleetAutoscalerDetail } from './views/fleetautoscalers/Detail';
import { FleetAutoscalerList } from './views/fleetautoscalers/List';
@@ -74,7 +75,11 @@ registerRoute({
sidebar: 'agones-overview',
name: 'agones-overview',
exact: true,
- component: () => ,
+ component: () => (
+
+
+
+ ),
});
registerRoute({
@@ -82,13 +87,21 @@ registerRoute({
sidebar: 'agones-fleets',
name: 'agones-fleets',
exact: true,
- component: () => ,
+ component: () => (
+
+
+
+ ),
});
registerRoute({
path: '/agones/fleets/:namespace/:name',
sidebar: 'agones-fleets',
name: 'agones-fleet',
- component: () => ,
+ component: () => (
+
+
+
+ ),
});
registerRoute({
@@ -96,13 +109,21 @@ registerRoute({
sidebar: 'agones-gameservers',
name: 'agones-gameservers',
exact: true,
- component: () => ,
+ component: () => (
+
+
+
+ ),
});
registerRoute({
path: '/agones/gameservers/:namespace/:name',
sidebar: 'agones-gameservers',
name: 'agones-gameserver',
- component: () => ,
+ component: () => (
+
+
+
+ ),
});
registerRoute({
@@ -110,11 +131,19 @@ registerRoute({
sidebar: 'agones-fleetautoscalers',
name: 'agones-fleetautoscalers',
exact: true,
- component: () => ,
+ component: () => (
+
+
+
+ ),
});
registerRoute({
path: '/agones/fleetautoscalers/:namespace/:name',
sidebar: 'agones-fleetautoscalers',
name: 'agones-fleetautoscaler',
- component: () => ,
+ component: () => (
+
+
+
+ ),
});
diff --git a/src/isAgonesInstalled.ts b/src/isAgonesInstalled.ts
new file mode 100644
index 0000000..4010e9e
--- /dev/null
+++ b/src/isAgonesInstalled.ts
@@ -0,0 +1,28 @@
+/*
+ * Copyright Contributors to Agones a Series of LF Projects, LLC.
+ *
+ * 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 { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
+
+export async function isAgonesInstalled(): Promise {
+ try {
+ const response = await ApiProxy.request('/apis/agones.dev/v1', {
+ method: 'GET',
+ });
+ return !!response;
+ } catch (error) {
+ return false;
+ }
+}
From ac453fd46aaa0c2af2410d692f2b2797a2c2663f Mon Sep 17 00:00:00 2001
From: ashwani yadav <22ashwaniyadav@gmail.com>
Date: Fri, 12 Jun 2026 17:30:12 +0530
Subject: [PATCH 2/4] plugin: Add unit tests for CRD detection guard
Signed-off-by: ashwani yadav <22ashwaniyadav@gmail.com>
---
package-lock.json | 10 ++--
package.json | 2 +
src/hooks/useAgonesInstalled.test.tsx | 62 +++++++++++++++++++++
src/isAgonesInstalled.test.ts | 80 +++++++++++++++++++++++++++
4 files changed, 149 insertions(+), 5 deletions(-)
create mode 100644 src/hooks/useAgonesInstalled.test.tsx
create mode 100644 src/isAgonesInstalled.test.ts
diff --git a/package-lock.json b/package-lock.json
index 003a04d..ac2980c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,8 @@
"version": "0.1.0",
"devDependencies": {
"@kinvolk/headlamp-plugin": "^0.14.0",
+ "@testing-library/dom": "^10.4.1",
+ "@testing-library/react": "^16.3.2",
"vite": "^6.4.2",
"vite-plugin-svgr": "^4.5.0"
}
@@ -4060,7 +4062,6 @@
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
@@ -4103,11 +4104,10 @@
"license": "MIT"
},
"node_modules/@testing-library/react": {
- "version": "16.3.0",
- "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz",
- "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==",
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
+ "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@babel/runtime": "^7.12.5"
},
diff --git a/package.json b/package.json
index 57c73da..fb9384f 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,8 @@
},
"devDependencies": {
"@kinvolk/headlamp-plugin": "^0.14.0",
+ "@testing-library/dom": "^10.4.1",
+ "@testing-library/react": "^16.3.2",
"vite": "^6.4.2",
"vite-plugin-svgr": "^4.5.0"
}
diff --git a/src/hooks/useAgonesInstalled.test.tsx b/src/hooks/useAgonesInstalled.test.tsx
new file mode 100644
index 0000000..e9ddd56
--- /dev/null
+++ b/src/hooks/useAgonesInstalled.test.tsx
@@ -0,0 +1,62 @@
+/*
+ * Copyright Contributors to Agones a Series of LF Projects, LLC.
+ *
+ * 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 { afterEach, describe, expect, it, vi } from 'vitest';
+import { renderHook, waitFor } from '@testing-library/react';
+import { useAgonesInstalled } from './useAgonesInstalled';
+
+// Mock the isAgonesInstalled function
+vi.mock('../isAgonesInstalled', () => ({
+ isAgonesInstalled: vi.fn(),
+}));
+
+import { isAgonesInstalled } from '../isAgonesInstalled';
+
+describe('useAgonesInstalled', () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ it('should start in loading state', () => {
+ vi.mocked(isAgonesInstalled).mockReturnValue(new Promise(() => {})); // never resolves
+ const { result } = renderHook(() => useAgonesInstalled());
+
+ expect(result.current.isAgonesInstalled).toBeNull();
+ expect(result.current.isAgonesCheckLoading).toBe(true);
+ });
+
+ it('should return isAgonesInstalled=true when Agones is detected', async () => {
+ vi.mocked(isAgonesInstalled).mockResolvedValue(true);
+ const { result } = renderHook(() => useAgonesInstalled());
+
+ await waitFor(() => {
+ expect(result.current.isAgonesInstalled).toBe(true);
+ });
+
+ expect(result.current.isAgonesCheckLoading).toBe(false);
+ });
+
+ it('should return isAgonesInstalled=false when Agones is not detected', async () => {
+ vi.mocked(isAgonesInstalled).mockResolvedValue(false);
+ const { result } = renderHook(() => useAgonesInstalled());
+
+ await waitFor(() => {
+ expect(result.current.isAgonesInstalled).toBe(false);
+ });
+
+ expect(result.current.isAgonesCheckLoading).toBe(false);
+ });
+});
diff --git a/src/isAgonesInstalled.test.ts b/src/isAgonesInstalled.test.ts
new file mode 100644
index 0000000..78275b0
--- /dev/null
+++ b/src/isAgonesInstalled.test.ts
@@ -0,0 +1,80 @@
+/*
+ * Copyright Contributors to Agones a Series of LF Projects, LLC.
+ *
+ * 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 { describe, expect, it, vi } from 'vitest';
+
+// Mock ApiProxy before importing the module under test
+vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
+ ApiProxy: {
+ request: vi.fn(),
+ },
+}));
+
+import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
+import { isAgonesInstalled } from './isAgonesInstalled';
+
+describe('isAgonesInstalled', () => {
+ it('should return true when the Agones API group responds successfully', async () => {
+ vi.mocked(ApiProxy.request).mockResolvedValue({ kind: 'APIResourceList' });
+
+ const result = await isAgonesInstalled();
+
+ expect(result).toBe(true);
+ expect(ApiProxy.request).toHaveBeenCalledWith('/apis/agones.dev/v1', {
+ method: 'GET',
+ });
+ });
+
+ it('should return true for any truthy response', async () => {
+ vi.mocked(ApiProxy.request).mockResolvedValue({ resources: [] });
+
+ const result = await isAgonesInstalled();
+
+ expect(result).toBe(true);
+ });
+
+ it('should return false when the API call throws (Agones not installed)', async () => {
+ vi.mocked(ApiProxy.request).mockRejectedValue(new Error('404 Not Found'));
+
+ const result = await isAgonesInstalled();
+
+ expect(result).toBe(false);
+ });
+
+ it('should return false on network errors', async () => {
+ vi.mocked(ApiProxy.request).mockRejectedValue(new Error('Network Error'));
+
+ const result = await isAgonesInstalled();
+
+ expect(result).toBe(false);
+ });
+
+ it('should return false when the response is null', async () => {
+ vi.mocked(ApiProxy.request).mockResolvedValue(null);
+
+ const result = await isAgonesInstalled();
+
+ expect(result).toBe(false);
+ });
+
+ it('should return false when the response is undefined', async () => {
+ vi.mocked(ApiProxy.request).mockResolvedValue(undefined);
+
+ const result = await isAgonesInstalled();
+
+ expect(result).toBe(false);
+ });
+});
From b33c7f097ae5ebbb3efb114f6a9f6698a80bc80a Mon Sep 17 00:00:00 2001
From: ashwani yadav <22ashwaniyadav@gmail.com>
Date: Fri, 12 Jun 2026 19:12:42 +0530
Subject: [PATCH 3/4] fix: make Agones installation check robust by validating
APIResourceList
Signed-off-by: ashwani yadav <22ashwaniyadav@gmail.com>
---
src/isAgonesInstalled.test.ts | 36 +++++++++++++++++++++++++++++++----
src/isAgonesInstalled.ts | 8 +++++++-
2 files changed, 39 insertions(+), 5 deletions(-)
diff --git a/src/isAgonesInstalled.test.ts b/src/isAgonesInstalled.test.ts
index 78275b0..b384aa0 100644
--- a/src/isAgonesInstalled.test.ts
+++ b/src/isAgonesInstalled.test.ts
@@ -27,8 +27,11 @@ import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
import { isAgonesInstalled } from './isAgonesInstalled';
describe('isAgonesInstalled', () => {
- it('should return true when the Agones API group responds successfully', async () => {
- vi.mocked(ApiProxy.request).mockResolvedValue({ kind: 'APIResourceList' });
+ it('should return true when the Agones API group responds with a valid APIResourceList', async () => {
+ vi.mocked(ApiProxy.request).mockResolvedValue({
+ kind: 'APIResourceList',
+ resources: [{ name: 'gameservers' }],
+ });
const result = await isAgonesInstalled();
@@ -38,8 +41,11 @@ describe('isAgonesInstalled', () => {
});
});
- it('should return true for any truthy response', async () => {
- vi.mocked(ApiProxy.request).mockResolvedValue({ resources: [] });
+ it('should return true when resources array is empty but valid', async () => {
+ vi.mocked(ApiProxy.request).mockResolvedValue({
+ kind: 'APIResourceList',
+ resources: [],
+ });
const result = await isAgonesInstalled();
@@ -77,4 +83,26 @@ describe('isAgonesInstalled', () => {
expect(result).toBe(false);
});
+
+ it('should return false when response is a non-APIResourceList object (error object)', async () => {
+ vi.mocked(ApiProxy.request).mockResolvedValue({
+ kind: 'Status',
+ status: 'Failure',
+ message: 'the server could not find the requested resource',
+ });
+
+ const result = await isAgonesInstalled();
+
+ expect(result).toBe(false);
+ });
+
+ it('should return false when response has no resources field', async () => {
+ vi.mocked(ApiProxy.request).mockResolvedValue({
+ kind: 'APIResourceList',
+ });
+
+ const result = await isAgonesInstalled();
+
+ expect(result).toBe(false);
+ });
});
diff --git a/src/isAgonesInstalled.ts b/src/isAgonesInstalled.ts
index 4010e9e..958d98c 100644
--- a/src/isAgonesInstalled.ts
+++ b/src/isAgonesInstalled.ts
@@ -18,11 +18,17 @@ import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
export async function isAgonesInstalled(): Promise {
try {
+ console.log('[Agones] Checking if Agones is installed...');
const response = await ApiProxy.request('/apis/agones.dev/v1', {
method: 'GET',
});
- return !!response;
+ console.log('[Agones] API response:', JSON.stringify(response));
+ // Verify the response is a real K8s API resource list, not an error object.
+ const result = response?.kind === 'APIResourceList' && Array.isArray(response?.resources);
+ console.log('[Agones] Detection result:', result);
+ return result;
} catch (error) {
+ console.log('[Agones] API error (not installed):', error);
return false;
}
}
From a09a9a167a6e379b3b955803c1ce263185db66b0 Mon Sep 17 00:00:00 2001
From: ashwani yadav <22ashwaniyadav@gmail.com>
Date: Mon, 15 Jun 2026 17:55:13 +0530
Subject: [PATCH 4/4] plugin: Move isAgonesInstalled into hook and add TSDoc
Signed-off-by: ashwani yadav <22ashwaniyadav@gmail.com>
---
src/hooks/useAgonesInstalled.test.tsx | 25 ++++++++++------
src/hooks/useAgonesInstalled.tsx | 41 +++++++++++++++++++++++++--
src/isAgonesInstalled.test.ts | 2 +-
src/isAgonesInstalled.ts | 34 ----------------------
4 files changed, 57 insertions(+), 45 deletions(-)
delete mode 100644 src/isAgonesInstalled.ts
diff --git a/src/hooks/useAgonesInstalled.test.tsx b/src/hooks/useAgonesInstalled.test.tsx
index e9ddd56..0fc874b 100644
--- a/src/hooks/useAgonesInstalled.test.tsx
+++ b/src/hooks/useAgonesInstalled.test.tsx
@@ -16,14 +16,17 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
-import { useAgonesInstalled } from './useAgonesInstalled';
-// Mock the isAgonesInstalled function
-vi.mock('../isAgonesInstalled', () => ({
- isAgonesInstalled: vi.fn(),
+// Mock ApiProxy so the hook's internal isAgonesInstalled() call
+// doesn't make real HTTP requests.
+vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
+ ApiProxy: {
+ request: vi.fn(),
+ },
}));
-import { isAgonesInstalled } from '../isAgonesInstalled';
+import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
+import { useAgonesInstalled } from './useAgonesInstalled';
describe('useAgonesInstalled', () => {
afterEach(() => {
@@ -31,7 +34,8 @@ describe('useAgonesInstalled', () => {
});
it('should start in loading state', () => {
- vi.mocked(isAgonesInstalled).mockReturnValue(new Promise(() => {})); // never resolves
+ // Never-resolving promise keeps the hook in loading state
+ vi.mocked(ApiProxy.request).mockReturnValue(new Promise(() => {}));
const { result } = renderHook(() => useAgonesInstalled());
expect(result.current.isAgonesInstalled).toBeNull();
@@ -39,7 +43,11 @@ describe('useAgonesInstalled', () => {
});
it('should return isAgonesInstalled=true when Agones is detected', async () => {
- vi.mocked(isAgonesInstalled).mockResolvedValue(true);
+ vi.mocked(ApiProxy.request).mockResolvedValue({
+ kind: 'APIResourceList',
+ resources: [{ name: 'gameservers' }],
+ });
+
const { result } = renderHook(() => useAgonesInstalled());
await waitFor(() => {
@@ -50,7 +58,8 @@ describe('useAgonesInstalled', () => {
});
it('should return isAgonesInstalled=false when Agones is not detected', async () => {
- vi.mocked(isAgonesInstalled).mockResolvedValue(false);
+ vi.mocked(ApiProxy.request).mockRejectedValue(new Error('404 Not Found'));
+
const { result } = renderHook(() => useAgonesInstalled());
await waitFor(() => {
diff --git a/src/hooks/useAgonesInstalled.tsx b/src/hooks/useAgonesInstalled.tsx
index ec2d23f..f117aa3 100644
--- a/src/hooks/useAgonesInstalled.tsx
+++ b/src/hooks/useAgonesInstalled.tsx
@@ -14,15 +14,52 @@
* limitations under the License.
*/
+import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
import { useEffect, useState } from 'react';
-import { isAgonesInstalled as checkAgonesInstallation } from '../isAgonesInstalled';
+/**
+ * Checks whether the Agones CRDs are installed on the current cluster by
+ * querying the {@link https://agones.dev/site/docs/reference/agones_crd_api_reference/ | Agones API group}
+ * at `/apis/agones.dev/v1`.
+ *
+ * The response is validated to be a genuine Kubernetes `APIResourceList`
+ * (not a `Status` error object that some proxies return for 404s).
+ *
+ * @returns `true` if Agones CRDs are present, `false` otherwise.
+ */
+export async function isAgonesInstalled(): Promise {
+ try {
+ const response = await ApiProxy.request('/apis/agones.dev/v1', {
+ method: 'GET',
+ });
+ // Verify the response is a real K8s API resource list, not an error object.
+ return response?.kind === 'APIResourceList' && Array.isArray(response?.resources);
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * React hook that asynchronously checks whether the Agones CRDs are installed
+ * on the current Kubernetes cluster.
+ *
+ * @returns An object with:
+ * - `isAgonesInstalled` — `null` while loading, `true` if detected, `false` if not.
+ * - `isAgonesCheckLoading` — `true` while the API check is in progress.
+ *
+ * @example
+ * ```tsx
+ * const { isAgonesInstalled, isAgonesCheckLoading } = useAgonesInstalled();
+ * if (isAgonesCheckLoading) return ;
+ * if (!isAgonesInstalled) return ;
+ * ```
+ */
export function useAgonesInstalled() {
const [isInstalled, setIsInstalled] = useState(null);
useEffect(() => {
async function checkInstalled() {
- const installed = await checkAgonesInstallation();
+ const installed = await isAgonesInstalled();
setIsInstalled(!!installed);
}
checkInstalled();
diff --git a/src/isAgonesInstalled.test.ts b/src/isAgonesInstalled.test.ts
index b384aa0..5d14e22 100644
--- a/src/isAgonesInstalled.test.ts
+++ b/src/isAgonesInstalled.test.ts
@@ -24,7 +24,7 @@ vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
}));
import { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
-import { isAgonesInstalled } from './isAgonesInstalled';
+import { isAgonesInstalled } from './hooks/useAgonesInstalled';
describe('isAgonesInstalled', () => {
it('should return true when the Agones API group responds with a valid APIResourceList', async () => {
diff --git a/src/isAgonesInstalled.ts b/src/isAgonesInstalled.ts
deleted file mode 100644
index 958d98c..0000000
--- a/src/isAgonesInstalled.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright Contributors to Agones a Series of LF Projects, LLC.
- *
- * 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 { ApiProxy } from '@kinvolk/headlamp-plugin/lib';
-
-export async function isAgonesInstalled(): Promise {
- try {
- console.log('[Agones] Checking if Agones is installed...');
- const response = await ApiProxy.request('/apis/agones.dev/v1', {
- method: 'GET',
- });
- console.log('[Agones] API response:', JSON.stringify(response));
- // Verify the response is a real K8s API resource list, not an error object.
- const result = response?.kind === 'APIResourceList' && Array.isArray(response?.resources);
- console.log('[Agones] Detection result:', result);
- return result;
- } catch (error) {
- console.log('[Agones] API error (not installed):', error);
- return false;
- }
-}