Yosef Mihretie %!s(int64=2) %!d(string=hai) anos
pai
achega
90cf429ed9

+ 1 - 1
dashboard/src/main/Main.tsx

@@ -237,7 +237,7 @@ export default class Main extends Component<PropsType, StateType> {
           }}
         />
          <Route
-          path={`/:status/:projectId?/:clusterId?`}
+          path={`/status`}
           render={() => {
             if (!this.state.isLoggedIn) {
               return <Redirect to="/login" />;

+ 181 - 0
dashboard/src/main/status/ClusterStatusSection.tsx

@@ -0,0 +1,181 @@
+import React, { useContext, useEffect, useState } from "react";
+
+import { ProjectListType, ClusterType } from "shared/types";
+
+import styled from "styled-components";
+import Container from "components/porter/Container";
+import Expandable from "components/porter/Expandable";
+import Image from "components/porter/Image";
+import Spacer from "components/porter/Spacer";
+import logo from "assets/logo.png";
+
+import Back from "components/porter/Back";
+import Text from "components/porter/Text";
+import { Context } from "shared/Context";
+
+import midnight from "shared/themes/midnight";
+import gradient from "assets/gradient.png";
+
+import api from "shared/api";
+
+import {StatusData, DailyHealthStatus} from "shared/types";
+
+type Props = { cluster: ClusterType, projectId: number};
+
+const ClusterStatusSection: React.FC<Props> = ({ cluster, projectId }) => { 
+    const [statusData, setStatusData] = useState<StatusData>({} as StatusData);
+
+    useEffect(() => {
+      if (!projectId || !cluster) {
+        return;
+      }
+  
+      api
+        .systemStatusHistory(
+          "<token>",
+          {},
+          {
+            projectId,
+            clusterId: cluster.id,
+          }
+        )
+        .then(({ data }) => {
+          console.log(data);
+          setStatusData({
+            cluster_health_histories: data.cluster_status_histories,
+            service_health_histories_grouped: {},
+          });
+        })
+        .catch((err) => {
+          console.error(err);
+        });
+    }, [projectId, cluster]);
+    return (
+        <>
+            <Expandable
+                key={cluster.id}
+                alt
+                header={
+                    <Container row>
+                        <Text size={16}> {cluster.name} </Text>
+                        <Spacer x={1} inline />
+                        <Text color="#01a05d">Operational</Text>
+                    </Container>
+                }
+            >
+          {
+                statusData?.cluster_health_histories &&
+                Object.keys(statusData?.cluster_health_histories).map((key) => {
+                return (
+                    <React.Fragment key={key}>
+                    <Text color="helper">{key}</Text>
+                    <Spacer y={0.25} />
+                    <StatusBars>
+                        {Array.from({ length: 90 }).map((_, i) => {
+                        const status =
+                            statusData?.cluster_health_histories[key][89 - i] ? "failure" : "healthy";
+                        return (
+                            <Bar
+                            key={i}
+                            isFirst={i === 0}
+                            isLast={i === 89}
+                            status={status}
+                            />
+                        );
+                        })}
+                    </StatusBars>
+                    <Spacer y={0.25} />
+                    </React.Fragment>
+                );
+                })}
+            </Expandable>
+        </>
+    );
+}
+
+
+export default ClusterStatusSection;
+
+const getBackgroundGradient = (status: string): string => {
+    switch (status) {
+      case "healthy":
+        return "linear-gradient(#01a05d, #0f2527)";
+      case "failure":
+        return "linear-gradient(#E1322E, #25100f)";
+      case "partial_failure":
+        return "linear-gradient(#E49621, #25270f)";
+      default:
+        return "linear-gradient(#76767644, #76767622)"; // Default or unknown status
+    }
+}
+
+const Badge = styled.div`
+  background: ${(props) => props.theme.clickable.bg};
+  padding: 5px 10px;
+  border: 1px solid ${(props) => props.theme.border};
+  border-radius: 5px;
+  font-size: 13px;
+`;
+
+const Letter = styled.div`
+  height: 100%;
+  width: 100%;
+  position: absolute;
+  padding-bottom: 2px;
+  font-weight: 500;
+  top: 0;
+  left: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+`;
+
+const ProjectImage = styled.img`
+  width: 100%;
+  height: 100%;
+`;
+
+const ProjectIcon = styled.div`
+  width: 26px;
+  min-width: 26px;
+  height: 26px;
+  border-radius: 3px;
+  overflow: hidden;
+  position: relative;
+  margin-right: 10px;
+  font-weight: 400;
+`;
+
+const Bar = styled.div<{ isFirst: boolean; isLast: boolean; status: string }>`
+  height: 20px;
+  display: flex;
+  flex: 1;
+  border-top-left-radius: ${(props) => (props.isFirst ? "5px" : "0")};
+  border-bottom-left-radius: ${(props) => (props.isFirst ? "5px" : "0")};
+  border-top-right-radius: ${(props) => (props.isLast ? "5px" : "0")};
+  border-bottom-right-radius: ${(props) => (props.isLast ? "5px" : "0")};
+  background: ${(props) => getBackgroundGradient(props.status)};
+`;
+
+const StatusBars = styled.div`
+  width: 100%;
+  display: flex;
+  justify-content: space-between;
+  gap: 2px;
+`;
+
+const StyledStatusPage = styled.div`
+  width: 100vw;
+  height: 100vh;
+  overflow: auto;
+  padding-top: 50px;
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+`;
+
+const StatusSection = styled.div`
+  width: calc(100% - 40px);
+  padding-bottom: 50px;
+  max-width: 1000px;
+`;

+ 154 - 0
dashboard/src/main/status/ProjectStatusSection.tsx

@@ -0,0 +1,154 @@
+import React, { useContext, useEffect, useState } from "react";
+
+import { ProjectListType, ClusterType } from "shared/types";
+
+import styled from "styled-components";
+import Container from "components/porter/Container";
+import Expandable from "components/porter/Expandable";
+import Image from "components/porter/Image";
+import Spacer from "components/porter/Spacer";
+import logo from "assets/logo.png";
+
+import Back from "components/porter/Back";
+import Text from "components/porter/Text";
+import { Context } from "shared/Context";
+
+import midnight from "shared/themes/midnight";
+import gradient from "assets/gradient.png";
+
+import api from "shared/api";
+
+import ClusterStatusSection from "./ClusterStatusSection";
+
+type Props = { project: ProjectListType };
+
+const ProjectStatusSection: React.FC<Props> = ({ project }) => {
+    const [clusters, setClusters] = useState<ClusterType[]>([]);
+
+    useEffect(() => {
+        if (!project || !project.id) {
+            console.log("project undefined")
+            return;
+        }
+        api.
+            getClusters(
+                "<token>",
+                {},
+                { id: project.id },
+            )
+            .then((res) => res.data as ClusterType[])
+            .then((clustersList) => {
+                console.log(clustersList);
+                setClusters(clustersList);
+            })
+            .catch((err) => {
+                console.log(err);
+            });
+    }, [project]);
+
+    return (
+        <>
+            <Expandable
+                key={project.id}
+                alt
+                header={
+                    <Container row>
+                        <Text size={16}> {project.name} </Text>
+                        <Spacer x={1} inline />
+                        <Text color="#01a05d">Operational</Text>
+                    </Container>
+                }
+            >
+                {clusters.map((cluster, _) => (
+                    <>
+                        <Spacer y={0.25} />
+                        <Container row spaced>
+                            <Text color="helper">{cluster.name}</Text>
+                            <Text color="#01a05d">Operational</Text>
+                        </Container>
+                        <Spacer y={0.25} />
+                        <StatusBars>
+                            {Array.from({ length: 90 }).map((_, i) => (
+                                <Bar key={i} isFirst={i === 0} isLast={i === 89} />
+                            ))}
+                        </StatusBars>
+                        <Spacer y={0.5} />
+                    </>
+                ))}
+            </Expandable>
+        </>
+    );
+}
+
+export default ProjectStatusSection;
+
+const Badge = styled.div`
+  background: ${(props) => props.theme.clickable.bg};
+  padding: 5px 10px;
+  border: 1px solid ${(props) => props.theme.border};
+  border-radius: 5px;
+  font-size: 13px;
+`;
+
+const Letter = styled.div`
+  height: 100%;
+  width: 100%;
+  position: absolute;
+  padding-bottom: 2px;
+  font-weight: 500;
+  top: 0;
+  left: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+`;
+
+const ProjectImage = styled.img`
+  width: 100%;
+  height: 100%;
+`;
+
+const ProjectIcon = styled.div`
+  width: 26px;
+  min-width: 26px;
+  height: 26px;
+  border-radius: 3px;
+  overflow: hidden;
+  position: relative;
+  margin-right: 10px;
+  font-weight: 400;
+`;
+
+const Bar = styled.div<{ isFirst: boolean; isLast: boolean }>`
+  height: 20px;
+  display: flex;
+  flex: 1;
+  border-top-left-radius: ${(props) => (props.isFirst ? "5px" : "0")};
+  border-bottom-left-radius: ${(props) => (props.isFirst ? "5px" : "0")};
+  border-top-right-radius: ${(props) => (props.isLast ? "5px" : "0")};
+  border-bottom-right-radius: ${(props) => (props.isLast ? "5px" : "0")};
+  background: linear-gradient(#01a05d, #0f2527);
+`;
+
+const StatusBars = styled.div`
+  width: 100%;
+  display: flex;
+  justify-content: space-between;
+  gap: 2px;
+`;
+
+const StyledStatusPage = styled.div`
+  width: 100vw;
+  height: 100vh;
+  overflow: auto;
+  padding-top: 50px;
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+`;
+
+const StatusSection = styled.div`
+  width: calc(100% - 40px);
+  padding-bottom: 50px;
+  max-width: 1000px;
+`;

+ 35 - 54
dashboard/src/main/status/StatusPage2.tsx → dashboard/src/main/status/StatusPage.tsx

@@ -1,4 +1,4 @@
-import React, { useMemo } from "react";
+import React, { useContext, useEffect, useState } from "react";
 import { withRouter, type RouteComponentProps } from "react-router";
 import styled, { ThemeProvider } from "styled-components";
 import { z } from "zod";
@@ -9,16 +9,47 @@ import Expandable from "components/porter/Expandable";
 import Image from "components/porter/Image";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
+import { Context } from "shared/Context";
 
 import midnight from "shared/themes/midnight";
 import gradient from "assets/gradient.png";
 import logo from "assets/logo.png";
 
+import { ProjectListType, ClusterType } from "shared/types";
+import api from "shared/api";
+import ProjectStatusSection from "./ProjectStatusSection";
+
+
 
 type Props = RouteComponentProps;
 
-const StatusPage: React.FC<Props> = ({ match }) => {
-  const projects = [{id: 0,  name: "project-1" }, {id: 1, name: "project-2" }];
+
+const StatusPage: React.FC<Props> = () => {
+  const {user} = useContext(Context);
+
+  const [projects, setProjects] = useState<ProjectListType[]>([{id: 0, name: "default"}]);
+
+  useEffect(() => {
+    if (user === undefined || user.userId === 0) {
+      console.log("no user defined")
+      return;
+    }
+    api
+    .getProjects(
+      "<token>",
+      {},
+      {id: user.userId},
+    )
+    .then((res) => res.data as ProjectListType[])
+    .then((projectList) => {
+      console.log(projectList);
+      setProjects(projectList);
+    })
+    .catch((err) => {
+      console.log(err);
+    });
+  }, [user]);
+
   return (
     <ThemeProvider theme={midnight}>
       <StyledStatusPage>
@@ -28,57 +59,7 @@ const StatusPage: React.FC<Props> = ({ match }) => {
             <>
               {projects.map((project, _) => (
                 <>
-                  <Expandable
-                    key={project.id}
-                    alt
-                    header={
-                      <Container row>
-                        <Text size={16}> { project.name } </Text>
-                        <Spacer x={1} inline />
-                        <Text color="#01a05d">Operational</Text>
-                      </Container>
-                    }
-                  >
-                    <Spacer y={0.25} />
-                    <Container row spaced>
-                      <Text color="helper">cluster-1</Text>
-                      <Text color="#01a05d">Operational</Text>
-                    </Container>
-                    <Spacer y={0.25} />
-                    <StatusBars>
-                      {Array.from({ length: 90 }).map((_, i) => (
-                        <Bar key={i} isFirst={i === 0} isLast={i === 89} />
-                      ))}
-                    </StatusBars>
-                    <Spacer y={0.5} />
-                    <Container row spaced>
-                      <Text color="helper">cluster-2</Text>
-                      <Text color="#01a05d">Operational</Text>
-                    </Container>
-                    <Spacer y={0.25} />
-                    <StatusBars>
-                      {Array.from({ length: 90 }).map((_, i) => (
-                        <Bar key={i} isFirst={i === 0} isLast={i === 89} />
-                      ))}
-                    </StatusBars>
-                    <Spacer y={0.5} />
-                    <Container row spaced>
-                      <Text color="helper">cluster-3</Text>
-                      <Text color="#01a05d">Operational</Text>
-                    </Container>
-                    <Spacer y={0.25} />
-                    <StatusBars>
-                      {Array.from({ length: 90 }).map((_, i) => (
-                        <Bar key={i} isFirst={i === 0} isLast={i === 89} />
-                      ))}
-                    </StatusBars>
-                    <Spacer y={0.5} />
-                    <Container row spaced>
-                      <Text color="helper">90 days ago</Text>
-                      <Text color="helper">Today</Text>
-                    </Container>
-                    <Spacer y={0.5} />
-                  </Expandable>
+                  <ProjectStatusSection project={project} />
                   <Spacer y={1} />
                 </>
               ))}

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

@@ -781,3 +781,39 @@ export type AppEventWebhook = {
   app_event_status: string;
   payload_encryption_key: string;
 };
+
+export type StatusData = {
+  cluster_health_histories: Record<string, Record<number, DailyHealthStatus>>;
+  service_health_histories_grouped: Record<string, GroupedService[]>;
+};
+
+export type SystemService = {
+  name: string;
+  namespace: string;
+  involved_object_type: string;
+};
+
+export type HealthStatus = {
+  start_time: string;
+  end_time: string;
+  status: "failure" | "healthy" | "partial_failure" | "undefined";
+  description: string;
+};
+
+export type DailyHealthStatus = {
+  status_percentages: Record<string, number>;
+  health_statuses: HealthStatus[];
+}
+
+export type ServiceStatusHistory = {
+  system_service: SystemService;
+  daily_health_history: Record<number, DailyHealthStatus>;
+};
+
+// If you're also grouping services by namespace and want a type for the grouped structure:
+export type GroupedService = {
+  system_service: SystemService;
+  daily_health_history: Record<number, DailyHealthStatus>;
+};
+
+export type GroupedServices = Record<string, GroupedService[]>;