diff --git a/strimzi/src/StrimziIntro.stories.tsx b/strimzi/src/StrimziIntro.stories.tsx index 58790156d..13a10d9a0 100644 --- a/strimzi/src/StrimziIntro.stories.tsx +++ b/strimzi/src/StrimziIntro.stories.tsx @@ -14,8 +14,8 @@ function StrimziIntro() { Strimzi Headlamp Plugin - This Storybook is for the Strimzi plugin. Here you can develop and review UI - (Kafka list, topics, users, topology) with mock data—no cluster required. + This Storybook is for the Strimzi plugin. Here you can develop and review UI (Kafka list, + topics, users, topology) with mock data—no cluster required. Add more *.stories.tsx next to your components to see them here. diff --git a/strimzi/src/components/KafkaClusterTopology.stories.tsx b/strimzi/src/components/KafkaClusterTopology.stories.tsx index 23e62e6be..d5f17dc61 100644 --- a/strimzi/src/components/KafkaClusterTopology.stories.tsx +++ b/strimzi/src/components/KafkaClusterTopology.stories.tsx @@ -14,8 +14,8 @@ function TopologyNote() { KafkaTopologyModal in the real app. - Open Strimzi → Kafka Clusters in Headlamp and click View in the Topology - column to see pods, StrimziPodSets, and zoom controls (React Flow). + Open Strimzi → Kafka Clusters in Headlamp and click View{' '} + in the Topology column to see pods, StrimziPodSets, and zoom controls (React Flow). For a modal shell-only preview in Storybook, see strimzi/KafkaTopologyModal. diff --git a/strimzi/src/components/KafkaClusterTopology.tsx b/strimzi/src/components/KafkaClusterTopology.tsx index 316a42683..b8e12015b 100644 --- a/strimzi/src/components/KafkaClusterTopology.tsx +++ b/strimzi/src/components/KafkaClusterTopology.tsx @@ -79,9 +79,9 @@ const LAYOUT = { NAMESPACE_BG_OPACITY: 0.25, // Progressive opacity for nested blocks - CLUSTER_BG_OPACITY: 0.30, + CLUSTER_BG_OPACITY: 0.3, GROUP_BG_OPACITY: 0.35, - POD_BG_OPACITY: 0.40, + POD_BG_OPACITY: 0.4, // Icon sizes for labels NAMESPACE_ICON_SIZE: 40, @@ -152,8 +152,8 @@ function calculatePodDimensions(params: { const calculatedWidth = LAYOUT.POD_ICON_SIZE + // Icon 20 + // Gap between icon and text - (maxTextLength * LAYOUT.POD_CONTENT_CHAR_WIDTH) + // Text content - (LAYOUT.POD_NODE_PADDING * 3); // Padding + maxTextLength * LAYOUT.POD_CONTENT_CHAR_WIDTH + // Text content + LAYOUT.POD_NODE_PADDING * 3; // Padding const width = Math.max(LAYOUT.POD_NODE_MIN_WIDTH, calculatedWidth); const height = LAYOUT.POD_NODE_BASE_HEIGHT; @@ -244,9 +244,8 @@ function GraphControlButton({ backgroundColor: theme.palette.background.paper, color: theme.palette.text.primary, border: 'none', - boxShadow: theme.palette.mode === 'dark' - ? '0 2px 4px rgba(0,0,0,0.3)' - : '0 2px 4px rgba(0,0,0,0.1)', + boxShadow: + theme.palette.mode === 'dark' ? '0 2px 4px rgba(0,0,0,0.3)' : '0 2px 4px rgba(0,0,0,0.1)', '&:hover': { backgroundColor: theme.palette.action.hover, }, @@ -257,13 +256,7 @@ function GraphControlButton({ }; return ( - ); @@ -285,18 +278,10 @@ function GraphControls({ children }: { children?: React.ReactNode }) { orientation="vertical" variant="contained" > - zoomIn()} - > + zoomIn()}> - zoomOut()} - > + zoomOut()}> @@ -347,7 +332,15 @@ function createPodNode(params: { position, data: { label: ( -
+
-
+
; -}) { +function StatusBadge({ ready }: { ready: boolean; theme?: ReturnType }) { const muiTheme = useTheme(); const isDark = muiTheme.palette.mode === 'dark'; @@ -447,16 +442,18 @@ function StatusBadge({ color={ready ? 'success' : 'warning'} sx={{ borderRadius: '4px', - ...(isDark && ready && { - borderColor: '#34d399', - color: '#34d399', - backgroundColor: 'rgba(52, 211, 153, 0.15)', - }), - ...(isDark && !ready && { - borderColor: '#f87171', - color: '#f87171', - backgroundColor: 'rgba(248, 113, 113, 0.15)', - }), + ...(isDark && + ready && { + borderColor: '#34d399', + color: '#34d399', + backgroundColor: 'rgba(52, 211, 153, 0.15)', + }), + ...(isDark && + !ready && { + borderColor: '#f87171', + color: '#f87171', + backgroundColor: 'rgba(248, 113, 113, 0.15)', + }), }} /> ); @@ -501,9 +498,28 @@ function createGroupLabel(params: { position: { x: 20, y: 20 }, data: { label: ( -
- -
+
+ +
- +
)}
@@ -607,7 +628,7 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { ([nodePoolData, podSetData, podData]: [ { items?: KafkaNodePool[] }, { items?: StrimziPodSet[] }, - { items?: Pod[] } + { items?: Pod[] }, ]) => { if (nodePoolData?.items) { const pools = nodePoolData.items.filter( @@ -631,7 +652,7 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { setLoading(false); } ) - .catch((err) => { + .catch(err => { console.error('Failed to fetch Kafka topology data:', err); setLoading(false); }); @@ -647,14 +668,30 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { // PHASE 1: Calculate ALL pod dimensions first (based on real data) // This ensures cluster/namespace dimensions are accurate - let allPoolDimensions: Array<{ poolName: string; podDimensions: PodDimensions[]; dimensions: NodePoolDimensions }> = []; - let kraftLegacyDimensions: { podDimensions: PodDimensions[]; groupWidth: number; groupHeight: number } | null = null; - let zkDimensions: { podDimensions: PodDimensions[]; groupWidth: number; groupHeight: number } | null = null; - let zkBrokerDimensions: { podDimensions: PodDimensions[]; groupWidth: number; groupHeight: number } | null = null; + let allPoolDimensions: Array<{ + poolName: string; + podDimensions: PodDimensions[]; + dimensions: NodePoolDimensions; + }> = []; + let kraftLegacyDimensions: { + podDimensions: PodDimensions[]; + groupWidth: number; + groupHeight: number; + } | null = null; + let zkDimensions: { + podDimensions: PodDimensions[]; + groupWidth: number; + groupHeight: number; + } | null = null; + let zkBrokerDimensions: { + podDimensions: PodDimensions[]; + groupWidth: number; + groupHeight: number; + } | null = null; if (nodePools.length > 0) { // Calculate dimensions for all NodePools - allPoolDimensions = nodePools.map((pool) => { + allPoolDimensions = nodePools.map(pool => { const poolName = pool.metadata.name; const roles = pool.spec.roles || []; const replicas = pool.spec.replicas; @@ -663,7 +700,7 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { const isBroker = roles.includes('broker'); const isDual = isController && isBroker; - const podDimensions = nodeIds.map((nodeId) => { + const podDimensions = nodeIds.map(nodeId => { const podName = `${clusterName}-${poolName}-${nodeId}`; const pod = pods.find(p => p.metadata?.name === podName); return calculatePodDimensions({ @@ -678,7 +715,7 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { return { poolName, podDimensions, - dimensions: calculateNodePoolDimensions(podDimensions) + dimensions: calculateNodePoolDimensions(podDimensions), }; }); } else if (isKRaft) { @@ -782,12 +819,26 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { totalPoolHeight += dimensions.poolHeight + LAYOUT.GROUP_SPACING; }); - clusterWidth = Math.max(maxPoolWidth + LAYOUT.CLUSTER_HORIZONTAL_MARGIN, LAYOUT.CLUSTER_MIN_WIDTH); - clusterHeight = LAYOUT.CLUSTER_LABEL_HEIGHT + LAYOUT.CLUSTER_VERTICAL_MARGIN_START + totalPoolHeight + LAYOUT.CLUSTER_VERTICAL_MARGIN_END; + clusterWidth = Math.max( + maxPoolWidth + LAYOUT.CLUSTER_HORIZONTAL_MARGIN, + LAYOUT.CLUSTER_MIN_WIDTH + ); + clusterHeight = + LAYOUT.CLUSTER_LABEL_HEIGHT + + LAYOUT.CLUSTER_VERTICAL_MARGIN_START + + totalPoolHeight + + LAYOUT.CLUSTER_VERTICAL_MARGIN_END; } else if (isKRaft && kraftLegacyDimensions) { // KRaft legacy - clusterWidth = Math.max(kraftLegacyDimensions.groupWidth + LAYOUT.CLUSTER_HORIZONTAL_MARGIN, LAYOUT.CLUSTER_MIN_WIDTH); - clusterHeight = LAYOUT.CLUSTER_LABEL_HEIGHT + LAYOUT.CLUSTER_VERTICAL_MARGIN_LEGACY + kraftLegacyDimensions.groupHeight + LAYOUT.CLUSTER_VERTICAL_MARGIN_END; + clusterWidth = Math.max( + kraftLegacyDimensions.groupWidth + LAYOUT.CLUSTER_HORIZONTAL_MARGIN, + LAYOUT.CLUSTER_MIN_WIDTH + ); + clusterHeight = + LAYOUT.CLUSTER_LABEL_HEIGHT + + LAYOUT.CLUSTER_VERTICAL_MARGIN_LEGACY + + kraftLegacyDimensions.groupHeight + + LAYOUT.CLUSTER_VERTICAL_MARGIN_END; } else { // ZooKeeper mode - use pre-calculated dimensions const zkGroupWidth = zkDimensions?.groupWidth || 0; @@ -796,7 +847,10 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { const brokerGroupHeight = zkBrokerDimensions?.groupHeight || 0; maxPodSetWidth = Math.max(zkGroupWidth, brokerGroupWidth); - clusterWidth = Math.max(maxPodSetWidth + LAYOUT.CLUSTER_HORIZONTAL_MARGIN, LAYOUT.CLUSTER_MIN_WIDTH); + clusterWidth = Math.max( + maxPodSetWidth + LAYOUT.CLUSTER_HORIZONTAL_MARGIN, + LAYOUT.CLUSTER_MIN_WIDTH + ); clusterHeight = LAYOUT.CLUSTER_LABEL_HEIGHT + LAYOUT.CLUSTER_VERTICAL_MARGIN_START + @@ -807,7 +861,8 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { // Namespace dimensions const namespaceWidth = clusterWidth + LAYOUT.NAMESPACE_PADDING * 2; - const namespaceHeight = clusterHeight + LAYOUT.NAMESPACE_PADDING * 2 + LAYOUT.NAMESPACE_LABEL_HEIGHT + 10; + const namespaceHeight = + clusterHeight + LAYOUT.NAMESPACE_PADDING * 2 + LAYOUT.NAMESPACE_LABEL_HEIGHT + 10; // Namespace node (root) generatedNodes.push({ @@ -833,8 +888,19 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { data: { label: (
- -
+ +
- -
+ +
- +
)}
), - editInfo: onEditResource ? { - resourceUrl: `/apis/kafka.strimzi.io/${kafkaVersion}/namespaces/${namespace}/kafkas/${clusterName}`, - resourceName: clusterName, - resourceKind: 'Kafka', - } : undefined, + editInfo: onEditResource + ? { + resourceUrl: `/apis/kafka.strimzi.io/${kafkaVersion}/namespaces/${namespace}/kafkas/${clusterName}`, + resourceName: clusterName, + resourceKind: 'Kafka', + } + : undefined, }, style: { background: 'transparent', @@ -1060,11 +1145,13 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { theme, resourceType: 'KafkaNodePool', replicaInfo: `Replicas: ${replicas}`, - editInfo: onEditResource ? { - resourceUrl: `/apis/kafka.strimzi.io/${kafkaVersion}/namespaces/${namespace}/kafkanodepools/${poolName}`, - resourceName: poolName, - resourceKind: 'KafkaNodePool', - } : undefined, + editInfo: onEditResource + ? { + resourceUrl: `/apis/kafka.strimzi.io/${kafkaVersion}/namespaces/${namespace}/kafkanodepools/${poolName}`, + resourceName: poolName, + resourceKind: 'KafkaNodePool', + } + : undefined, }) ); @@ -1102,11 +1189,13 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { theme, resourceType: 'StrimziPodSet', replicaInfo: `Ready Pods: ${readyPodsCount}/${nodeIds.length}`, - editInfo: onEditResource ? { - resourceUrl: `/apis/core.strimzi.io/${coreVersion}/namespaces/${namespace}/strimzipodsets/${podSet.metadata.name}`, - resourceName: podSet.metadata.name, - resourceKind: 'StrimziPodSet', - } : undefined, + editInfo: onEditResource + ? { + resourceUrl: `/apis/core.strimzi.io/${coreVersion}/namespaces/${namespace}/strimzipodsets/${podSet.metadata.name}`, + resourceName: podSet.metadata.name, + resourceKind: 'StrimziPodSet', + } + : undefined, }) ); @@ -1185,11 +1274,13 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { theme, resourceType: 'StrimziPodSet', replicaInfo: `Ready Pods: ${readyPodsCount}/${brokerCount}`, - editInfo: onEditResource ? { - resourceUrl: `/apis/core.strimzi.io/${coreVersion}/namespaces/${namespace}/strimzipodsets/${kafkaPodSet.metadata.name}`, - resourceName: kafkaPodSet.metadata.name, - resourceKind: 'StrimziPodSet', - } : undefined, + editInfo: onEditResource + ? { + resourceUrl: `/apis/core.strimzi.io/${coreVersion}/namespaces/${namespace}/strimzipodsets/${kafkaPodSet.metadata.name}`, + resourceName: kafkaPodSet.metadata.name, + resourceKind: 'StrimziPodSet', + } + : undefined, }) ); @@ -1270,11 +1361,13 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { theme, resourceType: 'StrimziPodSet', replicaInfo: `Ready Pods: ${zkReadyPodsCount}/${zkCount}`, - editInfo: onEditResource ? { - resourceUrl: `/apis/core.strimzi.io/${coreVersion}/namespaces/${namespace}/strimzipodsets/${zkPodSet.metadata.name}`, - resourceName: zkPodSet.metadata.name, - resourceKind: 'StrimziPodSet', - } : undefined, + editInfo: onEditResource + ? { + resourceUrl: `/apis/core.strimzi.io/${coreVersion}/namespaces/${namespace}/strimzipodsets/${zkPodSet.metadata.name}`, + resourceName: zkPodSet.metadata.name, + resourceKind: 'StrimziPodSet', + } + : undefined, }) ); @@ -1311,7 +1404,8 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { // Kafka StrimziPodSet (below ZK) if (kafkaPodSet && zkBrokerDimensions) { // Use pre-calculated dimensions - const { podDimensions: brokerPodDimensions, groupHeight: brokerGroupHeight } = zkBrokerDimensions; + const { podDimensions: brokerPodDimensions, groupHeight: brokerGroupHeight } = + zkBrokerDimensions; const brokerGroupX = (clusterWidth - maxPodSetWidth) / 2; generatedNodes.push({ @@ -1347,11 +1441,13 @@ function TopologyFlow({ kafka, onEditResource }: TopologyProps) { theme, resourceType: 'StrimziPodSet', replicaInfo: `Ready Pods: ${kafkaReadyPodsCount}/${brokerCount}`, - editInfo: onEditResource ? { - resourceUrl: `/apis/core.strimzi.io/${coreVersion}/namespaces/${namespace}/strimzipodsets/${kafkaPodSet.metadata.name}`, - resourceName: kafkaPodSet.metadata.name, - resourceKind: 'StrimziPodSet', - } : undefined, + editInfo: onEditResource + ? { + resourceUrl: `/apis/core.strimzi.io/${coreVersion}/namespaces/${namespace}/strimzipodsets/${kafkaPodSet.metadata.name}`, + resourceName: kafkaPodSet.metadata.name, + resourceKind: 'StrimziPodSet', + } + : undefined, }) ); diff --git a/strimzi/src/components/KafkaConnectList.tsx b/strimzi/src/components/KafkaConnectList.tsx index 6443026f6..a6c85313f 100644 --- a/strimzi/src/components/KafkaConnectList.tsx +++ b/strimzi/src/components/KafkaConnectList.tsx @@ -70,6 +70,10 @@ export function KafkaConnectList() { ]; return ( - + ); } diff --git a/strimzi/src/components/KafkaConnectorList.stories.tsx b/strimzi/src/components/KafkaConnectorList.stories.tsx index 9fda6ac92..084faae85 100644 --- a/strimzi/src/components/KafkaConnectorList.stories.tsx +++ b/strimzi/src/components/KafkaConnectorList.stories.tsx @@ -7,10 +7,7 @@ import { SimpleTable, } from '@kinvolk/headlamp-plugin/lib/components/common'; import { Meta, StoryObj } from '@storybook/react'; -import type { - KafkaConnectorInterface, - KafkaConnectorState, -} from '../resources/kafkaConnector'; +import type { KafkaConnectorInterface, KafkaConnectorState } from '../resources/kafkaConnector'; import { getConnectorDesiredState } from '../crds-helpers'; import { mockKafkaConnectors } from '../storybookMocks/strimziMocks'; @@ -120,8 +117,7 @@ export function PureKafkaConnectorList({ items, onTogglePause }: PureKafkaConnec }, { label: 'Runtime state', - getter: row => - row.status?.connectorStatus?.connector?.state?.toLowerCase() ?? '-', + getter: row => row.status?.connectorStatus?.connector?.state?.toLowerCase() ?? '-', }, { label: 'Status', getter: statusChip }, { label: 'Actions', getter: action }, diff --git a/strimzi/src/components/KafkaConnectorList.tsx b/strimzi/src/components/KafkaConnectorList.tsx index 617b9b3a5..5457bbf6c 100644 --- a/strimzi/src/components/KafkaConnectorList.tsx +++ b/strimzi/src/components/KafkaConnectorList.tsx @@ -149,9 +149,7 @@ export function KafkaConnectorList() { size="small" variant="contained" color="primary" - onClick={() => - setPendingState({ connector: item.jsonData, targetState: 'running' }) - } + onClick={() => setPendingState({ connector: item.jsonData, targetState: 'running' })} > Resume @@ -163,9 +161,7 @@ export function KafkaConnectorList() { size="small" variant="contained" color="warning" - onClick={() => - setPendingState({ connector: item.jsonData, targetState: 'paused' }) - } + onClick={() => setPendingState({ connector: item.jsonData, targetState: 'paused' })} > Pause @@ -176,9 +172,7 @@ export function KafkaConnectorList() { size="small" variant="contained" color="primary" - onClick={() => - setPendingState({ connector: item.jsonData, targetState: 'running' }) - } + onClick={() => setPendingState({ connector: item.jsonData, targetState: 'running' })} > Start @@ -190,7 +184,11 @@ export function KafkaConnectorList() { return ( <> - + setPendingState(null)} @@ -200,8 +198,7 @@ export function KafkaConnectorList() { Change connector state - Set connector{' '} - {pendingState?.connector.metadata.name} to{' '} + Set connector {pendingState?.connector.metadata.name} to{' '} {pendingState?.targetState}? diff --git a/strimzi/src/components/KafkaList.tsx b/strimzi/src/components/KafkaList.tsx index 986f90ee2..a528ecbba 100644 --- a/strimzi/src/components/KafkaList.tsx +++ b/strimzi/src/components/KafkaList.tsx @@ -65,11 +65,7 @@ export function KafkaList() { return ( <> - + ( <> - - diff --git a/strimzi/src/components/KafkaTopicList.tsx b/strimzi/src/components/KafkaTopicList.tsx index 50fe5f6bc..417408717 100644 --- a/strimzi/src/components/KafkaTopicList.tsx +++ b/strimzi/src/components/KafkaTopicList.tsx @@ -1,6 +1,14 @@ import React from 'react'; import { useTheme } from '@mui/material/styles'; -import { Button, Chip, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } from '@mui/material'; +import { + Button, + Chip, + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, +} from '@mui/material'; import { ApiProxy } from '@kinvolk/headlamp-plugin/lib'; import { ResourceListView, @@ -40,11 +48,12 @@ export function KafkaTopicList() { const [loading, setLoading] = React.useState(false); const availableNamespacesForCreate = React.useMemo(() => { + if (!kafkaClusters) return []; return [...new Set(kafkaClusters.map(k => k.metadata.namespace))].sort(); }, [kafkaClusters]); const filteredClusterNames = React.useMemo(() => { - if (!formData.namespace) return []; + if (!formData.namespace || !kafkaClusters) return []; return kafkaClusters .filter(k => k.metadata.namespace === formData.namespace) .map(k => k.metadata.name) @@ -68,21 +77,22 @@ export function KafkaTopicList() { partitions: formData.partitions, replicas: formData.replicas, config: { - ...(formData.retentionMs != null ? { 'retention.ms': formData.retentionMs.toString() } : {}), + ...(formData.retentionMs != null + ? { 'retention.ms': formData.retentionMs.toString() } + : {}), ...(formData.compressionType ? { 'compression.type': formData.compressionType } : {}), - ...(formData.minInSyncReplicas != null ? { 'min.insync.replicas': formData.minInSyncReplicas.toString() } : {}), + ...(formData.minInSyncReplicas != null + ? { 'min.insync.replicas': formData.minInSyncReplicas.toString() } + : {}), }, }, }; - await ApiProxy.request( - `${kafkaApiPath}/namespaces/${formData.namespace}/kafkatopics`, - { - method: 'POST', - body: JSON.stringify(topicResource), - headers: { 'Content-Type': 'application/json' }, - } - ); + await ApiProxy.request(`${kafkaApiPath}/namespaces/${formData.namespace}/kafkatopics`, { + method: 'POST', + body: JSON.stringify(topicResource), + headers: { 'Content-Type': 'application/json' }, + }); setShowCreateDialog(false); setFormData({ @@ -112,9 +122,13 @@ export function KafkaTopicList() { replicas: formData.replicas, config: { ...editingTopic.spec.config, - ...(formData.retentionMs != null ? { 'retention.ms': formData.retentionMs.toString() } : {}), + ...(formData.retentionMs != null + ? { 'retention.ms': formData.retentionMs.toString() } + : {}), ...(formData.compressionType ? { 'compression.type': formData.compressionType } : {}), - ...(formData.minInSyncReplicas != null ? { 'min.insync.replicas': formData.minInSyncReplicas.toString() } : {}), + ...(formData.minInSyncReplicas != null + ? { 'min.insync.replicas': formData.minInSyncReplicas.toString() } + : {}), }, }, }; @@ -130,7 +144,10 @@ export function KafkaTopicList() { setShowEditDialog(false); setEditingTopic(null); - setToast({ message: `Topic "${editingTopic?.metadata.name}" updated successfully`, type: 'success' }); + setToast({ + message: `Topic "${editingTopic?.metadata.name}" updated successfully`, + type: 'success', + }); } catch (err: unknown) { setToast({ message: getErrorMessage(err) || 'Failed to update topic', type: 'error' }); } finally { @@ -153,7 +170,10 @@ export function KafkaTopicList() { `${kafkaApiPath}/namespaces/${deletingTopic.metadata.namespace}/kafkatopics/${deletingTopic.metadata.name}`, { method: 'DELETE' } ); - setToast({ message: `Topic "${deletingTopic.metadata.name}" deleted successfully`, type: 'success' }); + setToast({ + message: `Topic "${deletingTopic.metadata.name}" deleted successfully`, + type: 'success', + }); } catch (err: unknown) { setToast({ message: getErrorMessage(err) || 'Failed to delete topic', type: 'error' }); } finally { @@ -174,9 +194,13 @@ export function KafkaTopicList() { cluster: topic.metadata.labels?.['strimzi.io/cluster'] || 'my-cluster', partitions: topic.spec.partitions || 3, replicas: topic.spec.replicas || 3, - retentionMs: topic.spec.config?.['retention.ms'] ? parseInt(topic.spec.config['retention.ms'] as string) : undefined, + retentionMs: topic.spec.config?.['retention.ms'] + ? parseInt(topic.spec.config['retention.ms'] as string) + : undefined, compressionType: topic.spec.config?.['compression.type'] as string | undefined, - minInSyncReplicas: topic.spec.config?.['min.insync.replicas'] ? parseInt(topic.spec.config['min.insync.replicas'] as string) : undefined, + minInSyncReplicas: topic.spec.config?.['min.insync.replicas'] + ? parseInt(topic.spec.config['min.insync.replicas'] as string) + : undefined, }); setShowEditDialog(true); }; @@ -184,11 +208,11 @@ export function KafkaTopicList() { const openCreateDialog = () => { const firstNs = availableNamespacesForCreate[0] ?? ''; const firstCluster = - firstNs && kafkaClusters.length > 0 - ? kafkaClusters + firstNs && kafkaClusters && kafkaClusters.length > 0 + ? (kafkaClusters .filter(k => k.metadata.namespace === firstNs) .map(k => k.metadata.name) - .sort()[0] ?? '' + .sort()[0] ?? '') : ''; setFormData({ name: '', @@ -247,7 +271,12 @@ export function KafkaTopicList() { > Edit - @@ -266,7 +295,13 @@ export function KafkaTopicList() { columns={columns} headerProps={{ titleSideActions: [ - , ], @@ -297,7 +332,8 @@ export function KafkaTopicList() { Delete Topic - Are you sure you want to delete topic {deletingTopic?.metadata.name}? This action cannot be undone. + Are you sure you want to delete topic {deletingTopic?.metadata.name}? + This action cannot be undone. diff --git a/strimzi/src/components/KafkaTopologyModal.stories.tsx b/strimzi/src/components/KafkaTopologyModal.stories.tsx index 2679cc663..09e24bd28 100644 --- a/strimzi/src/components/KafkaTopologyModal.stories.tsx +++ b/strimzi/src/components/KafkaTopologyModal.stories.tsx @@ -47,7 +47,7 @@ function KafkaTopologyModalShell({ clusterName, namespace, onClose }: ModalShell ? '0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.3)' : '0 20px 25px -5px rgba(0, 0, 0, 0.2), 0 10px 10px -5px rgba(0, 0, 0, 0.1)', }} - onClick={(e) => e.stopPropagation()} + onClick={e => e.stopPropagation()} >
{clusterName}
-
+
{namespace} • Cluster Topology
diff --git a/strimzi/src/components/KafkaTopologyModal.tsx b/strimzi/src/components/KafkaTopologyModal.tsx index cb8fb1dd5..6581d549b 100644 --- a/strimzi/src/components/KafkaTopologyModal.tsx +++ b/strimzi/src/components/KafkaTopologyModal.tsx @@ -21,9 +21,12 @@ export function KafkaTopologyModal({ kafka, open, onClose }: KafkaTopologyModalP const isDark = theme.palette.mode === 'dark'; const [editTarget, setEditTarget] = React.useState(null); - const handleEditResource = useCallback((resourceUrl: string, resourceName: string, resourceKind: string) => { - setEditTarget({ resourceUrl, resourceName, resourceKind }); - }, []); + const handleEditResource = useCallback( + (resourceUrl: string, resourceName: string, resourceKind: string) => { + setEditTarget({ resourceUrl, resourceName, resourceKind }); + }, + [] + ); if (!kafka || !open) return null; @@ -58,7 +61,7 @@ export function KafkaTopologyModal({ kafka, open, onClose }: KafkaTopologyModalP ? '0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.3)' : '0 20px 25px -5px rgba(0, 0, 0, 0.2), 0 10px 10px -5px rgba(0, 0, 0, 0.1)', }} - onClick={(e) => e.stopPropagation()} + onClick={e => e.stopPropagation()} > {/* Header */}
{kafka.metadata.name}
-
+
{kafka.metadata.namespace} • Cluster Topology
@@ -94,11 +104,11 @@ export function KafkaTopologyModal({ kafka, open, onClose }: KafkaTopologyModalP justifyContent: 'center', transition: 'all 0.2s', }} - onMouseEnter={(e) => { + onMouseEnter={e => { e.currentTarget.style.backgroundColor = isDark ? '#475569' : '#cbd5e1'; e.currentTarget.style.color = theme.palette.text.primary; }} - onMouseLeave={(e) => { + onMouseLeave={e => { e.currentTarget.style.backgroundColor = isDark ? '#334155' : '#e2e8f0'; e.currentTarget.style.color = theme.palette.text.secondary; }} @@ -116,10 +126,7 @@ export function KafkaTopologyModal({ kafka, open, onClose }: KafkaTopologyModalP backgroundColor: theme.palette.background.default, }} > - +
diff --git a/strimzi/src/components/KafkaUserCreateFormModal.tsx b/strimzi/src/components/KafkaUserCreateFormModal.tsx index bbaa0691d..eacde8028 100644 --- a/strimzi/src/components/KafkaUserCreateFormModal.tsx +++ b/strimzi/src/components/KafkaUserCreateFormModal.tsx @@ -166,7 +166,16 @@ export function KafkaUserCreateFormModal({ - + - + setFormData({ ...formData, cluster: e.target.value })} @@ -225,10 +252,24 @@ export function KafkaUserCreateFormModal({ - + setFormData({ ...formData, authorizationType: e.target.value as 'simple' | 'none' })} + onChange={e => + setFormData({ ...formData, authorizationType: e.target.value as 'simple' | 'none' }) + } style={inputSx} > @@ -250,7 +302,9 @@ export function KafkaUserCreateFormModal({ {formData.authorizationType === 'simple' && ( - + - + - + updateACL(index, 'operations', e.target.value.split(',').map(s => s.trim()))} + onChange={e => + updateACL( + index, + 'operations', + e.target.value.split(',').map(s => s.trim()) + ) + } placeholder="e.g., Read,Write,Describe" style={smallInputSx} /> @@ -331,7 +432,12 @@ export function KafkaUserCreateFormModal({ - diff --git a/strimzi/src/components/KafkaUserList.stories.tsx b/strimzi/src/components/KafkaUserList.stories.tsx index 53db6b841..12e2284e8 100644 --- a/strimzi/src/components/KafkaUserList.stories.tsx +++ b/strimzi/src/components/KafkaUserList.stories.tsx @@ -104,7 +104,12 @@ export function PureKafkaUserList({ items, onViewSecret, onDelete }: PureKafkaUs > View Secret - diff --git a/strimzi/src/components/KafkaUserList.tsx b/strimzi/src/components/KafkaUserList.tsx index 204d6ca57..18158fcb4 100644 --- a/strimzi/src/components/KafkaUserList.tsx +++ b/strimzi/src/components/KafkaUserList.tsx @@ -1,6 +1,14 @@ import React from 'react'; import { useTheme } from '@mui/material/styles'; -import { Button, Chip, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } from '@mui/material'; +import { + Button, + Chip, + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, +} from '@mui/material'; import { ApiProxy } from '@kinvolk/headlamp-plugin/lib'; import { ResourceListView, @@ -43,11 +51,12 @@ export function KafkaUserList() { const [loading, setLoading] = React.useState(false); const availableNamespacesForCreate = React.useMemo(() => { + if (!kafkaClusters) return []; return [...new Set(kafkaClusters.map(k => k.metadata.namespace))].sort(); }, [kafkaClusters]); const filteredClusterNames = React.useMemo(() => { - if (!formData.namespace) return []; + if (!formData.namespace || !kafkaClusters) return []; return kafkaClusters .filter(k => k.metadata.namespace === formData.namespace) .map(k => k.metadata.name) @@ -60,7 +69,9 @@ export function KafkaUserList() { const secretName = user.metadata.name; const namespace = user.metadata.namespace; - const secret = await ApiProxy.request(`/api/v1/namespaces/${namespace}/secrets/${secretName}`); + const secret = await ApiProxy.request( + `/api/v1/namespaces/${namespace}/secrets/${secretName}` + ); try { if (user.spec.authentication.type === 'scram-sha-512') { @@ -111,14 +122,11 @@ export function KafkaUserList() { }; } - await ApiProxy.request( - `${kafkaApiPath}/namespaces/${formData.namespace}/kafkausers`, - { - method: 'POST', - body: JSON.stringify(userResource), - headers: { 'Content-Type': 'application/json' }, - } - ); + await ApiProxy.request(`${kafkaApiPath}/namespaces/${formData.namespace}/kafkausers`, { + method: 'POST', + body: JSON.stringify(userResource), + headers: { 'Content-Type': 'application/json' }, + }); setShowCreateDialog(false); const userName = formData.name; @@ -153,7 +161,10 @@ export function KafkaUserList() { `${kafkaApiPath}/namespaces/${deletingUser.metadata.namespace}/kafkausers/${deletingUser.metadata.name}`, { method: 'DELETE' } ); - setToast({ message: `User "${deletingUser.metadata.name}" deleted successfully`, type: 'success' }); + setToast({ + message: `User "${deletingUser.metadata.name}" deleted successfully`, + type: 'success', + }); } catch (err: unknown) { setToast({ message: getErrorMessage(err) || 'Failed to delete user', type: 'error' }); } finally { @@ -175,11 +186,11 @@ export function KafkaUserList() { const openCreateDialog = () => { const firstNs = availableNamespacesForCreate[0] ?? ''; const firstCluster = - firstNs && kafkaClusters.length > 0 - ? kafkaClusters + firstNs && kafkaClusters && kafkaClusters.length > 0 + ? (kafkaClusters .filter(k => k.metadata.namespace === firstNs) .map(k => k.metadata.name) - .sort()[0] ?? '' + .sort()[0] ?? '') : ''; setFormData({ name: '', @@ -263,7 +274,12 @@ export function KafkaUserList() { > View Secret - @@ -282,7 +298,13 @@ export function KafkaUserList() { columns={columns} headerProps={{ titleSideActions: [ - , ], @@ -304,7 +326,9 @@ export function KafkaUserList() { /> Delete User - Are you sure you want to delete user {deletingUser?.metadata.name}? This action cannot be undone. + Are you sure you want to delete user {deletingUser?.metadata.name}? + This action cannot be undone. diff --git a/strimzi/src/components/ResourceEditor.stories.tsx b/strimzi/src/components/ResourceEditor.stories.tsx index 9bfbdc0aa..6db1db456 100644 --- a/strimzi/src/components/ResourceEditor.stories.tsx +++ b/strimzi/src/components/ResourceEditor.stories.tsx @@ -10,11 +10,12 @@ function ResourceEditorNote() { return ( - ResourceEditor opens from the topology view when editing a Strimzi resource. It uses{' '} - ApiProxy and @monaco-editor/react. + ResourceEditor opens from the topology view when editing a Strimzi + resource. It uses ApiProxy and @monaco-editor/react. - Run the plugin in Headlamp, open a Kafka cluster topology, and use edit actions to see this component. + Run the plugin in Headlamp, open a Kafka cluster topology, and use edit actions to see this + component. ); diff --git a/strimzi/src/components/ResourceEditor.tsx b/strimzi/src/components/ResourceEditor.tsx index c435da6d9..e971a65d2 100644 --- a/strimzi/src/components/ResourceEditor.tsx +++ b/strimzi/src/components/ResourceEditor.tsx @@ -12,7 +12,13 @@ interface ResourceEditorProps { resourceKind: string; } -export function ResourceEditor({ open, onClose, resourceUrl, resourceName, resourceKind }: ResourceEditorProps) { +export function ResourceEditor({ + open, + onClose, + resourceUrl, + resourceName, + resourceKind, +}: ResourceEditorProps) { const theme = useTheme(); const isDark = theme.palette.mode === 'dark'; const [content, setContent] = React.useState(''); @@ -40,11 +46,15 @@ export function ResourceEditor({ open, onClose, resourceUrl, resourceName, resou setLoading(false); } }); - return () => { cancelled = true; }; + return () => { + cancelled = true; + }; }, [open, resourceUrl]); React.useEffect(() => { - return () => { clearTimeout(timeoutRef.current); }; + return () => { + clearTimeout(timeoutRef.current); + }; }, []); const handleSave = () => { @@ -106,7 +116,7 @@ export function ResourceEditor({ open, onClose, resourceUrl, resourceName, resou : '0 20px 25px -5px rgba(0, 0, 0, 0.2)', overflow: 'hidden', }} - onClick={(e) => e.stopPropagation()} + onClick={e => e.stopPropagation()} > {/* Header */}
Edit {resourceKind}
-
+
{resourceName}
@@ -134,7 +146,13 @@ export function ResourceEditor({ open, onClose, resourceUrl, resourceName, resou padding: '6px 16px', borderRadius: '6px', border: 'none', - backgroundColor: saving ? (isDark ? '#1e3a5f' : '#93c5fd') : (isDark ? '#2563eb' : '#3b82f6'), + backgroundColor: saving + ? isDark + ? '#1e3a5f' + : '#93c5fd' + : isDark + ? '#2563eb' + : '#3b82f6', color: '#fff', cursor: saving || loading ? 'not-allowed' : 'pointer', fontSize: '13px', diff --git a/strimzi/src/components/SearchFilter.stories.tsx b/strimzi/src/components/SearchFilter.stories.tsx index dbb1f0f9e..3da7e075b 100644 --- a/strimzi/src/components/SearchFilter.stories.tsx +++ b/strimzi/src/components/SearchFilter.stories.tsx @@ -67,6 +67,8 @@ export const WithFiltersPanel: Story = { export const EmptySearch: Story = { render: () => { const [term, setTerm] = React.useState(''); - return ; + return ( + + ); }, }; diff --git a/strimzi/src/components/SearchFilter.tsx b/strimzi/src/components/SearchFilter.tsx index 04d764bb6..9f3cd4fa6 100644 --- a/strimzi/src/components/SearchFilter.tsx +++ b/strimzi/src/components/SearchFilter.tsx @@ -44,7 +44,7 @@ export function SearchFilter({ onSearchChange(e.target.value)} + onChange={e => onSearchChange(e.target.value)} placeholder={placeholder} style={{ width: '100%', @@ -57,10 +57,10 @@ export function SearchFilter({ outline: 'none', transition: 'border-color 0.2s', }} - onFocus={(e) => { + onFocus={e => { e.target.style.borderColor = '#1976d2'; }} - onBlur={(e) => { + onBlur={e => { e.target.style.borderColor = colors.inputBorder; }} /> @@ -109,12 +109,12 @@ export function SearchFilter({ transition: 'all 0.2s', whiteSpace: 'nowrap', }} - onMouseEnter={(e) => { + onMouseEnter={e => { if (!showFilters) { e.currentTarget.style.backgroundColor = colors.chipBg; } }} - onMouseLeave={(e) => { + onMouseLeave={e => { if (!showFilters) { e.currentTarget.style.backgroundColor = colors.inputBg; } @@ -198,7 +198,7 @@ export function FilterSelect({ value, onChange, options }: FilterSelectProps) { return ( onMinChange(e.target.value === '' ? '' : Number(e.target.value))} + onChange={e => onMinChange(e.target.value === '' ? '' : Number(e.target.value))} placeholder={minPlaceholder} style={{ flex: 1, @@ -260,7 +260,7 @@ export function FilterNumberRange({ onMaxChange(e.target.value === '' ? '' : Number(e.target.value))} + onChange={e => onMaxChange(e.target.value === '' ? '' : Number(e.target.value))} placeholder={maxPlaceholder} style={{ flex: 1, diff --git a/strimzi/src/components/SecureSecretDisplay.tsx b/strimzi/src/components/SecureSecretDisplay.tsx index f376adb6b..5924f2f65 100644 --- a/strimzi/src/components/SecureSecretDisplay.tsx +++ b/strimzi/src/components/SecureSecretDisplay.tsx @@ -52,7 +52,7 @@ export function SecureSecretDisplay({ setIsCopied(true); setTimeout(() => setIsCopied(false), 2000); }) - .catch((error) => { + .catch(error => { console.error('Failed to copy secret to clipboard:', error); }); }; @@ -90,7 +90,7 @@ export function SecureSecretDisplay({ maxWidth: '500px', border: `2px solid #ff9800`, }} - onClick={(e) => e.stopPropagation()} + onClick={e => e.stopPropagation()} > {/* Warning Header */}
e.stopPropagation()} + onClick={e => e.stopPropagation()} > {/* Header with warning badge */}
- ⚠️ Remember: Close this window immediately after use. Do not share - these credentials via insecure channels. + ⚠️ Remember: Close this window immediately after use. Do not share these + credentials via insecure channels.
{/* Close Button */}
-
diff --git a/strimzi/src/components/StrimziErrorBoundary.tsx b/strimzi/src/components/StrimziErrorBoundary.tsx index 607edf09c..0a15631c6 100644 --- a/strimzi/src/components/StrimziErrorBoundary.tsx +++ b/strimzi/src/components/StrimziErrorBoundary.tsx @@ -15,18 +15,14 @@ interface State { * never propagate up to Headlamp's root. Without this, a render crash * inside the plugin can blank the entire Headlamp UI. */ -export class StrimziErrorBoundary extends React.Component< - React.PropsWithChildren, - State -> { +export class StrimziErrorBoundary extends React.Component, State> { constructor(props: React.PropsWithChildren) { super(props); this.state = { hasError: false, message: '' }; } static getDerivedStateFromError(error: unknown): State { - const message = - error instanceof Error ? error.message : 'An unexpected error occurred.'; + const message = error instanceof Error ? error.message : 'An unexpected error occurred.'; return { hasError: true, message }; } @@ -42,7 +38,12 @@ export class StrimziErrorBoundary extends React.Component< Something went wrong while rendering this view. - + {this.state.message} diff --git a/strimzi/src/components/StrimziNotInstalledMessage.tsx b/strimzi/src/components/StrimziNotInstalledMessage.tsx index c559d602a..524c3965a 100644 --- a/strimzi/src/components/StrimziNotInstalledMessage.tsx +++ b/strimzi/src/components/StrimziNotInstalledMessage.tsx @@ -12,16 +12,11 @@ export function StrimziNotInstalledMessage() { - The Strimzi operator was not found on this cluster. Install Strimzi to - use this plugin. + The Strimzi operator was not found on this cluster. Install Strimzi to use this plugin. See the{' '} - + Strimzi quick-start guide {' '} for installation instructions. diff --git a/strimzi/src/components/Toast.stories.tsx b/strimzi/src/components/Toast.stories.tsx index ede7c6b21..aa33a2eda 100644 --- a/strimzi/src/components/Toast.stories.tsx +++ b/strimzi/src/components/Toast.stories.tsx @@ -24,13 +24,17 @@ type Story = StoryObj; export const Success: Story = { render: () => ( - + ), }; export const Error: Story = { render: () => ( - + ), }; diff --git a/strimzi/src/components/Toast.tsx b/strimzi/src/components/Toast.tsx index b3acf34a8..768fd7590 100644 --- a/strimzi/src/components/Toast.tsx +++ b/strimzi/src/components/Toast.tsx @@ -157,10 +157,10 @@ export function Toast({ toast, onClose }: ToastProps) { transition: 'opacity 0.2s', flexShrink: 0, }} - onMouseEnter={(e) => { + onMouseEnter={e => { e.currentTarget.style.opacity = '1'; }} - onMouseLeave={(e) => { + onMouseLeave={e => { e.currentTarget.style.opacity = '0.8'; }} aria-label="Close notification" diff --git a/strimzi/src/components/TopicFormModal.tsx b/strimzi/src/components/TopicFormModal.tsx index 783f283de..84f30925a 100644 --- a/strimzi/src/components/TopicFormModal.tsx +++ b/strimzi/src/components/TopicFormModal.tsx @@ -95,7 +95,16 @@ export function TopicFormModal({ - + - + {isEdit ? ( - + ) : ( + ) : ( - + - + setFormData({ ...formData, retentionMs: e.target.value ? parseInt(e.target.value, 10) : undefined })} + onChange={e => + setFormData({ + ...formData, + retentionMs: e.target.value ? parseInt(e.target.value, 10) : undefined, + }) + } style={inputSx} /> - + - setFormData({ ...formData, minInSyncReplicas: e.target.value ? parseInt(e.target.value, 10) : undefined }) + setFormData({ + ...formData, + minInSyncReplicas: e.target.value ? parseInt(e.target.value, 10) : undefined, + }) } style={inputSx} /> @@ -244,7 +316,12 @@ export function TopicFormModal({ - diff --git a/strimzi/src/components/connectors/Detail.tsx b/strimzi/src/components/connectors/Detail.tsx index 7e82e0bc1..411b0fc71 100644 --- a/strimzi/src/components/connectors/Detail.tsx +++ b/strimzi/src/components/connectors/Detail.tsx @@ -16,7 +16,8 @@ import { SimpleTable, } from '@kinvolk/headlamp-plugin/lib/components/common'; import { useParams } from 'react-router-dom'; -import { KafkaConnector } from '../../resources/kafkaConnector'; +import { KafkaConnector, KafkaConnectorV1 } from '../../resources/kafkaConnector'; +import { useStrimziApiVersions } from '../../hooks/useStrimziApiVersions'; import { isSecretLikeKey } from '../../utils/secretKeys'; const MASK = '••••••••'; @@ -87,6 +88,8 @@ function ConfigValue({ export function KafkaConnectorDetail(props: { namespace?: string; name?: string }) { const params = useParams<{ namespace: string; name: string }>(); const { namespace = params.namespace, name = params.name } = props; + const { kafka: kafkaVersion } = useStrimziApiVersions(); + const KafkaConnectorClass = kafkaVersion === 'v1' ? KafkaConnectorV1 : KafkaConnector; const [acknowledged, setAcknowledged] = React.useState(false); const [revealed, setRevealed] = React.useState>(new Set()); @@ -127,7 +130,7 @@ export function KafkaConnectorDetail(props: { namespace?: string; name?: string return ( <> - The value of {pendingKey} may contain credentials - (passwords, tokens, API keys). Show it on this page anyway? + The value of {pendingKey} may contain credentials (passwords, tokens, + API keys). Show it on this page anyway? - Once acknowledged, other masked values on this page can be revealed - without re-prompting. Navigate away to reset. + Once acknowledged, other masked values on this page can be revealed without + re-prompting. Navigate away to reset. diff --git a/strimzi/src/components/connects/Detail.tsx b/strimzi/src/components/connects/Detail.tsx index afbc8733e..df2b1f4fa 100644 --- a/strimzi/src/components/connects/Detail.tsx +++ b/strimzi/src/components/connects/Detail.tsx @@ -7,7 +7,8 @@ import { SimpleTable, } from '@kinvolk/headlamp-plugin/lib/components/common'; import { useParams } from 'react-router-dom'; -import { KafkaConnect } from '../../resources/kafkaConnect'; +import { KafkaConnect, KafkaConnectV1 } from '../../resources/kafkaConnect'; +import { useStrimziApiVersions } from '../../hooks/useStrimziApiVersions'; /** * Detail page for a single `KafkaConnect` resource. @@ -19,10 +20,12 @@ import { KafkaConnect } from '../../resources/kafkaConnect'; export function KafkaConnectDetail(props: { namespace?: string; name?: string }) { const params = useParams<{ namespace: string; name: string }>(); const { namespace = params.namespace, name = params.name } = props; + const { kafka: kafkaVersion } = useStrimziApiVersions(); + const KafkaConnectClass = kafkaVersion === 'v1' ? KafkaConnectV1 : KafkaConnect; return ( (); const { namespace = params.namespace, name = params.name } = props; + const { kafka: kafkaVersion } = useStrimziApiVersions(); + const KafkaClass = kafkaVersion === 'v1' ? KafkaV1 : Kafka; return ( (); const { namespace = params.namespace, name = params.name } = props; + const { kafka: kafkaVersion } = useStrimziApiVersions(); + const KafkaTopicClass = kafkaVersion === 'v1' ? KafkaTopicV1 : KafkaTopic; return ( (); const { namespace = params.namespace, name = params.name } = props; + const { kafka: kafkaVersion } = useStrimziApiVersions(); + const KafkaUserClass = kafkaVersion === 'v1' ? KafkaUserV1 : KafkaUser; return ( ( - getStrimziApiVersions - ); + const [versions, setVersions] = React.useState(getStrimziApiVersions); React.useEffect(() => { let cancelled = false; diff --git a/strimzi/src/hooks/useTopologyTheme.ts b/strimzi/src/hooks/useTopologyTheme.ts index d59325b4a..4b61cc1f5 100644 --- a/strimzi/src/hooks/useTopologyTheme.ts +++ b/strimzi/src/hooks/useTopologyTheme.ts @@ -151,13 +151,9 @@ export function useTopologyTheme(): TopologyTheme { // Dark mode: use theme default nodeText: isDark ? muiTheme.palette.text.primary : '#374151', nodeTextSecondary: isDark ? muiTheme.palette.text.secondary : '#6b7280', - nodeHover: isDark - ? 'rgba(255, 255, 255, 0.08)' - : 'rgba(0, 0, 0, 0.04)', + nodeHover: isDark ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.04)', nodeSelected: muiTheme.palette.primary.main, - nodeShadow: isDark - ? '0 4px 6px rgba(0, 0, 0, 0.4)' - : '0 4px 6px rgba(0, 0, 0, 0.1)', + nodeShadow: isDark ? '0 4px 6px rgba(0, 0, 0, 0.4)' : '0 4px 6px rgba(0, 0, 0, 0.1)', // Edges edgeStroke: muiTheme.palette.divider, @@ -176,12 +172,8 @@ export function useTopologyTheme(): TopologyTheme { // Status colors statusReady: isDark ? '#34d399' : '#059669', statusNotReady: isDark ? '#f87171' : '#dc2626', - statusReadyBg: isDark - ? 'rgba(16, 185, 129, 0.25)' - : 'rgba(16, 185, 129, 0.15)', - statusNotReadyBg: isDark - ? 'rgba(239, 68, 68, 0.25)' - : 'rgba(239, 68, 68, 0.15)', + statusReadyBg: isDark ? 'rgba(16, 185, 129, 0.25)' : 'rgba(16, 185, 129, 0.15)', + statusNotReadyBg: isDark ? 'rgba(239, 68, 68, 0.25)' : 'rgba(239, 68, 68, 0.15)', // UI elements controlsBg: isDark ? '#334155' : '#f1f5f9', @@ -189,9 +181,7 @@ export function useTopologyTheme(): TopologyTheme { // Light mode: dark grey text for better readability controlsText: isDark ? muiTheme.palette.text.primary : '#374151', controlsHover: isDark ? '#475569' : '#e2e8f0', - overlay: isDark - ? 'rgba(0, 0, 0, 0.6)' - : 'rgba(255, 255, 255, 0.6)', + overlay: isDark ? 'rgba(0, 0, 0, 0.6)' : 'rgba(255, 255, 255, 0.6)', highlight: muiTheme.palette.primary.light, }; diff --git a/strimzi/src/mapView.tsx b/strimzi/src/mapView.tsx index 2a215ff18..925792247 100644 --- a/strimzi/src/mapView.tsx +++ b/strimzi/src/mapView.tsx @@ -16,7 +16,9 @@ import { KafkaUser, KafkaUserV1 } from './resources/kafkaUser'; const STRIMZI_BLUE = 'rgb(0, 132, 255)'; const CLUSTER_LABEL = 'strimzi.io/cluster'; -type KubeLike = { metadata: { uid: string; name: string; namespace?: string; labels?: Record } }; +type KubeLike = { + metadata: { uid: string; name: string; namespace?: string; labels?: Record }; +}; const makeEdge = (from: KubeLike, to: KubeLike) => ({ id: `${from.metadata.uid}-${to.metadata.uid}`, @@ -47,7 +49,10 @@ function makeDetailsComponent( * to mark which cluster they target. We match on (label, namespace) because * cluster names are only unique within a namespace. */ -function findClusterByLabel(child: KubeLike, clusters: T[] | null): T | undefined { +function findClusterByLabel( + child: KubeLike, + clusters: T[] | null +): T | undefined { const clusterName = child.metadata.labels?.[CLUSTER_LABEL]; if (!clusterName || !clusters) return undefined; return clusters.find( @@ -55,11 +60,17 @@ function findClusterByLabel(child: KubeLike, clusters: T[] | ); } -const kafkaIcon = ; +const kafkaIcon = ( + +); const topicIcon = ; const userIcon = ; -const connectIcon = ; -const connectorIcon = ; +const connectIcon = ( + +); +const connectorIcon = ( + +); /** * Strimzi 1.0+ retires the v1beta2 API and serves the CRDs as v1. Each map diff --git a/strimzi/src/storybookMocks/strimziMocks.ts b/strimzi/src/storybookMocks/strimziMocks.ts index 8418c9afb..55a9e8839 100644 --- a/strimzi/src/storybookMocks/strimziMocks.ts +++ b/strimzi/src/storybookMocks/strimziMocks.ts @@ -142,7 +142,11 @@ export const mockKafkaConnects: KafkaConnectInterface[] = [ url: 'http://analytics-connect-connect-api.default.svc:8083', replicas: 3, connectorPlugins: [ - { class: 'io.debezium.connector.postgresql.PostgresConnector', type: 'source', version: '2.5.0' }, + { + class: 'io.debezium.connector.postgresql.PostgresConnector', + type: 'source', + version: '2.5.0', + }, { class: 'io.confluent.connect.s3.S3SinkConnector', type: 'sink', version: '10.5.0' }, ], }, @@ -214,7 +218,7 @@ export const mockKafkaConnectors: KafkaConnectorInterface[] = [ tasksMax: 2, state: 'paused', config: { - 'topics': 'audit-events', + topics: 'audit-events', 's3.bucket.name': 'audit-events-prod', }, }, diff --git a/strimzi/src/utils/strimziApiVersion.test.ts b/strimzi/src/utils/strimziApiVersion.test.ts index 34a86f3c0..ae4d25124 100644 --- a/strimzi/src/utils/strimziApiVersion.test.ts +++ b/strimzi/src/utils/strimziApiVersion.test.ts @@ -46,9 +46,7 @@ describe('resolveStrimziApiVersions', () => { }); it('falls back to v1beta2 when only v1beta2 is served', async () => { - mockRequest.mockResolvedValue( - makeGroupList(['kafka.strimzi.io', 'core.strimzi.io']) - ); + mockRequest.mockResolvedValue(makeGroupList(['kafka.strimzi.io', 'core.strimzi.io'])); const result = await resolveStrimziApiVersions(); expect(result.installed).toBe(true);