Skip to main content
Glama
PodsLogViewer.tsx8.88 kB
import React, { useEffect, useState } from 'react'; import { Select, Card } from 'antd'; import { fetcher } from '@/components/Amis/fetcher'; import SSELogDisplayComponent from '@/components/Amis/custom/LogView/SSELogDisplay'; import SSELogDownloadComponent from '@/components/Amis/custom/LogView/SSELogDownload'; import LogOptionsComponent from '@/components/Amis/custom/LogView/LogOptions'; import { replacePlaceholders } from '@/utils/utils'; import { Container, Pod } from '@/store/pod'; interface PodLogViewerProps { url: string; data: Record<string, any>; } const PodLogViewerComponent: React.FC<PodLogViewerProps> = ({ url, data }) => { url = replacePlaceholders(url, data); const [pods, setPods] = useState<Pod[]>([]); const [selectedPod, setSelectedPod] = useState<{ name: string; namespace: string }>(); const [containers, setContainers] = useState<Container[]>([]); const [selectedContainer, setSelectedContainer] = useState<string>(''); const [isAllPods, setIsAllPods] = useState<boolean>(false); const [isAllContainers, setIsAllContainers] = useState<boolean>(false); const [labelSelector, setLabelSelector] = useState<string>(''); const [tailLines, setTailLines] = React.useState(100); const [follow, setFollow] = React.useState(true); const [timestamps, setTimestamps] = React.useState(false); const [previous, setPrevious] = React.useState(false); const [sinceTime, setSinceTime] = React.useState<string>(); // 在 useEffect 中处理 fetcher 的响应 useEffect(() => { fetcher({ url: url, method: 'get' }) .then((response) => { //@ts-ignore if (response?.data?.data?.rows) { //@ts-ignore const podList = response.data.data?.rows; setPods(podList); if (podList.length > 0) { const firstPod = podList[0]; setSelectedPod({ namespace: firstPod.metadata.namespace, name: firstPod.metadata.name }); } } else { console.warn('No pod data found in response:', response); setPods([]); } }) .catch(error => { console.error('Error fetching pod details:', error); setPods([]); }); }, [url]); useEffect(() => { // 处理 labels,转换为 labelSelector 格式 if (data?.metadata?.labels) { const labels = data.metadata.labels; // 将 labels 对象转换为 "key1=value1,key2=value2" 格式 const labelSelectorString = Object.entries(labels) .map(([key, value]) => `${key}=${value}`) .join(','); setLabelSelector(labelSelectorString); } else { setLabelSelector(''); } }, [data]); useEffect(() => { if (selectedPod) { const podData = pods.find(pod => pod.metadata.name === selectedPod.name && pod.metadata.namespace === selectedPod.namespace ); // 合并 initContainers 和 containers const allContainers = [ ...(podData?.spec?.containers || []), ...(podData?.spec?.initContainers || []), ...(podData?.spec?.ephemeralContainers || []), ]; if (allContainers.length > 0) { setContainers(allContainers); // 默认选择第一个容器 setSelectedContainer(allContainers[0].name); } else { setContainers([]); setSelectedContainer(''); } } }, [selectedPod, pods]); return ( <Card title={ <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}> <Select style={{ minWidth: 200 }} value={isAllPods ? 'ALL_PODS' : (selectedPod ? selectedPod.name : undefined)} onChange={(value) => { if (value === 'ALL_PODS') { setIsAllPods(true); setSelectedPod(undefined); } else { setIsAllPods(false); const namespace = pods.find(pod => pod.metadata.name === value)?.metadata.namespace || ''; setSelectedPod({ namespace, name: value }); } }} options={[ { label: '全部Pod', value: 'ALL_PODS' }, ...pods.map(pod => ({ label: pod.metadata.name, value: pod.metadata.name })) ]} placeholder="选择Pod" /> <Select style={{ minWidth: 200 }} value={isAllContainers ? 'ALL_CONTAINERS' : selectedContainer} onChange={(value) => { if (value === 'ALL_CONTAINERS') { setIsAllContainers(true); setSelectedContainer(''); } else { setIsAllContainers(false); setSelectedContainer(value); } }} options={[ // { label: '全部容器', value: 'ALL_CONTAINERS' }, ...containers.map(container => ({ label: container.name, value: container.name })) ]} placeholder="选择容器" disabled={!selectedPod && !isAllPods} /> </div> } variant="outlined" style={{ width: '100%', height: 'calc(100vh - 12px)' }} > <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}> <LogOptionsComponent tailLines={tailLines} follow={follow} timestamps={timestamps} previous={previous} sinceTime={sinceTime} onTailLinesChange={setTailLines} onFollowChange={setFollow} onTimestampsChange={setTimestamps} onPreviousChange={setPrevious} onSinceTimeChange={setSinceTime} /> {((selectedContainer && selectedPod) || isAllPods || isAllContainers) && ( <SSELogDownloadComponent url={`/k8s/pod/logs/download/ns/${data?.metadata?.namespace}/pod_name/${selectedPod?.name}/container/${selectedContainer}`} data={{ tailLines: tailLines, sinceTime: sinceTime, previous: previous, timestamps: timestamps, allPods: isAllPods, allContainers: isAllContainers, labelSelector: labelSelector, }} /> )} </div> <div style={{ background: '#f5f5f5', padding: '4px', borderRadius: '4px', height: 'calc(100vh - 150px)', overflow: 'auto' }}> {((selectedContainer && selectedPod) || isAllPods || isAllContainers) && ( <SSELogDisplayComponent url={`/k8s/pod/logs/sse/ns/${data?.metadata?.namespace}/pod_name/${selectedPod?.name}/container/${selectedContainer}` } data={{ tailLines: tailLines, sinceTime: sinceTime, follow: follow, previous: previous, timestamps: timestamps, allPods: isAllPods, allContainers: isAllContainers, labelSelector: labelSelector, }} /> )} </div> </Card> ); }; export default PodLogViewerComponent;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/weibaohui/k8m'

If you have feedback or need assistance with the MCP directory API, please join our Discord server