diff --git a/cyclops-ctrl/.env b/cyclops-ctrl/.env
index 78d4024a..b49b53cd 100644
--- a/cyclops-ctrl/.env
+++ b/cyclops-ctrl/.env
@@ -2,3 +2,4 @@ DISABLE_TELEMETRY=true
PORT=8888
WATCH_NAMESPACE=cyclops
CYCLOPS_VERSION=v0.0.0
+CONFIG_PATH=config.yaml
diff --git a/cyclops-ctrl/cmd/main/config.go b/cyclops-ctrl/cmd/main/config.go
new file mode 100644
index 00000000..2d732e7d
--- /dev/null
+++ b/cyclops-ctrl/cmd/main/config.go
@@ -0,0 +1,67 @@
+package main
+
+import (
+ "github.com/cyclops-ui/cyclops/cyclops-ctrl/pkg/cluster/k8sclient"
+ "gopkg.in/yaml.v3"
+ "io"
+ "os"
+)
+
+type ControllerConfig struct {
+ ChildLabels k8sclient.ChildLabels `json:"childLabels"`
+}
+
+func getConfig() ControllerConfig {
+ configPath := os.Getenv("CONFIG_PATH")
+ if configPath == "" {
+ configPath = "/etc/config/config.yaml"
+ }
+
+ configFile, err := os.Open(configPath)
+ if err != nil {
+ setupLog.Error(err, "Failed to open controller config", "configPath", configPath)
+ return ControllerConfig{}
+ }
+ defer configFile.Close()
+
+ b, err := io.ReadAll(configFile)
+ if err != nil {
+ setupLog.Error(err, "Failed to read controller config", "configPath", configPath)
+ return ControllerConfig{}
+ }
+
+ type resourceChildLabels struct {
+ Group string `yaml:"group"`
+ Version string `yaml:"version"`
+ Kind string `yaml:"kind"`
+ MatchLabels map[string]string `yaml:"matchLabels"`
+ }
+
+ type yamlConfig struct {
+ ChildLabels []resourceChildLabels `yaml:"childLabels"`
+ }
+
+ var c *yamlConfig
+ err = yaml.Unmarshal(b, &c)
+ if err != nil {
+ setupLog.Error(err, "Failed to YAML unmarshal controller config", "configPath", configPath)
+ return ControllerConfig{}
+ }
+
+ if c == nil {
+ return ControllerConfig{}
+ }
+
+ childLabels := make(map[k8sclient.GroupVersionKind]map[string]string)
+ for _, label := range c.ChildLabels {
+ childLabels[k8sclient.GroupVersionKind{
+ Group: label.Group,
+ Version: label.Version,
+ Kind: label.Kind,
+ }] = label.MatchLabels
+ }
+
+ return ControllerConfig{
+ ChildLabels: childLabels,
+ }
+}
diff --git a/cyclops-ctrl/cmd/main/main.go b/cyclops-ctrl/cmd/main/main.go
index 2f549a36..9e2265da 100644
--- a/cyclops-ctrl/cmd/main/main.go
+++ b/cyclops-ctrl/cmd/main/main.go
@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"strconv"
+ "strings"
"time"
_ "github.com/joho/godotenv/autoload"
@@ -168,3 +169,26 @@ func getWatchNamespace(key string) string {
}
return value
}
+
+func getCustomChildLabels() map[string]string {
+ labels := make(map[string]string)
+
+ value := os.Getenv("CHILD_LABEL_SELECTOR")
+ if value == "" {
+ return labels
+ }
+
+ pairs := strings.Split(value, ";")
+
+ for _, pair := range pairs {
+ kv := strings.SplitN(pair, ":", 2)
+
+ if len(kv) == 2 {
+ key := kv[0]
+ value := kv[1]
+ labels[key] = value
+ }
+ }
+
+ return labels
+}
diff --git a/cyclops-ctrl/config.yaml b/cyclops-ctrl/config.yaml
new file mode 100644
index 00000000..1b4df7bc
--- /dev/null
+++ b/cyclops-ctrl/config.yaml
@@ -0,0 +1,6 @@
+childLabels:
+ - group: postgresql.cnpg.io
+ version: v1
+ kind: Cluster
+ matchLabels:
+ cnpg.io/cluster: "{{ .metadata.name }}"
diff --git a/cyclops-ctrl/internal/models/dto/k8s.go b/cyclops-ctrl/internal/models/dto/k8s.go
index f3f880d3..91c2418c 100644
--- a/cyclops-ctrl/internal/models/dto/k8s.go
+++ b/cyclops-ctrl/internal/models/dto/k8s.go
@@ -626,13 +626,14 @@ type IPBlock struct {
}
type Other struct {
- Group string `json:"group"`
- Version string `json:"version"`
- Kind string `json:"kind"`
- Name string `json:"name"`
- Namespace string `json:"namespace"`
- Status string `json:"status"`
- Deleted bool `json:"deleted"`
+ Group string `json:"group"`
+ Version string `json:"version"`
+ Kind string `json:"kind"`
+ Name string `json:"name"`
+ Namespace string `json:"namespace"`
+ Status string `json:"status"`
+ Deleted bool `json:"deleted"`
+ Children []Resource `json:"children"`
}
func (s *Other) GetGroupVersionKind() string {
diff --git a/cyclops-ctrl/pkg/cluster/k8sclient/childlabels.go b/cyclops-ctrl/pkg/cluster/k8sclient/childlabels.go
new file mode 100644
index 00000000..fc25fdcd
--- /dev/null
+++ b/cyclops-ctrl/pkg/cluster/k8sclient/childlabels.go
@@ -0,0 +1,101 @@
+package k8sclient
+
+import (
+ "bytes"
+ "context"
+ "text/template"
+
+ "gopkg.in/yaml.v3"
+
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+)
+
+const (
+ cyclopsNamespace = "cyclops"
+)
+
+type GroupVersionKind struct {
+ Group string `json:"group"`
+ Version string `json:"version"`
+ Kind string `json:"kind"`
+}
+
+type ChildLabels map[GroupVersionKind]map[string]string
+
+func (k *KubernetesClient) getChildLabel(
+ group, version, kind string,
+ obj *unstructured.Unstructured,
+) (map[string]string, bool, error) {
+ labels, exists := k.childLabels[GroupVersionKind{
+ Group: group,
+ Version: version,
+ Kind: kind,
+ }]
+
+ if !exists {
+ return nil, false, nil
+ }
+
+ matchLabels := make(map[string]string)
+ for k, v := range labels {
+ t, err := template.New("matchLabel").Parse(v)
+ if err != nil {
+ return nil, false, err
+ }
+
+ var o bytes.Buffer
+ err = t.Execute(&o, obj.Object)
+ if err != nil {
+ return nil, false, err
+ }
+
+ matchLabels[k] = o.String()
+ }
+
+ return matchLabels, exists, nil
+}
+
+func (k *KubernetesClient) loadResourceRelationsLabels() {
+ configmap, err := k.clientset.CoreV1().ConfigMaps("cyclops").Get(context.Background(), "cyclops-ctrl", v1.GetOptions{})
+ if err != nil {
+ return
+ }
+
+ d, ok := configmap.Data["resource-relations"]
+ if !ok {
+ return
+ }
+
+ type resourceChildLabels struct {
+ Group string `yaml:"group"`
+ Version string `yaml:"version"`
+ Kind string `yaml:"kind"`
+ MatchLabels map[string]string `yaml:"matchLabels"`
+ }
+
+ type yamlConfig struct {
+ ChildLabels []resourceChildLabels `yaml:"childLabels"`
+ }
+
+ var c *yamlConfig
+ err = yaml.Unmarshal([]byte(d), &c)
+ if err != nil {
+ return
+ }
+
+ if c == nil {
+ return
+ }
+
+ childLabels := make(map[GroupVersionKind]map[string]string)
+ for _, label := range c.ChildLabels {
+ childLabels[GroupVersionKind{
+ Group: label.Group,
+ Version: label.Version,
+ Kind: label.Kind,
+ }] = label.MatchLabels
+ }
+
+ k.childLabels = childLabels
+}
diff --git a/cyclops-ctrl/pkg/cluster/k8sclient/client.go b/cyclops-ctrl/pkg/cluster/k8sclient/client.go
index 54cc96ed..4831df2f 100644
--- a/cyclops-ctrl/pkg/cluster/k8sclient/client.go
+++ b/cyclops-ctrl/pkg/cluster/k8sclient/client.go
@@ -2,7 +2,6 @@ package k8sclient
import (
"context"
-
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -18,25 +17,16 @@ import (
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/models/dto"
)
-const (
- cyclopsNamespace = "cyclops"
-)
-
type KubernetesClient struct {
- Dynamic dynamic.Interface
-
+ Dynamic dynamic.Interface
clientset *kubernetes.Clientset
-
discovery *discovery.DiscoveryClient
-
moduleset *client.CyclopsV1Alpha1Client
-}
-func New() (*KubernetesClient, error) {
- return createLocalClient()
+ childLabels ChildLabels
}
-func createLocalClient() (*KubernetesClient, error) {
+func New() (*KubernetesClient, error) {
config := ctrl.GetConfigOrDie()
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
@@ -55,12 +45,16 @@ func createLocalClient() (*KubernetesClient, error) {
panic(err.Error())
}
- return &KubernetesClient{
+ k := &KubernetesClient{
Dynamic: dynamic,
discovery: discovery,
clientset: clientset,
moduleset: moduleSet,
- }, nil
+ }
+
+ k.loadResourceRelationsLabels()
+
+ return k, nil
}
type IKubernetesClient interface {
diff --git a/cyclops-ctrl/pkg/cluster/k8sclient/modules.go b/cyclops-ctrl/pkg/cluster/k8sclient/modules.go
index e7902a09..e238e25f 100644
--- a/cyclops-ctrl/pkg/cluster/k8sclient/modules.go
+++ b/cyclops-ctrl/pkg/cluster/k8sclient/modules.go
@@ -107,6 +107,56 @@ func (k *KubernetesClient) GetResourcesForModule(name string) ([]dto.Resource, e
return out, nil
}
+func (k *KubernetesClient) GetResourcesForCRD(childLabels map[string]string, name string) ([]dto.Resource, error) {
+ out := make([]dto.Resource, 0, 0)
+
+ managedGVRs, err := k.getManagedGVRs("")
+ if err != nil {
+ return nil, err
+ }
+
+ other := make([]unstructured.Unstructured, 0)
+ for _, gvr := range managedGVRs {
+ rs, err := k.Dynamic.Resource(gvr).List(context.Background(), metav1.ListOptions{
+ LabelSelector: labels.Set(childLabels).String(),
+ })
+ if err != nil {
+ continue
+ }
+
+ for _, item := range rs.Items {
+ other = append(other, item)
+ }
+ }
+
+ for _, o := range other {
+ status, err := k.getResourceStatus(o)
+ if err != nil {
+ return nil, err
+ }
+
+ out = append(out, &dto.Other{
+ Group: o.GroupVersionKind().Group,
+ Version: o.GroupVersionKind().Version,
+ Kind: o.GroupVersionKind().Kind,
+ Name: o.GetName(),
+ Namespace: o.GetNamespace(),
+ Status: status,
+ Deleted: false,
+ })
+ }
+
+ sort.Slice(out, func(i, j int) bool {
+ if out[i].GetGroupVersionKind() != out[j].GetGroupVersionKind() {
+ return out[i].GetGroupVersionKind() < out[j].GetGroupVersionKind()
+ }
+
+ return out[i].GetName() < out[j].GetName()
+ })
+
+ return out, nil
+}
+
func (k *KubernetesClient) GetWorkloadsForModule(name string) ([]dto.Resource, error) {
out := make([]dto.Resource, 0, 0)
diff --git a/cyclops-ctrl/pkg/cluster/k8sclient/resources.go b/cyclops-ctrl/pkg/cluster/k8sclient/resources.go
index 8f22c1f2..321f46e5 100644
--- a/cyclops-ctrl/pkg/cluster/k8sclient/resources.go
+++ b/cyclops-ctrl/pkg/cluster/k8sclient/resources.go
@@ -141,11 +141,51 @@ func (k *KubernetesClient) GetResource(group, version, kind, name, namespace str
return k.mapRole(group, version, kind, name, namespace)
case isNetworkPolicy(group, version, kind):
return k.mapNetworkPolicy(group, version, kind, name, namespace)
+ default:
+ return k.mapDefaultResource(group, version, kind, name, namespace)
}
return nil, nil
}
+func (k *KubernetesClient) mapDefaultResource(group, version, kind, name, namespace string) (any, error) {
+ apiResourceName, err := k.GVKtoAPIResourceName(schema.GroupVersion{Group: group, Version: version}, kind)
+ if err != nil {
+ return nil, err
+ }
+
+ resource, err := k.Dynamic.Resource(schema.GroupVersionResource{
+ Group: group,
+ Version: version,
+ Resource: apiResourceName,
+ }).Namespace(namespace).Get(context.Background(), name, metav1.GetOptions{})
+ if err != nil {
+ return "", err
+ }
+
+ childLabels, exists, err := k.getChildLabel(group, version, kind, resource)
+ if err != nil {
+ return nil, err
+ }
+
+ var children []dto.Resource
+ if exists {
+ children, err = k.GetResourcesForCRD(childLabels, name)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ return &dto.Other{
+ Group: group,
+ Version: version,
+ Kind: kind,
+ Name: name,
+ Namespace: namespace,
+ Children: children,
+ }, nil
+}
+
func (k *KubernetesClient) Delete(resource dto.Resource) error {
apiResourceName, err := k.GVKtoAPIResourceName(
schema.GroupVersion{
diff --git a/cyclops-ui/src/components/k8s-resources/DefaultResource.tsx b/cyclops-ui/src/components/k8s-resources/DefaultResource.tsx
new file mode 100644
index 00000000..5373ca05
--- /dev/null
+++ b/cyclops-ui/src/components/k8s-resources/DefaultResource.tsx
@@ -0,0 +1,106 @@
+import React, { useCallback, useEffect, useState } from "react";
+import { Alert } from "antd";
+import axios from "axios";
+import { mapResponseError } from "../../utils/api/errors";
+import ResourceList from "./ResourceList/ResourceList";
+
+interface Props {
+ group: string;
+ version: string;
+ kind: string;
+ name: string;
+ namespace: string;
+ onResourceDelete: () => void;
+}
+
+const DefaultResource = ({
+ group,
+ version,
+ kind,
+ name,
+ namespace,
+ onResourceDelete,
+}: Props) => {
+ const [resource, setResource] = useState({
+ children: [],
+ });
+ const [error, setError] = useState({
+ message: "",
+ description: "",
+ });
+ const [loadResources, setLoadResources] = useState(false);
+
+ const fetchResource = useCallback(() => {
+ axios
+ .get(`/api/resources`, {
+ params: {
+ group: group,
+ version: version,
+ kind: kind,
+ name: name,
+ namespace: namespace,
+ },
+ })
+ .then((res) => {
+ setResource(res.data);
+ })
+ .catch((error) => {
+ setError(mapResponseError(error));
+ })
+ .finally(() => {
+ setLoadResources(true);
+ });
+ }, [name, namespace, group, version, kind]);
+
+ useEffect(() => {
+ fetchResource();
+
+ const interval = setInterval(() => fetchResource(), 15000);
+ return () => {
+ clearInterval(interval);
+ };
+ }, [fetchResource]);
+
+ const resourceList = () => {
+ if (resource.children) {
+ return (
+
+ );
+ }
+ };
+
+ return (
+
+ {error.message.length !== 0 && (
+
{
+ setError({
+ message: "",
+ description: "",
+ });
+ }}
+ style={{ marginBottom: "20px" }}
+ />
+ )}
+
+ {resourceList()}
+
+
+ );
+};
+
+export default DefaultResource;
diff --git a/cyclops-ui/src/components/k8s-resources/ResourceList/ResourceList.tsx b/cyclops-ui/src/components/k8s-resources/ResourceList/ResourceList.tsx
new file mode 100644
index 00000000..ffb13be4
--- /dev/null
+++ b/cyclops-ui/src/components/k8s-resources/ResourceList/ResourceList.tsx
@@ -0,0 +1,724 @@
+import React, { useState } from "react";
+import {
+ Divider,
+ Row,
+ Tooltip,
+ Button,
+ Collapse,
+ Col,
+ Checkbox,
+ Spin,
+ Popover,
+ Input,
+ Modal,
+ Alert,
+} from "antd";
+import axios from "axios";
+import ReactAce from "react-ace";
+import {
+ isWorkload,
+ ResourceRef,
+ resourceRefKey,
+} from "../../../utils/resourceRef";
+import Deployment from "../Deployment";
+import CronJob from "../CronJob";
+import Job from "../Job";
+import DaemonSet from "../DaemonSet";
+import StatefulSet from "../StatefulSet";
+import Pod from "../Pod";
+import Service from "../Service";
+import ConfigMap from "../ConfigMap";
+import PersistentVolumeClaim from "../PersistentVolumeClaim";
+import Secret from "../Secret";
+import {
+ CaretRightOutlined,
+ CheckCircleTwoTone,
+ ClockCircleTwoTone,
+ CloseSquareTwoTone,
+ CopyOutlined,
+ FileTextOutlined,
+ SearchOutlined,
+ WarningTwoTone,
+} from "@ant-design/icons";
+import { isStreamingEnabled } from "../../../utils/api/common";
+import { canRestart, RestartButton } from "../common/RestartButton";
+import { gvkString } from "../../../utils/k8s/gvk";
+import Title from "antd/es/typography/Title";
+import { mapResponseError } from "../../../utils/api/errors";
+import { CheckboxChangeEvent } from "antd/es/checkbox";
+import DefaultResource from "../DefaultResource";
+import { Workload } from "../../../utils/k8s/workload";
+
+interface Props {
+ loadResources: boolean;
+ resources: any[];
+ workloads: Map;
+ onResourceDelete: () => void;
+}
+
+const ResourceList = ({
+ loadResources,
+ resources,
+ workloads,
+ onResourceDelete,
+}: Props) => {
+ const [error, setError] = useState({
+ message: "",
+ description: "",
+ });
+
+ const [manifestModal, setManifestModal] = useState({
+ on: false,
+ resource: {
+ group: "",
+ version: "",
+ kind: "",
+ name: "",
+ namespace: "",
+ },
+ manifest: "",
+ });
+
+ const [deleteResourceModal, setDeleteResourceModal] = useState(false);
+ const [deleteResourceRef, setDeleteResourceRef] = useState({
+ group: "",
+ version: "",
+ kind: "",
+ name: "",
+ namespace: "",
+ });
+ const [deleteResourceVerify, setDeleteResourceVerify] = useState("");
+
+ const [showManagedFields, setShowManagedFields] = useState(false);
+
+ const [resourceFilter, setResourceFilter] = useState([]);
+ const [activeCollapses, setActiveCollapses] = useState(new Map());
+
+ function getWorkload(ref: ResourceRef): Workload | undefined {
+ let k = resourceRefKey(ref);
+ return workloads.get(k);
+ }
+
+ const handleCancelDeleteResource = () => {
+ setDeleteResourceModal(false);
+ setDeleteResourceRef({
+ group: "",
+ version: "",
+ kind: "",
+ name: "",
+ namespace: "",
+ });
+ setDeleteResourceVerify("");
+ };
+
+ const deleteResource = () => {
+ axios
+ .delete(`/api/resources`, {
+ data: {
+ group: deleteResourceRef.group,
+ version: deleteResourceRef.version,
+ kind: deleteResourceRef.kind,
+ name: deleteResourceRef.name,
+ namespace: deleteResourceRef.namespace,
+ },
+ })
+ .then(() => {
+ onResourceDelete();
+ // setLoadResources(false); TODO
+ setDeleteResourceModal(false);
+ // fetchModuleResources(); TODO
+ })
+ .catch((error) => {
+ // setLoading(false); TODO
+ setError(mapResponseError(error));
+ });
+ };
+
+ const changeDeleteResourceVerify = (e: any) => {
+ setDeleteResourceVerify(e.target.value);
+ };
+
+ const onResourceFilterUpdate = (kinds: string[]) => {
+ setResourceFilter(kinds);
+ };
+
+ const handleCancelManifest = () => {
+ setManifestModal({
+ ...manifestModal,
+ on: false,
+ });
+ };
+
+ const handleCheckboxChange = (e: CheckboxChangeEvent) => {
+ setShowManagedFields(e.target.checked);
+ fetchManifest(
+ manifestModal.resource.group,
+ manifestModal.resource.version,
+ manifestModal.resource.kind,
+ manifestModal.resource.namespace,
+ manifestModal.resource.name,
+ e.target.checked,
+ );
+ };
+
+ function fetchManifest(
+ group: string,
+ version: string,
+ kind: string,
+ namespace: string,
+ name: string,
+ showManagedFields: boolean,
+ ) {
+ axios
+ .get(`/api/manifest`, {
+ params: {
+ group: group,
+ version: version,
+ kind: kind,
+ name: name,
+ namespace: namespace,
+ includeManagedFields: showManagedFields,
+ },
+ })
+ .then((res) => {
+ setManifestModal((prev) => ({
+ ...prev,
+ manifest: res.data,
+ }));
+ })
+ .catch((error) => {
+ setError(mapResponseError(error));
+ });
+ }
+
+ const resourceFilterOptions = () => {
+ if (!loadResources) {
+ return ;
+ }
+
+ let uniqueGVKs = new Set();
+ resources.forEach(function (resource: any) {
+ uniqueGVKs.add(
+ gvkString(resource.group, resource.version, resource.kind),
+ );
+ });
+
+ let options: any[] = [];
+ uniqueGVKs.forEach(function (gvk: string) {
+ options.push(
+
+ {gvk}
+
,
+ );
+ });
+
+ return options;
+ };
+
+ const resourceFilterPopover = () => {
+ return (
+
+ {resourceFilterOptions()}
+
+ );
+ };
+
+ const getCollapseColor = (fieldName: string) => {
+ if (
+ activeCollapses.get(fieldName) &&
+ activeCollapses.get(fieldName) === true
+ ) {
+ return "#EFEFEF";
+ } else {
+ return "#FAFAFA";
+ }
+ };
+
+ const getCollapseWidth = (fieldName: string) => {
+ if (
+ activeCollapses.get(fieldName) &&
+ activeCollapses.get(fieldName) === true
+ ) {
+ return "166%";
+ } else {
+ return "100%";
+ }
+ };
+
+ const resourceCollapses: {} | any = [];
+ resources.forEach((resource: any, index) => {
+ let collapseKey =
+ resource.kind + "/" + resource.namespace + "/" + resource.name;
+
+ let resourceDetails = ;
+
+ let resourceRef: ResourceRef = {
+ group: resource.group,
+ version: resource.version,
+ kind: resource.kind,
+ name: resource.name,
+ namespace: resource.namespace,
+ };
+
+ switch (resource.kind) {
+ case "Deployment":
+ resourceDetails = (
+
+ );
+ break;
+ case "CronJob":
+ resourceDetails = (
+
+ );
+ break;
+ case "Job":
+ resourceDetails = (
+
+ );
+ break;
+ case "DaemonSet":
+ resourceDetails = (
+
+ );
+ break;
+ case "StatefulSet":
+ resourceDetails = (
+
+ );
+ break;
+ case "Pod":
+ resourceDetails = (
+
+ );
+ break;
+ case "Service":
+ resourceDetails = (
+
+ );
+ break;
+ case "ConfigMap":
+ resourceDetails = (
+
+ );
+ break;
+ case "PersistentVolumeClaim":
+ resourceDetails = (
+
+ );
+ break;
+ case "Secret":
+ resourceDetails = (
+
+ );
+ break;
+ default:
+ resourceDetails = (
+
+ );
+ break;
+ }
+
+ let deletedWarning = ;
+
+ if (resource.deleted) {
+ deletedWarning = (
+
+
+
+ );
+ }
+
+ let deleteButton = ;
+
+ if (resource.deleted) {
+ deleteButton = (
+
+ );
+ }
+
+ let resourceStatus = resource.status;
+ if (isStreamingEnabled() && isWorkload(resourceRef)) {
+ resourceStatus = getWorkload(resourceRef)?.status;
+ }
+
+ const genExtra = (resource: any, status?: string) => {
+ let statusIcon = <>>;
+ if (status === "progressing") {
+ statusIcon = (
+
+ );
+ }
+ if (status === "healthy") {
+ statusIcon = (
+
+ );
+ }
+ if (status === "unhealthy") {
+ statusIcon = (
+
+ );
+ }
+
+ let deletedIcon = <>>;
+ if (resource.deleted) {
+ deletedIcon = (
+
+ );
+ }
+
+ return (
+
+
+
+ {resource.name} {resource.kind} {statusIcon}
+
+
+
+ {deletedIcon}
+
+
+ );
+ };
+
+ const getResourceDisplay = (
+ group: string,
+ version: string,
+ kind: string,
+ ): string => {
+ if (resourceFilter.length === 0) {
+ return "";
+ }
+
+ for (let filter of resourceFilter) {
+ if (gvkString(group, version, kind) === filter) {
+ return "";
+ }
+ }
+
+ return "none";
+ };
+
+ const getStatusColor = (status: string, deleted: boolean) => {
+ if (status === "unhealthy") {
+ return "#FF0000";
+ }
+
+ if (deleted) {
+ return "#ff9f1a";
+ }
+
+ if (status === "progressing") {
+ return "#ffcc00";
+ }
+
+ return "#27D507";
+ };
+
+ const handleManifestClick = (resource: any) => {
+ setManifestModal({
+ on: true,
+ resource: {
+ group: resource.group,
+ version: resource.version,
+ kind: resource.kind,
+ name: resource.name,
+ namespace: resource.namespace,
+ },
+ manifest: "",
+ });
+ fetchManifest(
+ resource.group,
+ resource.version,
+ resource.kind,
+ resource.namespace,
+ resource.name,
+ showManagedFields,
+ );
+ };
+
+ resourceCollapses.push(
+
+
+ {deletedWarning}
+
+
+
+ {resource.name}
+
+
+
+
+ {deleteButton}
+
+
+
+ {resource.namespace}
+
+
+
+
+
+ {canRestart(resource.group, resource.version, resource.kind) && (
+
+
+
+ )}
+
+ {resourceDetails}
+ ,
+ );
+ });
+
+ const loadingResourceCollapses = () => {
+ if (loadResources) {
+ return (
+ (
+
+ )}
+ style={{
+ width: "60%",
+ border: "none",
+ backgroundColor: "#FFF",
+ }}
+ onChange={function (values: string | string[]) {
+ let m = new Map();
+ for (let value of values) {
+ m.set(value, true);
+ }
+
+ setActiveCollapses(m);
+ }}
+ >
+ {resourceCollapses}
+
+ );
+ }
+
+ return ;
+ };
+
+ return (
+
+ {error.message.length !== 0 && (
+
{
+ setError({
+ message: "",
+ description: "",
+ });
+ }}
+ style={{ marginBottom: "20px" }}
+ />
+ )}
+
+ {"Resources "}
+
+
+
+
+ {loadingResourceCollapses()}
+
+
+ Include Managed Fields
+
+
+
+
+
+
+
+
+
+
+ Delete
+
+ }
+ width={"40%"}
+ >
+
+ In order to confirm deleting this resource, type:{" "}
+ {deleteResourceRef.kind + " " + deleteResourceRef.name}
+
+
+
+
+ );
+};
+
+export default ResourceList;
diff --git a/cyclops-ui/src/components/pages/ModuleDetails.tsx b/cyclops-ui/src/components/pages/ModuleDetails.tsx
index e743c6a4..18073c09 100644
--- a/cyclops-ui/src/components/pages/ModuleDetails.tsx
+++ b/cyclops-ui/src/components/pages/ModuleDetails.tsx
@@ -2,15 +2,12 @@ import React, { useEffect, useState, useCallback } from "react";
import {
Alert,
Button,
- Checkbox,
Col,
- Collapse,
Descriptions,
Divider,
Input,
Modal,
notification,
- Popover,
Row,
Spin,
Tooltip,
@@ -21,7 +18,6 @@ import { useParams } from "react-router-dom";
import axios from "axios";
import {
BookOutlined,
- CaretRightOutlined,
CheckCircleTwoTone,
ClockCircleTwoTone,
CloseSquareTwoTone,
@@ -29,36 +25,18 @@ import {
DeleteOutlined,
EditOutlined,
FileTextOutlined,
- SearchOutlined,
UndoOutlined,
- WarningTwoTone,
} from "@ant-design/icons";
import "./custom.css";
import "ace-builds/src-noconflict/mode-jsx";
import ReactAce from "react-ace";
-import Deployment from "../k8s-resources/Deployment";
-import CronJob from "../k8s-resources/CronJob";
-import Job from "../k8s-resources/Job";
-import DaemonSet from "../k8s-resources/DaemonSet";
-import StatefulSet from "../k8s-resources/StatefulSet";
-import Pod from "../k8s-resources/Pod";
-import Service from "../k8s-resources/Service";
-import ConfigMap from "../k8s-resources/ConfigMap";
-import PersistentVolumeClaim from "../k8s-resources/PersistentVolumeClaim";
import {
moduleTemplateReferenceView,
templateRef,
} from "../../utils/templateRef";
-import { gvkString } from "../../utils/k8s/gvk";
import { mapResponseError } from "../../utils/api/errors";
-import Secret from "../k8s-resources/Secret";
-import { CheckboxChangeEvent } from "antd/es/checkbox";
-import {
- canRestart,
- RestartButton,
-} from "../k8s-resources/common/RestartButton";
import YAML from "yaml";
import { isStreamingEnabled } from "../../utils/api/common";
import { resourcesStream } from "../../utils/api/sse/resources";
@@ -67,6 +45,8 @@ import {
ResourceRef,
resourceRefKey,
} from "../../utils/resourceRef";
+import ResourceList from "../k8s-resources/ResourceList/ResourceList";
+import { Workload } from "../../utils/k8s/workload";
const languages = [
"javascript",
@@ -116,41 +96,24 @@ interface module {
iconURL: string;
}
-interface workload {
- status: string;
- pods: any[];
-}
-
const ModuleDetails = () => {
- const [manifestModal, setManifestModal] = useState({
- on: false,
- resource: {
- group: "",
- version: "",
- kind: "",
- name: "",
- namespace: "",
- },
- manifest: "",
- });
const [loading, setLoading] = useState(false);
const [loadModule, setLoadModule] = useState(false);
const [loadResources, setLoadResources] = useState(false);
const [loadingReconciliation, setLoadingReconciliation] = useState(false);
const [deleteName, setDeleteName] = useState("");
- const [deleteResourceVerify, setDeleteResourceVerify] = useState("");
const [resources, setResources] = useState([]);
- const [workloads, setWorkloads] = useState