diff --git a/frontend/src/components/common/Resource/LogsButton.tsx b/frontend/src/components/common/Resource/LogsButton.tsx new file mode 100644 index 00000000000..5b338b2b146 --- /dev/null +++ b/frontend/src/components/common/Resource/LogsButton.tsx @@ -0,0 +1,497 @@ +import { Box, FormControl, InputLabel, MenuItem, Select, styled, Switch } from '@mui/material'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import { Terminal as XTerminal } from '@xterm/xterm'; +import { useSnackbar } from 'notistack'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { request } from '../../../lib/k8s/apiProxy'; +import { KubeContainerStatus } from '../../../lib/k8s/cluster'; +import DaemonSet from '../../../lib/k8s/daemonSet'; +import Deployment from '../../../lib/k8s/deployment'; +import { KubeObject } from '../../../lib/k8s/KubeObject'; +import Pod from '../../../lib/k8s/pod'; +import ReplicaSet from '../../../lib/k8s/replicaSet'; +import ActionButton from '../ActionButton'; +import { LogViewer } from '../LogViewer'; +import { LightTooltip } from '../Tooltip'; + +// Component props interface +interface LogsButtonProps { + item: KubeObject | null; +} + +// Styled component for consistent padding in form controls +const PaddedFormControlLabel = styled(FormControlLabel)(({ theme }) => ({ + margin: 0, + paddingTop: theme.spacing(2), + paddingRight: theme.spacing(2), +})); + +export function LogsButton({ item }: LogsButtonProps) { + const [showLogs, setShowLogs] = useState(false); + const [pods, setPods] = useState([]); + const [selectedPodIndex, setSelectedPodIndex] = useState('all'); + const [selectedContainer, setSelectedContainer] = useState(''); + + const [logs, setLogs] = useState<{ logs: string[]; lastLineShown: number }>({ + logs: [], + lastLineShown: -1, + }); + const [allPodLogs, setAllPodLogs] = useState<{ [podName: string]: string[] }>({}); + + const [showTimestamps, setShowTimestamps] = useState(true); + const [follow, setFollow] = useState(true); + const [lines, setLines] = useState(100); + const [showPrevious, setShowPrevious] = React.useState(false); + const [showReconnectButton, setShowReconnectButton] = useState(false); + + const xtermRef = React.useRef(null); + const { t } = useTranslation(['glossary', 'translation']); + const { enqueueSnackbar } = useSnackbar(); + + const clearLogs = React.useCallback(() => { + if (xtermRef.current) { + xtermRef.current.clear(); + } + setLogs({ logs: [], lastLineShown: -1 }); + }, []); + + // Fetch related pods. + async function getRelatedPods(): Promise { + if (item instanceof Deployment || item instanceof ReplicaSet || item instanceof DaemonSet) { + try { + let labelSelector = ''; + const selector = item.spec.selector; + + if (selector.matchLabels) { + labelSelector = Object.entries(selector.matchLabels) + .map(([key, value]) => `${key}=${value}`) + .join(','); + } + + if (!labelSelector) { + const resourceType = + item instanceof Deployment + ? 'deployment' + : item instanceof ReplicaSet + ? 'replicaset' + : 'daemonset'; + throw new Error( + t('translation|No label selectors found for this {{type}}', { type: resourceType }) + ); + } + + const response = await request( + `/api/v1/namespaces/${item.metadata.namespace}/pods?labelSelector=${labelSelector}`, + { method: 'GET' } + ); + + if (!response?.items) { + throw new Error(t('translation|Invalid response from server')); + } + + return response.items.map((podData: any) => new Pod(podData)); + } catch (error) { + console.error('Error in getRelatedPods:', error); + throw new Error( + error instanceof Error ? error.message : t('translation|Failed to fetch related pods') + ); + } + } + return []; + } + + // Event handlers for log viewing options + function handleLinesChange(event: any) { + setLines(event.target.value); + } + + function handleTimestampsChange() { + setShowTimestamps(prev => !prev); + } + + function handleFollowChange() { + setFollow(prev => !prev); + } + + function handlePreviousChange() { + setShowPrevious(previous => !previous); + } + + // Handler for initial logs button click + async function handleClick() { + if (item instanceof Deployment || item instanceof ReplicaSet || item instanceof DaemonSet) { + try { + const fetchedPods = await getRelatedPods(); + if (fetchedPods.length > 0) { + setPods(fetchedPods); + setSelectedPodIndex('all'); + setSelectedContainer(fetchedPods[0].spec.containers[0].name); + setShowLogs(true); + } else { + enqueueSnackbar(t('translation|No pods found for this workload'), { + variant: 'warning', + autoHideDuration: 3000, + }); + } + } catch (error) { + console.error('Error fetching pods:', error); + enqueueSnackbar( + t('translation|Failed to fetch pods: {{error}}', { + error: error instanceof Error ? error.message : t('translation|Unknown error'), + }), + { + variant: 'error', + autoHideDuration: 5000, + } + ); + } + } + } + + // Handler for closing the logs viewer + function handleClose() { + setShowLogs(false); + setPods([]); + setSelectedPodIndex('all'); + setSelectedContainer(''); + setLogs({ logs: [], lastLineShown: -1 }); + } + + // Get containers for the selected pod + const containers = React.useMemo(() => { + if (!pods.length) return []; + if (selectedPodIndex === 'all') + return pods[0]?.spec?.containers?.map(container => container.name) || []; + const selectedPod = pods[selectedPodIndex as number]; + return selectedPod?.spec?.containers?.map(container => container.name) || []; + }, [pods, selectedPodIndex]); + + // Check if a container has been restarted + function hasContainerRestarted(podName: string | undefined, containerName: string) { + if (!podName) return false; + const pod = pods.find(p => p.getName() === podName); + const cont = pod?.status?.containerStatuses?.find( + (c: KubeContainerStatus) => c.name === containerName + ); + if (!cont) { + return false; + } + + return cont.restartCount > 0; + } + + // Handler for reconnecting to logs stream + function handleReconnect() { + if (pods.length && selectedContainer) { + setShowReconnectButton(false); + setLogs({ logs: [], lastLineShown: -1 }); + } + } + + // Function to process and display all logs + const processAllLogs = React.useCallback(() => { + const allLogs: string[] = []; + Object.entries(allPodLogs).forEach(([podName, podLogs]) => { + podLogs.forEach(log => { + allLogs.push(`[${podName}] ${log}`); + }); + }); + + // Sort logs by timestamp + allLogs.sort((a, b) => { + const timestampA = a.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)?.[0] || ''; + const timestampB = b.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)?.[0] || ''; + return timestampA.localeCompare(timestampB); + }); + + if (xtermRef.current) { + xtermRef.current.clear(); + xtermRef.current.write(allLogs.join('').replaceAll('\n', '\r\n')); + } + + setLogs({ + logs: allLogs, + lastLineShown: allLogs.length - 1, + }); + }, [allPodLogs]); + + // Function to fetch and aggregate logs from all pods + function fetchAllPodsLogs(pods: Pod[], container: string): () => void { + clearLogs(); + setAllPodLogs({}); + + const cleanups: Array<() => void> = []; + + pods.forEach(pod => { + const cleanup = pod.getLogs( + container, + (newLogs: string[]) => { + const podName = pod.getName(); + setAllPodLogs(current => { + const updated = { + ...current, + [podName]: newLogs, + }; + return updated; + }); + }, + { + tailLines: lines, + showPrevious, + showTimestamps, + follow, + onReconnectStop: () => { + setShowReconnectButton(true); + }, + } + ); + cleanups.push(cleanup); + }); + + return () => cleanups.forEach(cleanup => cleanup()); + } + + // Effect for fetching and updating logs + React.useEffect(() => { + let cleanup: (() => void) | null = null; + let isSubscribed = true; + + if (showLogs && selectedContainer) { + clearLogs(); + setAllPodLogs({}); // Clear aggregated logs when switching pods + + // Handle paused logs state + if (!follow && logs.logs.length > 0) { + xtermRef.current?.write( + '\n\n' + + t('translation|Logs are paused. Click the follow button to resume following them.') + + '\r\n' + ); + return; + } + + if (selectedPodIndex === 'all') { + cleanup = fetchAllPodsLogs(pods, selectedContainer); + } else { + const pod = pods[selectedPodIndex as number]; + if (pod) { + let lastLogLength = 0; + cleanup = pod.getLogs( + selectedContainer, + (newLogs: string[]) => { + if (!isSubscribed) return; + + setLogs(current => { + const terminalRef = xtermRef.current; + if (!terminalRef) return current; + + // Only process new logs in chunks for better performance + if (newLogs.length > lastLogLength) { + const CHUNK_SIZE = 1000; // Process 1000 lines at a time + const startIdx = lastLogLength; + const endIdx = Math.min(startIdx + CHUNK_SIZE, newLogs.length); + + // Process only the new chunk of logs + const newLogContent = newLogs + .slice(startIdx, endIdx) + .join('') + .replaceAll('\n', '\r\n'); + + terminalRef.write(newLogContent); + lastLogLength = endIdx; + + // If there are more logs to process, schedule them for the next frame + if (endIdx < newLogs.length) { + requestAnimationFrame(() => { + setLogs(current => ({ + ...current, + logs: newLogs, + lastLineShown: endIdx - 1, + })); + }); + return current; + } + } + + return { + logs: newLogs, + lastLineShown: newLogs.length - 1, + }; + }); + }, + { + tailLines: lines, + showPrevious, + showTimestamps, + follow, + onReconnectStop: () => { + if (isSubscribed) { + setShowReconnectButton(true); + } + }, + } + ); + } + } + } + + return () => { + isSubscribed = false; + if (cleanup) { + cleanup(); + } + }; + }, [ + selectedPodIndex, + selectedContainer, + showLogs, + lines, + showTimestamps, + follow, + clearLogs, + t, + pods, + ]); + + // Effect to process logs when allPodLogs changes - only for "All Pods" mode + React.useEffect(() => { + if (selectedPodIndex === 'all' && showLogs && Object.keys(allPodLogs).length > 0) { + processAllLogs(); + } + }, [allPodLogs, selectedPodIndex, showLogs, processAllLogs]); + + const topActions = [ + + {/* Pod selection dropdown */} + + {t('translation|Select Pod')} + + + + {/* Container selection dropdown */} + + {t('translation|Container')} + + + + {/* Lines selector */} + + Lines + + + + {/* Show previous logs switch */} + + } + /> + + + {/* Timestamps switch */} + } + label={t('translation|Timestamps')} + /> + + {/* Follow logs switch */} + } + label={t('translation|Follow')} + /> + , + ]; + + return ( + <> + {/* Show logs button for supported workload types */} + {(item instanceof Deployment || item instanceof ReplicaSet || item instanceof DaemonSet) && ( + + )} + + {/* Logs viewer dialog */} + {showLogs && ( + + )} + + ); +} diff --git a/frontend/src/components/common/Resource/index.test.ts b/frontend/src/components/common/Resource/index.test.ts index 8a9de0f0c88..e50a5a7eb53 100644 --- a/frontend/src/components/common/Resource/index.test.ts +++ b/frontend/src/components/common/Resource/index.test.ts @@ -36,6 +36,7 @@ const checkExports = [ 'SimpleEditor', 'ViewButton', 'AuthVisible', + 'LogsButton', ]; function getFilesToVerify() { diff --git a/frontend/src/components/common/Resource/index.tsx b/frontend/src/components/common/Resource/index.tsx index d7e64f62455..46db337a9c9 100644 --- a/frontend/src/components/common/Resource/index.tsx +++ b/frontend/src/components/common/Resource/index.tsx @@ -27,6 +27,7 @@ export { default as ResourceTableColumnChooser } from './ResourceTableColumnChoo export { addResourceTableColumnsProcessor } from './resourceTableSlice'; export * from './RestartButton'; export * from './ScaleButton'; +export * from './LogsButton'; export { default as ScaleButton } from './ScaleButton'; export * from './SimpleEditor'; export { default as SimpleEditor } from './SimpleEditor'; diff --git a/frontend/src/components/workload/Details.tsx b/frontend/src/components/workload/Details.tsx index fbbf0e44f24..8f68705c3cb 100644 --- a/frontend/src/components/workload/Details.tsx +++ b/frontend/src/components/workload/Details.tsx @@ -6,6 +6,7 @@ import { ConditionsSection, ContainersSection, DetailsGrid, + LogsButton, MetadataDictGrid, OwnedPodsSection, } from '../common/Resource'; @@ -85,6 +86,18 @@ export default function WorkloadDetails(props: Workload name={name} withEvents namespace={namespace} + actions={item => { + if (!item) return []; + const isLoggable = ['Deployment', 'ReplicaSet', 'DaemonSet'].includes(workloadKind.kind); + if (!isLoggable) return []; + + return [ + { + id: 'logs', + action: , + }, + ]; + }} extraInfo={item => item && [ { diff --git a/frontend/src/i18n/locales/de/translation.json b/frontend/src/i18n/locales/de/translation.json index 42246790b43..b1c5c401dc3 100644 --- a/frontend/src/i18n/locales/de/translation.json +++ b/frontend/src/i18n/locales/de/translation.json @@ -221,6 +221,22 @@ "Are you sure?": "Sind Sie sicher?", "This will discard your changes in the editor. Do you want to proceed?": "Dadurch werden Ihre Änderungen im Editor verworfen. Möchten Sie fortfahren?", "Undo Changes": "Änderungen rückgängig machen", + "No label selectors found for this {{type}}": "", + "Invalid response from server": "", + "Failed to fetch related pods": "", + "No pods found for this workload": "", + "Failed to fetch pods: {{error}}": "", + "Logs are paused. Click the follow button to resume following them.": "Ereignisprotokolle wurden angehalten. Klicken Sie auf die Schaltfläche \"Verfolgen\", um die Protokollanzeige fortzusetzen.", + "Select Pod": "", + "All Pods": "", + "Container": "", + "Restarted": "", + "Show logs for previous instances of this container.": "Ereignisprotokolle für frühere Instanzen dieses Containers anzeigen.", + "You can only select this option for containers that have been restarted.": "Sie können diese Option nur für Container wählen, die neu gestartet wurden.", + "Show previous": "Vorherige anzeigen", + "Timestamps": "Zeitstempel anzeigen", + "Follow": "Verfolgen", + "Show logs": "", "Loading resource data": "Lade Ressourcendaten", "Creation": "Erstellung", "Labels": "Labels", @@ -387,13 +403,7 @@ "None": "", "Software": "Software", "Redirecting to main page…": "Weiterleiten zur Hauptseite…", - "Logs are paused. Click the follow button to resume following them.": "Ereignisprotokolle wurden angehalten. Klicken Sie auf die Schaltfläche \"Verfolgen\", um die Protokollanzeige fortzusetzen.", "Lines": "Zeilen", - "Show logs for previous instances of this container.": "Ereignisprotokolle für frühere Instanzen dieses Containers anzeigen.", - "You can only select this option for containers that have been restarted.": "Sie können diese Option nur für Container wählen, die neu gestartet wurden.", - "Show previous": "Vorherige anzeigen", - "Timestamps": "Zeitstempel anzeigen", - "Follow": "Verfolgen", "Max Unavailable": "Max Nicht verfügbar", "Min Available": "Min verfügbar", "Allowed disruptions": "Erlaubte Unterbrechungen", diff --git a/frontend/src/i18n/locales/en/translation.json b/frontend/src/i18n/locales/en/translation.json index 73eebfd6add..f7229ff5894 100644 --- a/frontend/src/i18n/locales/en/translation.json +++ b/frontend/src/i18n/locales/en/translation.json @@ -221,6 +221,22 @@ "Are you sure?": "Are you sure?", "This will discard your changes in the editor. Do you want to proceed?": "This will discard your changes in the editor. Do you want to proceed?", "Undo Changes": "Undo Changes", + "No label selectors found for this {{type}}": "No label selectors found for this {{type}}", + "Invalid response from server": "Invalid response from server", + "Failed to fetch related pods": "Failed to fetch related pods", + "No pods found for this workload": "No pods found for this workload", + "Failed to fetch pods: {{error}}": "Failed to fetch pods: {{error}}", + "Logs are paused. Click the follow button to resume following them.": "Logs are paused. Click the follow button to resume following them.", + "Select Pod": "Select Pod", + "All Pods": "All Pods", + "Container": "Container", + "Restarted": "Restarted", + "Show logs for previous instances of this container.": "Show logs for previous instances of this container.", + "You can only select this option for containers that have been restarted.": "You can only select this option for containers that have been restarted.", + "Show previous": "Show previous", + "Timestamps": "Timestamps", + "Follow": "Follow", + "Show logs": "Show logs", "Loading resource data": "Loading resource data", "Creation": "Creation", "Labels": "Labels", @@ -387,13 +403,7 @@ "None": "None", "Software": "Software", "Redirecting to main page…": "Redirecting to main page…", - "Logs are paused. Click the follow button to resume following them.": "Logs are paused. Click the follow button to resume following them.", "Lines": "Lines", - "Show logs for previous instances of this container.": "Show logs for previous instances of this container.", - "You can only select this option for containers that have been restarted.": "You can only select this option for containers that have been restarted.", - "Show previous": "Show previous", - "Timestamps": "Timestamps", - "Follow": "Follow", "Max Unavailable": "Max Unavailable", "Min Available": "Min Available", "Allowed disruptions": "Allowed disruptions", diff --git a/frontend/src/i18n/locales/es/translation.json b/frontend/src/i18n/locales/es/translation.json index 0f00c06ce74..44d424d1ea4 100644 --- a/frontend/src/i18n/locales/es/translation.json +++ b/frontend/src/i18n/locales/es/translation.json @@ -222,6 +222,22 @@ "Are you sure?": "¿Está seguro?", "This will discard your changes in the editor. Do you want to proceed?": "Esto descartará sus cambios en el editor. ¿Desea avanzar?", "Undo Changes": "Deshacer cambios", + "No label selectors found for this {{type}}": "", + "Invalid response from server": "", + "Failed to fetch related pods": "", + "No pods found for this workload": "", + "Failed to fetch pods: {{error}}": "", + "Logs are paused. Click the follow button to resume following them.": "Los registros están en pausa. Haga clic en el botón siguiente para continuar a seguirlos.", + "Select Pod": "", + "All Pods": "", + "Container": "", + "Restarted": "", + "Show logs for previous instances of this container.": "Mostrar registros para instancias anteriores de este contenedor.", + "You can only select this option for containers that have been restarted.": "Sólo podrá seleccionar esta opción para los contenedores que se han reiniciado.", + "Show previous": "Mostrar anteriores", + "Timestamps": "Marcas de tiempo", + "Follow": "Seguir", + "Show logs": "", "Loading resource data": "Cargando datos del recurso", "Creation": "Creación", "Labels": "Etiquetas", @@ -388,13 +404,7 @@ "None": "", "Software": "Software", "Redirecting to main page…": "Redireccionando a la página principal…", - "Logs are paused. Click the follow button to resume following them.": "Los registros están en pausa. Haga clic en el botón siguiente para continuar a seguirlos.", "Lines": "Líneas", - "Show logs for previous instances of this container.": "Mostrar registros para instancias anteriores de este contenedor.", - "You can only select this option for containers that have been restarted.": "Sólo podrá seleccionar esta opción para los contenedores que se han reiniciado.", - "Show previous": "Mostrar anteriores", - "Timestamps": "Marcas de tiempo", - "Follow": "Seguir", "Max Unavailable": "Máx. Disponible", "Min Available": "Min. Disponible", "Allowed disruptions": "Perturbaciones permitidas", diff --git a/frontend/src/i18n/locales/fr/translation.json b/frontend/src/i18n/locales/fr/translation.json index 13d60793718..07e0fd29327 100644 --- a/frontend/src/i18n/locales/fr/translation.json +++ b/frontend/src/i18n/locales/fr/translation.json @@ -222,6 +222,22 @@ "Are you sure?": "Êtes-vous sûr ?", "This will discard your changes in the editor. Do you want to proceed?": "Vos modifications seront perdues. Voulez-vous continuer ?", "Undo Changes": "Annuler les modifications", + "No label selectors found for this {{type}}": "", + "Invalid response from server": "", + "Failed to fetch related pods": "", + "No pods found for this workload": "", + "Failed to fetch pods: {{error}}": "", + "Logs are paused. Click the follow button to resume following them.": "Les journaux sont en pause. Cliquez sur le bouton suivre pour reprendre leur suivi.", + "Select Pod": "", + "All Pods": "", + "Container": "", + "Restarted": "", + "Show logs for previous instances of this container.": "Afficher les journaux des instances précédentes de ce conteneur.", + "You can only select this option for containers that have been restarted.": "Vous ne pouvez sélectionner cette option que pour les conteneurs qui ont été redémarrés.", + "Show previous": "Montrer le précédent", + "Timestamps": "Horodatages", + "Follow": "Suivez", + "Show logs": "", "Loading resource data": "Chargement des données sur les ressources", "Creation": "Création", "Labels": "Étiquettes", @@ -388,13 +404,7 @@ "None": "", "Software": "Logiciel", "Redirecting to main page…": "Redirection vers la page principale…", - "Logs are paused. Click the follow button to resume following them.": "Les journaux sont en pause. Cliquez sur le bouton suivre pour reprendre leur suivi.", "Lines": "Lignes", - "Show logs for previous instances of this container.": "Afficher les journaux des instances précédentes de ce conteneur.", - "You can only select this option for containers that have been restarted.": "Vous ne pouvez sélectionner cette option que pour les conteneurs qui ont été redémarrés.", - "Show previous": "Montrer le précédent", - "Timestamps": "Horodatages", - "Follow": "Suivez", "Max Unavailable": "Max Indisponible", "Min Available": "Min Disponible", "Allowed disruptions": "Perturbations autorisées", diff --git a/frontend/src/i18n/locales/pt/translation.json b/frontend/src/i18n/locales/pt/translation.json index ee3c458bd5c..eb1e4529a62 100644 --- a/frontend/src/i18n/locales/pt/translation.json +++ b/frontend/src/i18n/locales/pt/translation.json @@ -222,6 +222,22 @@ "Are you sure?": "Tem a certeza?", "This will discard your changes in the editor. Do you want to proceed?": "Irá descartar as suas modificações no editor. Deseja prosseguir?", "Undo Changes": "Desfazer modificações", + "No label selectors found for this {{type}}": "", + "Invalid response from server": "", + "Failed to fetch related pods": "", + "No pods found for this workload": "", + "Failed to fetch pods: {{error}}": "", + "Logs are paused. Click the follow button to resume following them.": "Os \"logs\" estão pausados. Clique no seguinte botão para voltar a segui-los.", + "Select Pod": "", + "All Pods": "", + "Container": "", + "Restarted": "", + "Show logs for previous instances of this container.": "Mostrar \"logs\" para instâncias anteriores deste \"container\".", + "You can only select this option for containers that have been restarted.": "Só pode escolher esta opção para \"containers\" que tenham sido reiniciados.", + "Show previous": "Mostrar anteriores", + "Timestamps": "Marcações de tempo", + "Follow": "Seguir", + "Show logs": "", "Loading resource data": "A carregar dados do recurso", "Creation": "Criação", "Labels": "Etiquetas", @@ -388,13 +404,7 @@ "None": "", "Software": "Software", "Redirecting to main page…": "A redireccionar para a página principal…", - "Logs are paused. Click the follow button to resume following them.": "Os \"logs\" estão pausados. Clique no seguinte botão para voltar a segui-los.", "Lines": "Linhas", - "Show logs for previous instances of this container.": "Mostrar \"logs\" para instâncias anteriores deste \"container\".", - "You can only select this option for containers that have been restarted.": "Só pode escolher esta opção para \"containers\" que tenham sido reiniciados.", - "Show previous": "Mostrar anteriores", - "Timestamps": "Marcações de tempo", - "Follow": "Seguir", "Max Unavailable": "Máx. Indisponíveis", "Min Available": "Mín. Disponíveis", "Allowed disruptions": "Perturbações permitidas", diff --git a/frontend/src/i18n/locales/zh-tw/translation.json b/frontend/src/i18n/locales/zh-tw/translation.json index f326ac45b1a..f616b8f3c7f 100644 --- a/frontend/src/i18n/locales/zh-tw/translation.json +++ b/frontend/src/i18n/locales/zh-tw/translation.json @@ -220,6 +220,22 @@ "Are you sure?": "您確定嗎?", "This will discard your changes in the editor. Do you want to proceed?": "這將放棄您在編輯器中的更改。您要繼續嗎?", "Undo Changes": "撤銷更改", + "No label selectors found for this {{type}}": "", + "Invalid response from server": "", + "Failed to fetch related pods": "", + "No pods found for this workload": "", + "Failed to fetch pods: {{error}}": "", + "Logs are paused. Click the follow button to resume following them.": "日誌已暫停。點擊跟隨按鈕以繼續跟隨它們。", + "Select Pod": "", + "All Pods": "", + "Container": "", + "Restarted": "", + "Show logs for previous instances of this container.": "顯示此容器先前實例的日誌。", + "You can only select this option for containers that have been restarted.": "您只能為已重啟的容器選擇此選項。", + "Show previous": "顯示先前", + "Timestamps": "時間戳", + "Follow": "跟隨", + "Show logs": "", "Loading resource data": "讀取資源數據", "Creation": "新增", "Labels": "標籤", @@ -386,13 +402,7 @@ "None": "無", "Software": "軟體", "Redirecting to main page…": "正在重定向到首頁…", - "Logs are paused. Click the follow button to resume following them.": "日誌已暫停。點擊跟隨按鈕以繼續跟隨它們。", "Lines": "行", - "Show logs for previous instances of this container.": "顯示此容器先前實例的日誌。", - "You can only select this option for containers that have been restarted.": "您只能為已重啟的容器選擇此選項。", - "Show previous": "顯示先前", - "Timestamps": "時間戳", - "Follow": "跟隨", "Max Unavailable": "最大不可用", "Min Available": "最小可用", "Allowed disruptions": "允許的中斷", diff --git a/frontend/src/lib/router.tsx b/frontend/src/lib/router.tsx index ce8e8d5a099..d627b414fc6 100644 --- a/frontend/src/lib/router.tsx +++ b/frontend/src/lib/router.tsx @@ -22,7 +22,6 @@ import CustomResourceDefinitionDetails from '../components/crd/Details'; import CustomResourceDefinitionList from '../components/crd/List'; import CronJobDetails from '../components/cronjob/Details'; import CronJobList from '../components/cronjob/List'; -import DaemonSetDetails from '../components/daemonset/Details'; import DaemonSetList from '../components/daemonset/List'; import DeploymentsList from '../components/deployments/List'; import EndpointDetails from '../components/endpoints/Details'; @@ -76,7 +75,6 @@ import ServiceList from '../components/service/List'; import ServiceAccountDetails from '../components/serviceaccount/Details'; import ServiceAccountList from '../components/serviceaccount/List'; import { DefaultSidebars } from '../components/Sidebar'; -import StatefulSetDetails from '../components/statefulset/Details'; import StatefulSetList from '../components/statefulset/List'; import PersistentVolumeClaimDetails from '../components/storage/ClaimDetails'; import PersistentVolumeClaimList from '../components/storage/ClaimList'; @@ -96,9 +94,11 @@ import helpers from '../helpers'; import LocaleSelect from '../i18n/LocaleSelect/LocaleSelect'; import store from '../redux/stores/store'; import { useCluster } from './k8s'; +import DaemonSet from './k8s/daemonSet'; import Deployment from './k8s/deployment'; import Job from './k8s/job'; import ReplicaSet from './k8s/replicaSet'; +import StatefulSet from './k8s/statefulSet'; import { getCluster, getClusterPrefixedPath } from './util'; export interface Route { @@ -232,13 +232,13 @@ const defaultRoutes: { path: '/daemonsets/:namespace/:name', exact: true, sidebar: 'DaemonSets', - component: () => , + component: () => , }, StatefulSet: { path: '/statefulsets/:namespace/:name', exact: true, sidebar: 'StatefulSets', - component: () => , + component: () => , }, Deployment: { path: '/deployments/:namespace/:name', diff --git a/frontend/src/plugin/__snapshots__/pluginLib.snapshot b/frontend/src/plugin/__snapshots__/pluginLib.snapshot index cce849dedc6..f2bd616c59d 100644 --- a/frontend/src/plugin/__snapshots__/pluginLib.snapshot +++ b/frontend/src/plugin/__snapshots__/pluginLib.snapshot @@ -55,6 +55,7 @@ "Link": [Function], "Loader": [Function], "LogViewer": [Function], + "LogsButton": [Function], "MatchExpressions": [Function], "MetadataDictGrid": [Function], "MetadataDisplay": [Function], @@ -77,6 +78,7 @@ "DocsViewer": [Function], "EditButton": [Function], "EditorDialog": [Function], + "LogsButton": [Function], "MatchExpressions": [Function], "MetadataDictGrid": [Function], "MetadataDisplay": [Function],