瀏覽代碼

modify agent installation flow

Alexander Belanger 3 年之前
父節點
當前提交
588b0f0334

+ 3 - 45
api/server/handlers/cluster/detect_agent_installed.go

@@ -2,18 +2,15 @@ package cluster
 
 import (
 	"errors"
-	"fmt"
 	"net/http"
 	"strings"
 
-	"github.com/Masterminds/semver/v3"
 	"github.com/porter-dev/porter/api/server/authz"
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
-	"github.com/porter-dev/porter/internal/helm/loader"
 	"github.com/porter-dev/porter/internal/kubernetes"
 	"github.com/porter-dev/porter/internal/models"
 	v1 "k8s.io/api/apps/v1"
@@ -60,46 +57,17 @@ func (c *DetectAgentInstalledHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 		ShouldUpgrade: false,
 	}
 
-	res.LatestVersion, err = getLatestAgentVersion(c.Config().ServerConf.DefaultAddonHelmRepoURL)
-
-	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
-	if res.LatestVersion != res.Version {
-		versionSem, err := semver.NewConstraint(fmt.Sprintf("> %s", strings.TrimPrefix(res.Version, "v")))
-
-		if err != nil {
-			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-			return
-		}
-
-		latestVersionSem, err := semver.NewVersion(strings.TrimPrefix(res.LatestVersion, "v"))
-
-		if err != nil {
-			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-			return
-		}
-
-		if versionSem.Check(latestVersionSem) {
-			res.ShouldUpgrade = true
-		}
+	if res.Version != "v3" {
+		res.ShouldUpgrade = true
 	}
 
 	res.Version = "v" + strings.TrimPrefix(res.Version, "v")
-	res.LatestVersion = "v" + strings.TrimPrefix(res.LatestVersion, "v")
 
 	c.WriteResult(w, r, res)
 }
 
 func getAgentVersionFromDeployment(depl *v1.Deployment) string {
-	versionAnn, ok := depl.ObjectMeta.Annotations["porter.run/agent-version"]
-
-	if !ok {
-		// fallback to porter agent v2 annotation
-		versionAnn = depl.ObjectMeta.Annotations["porter.run/agent-major-version"]
-	}
+	versionAnn := depl.ObjectMeta.Annotations["porter.run/agent-major-version"]
 
 	if versionAnn != "" {
 		return versionAnn
@@ -107,13 +75,3 @@ func getAgentVersionFromDeployment(depl *v1.Deployment) string {
 
 	return "v1"
 }
-
-func getLatestAgentVersion(helmRepoURL string) (string, error) {
-	chart, err := loader.LoadChartPublic(helmRepoURL, "porter-agent", "")
-
-	if err != nil {
-		return "", fmt.Errorf("could not load latest porter-agent chart: %w", err)
-	}
-
-	return chart.Metadata.Version, nil
-}

+ 10 - 16
api/server/handlers/cluster/install_agent.go

@@ -53,14 +53,14 @@ func (c *InstallAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	err = checkAndDeleteOlderAgent(k8sAgent)
+	helmAgent, err := c.GetHelmAgent(r, cluster, "porter-agent-system")
 
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
-	helmAgent, err := c.GetHelmAgent(r, cluster, "porter-agent-system")
+	err = checkAndDeleteOlderAgent(k8sAgent, helmAgent)
 
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
@@ -157,7 +157,7 @@ func (c *InstallAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	w.WriteHeader(http.StatusOK)
 }
 
-func checkAndDeleteOlderAgent(k8sAgent *kubernetes.Agent) error {
+func checkAndDeleteOlderAgent(k8sAgent *kubernetes.Agent, helmAgent *helm.Agent) error {
 	namespaceList, err := k8sAgent.Clientset.CoreV1().Namespaces().List(context.Background(), v1.ListOptions{})
 
 	if err != nil {
@@ -177,23 +177,17 @@ func checkAndDeleteOlderAgent(k8sAgent *kubernetes.Agent) error {
 		return nil
 	}
 
-	podList, err := k8sAgent.Clientset.CoreV1().Pods("porter-agent-system").List(context.Background(), v1.ListOptions{
-		LabelSelector: olderAgentLabel,
-	})
+	// detect if the `porter-agent` release is installed
+	helmRelease, err := helmAgent.GetRelease("porter-agent", 0, false)
 
-	if err != nil {
-		return fmt.Errorf("error listing pods for older porter-agent: %w", err)
+	if err != nil || helmRelease == nil {
+		return nil
 	}
 
-	if len(podList.Items) > 0 {
-		// older porter-agent exists, delete the entire namespace
-		err := k8sAgent.Clientset.CoreV1().Namespaces().Delete(
-			context.Background(), "porter-agent-system", v1.DeleteOptions{},
-		)
+	_, err = helmAgent.UninstallChart("porter-agent")
 
-		if err != nil {
-			return fmt.Errorf("error deleting older porter-agent's namespace: %w", err)
-		}
+	if err != nil {
+		return err
 	}
 
 	return nil

+ 0 - 202
dashboard/src/components/events/EventCard.tsx

@@ -1,202 +0,0 @@
-import React, { useState } from "react";
-import styled from "styled-components";
-
-type CardProps = {
-  event: any;
-  selectEvent?: (event: any) => void;
-  overrideName?: string;
-};
-
-export const getReadableDate = (s: string) => {
-  let ts = new Date(s);
-  let date = ts.toLocaleDateString();
-  let time = ts.toLocaleTimeString([], {
-    hour: "numeric",
-    minute: "2-digit",
-  });
-  return `${time} ${date}`;
-};
-
-// Rename to Event Card
-const EventCard: React.FunctionComponent<CardProps> = ({
-  event,
-  selectEvent,
-  overrideName,
-}) => {
-  const [showTooltip, setShowTooltip] = useState(false);
-  return (
-    <>
-      <StyledCard
-        onClick={() => selectEvent(event)}
-        status={event.event_type.toLowerCase()}
-      >
-        <ContentContainer>
-          <Icon
-            status={event.event_type.toLowerCase() as any}
-            className="material-icons-outlined"
-          >
-            {event.event_type === "critical" ? "report_problem" : "info"}
-          </Icon>
-          <EventInformation>
-            <EventName>
-              <Helper>{event.resource_type}:</Helper>
-              {event.name}
-            </EventName>
-            <EventReason>{event.last_message}</EventReason>
-          </EventInformation>
-        </ContentContainer>
-        <ActionContainer>
-          <TimestampContainer>
-            <TimestampIcon className="material-icons-outlined">
-              access_time
-            </TimestampIcon>
-            <span>{getReadableDate(event.timestamp)}</span>
-          </TimestampContainer>
-        </ActionContainer>
-      </StyledCard>
-    </>
-  );
-};
-
-export default EventCard;
-
-const StyledCard = styled.div<{ status: string }>`
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  border: 1px solid
-    ${({ status }) => (status === "critical" ? "#ff385d" : "#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
-      ${({ status }) => (status === "critical" ? "#ff385d" : "#ffffff66")};
-  }
-  animation: fadeIn 0.5s;
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
-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 HistoryButton = styled.button`
-  position: relative;
-  border: none;
-  background: none;
-  color: white;
-  padding: 5px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  border-radius: 50%;
-  color: #ffffff44;
-  :hover {
-    background: #32343a;
-    cursor: pointer;
-  }
-`;
-
-const Tooltip = styled.div`
-  position: absolute;
-  left: 0px;
-  word-wrap: break-word;
-  top: 38px;
-  min-height: 18px;
-  padding: 5px 7px;
-  background: #272731;
-  z-index: 999;
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  flex: 1;
-  color: white;
-  text-transform: none;
-  font-size: 12px;
-  font-family: "Work Sans", sans-serif;
-  outline: 1px solid #ffffff55;
-  opacity: 0;
-  animation: faded-in 0.2s 0.15s;
-  animation-fill-mode: forwards;
-  @keyframes faded-in {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
-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;
-`;

+ 0 - 360
dashboard/src/components/events/SubEventsList.tsx

@@ -1,360 +0,0 @@
-import React, { useContext, useEffect, useMemo, useState } from "react";
-import styled from "styled-components";
-import api from "shared/api";
-import { Context } from "shared/Context";
-import SubEventCard from "./sub-events/SubEventCard";
-import Loading from "components/Loading";
-import LogBucketCard from "./sub-events/LogBucketCard";
-import useLastSeenPodStatus from "./useLastSeenPodStatus";
-
-const getReadableDate = (s: number) => {
-  let ts = new Date(s);
-  let date = ts.toLocaleDateString();
-  let time = ts.toLocaleTimeString([], {
-    hour: "numeric",
-    minute: "2-digit",
-  });
-  return `${time} ${date}`;
-};
-
-const SubEventsList: React.FC<{
-  clearSelectedEvent: () => void;
-  event: any;
-  enableTopMargin?: boolean;
-}> = ({ event, clearSelectedEvent, enableTopMargin }) => {
-  const { currentProject, currentCluster } = useContext(Context);
-  const {
-    status,
-    hasError: hasPodStatusErrored,
-    isLoading: isPodStatusLoading,
-  } = useLastSeenPodStatus({
-    podName: event.name,
-    namespace: event.namespace,
-    resource_type: event.resource_type,
-  });
-  const [isLoading, setIsLoading] = useState(true);
-  const [subEvents, setSubEvents] = useState(null);
-
-  const getData = async () => {
-    const project_id = currentProject?.id;
-    const cluster_id = currentCluster?.id;
-    const kube_event_id = event?.id;
-    let updatedEvent: any = null;
-    try {
-      updatedEvent = await api
-        .getKubeEvent("<token>", {}, { project_id, cluster_id, kube_event_id })
-        .then((res) => res?.data);
-    } catch (error) {
-      console.error(error);
-    }
-
-    let logBucketsParsed = [];
-    try {
-      const logBucketsData = await api
-        .getLogBuckets("token", {}, { project_id, cluster_id, kube_event_id })
-        .then((res) => res?.data);
-
-      logBucketsParsed = logBucketsData.log_buckets.map((bucket: string) => {
-        const [
-          _resourceType,
-          _namespace,
-          resource_name,
-          timestamp,
-        ] = bucket.split(":");
-        return {
-          event_type: "log_bucket",
-          resource_name,
-          timestamp: new Date(Number(timestamp) * 1000).toUTCString(),
-          parent_id: updatedEvent?.id,
-        };
-      });
-    } catch (error) {
-      console.error(error);
-    }
-
-    const subEventsSorted = (updatedEvent.sub_events as any[])
-      .map((s: any) => ({
-        ...s,
-        timestamp: new Date(s.timestamp).getTime(),
-      }))
-      .sort((prev: any, next: any) => next.timestamp - prev.timestamp);
-
-    const firstEvent = subEventsSorted.shift();
-    const lastEvent = subEventsSorted.pop();
-
-    const filteredLogBuckets = (logBucketsParsed as any[]).filter((bucket) => {
-      const bucketTime = new Date(bucket.timestamp).getTime();
-      return (
-        bucketTime >= lastEvent.timestamp && bucketTime <= firstEvent.timestamp
-      );
-    });
-
-    setSubEvents([...updatedEvent.sub_events, ...filteredLogBuckets]);
-    setIsLoading(false);
-  };
-
-  useEffect(() => {
-    getData();
-  }, [event, currentCluster, currentProject]);
-
-  const sortedSubEvents = useMemo(() => {
-    if (!Array.isArray(subEvents)) {
-      return [];
-    }
-    return subEvents
-      .map((s) => ({
-        ...s,
-        timestamp: new Date(s.timestamp).getTime(),
-      }))
-      .sort((prev, next) => next.timestamp - prev.timestamp)
-      .map((s) => ({
-        ...s,
-        timestamp: new Date(s.timestamp).toUTCString(),
-      }));
-  }, [subEvents]);
-
-  return (
-    <>
-      <Timeline enableTopMargin={enableTopMargin}>
-        <ControlRow>
-          <BackButton onClick={clearSelectedEvent}>
-            <i className="material-icons">close</i>
-          </BackButton>
-          <Icon
-            status={event.event_type.toLowerCase() as any}
-            className="material-icons-outlined"
-          >
-            {event.event_type === "critical" ? "report_problem" : "info"}
-          </Icon>
-          <div>
-            Pod {event.name} crashed
-            {event?.resource_type?.toLowerCase() === "pod" && (
-              <StyledHelper>
-                {hasPodStatusErrored ? (
-                  "We couldn't retrieve last pod status, please try again later"
-                ) : (
-                  <>
-                    {isPodStatusLoading ? (
-                      "Loading last seen pod status"
-                    ) : (
-                      <>
-                        Last seen pod status: {status}{" "}
-                        <StatusColor
-                          status={status?.toLowerCase()}
-                        ></StatusColor>
-                      </>
-                    )}
-                  </>
-                )}
-              </StyledHelper>
-            )}
-          </div>
-        </ControlRow>
-        {isLoading ? (
-          <Placeholder>
-            <Loading />
-          </Placeholder>
-        ) : sortedSubEvents?.length ? (
-          <EventsGrid>
-            <Rail />
-            {sortedSubEvents.map((subEvent: any, i: number) => {
-              if (subEvent?.event_type === "log_bucket") {
-                return (
-                  <Wrapper>
-                    <TimelineNode>
-                      <Penumbra>
-                        <Circle />
-                      </Penumbra>
-                      {getReadableDate(subEvent.timestamp)}
-                    </TimelineNode>
-                    <LogBucketCard logEvent={subEvent} />
-                    {i === sortedSubEvents.length - 1 && <RailCover />}
-                  </Wrapper>
-                );
-              }
-              return (
-                <Wrapper>
-                  <TimelineNode>
-                    <Penumbra>
-                      <Circle />
-                    </Penumbra>
-                    {getReadableDate(subEvent.timestamp)}
-                  </TimelineNode>
-                  <SubEventCard subEvent={subEvent} />
-                  {i === sortedSubEvents.length - 1 && <RailCover />}
-                </Wrapper>
-              );
-            })}
-          </EventsGrid>
-        ) : (
-          <Placeholder>
-            <i className="material-icons">search</i>
-            No sub-events were found.
-          </Placeholder>
-        )}
-      </Timeline>
-    </>
-  );
-};
-
-export default SubEventsList;
-
-const StyledHelper = styled.div`
-  color: #aaaabb;
-  line-height: 1.6em;
-  font-size: 13px;
-`;
-
-const Placeholder = styled.div`
-  padding: 30px;
-  padding-bottom: 40px;
-  font-size: 13px;
-  color: #ffffff44;
-  min-height: 340px;
-  margin-top: 20px;
-  background: #ffffff08;
-  height: calc(50vh - 60px);
-  border-radius: 8px;
-  width: 100%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-
-  > i {
-    font-size: 18px;
-    margin-right: 8px;
-  }
-`;
-
-const RailCover = styled.div`
-  background: #202227;
-  height: 100%;
-  width: 35px;
-  position: absolute;
-  top: 20px;
-  left: 0;
-`;
-
-const Penumbra = styled.div`
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background: #202227;
-  padding: 8px;
-  border-radius: 30px;
-  margin-right: 4px;
-`;
-
-const TimelineNode = styled.div`
-  position: absolute;
-  top: 0;
-  left: 7px;
-  display: flex;
-  align-items: center;
-  color: #aaaabb;
-  font-size: 13px;
-`;
-
-const Circle = styled.div`
-  width: 7px;
-  height: 7px;
-  border-radius: 20px;
-  background: #aaaabb;
-`;
-
-const Wrapper = styled.div`
-  position: relative;
-  width: 100%;
-  padding-top: 35px;
-  padding-left: 35px;
-`;
-
-const Rail = styled.div`
-  position: absolute;
-  top: -8px;
-  left: 17px;
-  width: 3px;
-  height: 100%;
-  z-index: -1;
-  background: #36383d;
-`;
-
-const Timeline = styled.div`
-  margin-top: ${(props: { enableTopMargin: boolean }) =>
-    props.enableTopMargin ? "30px" : "unset"};
-  animation: floatIn 0.3s;
-  animation-timing-function: ease-out;
-  animation-fill-mode: forwards;
-  @keyframes floatIn {
-    from {
-      opacity: 0;
-      transform: translateY(10px);
-    }
-    to {
-      opacity: 1;
-      transform: translateY(0px);
-    }
-  }
-`;
-
-const Icon = styled.span<{ status: "critical" | "normal" }>`
-  font-size: 26px;
-  margin-left: 17px;
-  margin-right: 10px;
-  color: ${({ status }) => (status === "critical" ? "#ff385d" : "#aaaabb")};
-`;
-
-const ControlRow = styled.div`
-  display: flex;
-  justify-content: flex-start;
-  align-items: center;
-  margin-bottom: 15px;
-  padding-left: 0px;
-  font-weight: 500;
-`;
-
-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;
-
-  > i {
-    font-size: 20px;
-  }
-
-  :hover {
-    background: #ffffff22;
-    > img {
-      opacity: 1;
-    }
-  }
-`;
-
-const EventsGrid = styled.div`
-  position: relative;
-  padding-top: 9px;
-`;
-
-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;
-`;

+ 0 - 166
dashboard/src/components/events/sub-events/LogBucketCard.tsx

@@ -1,166 +0,0 @@
-import React, { useContext, useEffect, useState } from "react";
-import api from "shared/api";
-import { Context } from "shared/Context";
-import styled, { keyframes } from "styled-components";
-
-type LogBucketCardProps = {
-  logEvent: any;
-};
-
-const LogBucketCard: React.FunctionComponent<LogBucketCardProps> = ({
-  logEvent,
-}) => {
-  const { currentProject, currentCluster } = useContext(Context);
-  const [isLoading, setIsLoading] = useState(true);
-  const [isExpanded, setIsExpanded] = useState(false);
-  const [logs, setLogs] = useState([]);
-
-  const getLogsForBucket = async () => {
-    const project_id = currentProject?.id;
-    const cluster_id = currentCluster?.id;
-    const kube_event_id = logEvent?.parent_id;
-    const timestamp = logEvent?.timestamp;
-    try {
-      const logsData = await api
-        .getLogBucketLogs(
-          "<token>",
-          { timestamp: new Date(timestamp).getTime() },
-          { project_id, cluster_id, kube_event_id }
-        )
-        .then((res) => res?.data);
-
-      if (!Array.isArray(logsData.logs)) {
-        setLogs([]);
-        setIsLoading(false);
-        return;
-      }
-
-      const filteredLogs = logsData.logs.filter((log: string | unknown) => {
-        if (typeof log === "string") {
-          return log.length;
-        }
-        return false;
-      });
-      setLogs(filteredLogs);
-      setIsLoading(false);
-    } catch (error) {
-      console.error(error);
-    }
-  };
-
-  useEffect(() => {
-    if (!isExpanded) {
-      return;
-    }
-
-    if (!Array.isArray(logs) || !logs.length) {
-      getLogsForBucket();
-    }
-  }, [currentProject, currentCluster, logEvent, isExpanded]);
-
-  return (
-    <StyledCard>
-      <FlexCenter expandLogs={isExpanded}>
-        <ShowLogsButton
-          onClick={() => setIsExpanded((prevIsExpanded) => !prevIsExpanded)}
-        >
-          {isExpanded ? "Hide logs" : "Display logs"}
-          <ButtonIcon className="material-icons">
-            {isExpanded ? "arrow_drop_up" : "arrow_drop_down"}
-          </ButtonIcon>
-        </ShowLogsButton>
-      </FlexCenter>
-      {isExpanded && (
-        <>
-          {/* Case: Is still getting logs and user triggered expanded */}
-          {isLoading && <Loading>Loading . . .</Loading>}
-          {/* Case: No logs found after the api call */}
-          {!isLoading && !logs?.length && <Loading>No logs found.</Loading>}
-          {/* Case: Logs were found successfully  */}
-          {!isLoading && !!logs?.length && logs?.map((l) => <Log>{l}</Log>)}
-        </>
-      )}
-    </StyledCard>
-  );
-};
-
-export default LogBucketCard;
-
-const Loading = styled.div`
-  margin-top: 5px;
-  margin-left: 5px;
-`;
-
-const Log = styled.div`
-  font-family: monospace, sans-serif;
-  font-size: 12px;
-  color: white;
-`;
-
-const FlexCenter = styled.div`
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  ${(props: { expandLogs: boolean }) => {
-    if (!props.expandLogs) {
-      return "";
-    }
-
-    return `
-      border-bottom: solid 1px;
-      padding-bottom: 15px;
-      margin-bottom: 15px;
-      border-color: #515256;
-    `;
-  }}
-  transition-property: all;
-  transition-duration: 0.5s;
-  transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
-`;
-
-const fadeInKeyframe = keyframes`
-  from {
-    opacity: 0;
-  }
-  to {
-    opacity: 1;
-  }
-`;
-
-const StyledCard = styled.div`
-  border: 1px solid #ffffff44;
-  margin-bottom: 30px;
-  border-radius: 10px;
-  padding: 14px;
-  padding-left: 13px;
-  font-size: 13px;
-  background: #121318;
-  user-select: text;
-  overflow-wrap: break-word;
-  overflow-y: auto;
-  min-height: 55px;
-  color: #aaaabb;
-
-  animation: ${fadeInKeyframe} 0.5s;
-`;
-
-const ShowLogsButton = styled.button`
-  border: solid 1px;
-  border-radius: 10px;
-  border-color: #515256;
-  color: white;
-  background: none;
-  padding: 8px 12px 8px 20px;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-
-  :hover {
-    cursor: pointer;
-    background: #5152569c;
-  }
-`;
-
-const ButtonIcon = styled.i`
-  padding-left: 5px;
-`;

+ 0 - 57
dashboard/src/components/events/sub-events/SubEventCard.tsx

@@ -1,57 +0,0 @@
-import React, { useState } from "react";
-import styled from "styled-components";
-
-type CardProps = {
-  subEvent: any;
-};
-
-const SubEventCard: React.FunctionComponent<CardProps> = ({ subEvent }) => {
-  return (
-    <StyledCard status={subEvent.event_type.toLowerCase()}>
-      <Icon
-        status={subEvent.event_type.toLowerCase() as any}
-        className="material-icons-outlined"
-      >
-        {subEvent.event_type.toLowerCase() === "critical"
-          ? "report_problem"
-          : "info"}
-      </Icon>
-      {subEvent.message}
-    </StyledCard>
-  );
-};
-
-export default SubEventCard;
-
-const StyledCard = styled.div<{ status: string }>`
-  display: flex;
-  align-items: center;
-  justify-content: flex-start;
-  border: 1px solid
-    ${({ status }) => (status === "critical" ? "#ff385d" : "#ffffff44")};
-  background: #ffffff08;
-  margin-bottom: 30px;
-  border-radius: 10px;
-  padding: 14px;
-  padding-left: 13px;
-  overflow: hidden;
-  min-height: 55px;
-  font-size: 13px;
-  color: #aaaabb;
-  animation: fadeIn 0.5s;
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
-    }
-    to {
-      opacity: 1;
-    }
-  }
-`;
-
-const Icon = styled.span<{ status: "critical" | "normal" }>`
-  font-size: 20px;
-  margin-left: 10px;
-  margin-right: 13px;
-  color: ${({ status }) => (status === "critical" ? "#ff385d" : "#aaaabb")};
-`;

+ 0 - 217
dashboard/src/components/events/useEvents.ts

@@ -1,217 +0,0 @@
-import { unionBy } from "lodash";
-import { useContext, useEffect, useMemo, useState } from "react";
-import api from "shared/api";
-import { Context } from "shared/Context";
-import { KubeEvent } from "shared/types";
-
-type UseKubeEventsProps = {
-  resourceType: "NODE" | "POD" | "HPA";
-  ownerName?: string;
-  ownerType?: string;
-  shouldWaitForOwner?: boolean;
-  ownerNamespace?: string;
-};
-
-export const useKubeEvents = ({
-  resourceType,
-  ownerName,
-  ownerType,
-  shouldWaitForOwner,
-  ownerNamespace,
-}: UseKubeEventsProps) => {
-  const { currentCluster, currentProject } = useContext(Context);
-  const [hasPorterAgent, setHasPorterAgent] = useState(false);
-
-  const [isLoading, setIsLoading] = useState(true);
-  const [kubeEvents, setKubeEvents] = useState<KubeEvent[]>([]);
-  const [hasMore, setHasMore] = useState(true);
-  const [totalCount, setTotalCount] = useState(0);
-
-  // Check if the porter agent is installed or not
-  useEffect(() => {
-    let isSubscribed = true;
-
-    const project_id = currentProject?.id;
-    const cluster_id = currentCluster?.id;
-
-    api
-      .detectPorterAgent("<token>", {}, { project_id, cluster_id })
-      .then(() => {
-        setHasPorterAgent(true);
-      })
-      .catch(() => {
-        setHasPorterAgent(false);
-        setIsLoading(false);
-      });
-
-    return () => {
-      isSubscribed = false;
-    };
-  }, [currentProject, currentCluster]);
-
-  // Get events
-  useEffect(() => {
-    let isSubscribed = true;
-
-    if (shouldWaitForOwner && !ownerName?.length && !ownerType?.length) {
-      return () => {
-        isSubscribed = false;
-      };
-    }
-
-    if (hasPorterAgent) {
-      fetchData(true).then(() => {
-        if (isSubscribed) {
-          setIsLoading(false);
-        }
-      });
-    }
-
-    return () => {
-      isSubscribed = false;
-    };
-  }, [
-    currentProject?.id,
-    currentCluster?.id,
-    hasPorterAgent,
-    resourceType,
-    ownerType,
-    ownerName,
-  ]);
-
-  const fetchData = async (clear?: boolean) => {
-    const project_id = currentProject?.id;
-    const cluster_id = currentCluster?.id;
-    let skipBy;
-    if (!clear) {
-      skipBy = kubeEvents?.length;
-    } else {
-      setHasMore(true);
-    }
-
-    const type = resourceType;
-
-    try {
-      const data = await api
-        .getKubeEvents(
-          "<token>",
-          {
-            skip: skipBy,
-            resource_type: type,
-            owner_name: ownerName,
-            owner_type: ownerType,
-            namespace: ownerNamespace,
-          },
-          { project_id, cluster_id }
-        )
-        .then((res) => res.data);
-
-      const newKubeEvents = data?.kube_events;
-      const totalCount = data?.count;
-
-      setTotalCount(totalCount);
-
-      if (!newKubeEvents?.length) {
-        setHasMore(false);
-        return;
-      }
-
-      if (clear) {
-        setKubeEvents(newKubeEvents);
-
-        if (totalCount === newKubeEvents.length) {
-          setHasMore(false);
-        } else {
-          setHasMore(true);
-        }
-
-        return;
-      }
-
-      const newEvents = unionBy(kubeEvents, newKubeEvents, "id");
-
-      if (totalCount === newEvents.length) {
-        setHasMore(false);
-      } else {
-        setHasMore(true);
-      }
-
-      setKubeEvents(newEvents);
-    } catch (error) {
-      console.log(error);
-    }
-  };
-
-  const installPorterAgent = () => {
-    const project_id = currentProject?.id;
-    const cluster_id = currentCluster?.id;
-
-    api
-      .installPorterAgent("<token>", {}, { project_id, cluster_id })
-      .then(() => {
-        setHasPorterAgent(true);
-      })
-      .catch(() => {
-        setHasPorterAgent(false);
-      });
-  };
-
-  const getLastSubEvent = (
-    subEvents: {
-      event_type: string;
-      message: string;
-      reason: string;
-      timestamp: string;
-    }[]
-  ) => {
-    const sortedEvents = subEvents
-      .map((s) => {
-        return {
-          ...s,
-          timestamp: new Date(s.timestamp).getTime(),
-        };
-      })
-      .sort((prev, next) => next.timestamp - prev.timestamp);
-
-    return sortedEvents[0];
-  };
-
-  // Fill up the data missing on events with the subevents
-  const processedKubeEvents = useMemo(() => {
-    return kubeEvents
-      .filter((event) => {
-        if (
-          !Array.isArray(event?.sub_events) ||
-          event.sub_events.length === 0
-        ) {
-          return false;
-        }
-        return true;
-      })
-      .map((e: any) => {
-        const lastSubEvent = getLastSubEvent(e.sub_events);
-
-        return {
-          ...e,
-          event_type: lastSubEvent.event_type,
-          timestamp: new Date(lastSubEvent.timestamp).getTime(),
-          last_message: lastSubEvent.message,
-        };
-      })
-      .sort((prev, next) => next.timestamp - prev.timestamp)
-      .map((s) => ({
-        ...s,
-        timestamp: new Date(s.timestamp).toUTCString(),
-      }));
-  }, [kubeEvents]);
-
-  return {
-    hasPorterAgent,
-    isLoading,
-    kubeEvents: processedKubeEvents,
-    hasMore,
-    totalCount,
-    loadMoreEvents: () => fetchData(),
-    triggerInstall: installPorterAgent,
-  };
-};

+ 0 - 89
dashboard/src/components/events/useLastSeenPodStatus.ts

@@ -1,89 +0,0 @@
-import { useContext, useEffect, useState } from "react";
-import api from "shared/api";
-import { Context } from "shared/Context";
-
-const useLastSeenPodStatus = ({
-  podName,
-  namespace,
-  resource_type,
-}: {
-  podName: string;
-  namespace: string;
-  resource_type: string;
-}) => {
-  const [status, setCurrentStatus] = useState(null);
-  const [isLoading, setIsLoading] = useState(true);
-  const [hasError, setHasError] = useState(false);
-  const { currentProject, currentCluster } = useContext(Context);
-
-  const getPodStatus = (status: any) => {
-    if (
-      status?.phase === "Pending" &&
-      status?.containerStatuses !== undefined
-    ) {
-      return status.containerStatuses[0].state?.waiting?.reason || "Pending";
-    } else if (status?.phase === "Pending") {
-      return "Pending";
-    }
-
-    if (status?.phase === "Failed") {
-      return "failed";
-    }
-
-    if (status?.phase === "Running") {
-      let collatedStatus = "running";
-
-      status?.containerStatuses?.forEach((s: any) => {
-        if (s.state?.waiting) {
-          collatedStatus =
-            s.state?.waiting?.reason === "CrashLoopBackOff"
-              ? "failed"
-              : "waiting";
-        } else if (s.state?.terminated) {
-          collatedStatus = "failed";
-        }
-      });
-      return collatedStatus;
-    }
-  };
-
-  const updatePods = async () => {
-    try {
-      const res = await api.getPodByName(
-        "<token>",
-        {},
-        {
-          project_id: currentProject.id,
-          cluster_id: currentCluster.id,
-          namespace: "default",
-          name: podName,
-        }
-      );
-      // console.log(getPodStatus(res.data.status));
-
-      setCurrentStatus(getPodStatus(res.data.status));
-    } catch (error) {
-      if (error?.response?.status === 404) {
-        setCurrentStatus("Deleted");
-      } else {
-        setHasError(true);
-      }
-    } finally {
-      setIsLoading(false);
-    }
-  };
-
-  useEffect(() => {
-    if (resource_type?.toLowerCase() === "pod") {
-      updatePods();
-    }
-  }, [podName, namespace, resource_type]);
-
-  return {
-    status,
-    isLoading,
-    hasError,
-  };
-};
-
-export default useLastSeenPodStatus;

+ 0 - 212
dashboard/src/main/home/cluster-dashboard/dashboard/events/EventsTab.tsx

@@ -1,212 +0,0 @@
-import React, { useContext, useEffect, useState } from "react";
-import styled from "styled-components";
-import EventCard from "components/events/EventCard";
-import Loading from "components/Loading";
-import InfiniteScroll from "react-infinite-scroll-component";
-import { useKubeEvents } from "components/events/useEvents";
-import SubEventsList from "components/events/SubEventsList";
-
-const availableResourceTypes = [
-  { label: "Pods", value: "POD" },
-  { label: "HPA", value: "HPA" },
-  { label: "Nodes", value: "NODE" },
-];
-
-const EventsTab = () => {
-  const [resourceType, setResourceType] = useState(availableResourceTypes[0]);
-  const [currentEvent, setCurrentEvent] = useState(null);
-
-  const {
-    isLoading,
-    hasPorterAgent,
-    triggerInstall,
-    kubeEvents,
-    loadMoreEvents,
-    hasMore,
-  } = useKubeEvents({ resourceType: resourceType.value as any });
-
-  if (isLoading) {
-    return (
-      <Placeholder>
-        <Loading />
-      </Placeholder>
-    );
-  }
-
-  if (!hasPorterAgent) {
-    return (
-      <Placeholder>
-        <div>
-          <Header>We couldn't detect the Porter agent on your cluster</Header>
-          In order to use the events tab, you need to install the Porter agent.
-          <InstallPorterAgentButton onClick={() => triggerInstall()}>
-            <i className="material-icons">add</i> Install Porter agent
-          </InstallPorterAgentButton>
-        </div>
-      </Placeholder>
-    );
-  }
-
-  if (currentEvent) {
-    return (
-      <SubEventsList
-        event={currentEvent}
-        clearSelectedEvent={() => setCurrentEvent(null)}
-        enableTopMargin
-      />
-    );
-  }
-
-  return (
-    <EventsPageWrapper>
-      {kubeEvents.length > 0 ? (
-        <>
-          <ControlRow>
-            {/*
-              <Dropdown
-                selectedOption={resourceType}
-                options={availableResourceTypes}
-                onSelect={(o) => setResourceType({ ...o, value: o.value as string })}
-              />
-              */}
-          </ControlRow>
-          <InfiniteScroll
-            dataLength={kubeEvents.length}
-            next={loadMoreEvents}
-            hasMore={hasMore}
-            loader={<h4>Loading...</h4>}
-            scrollableTarget="HomeViewWrapper"
-          >
-            <EventsGrid>
-              {kubeEvents.map((event, i) => {
-                return (
-                  <React.Fragment key={i}>
-                    <EventCard
-                      event={event}
-                      selectEvent={() => setCurrentEvent(event)}
-                    />
-                  </React.Fragment>
-                );
-              })}
-            </EventsGrid>
-          </InfiniteScroll>
-        </>
-      ) : (
-        <Placeholder>
-          <i className="material-icons">search</i>
-          No matching events were found.
-        </Placeholder>
-      )}
-    </EventsPageWrapper>
-  );
-};
-
-export default EventsTab;
-
-const Label = styled.div`
-  color: #ffffff44;
-  margin-right: 8px;
-  font-size: 13px;
-`;
-
-const ControlRow = styled.div`
-  display: flex;
-  align-items: center;
-  margin-bottom: 30px;
-  padding-left: 0px;
-  font-size: 13px;
-`;
-
-const EventsPageWrapper = styled.div`
-  font-size: 13px;
-  padding-bottom: 80px;
-  border-radius: 8px;
-  animation: floatIn 0.3s;
-  animation-timing-function: ease-out;
-  animation-fill-mode: forwards;
-  @keyframes floatIn {
-    from {
-      opacity: 0;
-      transform: translateY(10px);
-    }
-    to {
-      opacity: 1;
-      transform: translateY(0px);
-    }
-  }
-`;
-
-const EventsGrid = styled.div`
-  display: grid;
-  grid-row-gap: 15px;
-  grid-template-columns: 1;
-`;
-
-const InstallPorterAgentButton = styled.button`
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  font-size: 13px;
-  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;
-  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;
-  width: 100%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-
-  > i {
-    font-size: 18px;
-    margin-right: 8px;
-  }
-`;
-
-const Header = styled.div`
-  font-weight: 500;
-  color: #aaaabb;
-  font-size: 16px;
-  margin-bottom: 15px;
-`;

+ 14 - 4
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -141,7 +141,6 @@ const ExpandedChart: React.FC<Props> = (props) => {
   };
 
   const getControllers = async (chart: ChartType) => {
-    
     // don't retrieve controllers for chart that failed to even deploy.
     if (chart.info.status == "failed") return;
 
@@ -625,7 +624,9 @@ const ExpandedChart: React.FC<Props> = (props) => {
       return (
         <Url>
           <i className="material-icons">link</i>
-          <a href={url} target="_blank">{url}</a>
+          <a href={url} target="_blank">
+            {url}
+          </a>
         </Url>
       );
     }
@@ -731,7 +732,13 @@ const ExpandedChart: React.FC<Props> = (props) => {
           cluster_id: currentCluster.id,
         }
       )
-      .then(() => setIsAgentInstalled(true))
+      .then((res) => {
+        if (res.data?.version == "v3") {
+          setIsAgentInstalled(true);
+        } else {
+          setIsAgentInstalled(false);
+        }
+      })
       .catch((err) => {
         setIsAgentInstalled(false);
 
@@ -885,7 +892,10 @@ const ExpandedChart: React.FC<Props> = (props) => {
                     margin_left={"0px"}
                   />
                   */}
-                  <DeployStatusSection chart={currentChart} setLogData={renderLogsAtTimestamp} />
+                  <DeployStatusSection
+                    chart={currentChart}
+                    setLogData={renderLogsAtTimestamp}
+                  />
                   <LastDeployed>
                     <Dot>•</Dot>Last deployed
                     {" " + getReadableDate(currentChart.info.last_deployed)}

+ 47 - 6
dashboard/src/main/home/cluster-dashboard/expanded-chart/events/EventsTab.tsx

@@ -20,18 +20,50 @@ const EventsTab: React.FC<Props> = ({
   overridingJobName,
 }) => {
   const [hasPorterAgent, setHasPorterAgent] = useState(true);
+  const [isPorterAgentInstalling, setIsPorterAgentInstalling] = useState(false);
   const { currentProject, currentCluster } = useContext(Context);
   const [isLoading, setIsLoading] = useState(true);
 
   useEffect(() => {
+    // determine if the agent is installed properly - if not, start by render upgrade screen
+    checkForAgent();
+  }, []);
+
+  useEffect(() => {
+    if (!isPorterAgentInstalling) {
+      return;
+    }
+
+    const checkForAgentInterval = setInterval(checkForAgent, 3000);
+
+    return () => clearInterval(checkForAgentInterval);
+  }, [isPorterAgentInstalling]);
+
+  const checkForAgent = () => {
     const project_id = currentProject?.id;
     const cluster_id = currentCluster?.id;
 
-    // determine if the agent is installed properly - if not, render upgrade screen
     api
       .detectPorterAgent("<token>", {}, { project_id, cluster_id })
       .then((res) => {
-        console.log(res.data);
+        if (res.data?.version != "v3") {
+          setHasPorterAgent(false);
+        } else {
+          // next, check whether events can be queried - if they can, we're good to go
+          api
+            .listPorterEvents("<token>", getFilters(), {
+              project_id: currentProject.id,
+              cluster_id: currentCluster.id,
+            })
+            .then(() => {
+              setHasPorterAgent(true);
+              setIsPorterAgentInstalling(false);
+            })
+            .catch((err) => {
+              // do nothing - this is expected while installing
+            });
+        }
+
         setIsLoading(false);
       })
       .catch((err) => {
@@ -40,18 +72,19 @@ const EventsTab: React.FC<Props> = ({
           setIsLoading(false);
         }
       });
-  }, []);
+  };
 
   const installAgent = async () => {
     const project_id = currentProject?.id;
     const cluster_id = currentCluster?.id;
 
+    setIsPorterAgentInstalling(true);
+
     api
       .installPorterAgent("<token>", {}, { project_id, cluster_id })
-      .then(() => {
-        setHasPorterAgent(true);
-      })
+      .then()
       .catch((err) => {
+        setIsPorterAgentInstalling(false);
         console.log(err);
       });
   };
@@ -75,6 +108,14 @@ const EventsTab: React.FC<Props> = ({
     };
   };
 
+  if (isPorterAgentInstalling) {
+    return (
+      <Placeholder>
+        <Header>Installing agent...</Header>
+      </Placeholder>
+    );
+  }
+
   if (isLoading) {
     return (
       <Placeholder>