Explorar el Código

add casing for agent integration enabled

Alexander Belanger hace 3 años
padre
commit
c95f1648ce

+ 7 - 1
api/server/handlers/cluster/update.go

@@ -61,7 +61,13 @@ func (c *ClusterUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		cluster.AWSClusterID = request.AWSClusterID
 	}
 
-	cluster.Name = request.Name
+	if request.AgentIntegrationEnabled != nil {
+		cluster.AgentIntegrationEnabled = *request.AgentIntegrationEnabled
+	}
+
+	if request.Name != "" && cluster.Name != request.Name {
+		cluster.Name = request.Name
+	}
 
 	cluster, err := c.Repo().Cluster().UpdateCluster(cluster)
 

+ 6 - 1
api/types/cluster.go

@@ -24,6 +24,9 @@ type Cluster struct {
 	// The integration service for this cluster
 	Service ClusterService `json:"service"`
 
+	// Whether or not the Porter agent integration is enabled
+	AgentIntegrationEnabled bool `json:"agent_integration_enabled"`
+
 	// The infra id, if cluster was provisioned with Porter
 	InfraID uint `json:"infra_id"`
 
@@ -262,9 +265,11 @@ type CreateClusterCandidateRequest struct {
 }
 
 type UpdateClusterRequest struct {
-	Name string `json:"name" form:"required"`
+	Name string `json:"name"`
 
 	AWSClusterID string `json:"aws_cluster_id"`
+
+	AgentIntegrationEnabled *bool `json:"agent_integration_enabled"`
 }
 
 type ListClusterResponse []*Cluster

+ 75 - 16
dashboard/src/main/home/cluster-dashboard/dashboard/ClusterSettings.tsx

@@ -1,18 +1,26 @@
-import React, { useContext, useState } from "react";
+import React, { useContext, useEffect, useState } from "react";
 import styled from "styled-components";
 import Heading from "components/form-components/Heading";
 import Helper from "components/form-components/Helper";
 import InputRow from "components/form-components/InputRow";
 import { Context } from "shared/Context";
 import api from "shared/api";
+import CheckboxRow from "components/form-components/CheckboxRow";
+import Loading from "components/Loading";
 
 const ClusterSettings: React.FC = () => {
-  const context = useContext(Context);
+  const {
+    currentProject,
+    currentCluster,
+    setCurrentCluster,
+    setCurrentModal,
+    capabilities,
+  } = useContext(Context);
   const [newClusterName, setNewClusterName] = useState<string>(
-    context.currentCluster.name
+    currentCluster.name
   );
   const [newAWSClusterID, setNewAWSClusterID] = useState<string>(
-    context.currentCluster.aws_cluster_id
+    currentCluster.aws_cluster_id
   );
   const [successfulRename, setSuccessfulRename] = useState<boolean>(false);
 
@@ -20,19 +28,23 @@ const ClusterSettings: React.FC = () => {
   const [secretKey, setSecretKey] = useState<string>("");
   const [startRotateCreds, setStartRotateCreds] = useState<boolean>(false);
   const [successfulRotate, setSuccessfulRotate] = useState<boolean>(false);
+  const [enableAgent, setEnableAgent] = useState(
+    currentCluster.agent_integration_enabled
+  );
+  const [agentLoading, setAgentLoading] = useState(false);
 
   let rotateCredentials = () => {
     api
       .overwriteAWSIntegration(
         "<token>",
         {
-          aws_integration_id: context.currentCluster.aws_integration_id,
+          aws_integration_id: currentCluster.aws_integration_id,
           aws_access_key_id: accessKeyId,
           aws_secret_access_key: secretKey,
-          cluster_id: context.currentCluster.id,
+          cluster_id: currentCluster.id,
         },
         {
-          project_id: context.currentProject.id,
+          project_id: currentProject.id,
         }
       )
       .then(({ data }) => {
@@ -45,15 +57,15 @@ const ClusterSettings: React.FC = () => {
 
   let updateClusterName = () => {
     api
-      .updateClusterName(
+      .updateCluster(
         "<token>",
         {
           name: newClusterName,
           aws_cluster_id: newAWSClusterID,
         },
         {
-          project_id: context.currentProject.id,
-          cluster_id: context.currentCluster.id,
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
         }
       )
       .then(({ data }) => {
@@ -64,6 +76,29 @@ const ClusterSettings: React.FC = () => {
       });
   };
 
+  let updateAgentIntegrationEnabled = () => {
+    setAgentLoading(true);
+
+    api
+      .updateCluster(
+        "<token>",
+        {
+          agent_integration_enabled: enableAgent,
+        },
+        {
+          project_id: currentProject.id,
+          cluster_id: currentCluster.id,
+        }
+      )
+      .then(({ data }) => {
+        setCurrentCluster(data);
+        setAgentLoading(false);
+      })
+      .catch(() => {
+        setAgentLoading(false);
+      });
+  };
+
   let helperText = (
     <Helper>
       Delete this cluster and underlying infrastructure. To ensure that
@@ -80,7 +115,7 @@ const ClusterSettings: React.FC = () => {
     </Helper>
   );
 
-  if (!context.currentCluster?.infra_id || !context.currentCluster?.service) {
+  if (!currentCluster?.infra_id || !currentCluster?.service) {
     helperText = (
       <Helper>
         Remove this cluster from Porter. Since this cluster was not provisioned
@@ -94,8 +129,8 @@ const ClusterSettings: React.FC = () => {
   let keyRotationSection = null;
 
   if (
-    context.currentCluster?.aws_integration_id &&
-    context.currentCluster?.aws_integration_id != 0
+    currentCluster?.aws_integration_id &&
+    currentCluster?.aws_integration_id != 0
   ) {
     if (successfulRotate) {
       keyRotationSection = (
@@ -148,8 +183,8 @@ const ClusterSettings: React.FC = () => {
   }
 
   let overrideAWSClusterNameSection =
-    context.currentCluster?.aws_integration_id &&
-    context.currentCluster?.aws_integration_id != 0 ? (
+    currentCluster?.aws_integration_id &&
+    currentCluster?.aws_integration_id != 0 ? (
       <InputRow
         type="text"
         value={newAWSClusterID}
@@ -180,6 +215,28 @@ const ClusterSettings: React.FC = () => {
     </div>
   );
 
+  let enableAgentIntegration = (
+    <div>
+      <Heading>Enable Agent</Heading>
+      <CheckboxRow
+        label={"Allow the Porter agent to be installed on the cluster"}
+        toggle={() => setEnableAgent(!enableAgent)}
+        checked={enableAgent}
+      />
+      <Button color="#616FEEcc" onClick={updateAgentIntegrationEnabled}>
+        Save
+      </Button>
+    </div>
+  );
+
+  if (agentLoading) {
+    enableAgentIntegration = <Loading />;
+  }
+
+  if (capabilities.version == "production") {
+    enableAgentIntegration = null;
+  }
+
   if (successfulRename) {
     renameClusterSection = (
       <div>
@@ -192,6 +249,8 @@ const ClusterSettings: React.FC = () => {
   return (
     <div>
       <StyledSettingsSection>
+        {enableAgentIntegration}
+        <DarkMatter />
         {keyRotationSection}
         <DarkMatter />
         {renameClusterSection}
@@ -200,7 +259,7 @@ const ClusterSettings: React.FC = () => {
         {helperText}
         <Button
           color="#b91133"
-          onClick={() => context.setCurrentModal("UpdateClusterModal")}
+          onClick={() => setCurrentModal("UpdateClusterModal")}
         >
           Delete Cluster
         </Button>

+ 0 - 3
dashboard/src/main/home/cluster-dashboard/dashboard/Dashboard.tsx

@@ -14,7 +14,6 @@ import useAuth from "shared/auth/useAuth";
 import Metrics from "./Metrics";
 import { useLocation } from "react-router";
 import { getQueryParam } from "shared/routing";
-import IncidentsTab from "./incidents/IncidentsTab";
 
 import CopyToClipboard from "components/CopyToClipboard";
 import Loading from "components/Loading";
@@ -47,8 +46,6 @@ export const Dashboard: React.FunctionComponent = () => {
   const context = useContext(Context);
   const renderTab = () => {
     switch (currentTab) {
-      case "incidents":
-        return <IncidentsTab />;
       case "settings":
         return <ClusterSettings />;
       case "metrics":

+ 0 - 4
dashboard/src/main/home/cluster-dashboard/dashboard/Routes.tsx

@@ -2,7 +2,6 @@ import React, { useContext } from "react";
 import { Redirect, Route, Switch, useRouteMatch } from "react-router";
 import { Context } from "shared/Context";
 import { Dashboard } from "./Dashboard";
-import IncidentPage from "./incidents/IncidentPage";
 import ExpandedNodeView from "./node-view/ExpandedNodeView";
 
 export const Routes = () => {
@@ -11,9 +10,6 @@ export const Routes = () => {
   return (
     <>
       <Switch>
-        <Route path={`${url}/incidents/:incident_id`}>
-          <IncidentPage />
-        </Route>
         <Route path={`${url}/node-view/:nodeId`}>
           <ExpandedNodeView />
         </Route>

+ 0 - 233
dashboard/src/main/home/cluster-dashboard/dashboard/incidents/EventDrawer.tsx

@@ -1,233 +0,0 @@
-import Description from "components/Description";
-import useLastSeenPodStatus from "components/events/useLastSeenPodStatus";
-import Heading from "components/form-components/Heading";
-import Loading from "components/Loading";
-import { isEmpty } from "lodash";
-import React, { useContext, useEffect, useMemo, useState } from "react";
-import api from "shared/api";
-import { Context } from "shared/Context";
-import { capitalize } from "shared/string_utils";
-import styled from "styled-components";
-import ExpandedContainer from "./ExpandedContainer";
-import { IncidentContainerEvent, IncidentEvent } from "./IncidentPage";
-
-const EventDrawer: React.FC<{
-  event: IncidentEvent;
-  closeDrawer: () => void;
-}> = ({ event, closeDrawer }) => {
-  const { currentProject, currentCluster } = useContext(Context);
-
-  const [containerLogs, setContainerLogs] = useState<{ [key: string]: string }>(
-    null
-  );
-
-  const {
-    status,
-    hasError: hasPodStatusErrored,
-    isLoading: isPodStatusLoading,
-  } = useLastSeenPodStatus({
-    podName: event?.pod_name,
-    namespace: event?.namespace,
-    resource_type: "pod",
-  });
-
-  const containers: IncidentContainerEvent[] = useMemo(() => {
-    if (isEmpty(event?.container_events)) {
-      return [];
-    }
-
-    return Object.values(event?.container_events || {});
-  }, [event]);
-
-  useEffect(() => {
-    if (!event) {
-      return () => {};
-    }
-
-    let isSubscribed = true;
-
-    const containersWithLogs = containers.filter(
-      (container) => container.log_id
-    );
-
-    const promises = containersWithLogs.map((container) => {
-      return api
-        .getIncidentLogsByLogId<{ contents: string }>(
-          "<token>",
-          {
-            log_id: container.log_id,
-          },
-          {
-            project_id: currentProject.id,
-            cluster_id: currentCluster.id,
-          }
-        )
-        .then((res) => ({
-          contents: res.data?.contents,
-          container_name: container.container_name,
-        }));
-    });
-
-    Promise.all(promises)
-      .then((data) => {
-        if (!isSubscribed) {
-          return;
-        }
-
-        const tmpContainerLogs = data.reduce<{ [key: string]: string }>(
-          (acc, c) => {
-            acc[c.container_name] = c.contents;
-            return acc;
-          },
-          {}
-        );
-
-        setContainerLogs(tmpContainerLogs);
-      })
-      .catch(() => console.log("nope"));
-
-    return () => {
-      isSubscribed = false;
-    };
-  }, [containers]);
-
-  if (!event) {
-    return null;
-  }
-
-  if (!containerLogs) {
-    return <Loading />;
-  }
-
-  return (
-    <EventDrawerContainer>
-      <EventDrawerTitleContainer>
-        <EventDrawerTitle>Pod: {event?.pod_name}</EventDrawerTitle>
-        <BackButton onClick={closeDrawer}>
-          <i className="material-icons">close</i>
-        </BackButton>
-      </EventDrawerTitleContainer>
-
-      <StyledHelper>
-        {hasPodStatusErrored ? (
-          "We couldn't retrieve last pod status, please try again later"
-        ) : (
-          <>
-            {isPodStatusLoading ? (
-              <Loading />
-            ) : (
-              <>
-                Latest pod status: {capitalize(status)}{" "}
-                <StatusColor status={status?.toLowerCase()}></StatusColor>
-              </>
-            )}
-          </>
-        )}
-      </StyledHelper>
-      <MetadataContainer>
-        <Heading>Overview</Heading>
-        <Description>
-          Event reported on{" "}
-          {Intl.DateTimeFormat([], {
-            // @ts-ignore
-            dateStyle: "full",
-            timeStyle: "long",
-          }).format(new Date(event?.timestamp))}
-        </Description>
-        <Description>{event?.message}</Description>
-        <Br />
-      </MetadataContainer>
-      {containers.map((container) => (
-        <ExpandedContainer
-          container={container}
-          logs={containerLogs[container.container_name]}
-        />
-      ))}
-    </EventDrawerContainer>
-  );
-};
-
-export default EventDrawer;
-
-const EventDrawerContainer = styled.div`
-  position: relative;
-  color: #ffffff;
-  padding: 25px 30px;
-`;
-
-const EventDrawerTitle = styled.span`
-  display: block;
-  font-size: 24px;
-  font-weight: bold;
-  color: #ffffff;
-`;
-
-const Br = styled.div`
-  width: 100%;
-  height: 20px;
-`;
-
-const MetadataContainer = styled.div`
-  border-radius: 6px;
-  background: #2e3135;
-  padding: 0 20px;
-  overflow-y: auto;
-  min-height: 100px;
-  font-size: 13px;
-  margin: 12px 0;
-`;
-
-const StyledHelper = styled.div`
-  color: #aaaabb;
-  line-height: 1.6em;
-  font-size: 13px;
-  margin-top: 6px;
-`;
-
-const BackButton = styled.div`
-  display: flex;
-  width: 37px;
-  z-index: 1;
-  cursor: pointer;
-  height: 37px;
-  align-items: center;
-  justify-content: center;
-  border: 1px solid #ffffff55;
-  border-radius: 100px;
-  background: #ffffff11;
-  color: #ffffffaa;
-
-  > i {
-    font-size: 20px;
-  }
-
-  :hover {
-    background: #ffffff22;
-    > img {
-      opacity: 1;
-    }
-  }
-`;
-
-const StatusColor = styled.div`
-  display: inline-block;
-  margin-right: 7px;
-  width: 7px;
-  min-width: 7px;
-  height: 7px;
-  background: ${(props: { status: string }) =>
-    props.status === "running"
-      ? "#4797ff"
-      : props.status === "failed" || props.status === "deleted"
-      ? "#ed5f85"
-      : props.status === "completed"
-      ? "#00d12a"
-      : "#f5cb42"};
-  border-radius: 20px;
-`;
-
-const EventDrawerTitleContainer = styled.div`
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-`;

+ 0 - 62
dashboard/src/main/home/cluster-dashboard/dashboard/incidents/ExpandedContainer.tsx

@@ -1,62 +0,0 @@
-import Description from "components/Description";
-import Heading from "components/form-components/Heading";
-import React from "react";
-import styled from "styled-components";
-import { IncidentContainerEvent } from "./IncidentPage";
-
-type Props = {
-  container: IncidentContainerEvent;
-  logs: string;
-};
-
-const ExpandedContainer: React.FC<Props> = ({ container, logs }) => {
-  return (
-    <StyledCard>
-      <MetadataContainer>
-        <Heading>Container: {container.container_name}</Heading>
-        <Description>
-          Container exited with code {container.exit_code}, {container.message}
-        </Description>
-        <Description>
-          The following are the container logs from this application instance:
-        </Description>
-        <LogContainer>
-          {logs ? <>{logs}</> : <>No logs available for this container.</>}
-        </LogContainer>
-      </MetadataContainer>
-    </StyledCard>
-  );
-};
-
-export default ExpandedContainer;
-
-const StyledCard = styled.div`
-  display: grid;
-  grid-row-gap: 15px;
-  grid-template-columns: 1;
-`;
-
-const MetadataContainer = styled.div`
-  margin-bottom: 3px;
-  border-radius: 6px;
-  background: #2e3135;
-  padding: 0 20px;
-  overflow-y: auto;
-  min-height: 100px;
-  font-size: 13px;
-  margin: 12px 0;
-`;
-
-const LogContainer = styled.div`
-  padding: 14px;
-  font-size: 13px;
-  background: #121318;
-  user-select: text;
-  overflow-wrap: break-word;
-  overflow-y: auto;
-  min-height: 55px;
-  color: #aaaabb;
-  height: 400px;
-  border-radius: 4px;
-  margin: 12px 0 24px 0;
-`;

+ 0 - 524
dashboard/src/main/home/cluster-dashboard/dashboard/incidents/IncidentPage.tsx

@@ -1,524 +0,0 @@
-import Loading from "components/Loading";
-import React, { useContext, useEffect, useMemo, useState } from "react";
-import { useParams } from "react-router";
-import styled from "styled-components";
-
-import loading from "assets/loading.gif";
-import { Drawer, withStyles } from "@material-ui/core";
-import EventDrawer from "./EventDrawer";
-import { useRouting } from "shared/routing";
-import api from "shared/api";
-import { Context } from "shared/Context";
-import DynamicLink from "components/DynamicLink";
-import Header from "components/expanded-object/Header";
-import { capitalize } from "shared/string_utils";
-import Description from "components/Description";
-import { dateFormatter } from "../../chart/JobRunTable";
-
-type IncidentPageParams = {
-  incident_id: string;
-};
-
-const IncidentPage = () => {
-  const { incident_id } = useParams<IncidentPageParams>();
-
-  const { currentProject, currentCluster } = useContext(Context);
-
-  const [incident, setIncident] = useState<Incident>(null);
-
-  const [isRefreshing, setIsRefreshing] = useState(false);
-  const [selectedEvent, setSelectedEvent] = useState<IncidentEvent>(null);
-
-  const { getQueryParam, pushFiltered } = useRouting();
-
-  useEffect(() => {
-    let isSubscribed = true;
-
-    setIncident(null);
-
-    api
-      .getIncidentById<Incident>(
-        "<token>",
-        { incident_id },
-        {
-          cluster_id: currentCluster.id,
-          project_id: currentProject.id,
-        }
-      )
-      .then((res) => {
-        if (!isSubscribed) {
-          return;
-        }
-
-        let incident = res.data;
-
-        incident.events = convertEventsTimestampsToMilliseconds(
-          incident.events
-        );
-
-        setIncident(incident);
-      });
-
-    return () => {
-      isSubscribed = false;
-    };
-  }, [incident_id]);
-
-  const refreshIncident = async () => {
-    setIsRefreshing(true);
-    try {
-      let incident = await api
-        .getIncidentById<Incident>(
-          "<token>",
-          { incident_id },
-          {
-            cluster_id: currentCluster.id,
-            project_id: currentProject.id,
-          }
-        )
-        .then((res) => res.data);
-
-      incident.events = convertEventsTimestampsToMilliseconds(incident.events);
-
-      setIncident(incident);
-    } catch (error) {
-    } finally {
-      setIsRefreshing(false);
-    }
-  };
-
-  const events = useMemo(() => {
-    return groupEventsByDate(incident?.events);
-  }, [incident]);
-
-  if (incident === null) {
-    return <Loading />;
-  }
-
-  const getBackLink = () => {
-    return (
-      getQueryParam("redirect_url") ||
-      "/cluster-dashboard?selected_tab=incidents"
-    );
-  };
-
-  const getResourceLink = () => {
-    let chartName = incident?.chart_name.split("-")[0] || "web";
-    let namespace = incident?.incident_id.split(":")[2] || "default";
-
-    if (chartName == "job") {
-      return `/jobs/${currentCluster.name}/${namespace}/${incident?.release_name}`;
-    }
-
-    return `/applications/${currentCluster.name}/${namespace}/${incident?.release_name}`;
-  };
-
-  return (
-    <StyledExpandedNodeView>
-      <HeaderWrapper>
-        <Header
-          last_updated={dateFormatter(incident.updated_at * 1000)}
-          back_link={getBackLink()}
-          name={"Incident for " + incident.release_name}
-          icon={"error"}
-          materialIconClass="material-icons"
-          inline_title_items={[
-            <ResourceLink
-              key="resource_link"
-              to={getResourceLink()}
-              target="_blank"
-              onClick={(e) => e.stopPropagation()}
-            >
-              {incident.release_name}
-              <i className="material-icons">open_in_new</i>
-            </ResourceLink>,
-          ]}
-          sub_title_items={[
-            <StatusContainer>
-              <Status>
-                <StatusDot status={incident.latest_state} />
-                {capitalize(incident.latest_state)}
-              </Status>
-              <StatusText>
-                - started {dateFormatter(incident.created_at * 1000)}, last
-                updated {dateFormatter(incident.updated_at * 1000)}
-              </StatusText>
-              <Description></Description>
-            </StatusContainer>,
-          ]}
-        />
-      </HeaderWrapper>
-      <LineBreak />
-      <BodyWrapper>
-        <RefreshButton onClick={refreshIncident} disabled={isRefreshing}>
-          {isRefreshing ? (
-            <>
-              <img src={loading} alt="loading icon" />
-            </>
-          ) : (
-            <i className="material-icons">refresh</i>
-          )}
-        </RefreshButton>
-        {Object.entries(events).map(([date, events_list]) => (
-          <React.Fragment key={date}>
-            <StyledDate>{date}</StyledDate>
-
-            {events_list.map((event) => {
-              return (
-                <StyledCard
-                  key={event.event_id}
-                  onClick={() => setSelectedEvent(event)}
-                  active={selectedEvent?.event_id === event.event_id}
-                >
-                  <ContentContainer>
-                    <Icon status={"normal"} className="material-icons-outlined">
-                      info
-                    </Icon>
-                    <EventInformation>
-                      <EventName>
-                        <Helper>Pod:</Helper>
-                        {event.pod_name}
-                      </EventName>
-                      <EventReason>{event.message}</EventReason>
-                    </EventInformation>
-                  </ContentContainer>
-                  <ActionContainer>
-                    <TimestampContainer>
-                      <TimestampIcon className="material-icons-outlined">
-                        access_time
-                      </TimestampIcon>
-                      <span>
-                        {Intl.DateTimeFormat([], {
-                          // @ts-ignore
-                          dateStyle: "full",
-                          timeStyle: "long",
-                        }).format(new Date(event.timestamp))}
-                      </span>
-                    </TimestampContainer>
-                  </ActionContainer>
-                </StyledCard>
-              );
-            })}
-          </React.Fragment>
-        ))}
-      </BodyWrapper>
-      <StyledDrawer
-        anchor="right"
-        open={!!selectedEvent}
-        onClose={() => setSelectedEvent(null)}
-      >
-        <EventDrawer
-          event={selectedEvent}
-          closeDrawer={() => setSelectedEvent(null)}
-        />
-      </StyledDrawer>
-    </StyledExpandedNodeView>
-  );
-};
-
-export default IncidentPage;
-
-const convertEventsTimestampsToMilliseconds = (events: IncidentEvent[]) => {
-  return events.map((e) => {
-    let newEvent = e;
-
-    newEvent.timestamp = newEvent.timestamp * 1000;
-
-    return newEvent;
-  });
-};
-
-const groupEventsByDate = (
-  events: IncidentEvent[]
-): { [key: string]: IncidentEvent[] } => {
-  if (!events?.length) {
-    return {};
-  }
-
-  return events.reduce<{ [key: string]: IncidentEvent[] }>(
-    (accumulator, current) => {
-      // @ts-ignore
-      const date = Intl.DateTimeFormat([], { dateStyle: "full" }).format(
-        new Date(current.timestamp)
-      );
-
-      if (accumulator[date]?.length) {
-        accumulator[date].push(current);
-      } else {
-        accumulator[date] = [current];
-      }
-
-      return accumulator;
-    },
-    {}
-  );
-};
-
-export type IncidentContainerEvent = {
-  container_name: string;
-  reason: string;
-  message: string;
-  exit_code: number;
-  log_id: string;
-};
-
-export type IncidentEvent = {
-  event_id: string;
-  pod_name: string;
-  cluster: string;
-  namespace: string;
-  release_name: string;
-  release_type: string;
-  timestamp: number;
-  pod_phase: string;
-  pod_status: string;
-  reason: string;
-  message: string;
-  container_events: {
-    [key: string]: IncidentContainerEvent;
-  };
-};
-
-export type Incident = {
-  incident_id: string;
-  release_name: string; // eg: "sample-web"
-  latest_state: string; // "ONGOING" or "RESOLVED"
-  latest_reason: string; // eg: "Out of memory",
-  latest_message: string; // eg: "Application crash due to out of memory issue"
-  events: IncidentEvent[];
-  created_at: number;
-  updated_at: number;
-  chart_name: string;
-};
-
-const RefreshButton = styled.button`
-  position: absolute;
-  right: 0px;
-  top: 20px;
-  border: 1px solid #ffffff00;
-  border-radius: 50%;
-  background: inherit;
-  color: #ffffff;
-  cursor: pointer;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  width: 35px;
-  height: 35px;
-
-  > i {
-    font-size: 20px;
-  }
-  > img {
-    width: 20px;
-    height: 20px;
-  }
-
-  :hover {
-    color: #ffffff88;
-    border-color: #ffffff88;
-  }
-`;
-
-const LineBreak = styled.div`
-  width: calc(100% - 0px);
-  height: 1px;
-  background: #494b4f;
-  margin: 10px 0px 35px;
-`;
-
-const BodyWrapper = styled.div`
-  position: relative;
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-`;
-
-const HeaderWrapper = styled.div`
-  position: relative;
-`;
-
-const StyledExpandedNodeView = styled.div`
-  width: 100%;
-  z-index: 0;
-  animation: fadeIn 0.3s;
-  animation-timing-function: ease-out;
-  animation-fill-mode: forwards;
-  display: flex;
-  overflow-y: auto;
-  padding-bottom: 120px;
-  flex-direction: column;
-  overflow: visible;
-
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
-const StyledDate = styled.div`
-  font-size: 18px;
-  font-weight: bold;
-  color: #ffffff;
-  margin-bottom: 20px;
-  margin-top: 20px;
-  :first-child {
-    margin-top: 0px;
-  }
-`;
-
-const StyledCard = styled.div<{ active: boolean }>`
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  border: 1px solid ${({ active }) => (active ? "#819bfd" : "#ffffff44")};
-  background: #ffffff08;
-  margin-bottom: 5px;
-  border-radius: 10px;
-  padding: 14px;
-  overflow: hidden;
-  height: 80px;
-  font-size: 13px;
-  cursor: pointer;
-  :hover {
-    background: #ffffff11;
-    border: 1px solid ${({ active }) => (active ? "#819bfd" : "#ffffff66")};
-  }
-  animation: fadeIn 0.5s;
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-  :not(:last-child) {
-    margin-bottom: 15px;
-  }
-`;
-
-const ContentContainer = styled.div`
-  display: flex;
-  height: 100%;
-  width: 100%;
-  align-items: center;
-`;
-
-const Icon = styled.span<{ status: "critical" | "normal" }>`
-  font-size: 20px;
-  margin-left: 10px;
-  margin-right: 20px;
-  color: ${({ status }) => (status === "critical" ? "#ff385d" : "#aaaabb")};
-`;
-
-const EventInformation = styled.div`
-  display: flex;
-  flex-direction: column;
-  justify-content: space-around;
-  height: 100%;
-`;
-
-const EventName = styled.div`
-  font-family: "Work Sans", sans-serif;
-  font-weight: 500;
-  color: #ffffff;
-`;
-
-const Helper = styled.span`
-  text-transform: capitalize;
-  color: #ffffff44;
-  margin-right: 5px;
-`;
-
-const EventReason = styled.div`
-  font-family: "Work Sans", sans-serif;
-  color: #aaaabb;
-  margin-top: 5px;
-`;
-
-const ActionContainer = styled.div`
-  display: flex;
-  align-items: center;
-  white-space: nowrap;
-  height: 100%;
-`;
-
-const TimestampContainer = styled.div`
-  display: flex;
-  white-space: nowrap;
-  align-items: center;
-  justify-self: flex-end;
-  color: #ffffff55;
-  margin-right: 10px;
-  font-size: 13px;
-  min-width: 130px;
-  justify-content: space-between;
-`;
-
-const TimestampIcon = styled.span`
-  margin-right: 7px;
-  font-size: 18px;
-`;
-
-const StyledDrawer = withStyles({
-  paperAnchorRight: {
-    background: "#202227",
-    minWidth: "700px",
-  },
-})(Drawer);
-
-const ResourceLink = styled(DynamicLink)`
-  font-size: 13px;
-  font-weight: 400;
-  margin-left: 20px;
-  color: #aaaabb;
-  display: flex;
-  align-items: center;
-
-  :hover {
-    text-decoration: underline;
-    color: white;
-  }
-
-  > i {
-    margin-left: 7px;
-    font-size: 17px;
-  }
-`;
-
-const Status = styled.span`
-  font-size: 13px;
-  display: flex;
-  align-items: center;
-  margin-left: 1px;
-  min-height: 17px;
-  color: #a7a6bb;
-  margin-right: 6px;
-`;
-
-const StatusDot = styled.div`
-  width: 8px;
-  height: 8px;
-  background: ${(props: { status: string }) =>
-    props.status === "ONGOING" ? "#ed5f85" : "#4797ff"};
-  border-radius: 20px;
-  margin-left: 3px;
-  margin-right: 15px;
-`;
-
-const StatusContainer = styled.div`
-  display: flex;
-  align-items: center;
-  font-size: 13px;
-  color: #aaaabb;
-  width: 100%;
-`;
-
-const StatusText = styled.div`
-  width: 100%;
-`;

+ 0 - 215
dashboard/src/main/home/cluster-dashboard/dashboard/incidents/IncidentsTab.tsx

@@ -1,215 +0,0 @@
-import Loading from "components/Loading";
-import React, { useContext, useEffect, useState } from "react";
-import api from "shared/api";
-import { Context } from "shared/Context";
-import styled from "styled-components";
-import IncidentsTable from "./IncidentsTable";
-
-export type DetectAgentResponse = {
-  version: string;
-};
-
-const IncidentsTab = () => {
-  const { currentProject, currentCluster } = useContext(Context);
-  const [isAgentInstalled, setIsAgentInstalled] = useState(false);
-  const [isAgentOutdated, setIsAgentOutdated] = useState(false);
-  const [isLoading, setIsLoading] = useState(true);
-
-  useEffect(() => {
-    api
-      .detectPorterAgent<DetectAgentResponse>(
-        "<token>",
-        {},
-        {
-          project_id: currentProject.id,
-          cluster_id: currentCluster.id,
-        }
-      )
-      .then((res) => res.data)
-      .then((data) => {
-        if (data.version === "v1") {
-          setIsAgentInstalled(true);
-          setIsAgentOutdated(true);
-        } else {
-          setIsAgentInstalled(true);
-          setIsAgentOutdated(false);
-        }
-      })
-      .catch(() => {
-        setIsAgentInstalled(false);
-      })
-      .finally(() => {
-        setIsLoading(false);
-      });
-  }, []);
-
-  const upgradeAgent = async () => {
-    const project_id = currentProject?.id;
-    const cluster_id = currentCluster?.id;
-    try {
-      await api.upgradePorterAgent(
-        "<token>",
-        {},
-        {
-          project_id,
-          cluster_id,
-        }
-      );
-      setIsAgentOutdated(false);
-    } catch (err) {
-      setIsAgentOutdated(true);
-    }
-  };
-
-  const installAgent = async () => {
-    const project_id = currentProject?.id;
-    const cluster_id = currentCluster?.id;
-
-    api
-      .installPorterAgent("<token>", {}, { project_id, cluster_id })
-      .then(() => {
-        setIsAgentInstalled(true);
-      })
-      .catch(() => {
-        setIsAgentInstalled(false);
-      });
-  };
-
-  const triggerInstall = () => {
-    if (isAgentOutdated) {
-      upgradeAgent();
-      return;
-    }
-
-    installAgent();
-  };
-
-  if (isLoading) {
-    return (
-      <StyledCard>
-        <Loading height="200px" />
-      </StyledCard>
-    );
-  }
-
-  if (!isAgentInstalled || isAgentOutdated) {
-    return (
-      <Placeholder>
-        <AgentButtonContainer>
-          <Header>Incident detection is not enabled on this cluster.</Header>
-          <Subheader>
-            In order to view incidents, you must enable incident detection on
-            this cluster.
-          </Subheader>
-          <InstallPorterAgentButton onClick={() => triggerInstall()}>
-            <i className="material-icons">add</i> Enable Incident Detection
-          </InstallPorterAgentButton>
-        </AgentButtonContainer>
-      </Placeholder>
-    );
-  }
-
-  return (
-    <StyledCard>
-      <IncidentsTable />
-    </StyledCard>
-  );
-};
-
-export default IncidentsTab;
-
-const StyledCard = styled.div`
-  margin-top: 35px;
-  background: #26282f;
-  padding: 14px;
-  border-radius: 8px;
-  box-shadow: 0 4px 15px 0px #00000055;
-  position: relative;
-  border: 2px solid #9eb4ff00;
-  width: 100%;
-  :not(:last-child) {
-    margin-bottom: 25px;
-  }
-`;
-
-const InstallPorterAgentButton = styled.button`
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  font-size: 13px;
-  width: 200px;
-  cursor: pointer;
-  font-family: "Work Sans", sans-serif;
-  border: none;
-  border-radius: 5px;
-  color: white;
-  height: 35px;
-  padding: 0px 8px;
-  padding-bottom: 1px;
-  margin-top: 20px;
-  font-weight: 500;
-  padding-right: 15px;
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  box-shadow: 0 5px 8px 0px #00000010;
-  cursor: ${(props: { disabled?: boolean }) =>
-    props.disabled ? "not-allowed" : "pointer"};
-  background: ${(props: { disabled?: boolean }) =>
-    props.disabled ? "#aaaabbee" : "#5561C0"};
-  :hover {
-    filter: ${(props) => (!props.disabled ? "brightness(120%)" : "")};
-  }
-  > i {
-    color: white;
-    width: 18px;
-    height: 18px;
-    font-weight: 600;
-    font-size: 12px;
-    border-radius: 20px;
-    display: flex;
-    align-items: center;
-    margin-right: 5px;
-    justify-content: center;
-  }
-`;
-
-const Placeholder = styled.div`
-  padding: 30px;
-  margin-top: 35px;
-  padding-bottom: 40px;
-  font-size: 13px;
-  color: #ffffff44;
-  min-height: 400px;
-  height: 50vh;
-  background: #ffffff11;
-  border-radius: 8px;
-  display: flex;
-  align-items: left;
-  justify-content: center;
-  flex-direction: column;
-
-  > i {
-    font-size: 18px;
-    margin-right: 8px;
-  }
-`;
-
-const AgentButtonContainer = styled.div`
-  display: flex;
-  align-items: left;
-  justify-content: center;
-  flex-direction: column;
-  width: 500px;
-  margin: 0 auto;
-`;
-
-const Header = styled.div`
-  font-weight: 500;
-  color: #aaaabb;
-  font-size: 16px;
-  margin-bottom: 15px;
-`;
-
-const Subheader = styled.div``;

+ 0 - 209
dashboard/src/main/home/cluster-dashboard/dashboard/incidents/IncidentsTable.tsx

@@ -1,209 +0,0 @@
-import Table from "components/Table";
-import React, { useContext, useEffect, useMemo, useState } from "react";
-import { Column } from "react-table";
-import api from "shared/api";
-import { Context } from "shared/Context";
-import { hardcodedIcons } from "shared/hardcodedNameDict";
-import { useRouting } from "shared/routing";
-import { capitalize } from "shared/string_utils";
-import styled from "styled-components";
-import { dateFormatter } from "../../chart/JobRunTable";
-import { Incident } from "./IncidentPage";
-
-export type IncidentsWithoutEvents = Omit<
-  Incident,
-  "events" | "incident_id"
-> & {
-  id: string;
-};
-
-const IncidentsTable = () => {
-  const { currentCluster, currentProject, setCurrentError } = useContext(
-    Context
-  );
-  const { pushFiltered } = useRouting();
-
-  const [incidents, setIncidents] = useState<IncidentsWithoutEvents[]>(null);
-  const [hasError, setHasError] = useState(false);
-
-  const [isRefreshing, setIsRefreshing] = useState(false);
-
-  useEffect(() => {
-    let isSubscribed = true;
-    setIncidents(null);
-    setHasError(false);
-
-    api
-      .getIncidents<{ incidents: IncidentsWithoutEvents[] }>(
-        "<token>",
-        {},
-        {
-          project_id: currentProject.id,
-          cluster_id: currentCluster.id,
-        }
-      )
-      .then((res) => {
-        if (!isSubscribed) {
-          return;
-        }
-
-        setIncidents(res.data?.incidents || []);
-      })
-      .catch((err) => {
-        setHasError(true);
-        setCurrentError(err);
-      });
-
-    return () => {
-      isSubscribed = false;
-    };
-  }, [currentCluster, currentProject]);
-
-  const refreshIncidents = async () => {
-    setIsRefreshing(true);
-    try {
-      const incidents = await api
-        .getIncidents<{ incidents: IncidentsWithoutEvents[] }>(
-          "<token>",
-          {},
-          {
-            project_id: currentProject.id,
-            cluster_id: currentCluster.id,
-          }
-        )
-        .then((res) => res.data?.incidents || []);
-
-      setIncidents(incidents);
-    } catch (err) {
-      setHasError(true);
-      setCurrentError(err);
-    } finally {
-      setIsRefreshing(false);
-    }
-  };
-
-  const columns = useMemo(() => {
-    return [
-      {
-        Header: "Release",
-        accessor: "release_name",
-        Cell: ({ row }) => {
-          let original = row.original;
-
-          let chartName = original?.chart_name.split("-")[0] || "web";
-
-          return (
-            <KindContainer>
-              <Icon src={hardcodedIcons[chartName] || hardcodedIcons["web"]} />
-              <Kind>{original.release_name}</Kind>
-            </KindContainer>
-          );
-        },
-      },
-      {
-        Header: "Status",
-        accessor: "latest_state",
-        Cell: ({ row }) => {
-          let original = row.original;
-
-          return (
-            <Status>
-              <StatusDot status={original.latest_state} />
-              {capitalize(original.latest_state)}
-            </Status>
-          );
-        },
-      },
-      {
-        Header: "Message",
-        accessor: "latest_message",
-        Cell: ({ row }) => {
-          let original = row.original;
-
-          return <Message>{original.latest_message}</Message>;
-        },
-      },
-      {
-        Header: "Started",
-        accessor: "created_at",
-        Cell: ({ row }) => {
-          let original = row.original;
-
-          return dateFormatter(original.created_at * 1000);
-        },
-      },
-      {
-        Header: "Last Updated",
-        accessor: "updated_at",
-        Cell: ({ row }) => {
-          let original = row.original;
-
-          return dateFormatter(original.updated_at * 1000);
-        },
-      },
-    ] as Column<IncidentsWithoutEvents>[];
-  }, []);
-
-  const data = useMemo(() => {
-    if (!incidents) {
-      return [];
-    }
-    return incidents;
-  }, [incidents]);
-
-  return (
-    <Table
-      columns={columns}
-      data={data}
-      isLoading={incidents === null}
-      onRowClick={(row: any) => {
-        pushFiltered(`/cluster-dashboard/incidents/${row?.original?.id}`, []);
-      }}
-      hasError={hasError}
-      onRefresh={refreshIncidents}
-      isRefreshing={isRefreshing}
-    />
-  );
-};
-
-export default IncidentsTable;
-
-const KindContainer = styled.div`
-  display: flex;
-  align-items: center;
-  min-width: 200px;
-`;
-
-const Kind = styled.div`
-  margin-left: 8px;
-`;
-
-const Icon = styled.img`
-  height: 20px;
-`;
-
-const Status = styled.span`
-  font-size: 13px;
-  display: flex;
-  align-items: center;
-  margin-left: 1px;
-  min-height: 17px;
-  color: #a7a6bb;
-`;
-
-const StatusDot = styled.div`
-  width: 8px;
-  height: 8px;
-  background: ${(props: { status: string }) =>
-    props.status === "ONGOING" ? "#ed5f85" : "#4797ff"};
-  border-radius: 20px;
-  margin-left: 3px;
-  margin-right: 15px;
-`;
-
-const Message = styled.div`
-  white-space: nowrap;
-  overflow-x: hidden;
-  text-overflow: ellipsis;
-  max-width: 500px;
-`;

+ 2 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -551,7 +551,8 @@ const ExpandedChart: React.FC<Props> = (props) => {
       currentChart.chart.metadata.home === "https://getporter.dev/" &&
       (currentChart.chart.metadata.name === "web" ||
         currentChart.chart.metadata.name === "worker" ||
-        currentChart.chart.metadata.name === "job")
+        currentChart.chart.metadata.name === "job") &&
+      currentCluster.agent_integration_enabled
     ) {
       leftTabOptions.push({ label: "Events", value: "events" });
 

+ 0 - 217
dashboard/src/main/home/cluster-dashboard/expanded-chart/incidents/EventsTab.tsx

@@ -1,217 +0,0 @@
-import Loading from "components/Loading";
-import React, { useContext, useEffect, useState } from "react";
-import api from "shared/api";
-import { Context } from "shared/Context";
-import styled from "styled-components";
-import IncidentsTable from "./IncidentsTable";
-
-export type DetectAgentResponse = {
-  version: string;
-};
-
-const IncidentsTab = (props: {
-  releaseName: string;
-  namespace: string;
-}): JSX.Element => {
-  const { currentProject, currentCluster } = useContext(Context);
-  const [isAgentInstalled, setIsAgentInstalled] = useState(false);
-  const [isAgentOutdated, setIsAgentOutdated] = useState(false);
-  const [isLoading, setIsLoading] = useState(true);
-
-  useEffect(() => {
-    api
-      .detectPorterAgent<DetectAgentResponse>(
-        "<token>",
-        {},
-        {
-          project_id: currentProject.id,
-          cluster_id: currentCluster.id,
-        }
-      )
-      .then((res) => res.data)
-      .then((data) => {
-        if (data.version === "v1") {
-          setIsAgentInstalled(true);
-          setIsAgentOutdated(true);
-        } else {
-          setIsAgentInstalled(true);
-          setIsAgentOutdated(false);
-        }
-      })
-      .catch(() => {
-        setIsAgentInstalled(false);
-      })
-      .finally(() => {
-        setIsLoading(false);
-      });
-  }, []);
-
-  const upgradeAgent = async () => {
-    const project_id = currentProject?.id;
-    const cluster_id = currentCluster?.id;
-    try {
-      await api.upgradePorterAgent(
-        "<token>",
-        {},
-        {
-          project_id,
-          cluster_id,
-        }
-      );
-      setIsAgentOutdated(false);
-    } catch (err) {
-      setIsAgentOutdated(true);
-    }
-  };
-
-  const installAgent = async () => {
-    const project_id = currentProject?.id;
-    const cluster_id = currentCluster?.id;
-
-    api
-      .installPorterAgent("<token>", {}, { project_id, cluster_id })
-      .then(() => {
-        setIsAgentInstalled(true);
-      })
-      .catch(() => {
-        setIsAgentInstalled(false);
-      });
-  };
-
-  const triggerInstall = () => {
-    if (isAgentOutdated) {
-      upgradeAgent();
-      return;
-    }
-
-    installAgent();
-  };
-
-  if (isLoading) {
-    return (
-      <StyledCard>
-        <Loading height="200px" />
-      </StyledCard>
-    );
-  }
-
-  if (!isAgentInstalled || isAgentOutdated) {
-    return (
-      <Placeholder>
-        <AgentButtonContainer>
-          <Header>Incident detection is not enabled on this cluster.</Header>
-          <Subheader>
-            In order to view incidents, you must enable incident detection on
-            this cluster.
-          </Subheader>
-          <InstallPorterAgentButton onClick={() => triggerInstall()}>
-            <i className="material-icons">add</i> Enable Incident Detection
-          </InstallPorterAgentButton>
-        </AgentButtonContainer>
-      </Placeholder>
-    );
-  }
-
-  return (
-    <StyledCard>
-      <IncidentsTable {...props} />
-    </StyledCard>
-  );
-};
-
-export default IncidentsTab;
-
-const StyledCard = styled.div`
-  background: #26282f;
-  padding: 14px;
-  border-radius: 8px;
-  box-shadow: 0 4px 15px 0px #00000055;
-  position: relative;
-  border: 2px solid #9eb4ff00;
-  width: 100%;
-  :not(:last-child) {
-    margin-bottom: 25px;
-  }
-`;
-
-const InstallPorterAgentButton = styled.button`
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  font-size: 13px;
-  width: 200px;
-  cursor: pointer;
-  font-family: "Work Sans", sans-serif;
-  border: none;
-  border-radius: 5px;
-  color: white;
-  height: 35px;
-  padding: 0px 8px;
-  padding-bottom: 1px;
-  margin-top: 20px;
-  font-weight: 500;
-  padding-right: 15px;
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-  box-shadow: 0 5px 8px 0px #00000010;
-  cursor: ${(props: { disabled?: boolean }) =>
-    props.disabled ? "not-allowed" : "pointer"};
-  background: ${(props: { disabled?: boolean }) =>
-    props.disabled ? "#aaaabbee" : "#5561C0"};
-  :hover {
-    filter: ${(props) => (!props.disabled ? "brightness(120%)" : "")};
-  }
-  > i {
-    color: white;
-    width: 18px;
-    height: 18px;
-    font-weight: 600;
-    font-size: 12px;
-    border-radius: 20px;
-    display: flex;
-    align-items: center;
-    margin-right: 5px;
-    justify-content: center;
-  }
-`;
-
-const Placeholder = styled.div`
-  padding: 30px;
-  margin-top: 35px;
-  padding-bottom: 40px;
-  font-size: 13px;
-  color: #ffffff44;
-  min-height: 400px;
-  height: 50vh;
-  background: #ffffff11;
-  border-radius: 8px;
-  display: flex;
-  align-items: left;
-  justify-content: center;
-  flex-direction: column;
-
-  > i {
-    font-size: 18px;
-    margin-right: 8px;
-  }
-`;
-
-const AgentButtonContainer = styled.div`
-  display: flex;
-  align-items: left;
-  justify-content: center;
-  flex-direction: column;
-  width: 500px;
-  margin: 0 auto;
-`;
-
-const Header = styled.div`
-  font-weight: 500;
-  color: #aaaabb;
-  font-size: 16px;
-  margin-bottom: 15px;
-`;
-
-const Subheader = styled.div``;

+ 0 - 217
dashboard/src/main/home/cluster-dashboard/expanded-chart/incidents/IncidentsTable.tsx

@@ -1,217 +0,0 @@
-import Table from "components/Table";
-import React, { useContext, useEffect, useMemo, useState } from "react";
-import { useLocation } from "react-router";
-import { Column } from "react-table";
-import api from "shared/api";
-import { Context } from "shared/Context";
-import { hardcodedIcons } from "shared/hardcodedNameDict";
-import { useRouting } from "shared/routing";
-import { capitalize } from "shared/string_utils";
-import styled from "styled-components";
-import { dateFormatter } from "../../chart/JobRunTable";
-import { IncidentsWithoutEvents } from "../../dashboard/incidents/IncidentsTable";
-
-const IncidentsTable = ({
-  releaseName,
-  namespace,
-}: {
-  releaseName: string;
-  namespace: string;
-}) => {
-  const { currentCluster, currentProject, setCurrentError } = useContext(
-    Context
-  );
-  const { pushFiltered } = useRouting();
-  const location = useLocation();
-
-  const [incidents, setIncidents] = useState<IncidentsWithoutEvents[]>(null);
-  const [hasError, setHasError] = useState(false);
-  const [isRefreshing, setIsRefreshing] = useState(false);
-
-  useEffect(() => {
-    let isSubscribed = true;
-    setIncidents(null);
-    setHasError(false);
-
-    api
-      .getIncidentsByReleaseName<{ incidents: IncidentsWithoutEvents[] }>(
-        "<token>",
-        {
-          namespace: namespace,
-          release_name: releaseName,
-        },
-        {
-          project_id: currentProject.id,
-          cluster_id: currentCluster.id,
-        }
-      )
-      .then((res) => {
-        if (!isSubscribed) {
-          return;
-        }
-
-        setIncidents(res.data?.incidents || []);
-      })
-      .catch((err) => {
-        setHasError(true);
-        setCurrentError(err);
-      });
-
-    return () => {
-      isSubscribed = false;
-    };
-  }, [currentCluster, currentProject]);
-
-  const refreshIncidents = async () => {
-    setIsRefreshing(true);
-    try {
-      const incidents = await api
-        .getIncidentsByReleaseName<{ incidents: IncidentsWithoutEvents[] }>(
-          "<token>",
-          {
-            namespace: namespace,
-            release_name: releaseName,
-          },
-          {
-            project_id: currentProject.id,
-            cluster_id: currentCluster.id,
-          }
-        )
-        .then((res) => res.data?.incidents || []);
-
-      setIncidents(incidents);
-    } catch (err) {
-      setHasError(true);
-      setCurrentError(err);
-    } finally {
-      setIsRefreshing(false);
-    }
-  };
-
-  const columns = useMemo(() => {
-    return [
-      {
-        Header: "Status",
-        accessor: "latest_state",
-        Cell: ({ row }) => {
-          let original = row.original;
-
-          return (
-            <Status>
-              <StatusDot status={original.latest_state} />
-              {capitalize(original.latest_state)}
-            </Status>
-          );
-        },
-      },
-      {
-        Header: "Message",
-        accessor: "latest_message",
-        Cell: ({ row }) => {
-          let original = row.original;
-
-          return <Message>{original.latest_message}</Message>;
-        },
-      },
-      {
-        Header: "Started",
-        accessor: "created_at",
-        Cell: ({ row }) => {
-          let original = row.original;
-
-          return dateFormatter(original.created_at * 1000);
-        },
-      },
-      {
-        Header: "Last Updated",
-        accessor: "updated_at",
-        Cell: ({ row }) => {
-          let original = row.original;
-
-          return dateFormatter(original.updated_at * 1000);
-        },
-      },
-    ] as Column<IncidentsWithoutEvents>[];
-  }, []);
-
-  const data = useMemo(() => {
-    if (!incidents) {
-      return [];
-    }
-    return incidents;
-  }, [incidents]);
-
-  return (
-    <Table
-      columns={columns}
-      data={data}
-      isLoading={incidents === null}
-      onRowClick={(row: any) => {
-        pushFiltered(`/cluster-dashboard/incidents/${row?.original?.id}/`, [], {
-          redirect_url: location.pathname,
-        });
-      }}
-      hasError={hasError}
-      onRefresh={refreshIncidents}
-      isRefreshing={isRefreshing}
-    />
-  );
-};
-
-export default IncidentsTable;
-
-const TableWrapper = styled.div``;
-
-const StyledCard = styled.div`
-  background: #26282f;
-  padding: 14px;
-  border-radius: 8px;
-  box-shadow: 0 4px 15px 0px #00000055;
-  position: relative;
-  border: 2px solid #9eb4ff00;
-  width: 100%;
-  height: 100%;
-  :not(:last-child) {
-    margin-bottom: 25px;
-  }
-`;
-
-const KindContainer = styled.div`
-  display: flex;
-  align-items: center;
-  min-width: 200px;
-`;
-
-const Kind = styled.div`
-  margin-left: 8px;
-`;
-
-const Icon = styled.img`
-  height: 20px;
-`;
-
-const Status = styled.span`
-  font-size: 13px;
-  display: flex;
-  align-items: center;
-  margin-left: 1px;
-  min-height: 17px;
-  color: #a7a6bb;
-`;
-
-const StatusDot = styled.div`
-  width: 8px;
-  height: 8px;
-  background: ${(props: { status: string }) =>
-    props.status === "ONGOING" ? "#ed5f85" : "#4797ff"};
-  border-radius: 20px;
-  margin-left: 3px;
-  margin-right: 15px;
-`;
-
-const Message = styled.div`
-  white-space: nowrap;
-  overflow-x: hidden;
-  text-overflow: ellipsis;
-  max-width: 500px;
-`;

+ 28 - 21
dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/ExpandedJobRun.tsx

@@ -5,7 +5,7 @@ import styled from "styled-components";
 import leftArrow from "assets/left-arrow.svg";
 import KeyValueArray from "components/form-components/KeyValueArray";
 import Loading from "components/Loading";
-import TabRegion from "components/TabRegion";
+import TabRegion, { TabOption } from "components/TabRegion";
 import TitleSection from "components/TitleSection";
 import api from "shared/api";
 import { Context } from "shared/Context";
@@ -77,7 +77,7 @@ const ExpandedJobRun = ({
   );
   const [currentTab, setCurrentTab] = useState<
     "events" | "logs" | "metrics" | "config" | string
-  >("events");
+  >(currentCluster.agent_integration_enabled ? "events" : "logs");
   const [pods, setPods] = useState<any>(null);
   const [isLoading, setIsLoading] = useState(true);
   const { pushQueryParams } = useRouting();
@@ -176,7 +176,7 @@ const ExpandedJobRun = ({
   };
 
   const renderLogsSection = () => {
-    if (useDeprecatedLogs) {
+    if (useDeprecatedLogs || !currentCluster.agent_integration_enabled) {
       return (
         <JobLogsWrapper>
           <Logs
@@ -216,6 +216,30 @@ const ExpandedJobRun = ({
     return <Loading />;
   }
 
+  let options: TabOption[] = [];
+
+  if (currentCluster.agent_integration_enabled) {
+    options.push({
+      label: "Events",
+      value: "events",
+    });
+  }
+
+  options.push(
+    {
+      label: "Logs",
+      value: "logs",
+    },
+    {
+      label: "Metrics",
+      value: "metrics",
+    },
+    {
+      label: "Config",
+      value: "config",
+    }
+  );
+
   return (
     <StyledExpandedChart>
       <BreadcrumbRow>
@@ -248,24 +272,7 @@ const ExpandedJobRun = ({
         <TabRegion
           currentTab={currentTab}
           setCurrentTab={(x: string) => setCurrentTab(x)}
-          options={[
-            {
-              label: "Events",
-              value: "events",
-            },
-            {
-              label: "Logs",
-              value: "logs",
-            },
-            {
-              label: "Metrics",
-              value: "metrics",
-            },
-            {
-              label: "Config",
-              value: "config",
-            },
-          ]}
+          options={options}
         >
           {currentTab === "events" && renderEventsSection()}
           {currentTab === "logs" && renderLogsSection()}

+ 4 - 3
dashboard/src/shared/api.tsx

@@ -96,10 +96,11 @@ const overwriteAWSIntegration = baseApi<
   return `/api/projects/${pathParams.project_id}/integrations/aws/overwrite`;
 });
 
-const updateClusterName = baseApi<
+const updateCluster = baseApi<
   {
-    name: string;
+    name?: string;
     aws_cluster_id?: string;
+    agent_integration_enabled?: boolean;
   },
   {
     project_id: number;
@@ -2252,7 +2253,7 @@ export default {
   getGitlabIntegration,
   createAWSIntegration,
   overwriteAWSIntegration,
-  updateClusterName,
+  updateCluster,
   createAzureIntegration,
   createGitlabIntegration,
   createEmailVerification,

+ 1 - 0
dashboard/src/shared/types.tsx

@@ -5,6 +5,7 @@ export interface ClusterType {
   name: string;
   server: string;
   service_account_id: number;
+  agent_integration_enabled: boolean;
   infra_id?: number;
   service?: string;
   aws_integration_id?: number;

+ 12 - 8
internal/models/cluster.go

@@ -36,6 +36,9 @@ type Cluster struct {
 	// The project that this integration belongs to
 	ProjectID uint `json:"project_id"`
 
+	// Whether or not the Porter agent integration is enabled on the cluster
+	AgentIntegrationEnabled bool
+
 	// Name of the cluster
 	Name string `json:"name"`
 
@@ -95,14 +98,15 @@ func (c *Cluster) ToClusterType() *types.Cluster {
 	}
 
 	return &types.Cluster{
-		ID:               c.ID,
-		ProjectID:        c.ProjectID,
-		Name:             c.Name,
-		Server:           c.Server,
-		Service:          serv,
-		InfraID:          c.InfraID,
-		AWSIntegrationID: c.AWSIntegrationID,
-		AWSClusterID:     c.AWSClusterID,
+		ID:                      c.ID,
+		ProjectID:               c.ProjectID,
+		Name:                    c.Name,
+		Server:                  c.Server,
+		Service:                 serv,
+		AgentIntegrationEnabled: c.AgentIntegrationEnabled,
+		InfraID:                 c.InfraID,
+		AWSIntegrationID:        c.AWSIntegrationID,
+		AWSClusterID:            c.AWSClusterID,
 	}
 }