diff --git a/cyclops-ctrl/internal/handler/handler.go b/cyclops-ctrl/internal/handler/handler.go
index 0dca15f4..4603de9e 100644
--- a/cyclops-ctrl/internal/handler/handler.go
+++ b/cyclops-ctrl/internal/handler/handler.go
@@ -1,10 +1,11 @@
package handler
import (
+ "net/http"
+
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/controller/sse"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/integrations/helm"
"github.com/gin-gonic/gin"
- "net/http"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/controller"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/prometheus"
@@ -95,6 +96,8 @@ func (h *Handler) Start() error {
h.router.GET("/resources/pods/:namespace/:name/:container/logs", modulesController.GetLogs)
h.router.GET("/resources/pods/:namespace/:name/:container/logs/stream", sse.HeadersMiddleware(), modulesController.GetLogsStream)
h.router.GET("/resources/pods/:namespace/:name/:container/logs/download", modulesController.DownloadLogs)
+ // h.router.GET("/resources/deployments/:namespace/:deployment/:container/logs", modulesController.GetDeploymentLogs)
+ // h.router.GET("/resources/statefulsets/:namespace/:name/:container/logs", modulesController.GetStatefulSetsLogs)
h.router.GET("/manifest", modulesController.GetManifest)
h.router.GET("/resources", modulesController.GetResource)
diff --git a/cyclops-ui/src/components/k8s-resources/Deployment.tsx b/cyclops-ui/src/components/k8s-resources/Deployment.tsx
index e2c44813..2c2067ba 100644
--- a/cyclops-ui/src/components/k8s-resources/Deployment.tsx
+++ b/cyclops-ui/src/components/k8s-resources/Deployment.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect, useState } from "react";
+import { useCallback, useEffect, useState } from "react";
import { Col, Divider, Row, Alert, Spin } from "antd";
import { mapResponseError } from "../../utils/api/errors";
import PodTable from "./common/PodTable/PodTable";
diff --git a/cyclops-ui/src/components/k8s-resources/ResourceList/ResourceList.tsx b/cyclops-ui/src/components/k8s-resources/ResourceList/ResourceList.tsx
index f1f95272..b1c20f38 100644
--- a/cyclops-ui/src/components/k8s-resources/ResourceList/ResourceList.tsx
+++ b/cyclops-ui/src/components/k8s-resources/ResourceList/ResourceList.tsx
@@ -30,6 +30,7 @@ import ClusterRole from "../ClusterRole";
import ConfigMap from "../ConfigMap";
import PersistentVolumeClaim from "../PersistentVolumeClaim";
import Secret from "../Secret";
+import ObjectLogsButton from "../common/ObjectLogsButton";
import {
CaretRightOutlined,
CheckCircleTwoTone,
@@ -589,6 +590,16 @@ const ResourceList = ({
/>
)}
+ {(resource.kind === "Deployment" ||
+ resource.kind === "StatefulSet") && (
+
+
+
+ )}
{resourceDetails}
,
diff --git a/cyclops-ui/src/components/k8s-resources/StatefulSet.tsx b/cyclops-ui/src/components/k8s-resources/StatefulSet.tsx
index 3cf38ce5..edbe1538 100644
--- a/cyclops-ui/src/components/k8s-resources/StatefulSet.tsx
+++ b/cyclops-ui/src/components/k8s-resources/StatefulSet.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect, useState } from "react";
+import { useCallback, useEffect, useState } from "react";
import { Col, Divider, Row, Alert, Spin } from "antd";
import { mapResponseError } from "../../utils/api/errors";
import PodTable from "./common/PodTable/PodTable";
@@ -15,11 +15,11 @@ const StatefulSet = ({ name, namespace, workload }: Props) => {
const { fetchResource, streamingDisabled } = useResourceListActions();
const [loading, setLoading] = useState(true);
+
const [statefulSet, setStatefulSet] = useState({
status: "",
pods: [],
});
-
const [error, setError] = useState({
message: "",
description: "",
diff --git a/cyclops-ui/src/components/k8s-resources/common/ObjectLogsButton.tsx b/cyclops-ui/src/components/k8s-resources/common/ObjectLogsButton.tsx
new file mode 100644
index 00000000..8d2e60eb
--- /dev/null
+++ b/cyclops-ui/src/components/k8s-resources/common/ObjectLogsButton.tsx
@@ -0,0 +1,274 @@
+import { useState, useRef } from "react";
+import { Col, Divider, Alert, TabsProps, Button, Tabs, Modal } from "antd";
+import { logStream } from "../../../utils/api/sse/logs";
+import { mapResponseError } from "../../../utils/api/errors";
+import ReactAce from "react-ace/lib/ace";
+import { DownloadOutlined, ReadOutlined } from "@ant-design/icons";
+
+import { useResourceListActions } from "../ResourceList/ResourceListActionsContext";
+
+interface Props {
+ name: string;
+ namespace: string;
+ workload: any;
+}
+
+const ObjectLogsButton = ({ name, namespace, workload }: Props) => {
+ const { streamingDisabled, getPodLogs, downloadPodLogs, streamPodLogs } =
+ useResourceListActions();
+ const [logs, setLogs] = useState([]);
+ const [logsModal, setLogsModal] = useState({
+ on: false,
+ containers: [],
+ initContainers: [],
+ });
+
+ const logsSignalControllerRef = useRef(null);
+
+ const [error, setError] = useState({
+ message: "",
+ description: "",
+ });
+
+ const handleCancelLogs = () => {
+ setLogsModal({
+ on: false,
+ containers: [],
+ initContainers: [],
+ });
+ setLogs([]);
+
+ // send the abort signal
+ if (logsSignalControllerRef.current !== null) {
+ logsSignalControllerRef.current.abort();
+ }
+ };
+
+ const getTabItems = () => {
+ let items: TabsProps["items"] = [];
+
+ let container: any;
+
+ if (logsModal.containers !== null) {
+ for (container of logsModal.containers) {
+ items.push({
+ key: container.name,
+ label: container.name,
+ children: (
+
+ {downloadPodLogs ? (
+
+
}
+ onClick={downloadLogs(container.name)}
+ disabled={logs.length === 0}
+ >
+ Download
+
+
+
+ ) : (
+ <>>
+ )}
+
+
+ ),
+ });
+ }
+ }
+
+ if (logsModal.initContainers !== null) {
+ for (container of logsModal.initContainers) {
+ items.push({
+ key: container.name,
+ label: "(init container) " + container.name,
+ children: (
+
+ {downloadPodLogs ? (
+
+
}
+ onClick={downloadLogs(container.name)}
+ disabled={logs.length === 0}
+ >
+ Download
+
+
+
+ ) : (
+ <>>
+ )}
+
+
+ ),
+ });
+ }
+ }
+
+ return items;
+ };
+
+ const onLogsTabsChange = () => {
+ const controller = new AbortController();
+ if (logsSignalControllerRef.current !== null) {
+ logsSignalControllerRef.current.abort();
+ }
+ logsSignalControllerRef.current = controller; // store the controller to be able to abort the request
+ setLogs(() => []);
+
+ if (!streamingDisabled) {
+ logStream(
+ namespace,
+ name,
+ workload.pods[0].containers[0].name,
+ (log, isReset = false) => {
+ if (isReset) {
+ setLogs(() => []);
+ } else {
+ setLogs((prevLogs) => {
+ return [...prevLogs, log];
+ });
+ }
+ },
+ (err, isReset = false) => {
+ if (isReset) {
+ setError({
+ message: "",
+ description: "",
+ });
+ } else {
+ setError(mapResponseError(err));
+ }
+ },
+ controller,
+ streamPodLogs,
+ );
+ } else {
+ getPodLogs(namespace, name, workload.pods[0].containers[0].name)
+ .then((res) => {
+ if (res) {
+ setLogs(res);
+ } else {
+ setLogs(() => []);
+ }
+ })
+ .catch((error) => {
+ setError(mapResponseError(error));
+ });
+ }
+ };
+
+ const downloadLogs = (container: string) => {
+ return () => downloadPodLogs(namespace, workload.pods[0].name, container);
+ };
+
+ return (
+ <>
+
+
+ {error.message.length !== 0 && (
+ {
+ setError({
+ message: "",
+ description: "",
+ });
+ }}
+ style={{ marginBottom: "20px" }}
+ />
+ )}
+
+
+ >
+ );
+};
+
+export default ObjectLogsButton;