diff --git a/src/components/GameServerSetLink.tsx b/src/components/GameServerSetLink.tsx new file mode 100644 index 0000000..d0141a0 --- /dev/null +++ b/src/components/GameServerSetLink.tsx @@ -0,0 +1,33 @@ +/* + * 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 { Router } from '@kinvolk/headlamp-plugin/lib'; +import React from 'react'; + +interface GameServerSetLinkProps { + namespace: string; + name: string; + onClick?: (e: React.MouseEvent) => void; +} + +export function GameServerSetLink({ namespace, name, onClick }: GameServerSetLinkProps) { + const url = Router.createRouteURL('agones-gameserverset', { namespace, name }); + return ( + + {name} + + ); +} diff --git a/src/components/GameServerSetPhaseChip.tsx b/src/components/GameServerSetPhaseChip.tsx new file mode 100644 index 0000000..fe1139c --- /dev/null +++ b/src/components/GameServerSetPhaseChip.tsx @@ -0,0 +1,34 @@ +/* + * 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 Chip from '@mui/material/Chip'; +import React from 'react'; +import { GameServerSetPhase } from '../utils/gameServerSetHelpers'; + +const PHASE_STYLE: Record< + GameServerSetPhase, + { label: string; color: 'default' | 'success' | 'warning' } +> = { + active: { label: 'Active', color: 'success' }, + retiring: { label: 'Retiring', color: 'warning' }, + unknown: { label: '—', color: 'default' }, +}; + +export function GameServerSetPhaseChip({ phase }: { phase: GameServerSetPhase }) { + const { label, color } = PHASE_STYLE[phase]; + if (phase === 'unknown') return <>{label}; + return ; +} diff --git a/src/components/StateChip.tsx b/src/components/StateChip.tsx index cbdc7eb..3340fe6 100644 --- a/src/components/StateChip.tsx +++ b/src/components/StateChip.tsx @@ -21,7 +21,9 @@ type ChipColor = 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'succe const STATE_COLORS: Record = { // In-progress lifecycle states + PortAllocation: 'info', Creating: 'info', + Starting: 'info', Scheduled: 'info', RequestReady: 'info', // Stable states diff --git a/src/index.tsx b/src/index.tsx index 6c585b3..174abc8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -23,6 +23,8 @@ import { FleetDetail } from './views/fleets/Detail'; import { FleetList } from './views/fleets/List'; import { GameServerDetail } from './views/gameservers/Detail'; import { GameServerList } from './views/gameservers/List'; +import { GameServerSetDetail } from './views/gameserversets/Detail'; +import { GameServerSetList } from './views/gameserversets/List'; import { AgonesOverview } from './views/overview/Overview'; // ─── Map ───────────────────────────────────────────────────────────────────── @@ -35,6 +37,7 @@ registerSidebarEntry({ parent: null, name: 'agones', label: 'Agones', url: '/ago registerSidebarEntry({ parent: 'agones', name: 'agones-overview', label: 'Overview', url: '/agones' }); registerSidebarEntry({ parent: 'agones', name: 'agones-fleets', label: 'Fleets', url: '/agones/fleets' }); registerSidebarEntry({ parent: 'agones', name: 'agones-gameservers', label: 'Game Servers', url: '/agones/gameservers' }); +registerSidebarEntry({ parent: 'agones', name: 'agones-gameserversets', label: 'Game Server Sets', url: '/agones/gameserversets' }); registerSidebarEntry({ parent: 'agones', name: 'agones-fleetautoscalers', label: 'Autoscalers', url: '/agones/fleetautoscalers' }); // ─── Routes ────────────────────────────────────────────────────────────────── @@ -47,5 +50,8 @@ registerRoute({ path: '/agones/fleets/:namespace/:name', sidebar: 'agones-fleets registerRoute({ path: '/agones/gameservers', sidebar: 'agones-gameservers', name: 'agones-gameservers', exact: true, component: () => }); registerRoute({ path: '/agones/gameservers/:namespace/:name', sidebar: 'agones-gameservers', name: 'agones-gameserver', component: () => }); +registerRoute({ path: '/agones/gameserversets', sidebar: 'agones-gameserversets', name: 'agones-gameserversets', exact: true, component: () => }); +registerRoute({ path: '/agones/gameserversets/:namespace/:name', sidebar: 'agones-gameserversets', name: 'agones-gameserverset', component: () => }); + registerRoute({ path: '/agones/fleetautoscalers', sidebar: 'agones-fleetautoscalers', name: 'agones-fleetautoscalers', exact: true, component: () => }); registerRoute({ path: '/agones/fleetautoscalers/:namespace/:name', sidebar: 'agones-fleetautoscalers', name: 'agones-fleetautoscaler', component: () => }); \ No newline at end of file diff --git a/src/mapView.tsx b/src/mapView.tsx index 4c336e2..ccd0cfe 100644 --- a/src/mapView.tsx +++ b/src/mapView.tsx @@ -16,11 +16,12 @@ import { GraphSource } from '@kinvolk/headlamp-plugin/lib/components/resourceMap/graph/graphModel'; import { fleetsSource } from './views/map/fleetsSource'; +import { gameServerSetsSource } from './views/map/gameServerSetsSource'; import { gameServersSource } from './views/map/gameServersSource'; import { podsSource } from './views/map/podsSource'; export const agonesMapSource: GraphSource = { id: 'agones', label: 'Agones', - sources: [fleetsSource, gameServersSource, podsSource], + sources: [fleetsSource, gameServerSetsSource, gameServersSource, podsSource], }; diff --git a/src/resources/gameserver.ts b/src/resources/gameserver.ts index 7c4c8f5..7c2da90 100644 --- a/src/resources/gameserver.ts +++ b/src/resources/gameserver.ts @@ -15,6 +15,7 @@ */ import { KubeObject, KubeObjectInterface } from '@kinvolk/headlamp-plugin/lib/k8s/cluster'; +import { GAME_SERVER_SET_LABEL } from '../utils/agonesLabels'; export interface GameServerSpecPort { name?: string; @@ -101,6 +102,10 @@ export class GameServer extends KubeObject { return this.metadata.labels?.['agones.dev/fleet'] ?? ''; } + get gameServerSet(): string { + return this.metadata.labels?.[GAME_SERVER_SET_LABEL] ?? ''; + } + get ports(): string { const ports = this.status.ports ?? []; if (ports.length === 0) return ''; diff --git a/src/resources/gameserverset.ts b/src/resources/gameserverset.ts new file mode 100644 index 0000000..f583ce3 --- /dev/null +++ b/src/resources/gameserverset.ts @@ -0,0 +1,84 @@ +/* + * 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 { KubeObject, KubeObjectInterface } from '@kinvolk/headlamp-plugin/lib/k8s/cluster'; +import { FLEET_NAME_LABEL } from '../utils/agonesLabels'; + +export interface AgonesGameServerSet extends KubeObjectInterface { + spec: { + replicas: number; + scheduling: string; + template: object; + }; + status?: { + replicas?: number; + readyReplicas?: number; + reservedReplicas?: number; + allocatedReplicas?: number; + shutdownReplicas?: number; + }; +} + +export class GameServerSet extends KubeObject { + static apiVersion = 'agones.dev/v1'; + static kind = 'GameServerSet'; + static apiName = 'gameserversets'; + static isNamespaced = true; + + static get detailsRoute() { + return 'agones-gameserverset'; + } + + get spec() { + return this.jsonData.spec; + } + + get status() { + return this.jsonData.status || {}; + } + + get fleet(): string { + return this.metadata.labels?.[FLEET_NAME_LABEL] ?? ''; + } + + get scheduling(): string { + return this.spec.scheduling || 'Packed'; + } + + get desiredReplicas(): number { + return this.spec.replicas || 0; + } + + get currentReplicas(): number { + return this.status.replicas || 0; + } + + get readyReplicas(): number { + return this.status.readyReplicas || 0; + } + + get allocatedReplicas(): number { + return this.status.allocatedReplicas || 0; + } + + get reservedReplicas(): number { + return this.status.reservedReplicas || 0; + } + + get shutdownReplicas(): number { + return this.status.shutdownReplicas || 0; + } +} diff --git a/src/utils/agonesLabels.ts b/src/utils/agonesLabels.ts new file mode 100644 index 0000000..28f3e23 --- /dev/null +++ b/src/utils/agonesLabels.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** Agones label keys (pkg/apis/agones/v1). */ +export const FLEET_NAME_LABEL = 'agones.dev/fleet'; +export const GAME_SERVER_SET_LABEL = 'agones.dev/gameserverset'; +export const GAME_SERVER_POD_LABEL = 'agones.dev/gameserver'; diff --git a/src/utils/gameServerSetHelpers.ts b/src/utils/gameServerSetHelpers.ts new file mode 100644 index 0000000..0c434f0 --- /dev/null +++ b/src/utils/gameServerSetHelpers.ts @@ -0,0 +1,76 @@ +/* + * 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 { KubeObject } from '@kinvolk/headlamp-plugin/lib/k8s/cluster'; +import { Fleet } from '../resources/fleet'; +import { GameServerSet } from '../resources/gameserverset'; +import { FLEET_NAME_LABEL } from './agonesLabels'; + +/** Mirrors metav1.IsControlledBy — fleet must be the controlling owner. */ +export function isControlledBy(child: KubeObject, owner: KubeObject): boolean { + const refs = child.metadata.ownerReferences ?? []; + return refs.some( + ref => ref.uid === owner.metadata.uid && ref.controller === true + ); +} + +/** Template equality check (Agones uses Semantic.DeepEqual on spec.template). */ +export function templatesMatch( + a: { template?: object }, + b: { template?: object } +): boolean { + return JSON.stringify(a.template ?? {}) === JSON.stringify(b.template ?? {}); +} + +export type GameServerSetPhase = 'active' | 'retiring' | 'unknown'; + +/** + * Active when spec.template matches the fleet's template (pkg/fleets/controller.go). + * Among duplicates, the oldest creationTimestamp wins as active. + */ +export function getGameServerSetPhase( + gss: GameServerSet, + fleet: Fleet | undefined, + siblings: GameServerSet[] +): GameServerSetPhase { + if (!fleet) return 'unknown'; + if (!templatesMatch(gss.spec, fleet.spec)) return 'retiring'; + + const matching = siblings.filter(s => templatesMatch(s.spec, fleet.spec)); + if (matching.length <= 1) return 'active'; + + const oldest = matching.reduce((a, b) => + new Date(a.metadata.creationTimestamp ?? 0) <= + new Date(b.metadata.creationTimestamp ?? 0) + ? a + : b + ); + return oldest.metadata.uid === gss.metadata.uid ? 'active' : 'retiring'; +} + +/** Fleet-owned GameServerSets: label selector + ownerRef filter (pkg/fleets/fleets.go). */ +export function filterGameServerSetsByFleet( + sets: GameServerSet[], + fleet: Fleet +): GameServerSet[] { + const fleetName = fleet.metadata.name; + return sets.filter( + gss => + gss.metadata.namespace === fleet.metadata.namespace && + gss.metadata.labels?.[FLEET_NAME_LABEL] === fleetName && + isControlledBy(gss, fleet) + ); +} diff --git a/src/views/fleets/Detail.tsx b/src/views/fleets/Detail.tsx index b0480c9..07b319c 100644 --- a/src/views/fleets/Detail.tsx +++ b/src/views/fleets/Detail.tsx @@ -31,6 +31,7 @@ import { ReplicaBar } from '../../components/ReplicaBar'; import { UtilBar } from '../../components/UtilBar'; import { Fleet } from '../../resources/fleet'; import { GameServer } from '../../resources/gameserver'; +import { GameServerSetsSection } from './GameServerSetsSection'; interface WithGameServers { gameServers: GameServer[] | null } @@ -143,6 +144,7 @@ export function FleetDetail() { } extraSections={item => item && [ + , , , , diff --git a/src/views/fleets/GameServerSetsSection.tsx b/src/views/fleets/GameServerSetsSection.tsx new file mode 100644 index 0000000..9dc0ccb --- /dev/null +++ b/src/views/fleets/GameServerSetsSection.tsx @@ -0,0 +1,87 @@ +/* + * 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 { Link, SectionBox } from '@kinvolk/headlamp-plugin/lib/components/common'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import React, { useMemo } from 'react'; +import { GameServerSetPhaseChip } from '../../components/GameServerSetPhaseChip'; +import { ReplicaBar } from '../../components/ReplicaBar'; +import { Fleet } from '../../resources/fleet'; +import { GameServerSet } from '../../resources/gameserverset'; +import { + filterGameServerSetsByFleet, + getGameServerSetPhase, +} from '../../utils/gameServerSetHelpers'; + +export function GameServerSetsSection({ fleet }: { fleet: Fleet }) { + const [allSets] = GameServerSet.useList({ namespace: fleet.metadata.namespace }); + + const sets = useMemo(() => { + if (!allSets) return null; + return filterGameServerSetsByFleet(allSets, fleet); + }, [allSets, fleet]); + + if (!sets) return null; + if (sets.length === 0) { + return ( + + No GameServerSets found. + + ); + } + + return ( + + + + + Name + Phase + Desired + Replica Status + Shutdown + + + + {sets.map(gss => ( + + {gss.metadata.name} + + + + {gss.desiredReplicas} + + + + {gss.shutdownReplicas || '—'} + + ))} + +
+
+ ); +} diff --git a/src/views/gameservers/Detail.tsx b/src/views/gameservers/Detail.tsx index e0fc714..e43eedc 100644 --- a/src/views/gameservers/Detail.tsx +++ b/src/views/gameservers/Detail.tsx @@ -25,6 +25,8 @@ import TableRow from '@mui/material/TableRow'; import React from 'react'; import { useParams } from 'react-router-dom'; import { FleetLink } from '../../components/FleetLink'; +import { GameServerSetLink } from '../../components/GameServerSetLink'; +import { StateChip } from '../../components/StateChip'; import { UtilBar } from '../../components/UtilBar'; import { GameServer } from '../../resources/gameserver'; @@ -172,7 +174,18 @@ export function GameServerDetail() { ? : '—', }, - { name: 'State', value: item.state }, + { + name: 'Game Server Set', + value: item.gameServerSet + ? ( + + ) + : '—', + }, + { name: 'State', value: }, { name: 'Address', value: item.address || '—' }, { name: 'Ports', value: item.ports || '—' }, { name: 'Node', value: item.nodeName || '—' }, diff --git a/src/views/gameserversets/Detail.tsx b/src/views/gameserversets/Detail.tsx new file mode 100644 index 0000000..346f0dd --- /dev/null +++ b/src/views/gameserversets/Detail.tsx @@ -0,0 +1,90 @@ +/* + * 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 { DetailsGrid, SectionBox } from '@kinvolk/headlamp-plugin/lib/components/common'; +import React from 'react'; +import { useParams } from 'react-router-dom'; +import { FleetLink } from '../../components/FleetLink'; +import { GameServerTable } from '../../components/GameServerTable'; +import { GameServerSetPhaseChip } from '../../components/GameServerSetPhaseChip'; +import { ReplicaBar } from '../../components/ReplicaBar'; +import { Fleet } from '../../resources/fleet'; +import { GameServer } from '../../resources/gameserver'; +import { GameServerSet } from '../../resources/gameserverset'; +import { GAME_SERVER_SET_LABEL } from '../../utils/agonesLabels'; +import { + filterGameServerSetsByFleet, + getGameServerSetPhase, +} from '../../utils/gameServerSetHelpers'; + +function GameServersSection({ gameServers }: { gameServers: GameServer[] | null }) { + return ( + + + + ); +} + +export function GameServerSetDetail() { + const { namespace, name } = useParams<{ namespace: string; name: string }>(); + const [gameServers] = GameServer.useList({ + namespace, + labelSelector: `${GAME_SERVER_SET_LABEL}=${name}`, + }); + const [fleets] = Fleet.useList({ namespace }); + const [allSets] = GameServerSet.useList({ namespace }); + + return ( + { + if (!item) return []; + const fleet = fleets?.find(f => f.metadata.name === item.fleet); + const siblings = + fleet && allSets ? filterGameServerSetsByFleet(allSets, fleet) : []; + const phase = getGameServerSetPhase(item, fleet, siblings); + + return [ + { + name: 'Fleet', + value: item.fleet + ? + : '—', + }, + { name: 'Phase', value: }, + { name: 'Scheduling', value: item.scheduling }, + { name: 'Desired Replicas', value: item.desiredReplicas }, + { + name: 'Replica Status', + value: ( + + ), + }, + { name: 'Shutdown Replicas', value: item.shutdownReplicas }, + ]; + }} + extraSections={() => []} + /> + ); +} diff --git a/src/views/gameserversets/List.tsx b/src/views/gameserversets/List.tsx new file mode 100644 index 0000000..19ec39a --- /dev/null +++ b/src/views/gameserversets/List.tsx @@ -0,0 +1,110 @@ +/* + * 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 { Link, SectionBox } from '@kinvolk/headlamp-plugin/lib/components/common'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Typography from '@mui/material/Typography'; +import React, { useMemo } from 'react'; +import { FleetLink } from '../../components/FleetLink'; +import { GameServerSetPhaseChip } from '../../components/GameServerSetPhaseChip'; +import { ReplicaBar } from '../../components/ReplicaBar'; +import { Fleet } from '../../resources/fleet'; +import { GameServerSet } from '../../resources/gameserverset'; +import { filterGameServerSetsByFleet, getGameServerSetPhase } from '../../utils/gameServerSetHelpers'; + +export function GameServerSetList() { + const [sets] = GameServerSet.useList(); + const [fleets] = Fleet.useList(); + + const fleetByKey = useMemo(() => { + const m = new Map(); + for (const f of fleets ?? []) { + m.set(`${f.metadata.namespace}/${f.metadata.name}`, f); + } + return m; + }, [fleets]); + + const siblingsByFleet = useMemo(() => { + const m = new Map(); + if (!sets || !fleets) return m; + for (const fleet of fleets) { + m.set( + `${fleet.metadata.namespace}/${fleet.metadata.name}`, + filterGameServerSetsByFleet(sets, fleet) + ); + } + return m; + }, [sets, fleets]); + + return ( + + + + + Name + Namespace + Fleet + Phase + Desired + Replica Status + Shutdown + + + + {!sets ? ( + + + Loading… + + + ) : sets.map(gss => { + const fleetKey = `${gss.metadata.namespace}/${gss.fleet}`; + const fleet = fleetByKey.get(fleetKey); + const siblings = siblingsByFleet.get(fleetKey) ?? []; + const phase = getGameServerSetPhase(gss, fleet, siblings); + + return ( + + {gss.metadata.name} + {gss.metadata.namespace} + + {gss.fleet + ? + : '—'} + + + {gss.desiredReplicas} + + + + {gss.shutdownReplicas || '—'} + + ); + })} + +
+
+ ); +} diff --git a/src/views/map/gameServerSetsSource.ts b/src/views/map/gameServerSetsSource.ts new file mode 100644 index 0000000..431c431 --- /dev/null +++ b/src/views/map/gameServerSetsSource.ts @@ -0,0 +1,55 @@ +/* + * 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 { GraphEdge, GraphSource } from '@kinvolk/headlamp-plugin/lib/components/resourceMap/graph/graphModel'; +import { useMemo } from 'react'; +import { Fleet } from '../../resources/fleet'; +import { GameServerSet } from '../../resources/gameserverset'; +import { buildNameToUidMap, makeNode, ownerEdges } from './graphHelpers'; + +export const gameServerSetsSource: GraphSource = { + id: 'agones-gameserversets', + label: 'GameServerSets', + useData() { + const [sets] = GameServerSet.useList(); + const [fleets] = Fleet.useList(); + return useMemo(() => { + if (!sets) return null; + + const fleetUidMap = buildNameToUidMap(fleets ?? []); + const ownerRefEdges = sets.flatMap(ownerEdges); + const fleetLabelEdges: GraphEdge[] = []; + + for (const gss of sets) { + const fleetName = gss.fleet; + if (!fleetName) continue; + const fleetUid = fleetUidMap.get(`${gss.metadata.namespace}/${fleetName}`); + if (fleetUid) { + fleetLabelEdges.push({ + id: `fleet-gss-${fleetUid}-${gss.metadata.uid}`, + source: fleetUid, + target: gss.metadata.uid, + }); + } + } + + return { + nodes: sets.map(gss => makeNode(gss, 80)), + edges: [...ownerRefEdges, ...fleetLabelEdges], + }; + }, [sets, fleets]); + }, +}; diff --git a/src/views/map/gameServersSource.ts b/src/views/map/gameServersSource.ts index 8ffc2af..6247325 100644 --- a/src/views/map/gameServersSource.ts +++ b/src/views/map/gameServersSource.ts @@ -16,8 +16,8 @@ import { GraphEdge, GraphSource } from '@kinvolk/headlamp-plugin/lib/components/resourceMap/graph/graphModel'; import { useMemo } from 'react'; -import { Fleet } from '../../resources/fleet'; import { GameServer } from '../../resources/gameserver'; +import { GameServerSet } from '../../resources/gameserverset'; import { buildNameToUidMap, makeNode } from './graphHelpers'; export const gameServersSource: GraphSource = { @@ -25,21 +25,27 @@ export const gameServersSource: GraphSource = { label: 'GameServers', useData() { const [gameServers] = GameServer.useList(); - const [fleets] = Fleet.useList(); + const [sets] = GameServerSet.useList(); return useMemo(() => { if (!gameServers) return null; - const fleetUidMap = buildNameToUidMap(fleets ?? []); + const gssUidMap = buildNameToUidMap(sets ?? []); const edges: GraphEdge[] = []; for (const gs of gameServers) { - const fleetName = gs.fleet; - if (!fleetName) continue; - const uid = fleetUidMap.get(`${gs.metadata.namespace}/${fleetName}`); - if (uid) edges.push({ id: `fleet-gs-${uid}-${gs.metadata.uid}`, source: uid, target: gs.metadata.uid }); + const gssName = gs.gameServerSet; + if (!gssName) continue; + const uid = gssUidMap.get(`${gs.metadata.namespace}/${gssName}`); + if (uid) { + edges.push({ + id: `gss-gs-${uid}-${gs.metadata.uid}`, + source: uid, + target: gs.metadata.uid, + }); + } } return { nodes: gameServers.map(gs => makeNode(gs, 60)), edges }; - }, [gameServers, fleets]); + }, [gameServers, sets]); }, }; \ No newline at end of file