sdess09 před 2 roky
rodič
revize
ab50fa56cc

+ 88 - 16
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -28,6 +28,9 @@ import BuildSettingsTab from "./build-settings/BuildSettingsTab";
 import { DisabledNamespacesForIncidents } from "./incidents/DisabledNamespaces";
 import { useStackEnvGroups } from "./useStackEnvGroups";
 import DeployStatusSection from "./deploy-status-section/DeployStatusSection";
+import Banner from "components/porter/Banner";
+import Spacer from "components/porter/Spacer";
+
 
 type Props = {
   namespace: string;
@@ -48,6 +51,12 @@ const getReadableDate = (s: string) => {
   return `${time} on ${date}`;
 };
 
+const templateWhitelist = [
+  "elasticache-redis",
+  "rds-postgresql",
+  "rds-postgresql-aurora",
+];
+
 const ExpandedChart: React.FC<Props> = (props) => {
   const [currentChart, setCurrentChart] = useState<ChartType>(
     props.currentChart
@@ -79,6 +88,7 @@ const ExpandedChart: React.FC<Props> = (props) => {
   const [logData, setLogData] = useState<InitLogData>({});
   const [overrideCurrentTab, setOverrideCurrentTab] = useState("");
   const [isAgentInstalled, setIsAgentInstalled] = useState<boolean>(false);
+  const [databaseStatus, setDatabaseStatus] = useState<boolean>(true);
 
   const {
     isStack,
@@ -105,6 +115,26 @@ const ExpandedChart: React.FC<Props> = (props) => {
     setOverrideCurrentTab("logs");
   };
 
+  const updateDatabaseStatuses = async (): Promise<void> => {
+    try {
+
+      const statusRes = await api.getDatabaseStatus("<token>", {
+        name: currentChart.name,
+        type: currentChart.chart.metadata.name
+      }, {
+        project_id: currentProject?.id ?? 0,
+        cluster_id: currentCluster?.id ?? 0,
+      });
+      if (statusRes.data.status === "available") {
+        setDatabaseStatus(true);
+      }
+      else {
+        setDatabaseStatus(false);
+      }
+    } catch (err) {
+      setDatabaseStatus(false);
+    }
+  };
   // Retrieve full chart data (includes form and values)
   const getChartData = async (chart: ChartType) => {
     setIsLoadingChartData(true);
@@ -192,7 +222,7 @@ const ExpandedChart: React.FC<Props> = (props) => {
           if (
             oldControllers &&
             oldControllers[object.metadata.uid]?.status?.conditions ==
-              object.status?.conditions
+            object.status?.conditions
           ) {
             return oldControllers;
           }
@@ -463,7 +493,7 @@ const ExpandedChart: React.FC<Props> = (props) => {
                   being deployed
                 </Header>
                 {props.currentChart.git_action_config &&
-                props.currentChart.git_action_config.gitlab_integration_id ? (
+                  props.currentChart.git_action_config.gitlab_integration_id ? (
                   <>
                     Navigate to the{" "}
                     <A
@@ -776,6 +806,8 @@ const ExpandedChart: React.FC<Props> = (props) => {
       });
   }, [currentChart]);
 
+
+
   useEffect(() => {
     if (logData.revision) {
       api
@@ -818,7 +850,9 @@ const ExpandedChart: React.FC<Props> = (props) => {
           });
       });
     });
-
+    if (templateWhitelist.includes(currentChart.chart.metadata.name)) {
+      void updateDatabaseStatuses()
+    }
     return () => {
       closeAllWebsockets();
     };
@@ -881,6 +915,7 @@ const ExpandedChart: React.FC<Props> = (props) => {
         }
       })
       .catch(console.log);
+
     return () => (isSubscribed = false);
   }, [components, currentCluster, currentProject, currentChart]);
 
@@ -899,7 +934,7 @@ const ExpandedChart: React.FC<Props> = (props) => {
               isFullscreen={true}
               setIsFullscreen={setIsFullscreen}
               currentChart={currentChart}
-              setInitData={() => {}}
+              setInitData={() => { }}
             />
           ) : (
             <StyledExpandedChart>
@@ -937,15 +972,24 @@ const ExpandedChart: React.FC<Props> = (props) => {
                     margin_left={"0px"}
                   />
                   */}
-                  <DeployStatusSection
-                    chart={currentChart}
-                    setLogData={renderLogsAtTimestamp}
-                  />
-                  <LastDeployed>
-                    <Dot>•</Dot>Last deployed
-                    {" " + getReadableDate(currentChart.info.last_deployed)}
-                  </LastDeployed>
+                  {!templateWhitelist.includes(currentChart.chart.metadata.name) &&
+                    <><DeployStatusSection
+                      chart={currentChart}
+                      setLogData={renderLogsAtTimestamp} /><LastDeployed>
+                        <Dot>•</Dot>Last deployed
+                        {" " + getReadableDate(currentChart.info.last_deployed)}
+                      </LastDeployed></>
+                  }
                 </InfoWrapper>
+
+                {!databaseStatus &&
+                  <>
+                    <Banner>
+                      <BannerContents>
+                        <b>Database is being created</b>
+                      </BannerContents>
+                      <Spacer inline width="5px" />
+                    </Banner></>}
               </HeaderWrapper>
               {deleting ? (
                 <>
@@ -976,7 +1020,7 @@ const ExpandedChart: React.FC<Props> = (props) => {
                     shouldUpdate={
                       currentChart.latest_version &&
                       currentChart.latest_version !==
-                        currentChart.chart.metadata.version
+                      currentChart.chart.metadata.version
                     }
                     latestVersion={currentChart.latest_version}
                     upgradeVersion={handleUpgradeVersion}
@@ -1056,7 +1100,8 @@ const ExpandedChart: React.FC<Props> = (props) => {
             </StyledExpandedChart>
           )}
         </>
-      )}
+      )
+      }
     </>
   );
 };
@@ -1186,11 +1231,11 @@ const TabButton = styled.div`
   border-radius: 20px;
   text-shadow: 0px 0px 8px
     ${(props: { devOpsMode: boolean }) =>
-      props.devOpsMode ? "#ffffff66" : "none"};
+    props.devOpsMode ? "#ffffff66" : "none"};
   cursor: pointer;
   :hover {
     color: ${(props: { devOpsMode: boolean }) =>
-      props.devOpsMode ? "" : "#aaaabb99"};
+    props.devOpsMode ? "" : "#aaaabb99"};
   }
 
   > i {
@@ -1279,3 +1324,30 @@ const A = styled.a`
   text-decoration: underline;
   cursor: pointer;
 `;
+
+
+const BannerContents = styled.div`
+  display: flex;
+  flex-direction: column;
+  row-gap: 0.5rem;
+`;
+
+const CloseButton = styled.div`
+  display: block;
+  width: 40px;
+  height: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1;
+  border-radius: 50%;
+  cursor: pointer;
+  :hover {
+    background-color: #ffffff11;
+  }
+
+  > i {
+    font-size: 20px;
+    color: #aaaabb;
+  }
+`;

+ 177 - 77
dashboard/src/main/home/database-dashboard/DatabaseDashboard.tsx

@@ -22,7 +22,6 @@ import api from "shared/api";
 import { hardcodedIcons } from "shared/hardcodedNameDict";
 import { search } from "shared/search";
 
-import Loading from "components/Loading";
 import Button from "components/porter/Button";
 import Container from "components/porter/Container";
 import Fieldset from "components/porter/Fieldset";
@@ -37,6 +36,7 @@ import { readableDate } from "shared/string_utils";
 import ClusterProvisioningPlaceholder from "components/ClusterProvisioningPlaceholder";
 import DashboardPlaceholder from "components/porter/DashboardPlaceholder";
 import DashboardHeader from "main/home/cluster-dashboard/DashboardHeader";
+import loading from "assets/loading.gif";
 
 type Props = {};
 
@@ -46,7 +46,7 @@ const templateWhitelist = [
   "rds-postgresql-aurora",
 ];
 
-const Apps: React.FC<Props> = ({ 
+const Apps: React.FC<Props> = ({
 }) => {
   const { currentProject, currentCluster } = useContext(Context);
 
@@ -57,6 +57,7 @@ const Apps: React.FC<Props> = ({
   // Placeholder (replace w useQuery)
   const [databases, setDatabases] = useState([]);
   const [status, setStatus] = useState("");
+  const [databaseStatuses, setDatabaseStatuses] = useState({});
 
   const filteredDatabases = useMemo(() => {
     const filteredBySearch = search(
@@ -71,6 +72,36 @@ const Apps: React.FC<Props> = ({
     return _.sortBy(filteredBySearch);
   }, [databases, searchValue]);
 
+  const updateDatabaseStatuses = async (): Promise<void> => {
+    const newStatuses = {};
+    for (const db of filteredDatabases) {
+      try {
+        if (databaseStatuses[db.name] !== "available") {
+          console.log(db)
+          const statusRes = await api.getDatabaseStatus("<token>", {
+            name: db.name,
+            type: db.chart.metadata.name
+          }, {
+            project_id: currentProject?.id ?? 0,
+            cluster_id: currentCluster?.id ?? 0,
+          });
+          if (statusRes.data.status === "available") {
+            newStatuses[db.name] = statusRes.data.status;
+          }
+          else {
+            newStatuses[db.name] = "updating";
+          }
+        }// Assuming status is returned in this field
+      } catch (err) {
+        console.error("Error fetching database status for", db.name, err);
+        newStatuses[db.name] = "error"; // Or some error state
+      }
+
+    }
+    setDatabaseStatuses(newStatuses);
+  };
+
+
   const getExpandedChartLinkURL = useCallback((x: any) => {
     const params = new Proxy(new URLSearchParams(window.location.search), {
       get: (searchParams, prop: string) => searchParams.get(prop),
@@ -126,13 +157,54 @@ const Apps: React.FC<Props> = ({
     };
   };
 
+  useEffect(() => {
+    // Call once when the component mounts
+    void updateDatabaseStatuses();
+
+    // Set up the interval for polling every 5 minutes
+    const intervalId = setInterval(() => {
+      void updateDatabaseStatuses();
+    }, 60000); // 60000 milliseconds = 5 minutes
+
+    // Clear interval on component unmount
+    return () => clearInterval(intervalId);
+  }, [filteredDatabases]);
+
   useEffect(() => {
     // currentCluster sometimes returns as -1 and passes null check
+
     if (currentProject?.id >= 0 && currentCluster?.id >= 0) {
       getAddOns();
     }
   }, [currentCluster, currentProject]);
 
+  const renderStatusIcon = (dbName: string): JSX.Element => {
+    const status: string = databaseStatuses[dbName];
+    switch (status) {
+      case "available":
+        return <StatusIcon src={healthy} />;
+      case "":
+        return <></>;
+      case "error":
+        return <StatusText>
+          <StatusWrapper success={false}>
+            <Loading src={loading} />
+            {"Creating database"}
+          </StatusWrapper>
+        </StatusText>
+      case "updating":
+        return <StatusText>
+          <StatusWrapper success={false}>
+            <Loading src={loading} />
+            {"Creating database"}
+          </StatusWrapper>
+        </StatusText>
+      default:
+        return <></>;
+    }
+  };
+
+
   const renderContents = () => {
     if (currentCluster?.status === "UPDATING_UNAVAILABLE") {
       return <ClusterProvisioningPlaceholder />;
@@ -248,7 +320,7 @@ const Apps: React.FC<Props> = ({
                     <Text size={14}>{app.name}</Text>
                     <Spacer inline x={2} />
                   </Container>
-                  <StatusIcon src={healthy} />
+                  {renderStatusIcon(app.name)}
                   <Container row>
                     <SmallIcon opacity="0.4" src={time} />
                     <Text size={13} color="#ffffff44">
@@ -309,109 +381,137 @@ const Apps: React.FC<Props> = ({
 export default Apps;
 
 const MidIcon = styled.img<{ height?: string }>`
-  height: ${props => props.height || "18px"};
-  margin-right: 11px;
-`;
+          height: ${props => props.height || "18px"};
+          margin-right: 11px;
+          `;
 
 const Row = styled(Link) <{ isAtBottom?: boolean }>`
-  cursor: pointer;
-  display: block;
-  padding: 15px;
-  border-bottom: ${props => props.isAtBottom ? "none" : "1px solid #494b4f"};
-  background: ${props => props.theme.clickable.bg};
-  position: relative;
-  border: 1px solid #494b4f;
-  border-radius: 5px;
-  margin-bottom: 15px;
-  animation: fadeIn 0.3s 0s;
-`;
+            cursor: pointer;
+            display: block;
+            padding: 15px;
+            border-bottom: ${props => props.isAtBottom ? "none" : "1px solid #494b4f"};
+            background: ${props => props.theme.clickable.bg};
+            position: relative;
+            border: 1px solid #494b4f;
+            border-radius: 5px;
+            margin-bottom: 15px;
+            animation: fadeIn 0.3s 0s;
+            `;
 
 const List = styled.div`
-  overflow: hidden;
-`;
+            overflow: hidden;
+            `;
 
 const SmallIcon = styled.img<{ opacity?: string }>`
-  margin-left: 2px;
-  height: 14px;
-  opacity: ${props => props.opacity || 1};
-  margin-right: 10px;
-`;
+              margin-left: 2px;
+              height: 14px;
+              opacity: ${props => props.opacity || 1};
+              margin-right: 10px;
+              `;
 
 const StatusIcon = styled.img`
-  position: absolute;
-  top: 20px;
-  right: 20px;
-  height: 18px;
-`;
+              position: absolute;
+              top: 20px;
+              right: 20px;
+              height: 18px;
+              `;
 
 const Icon = styled.img`
-  height: 20px;
-  margin-right: 13px;
-`;
+              height: 20px;
+              margin-right: 13px;
+              `;
 
 const Block = styled(Link)`
-  height: 110px;
-  flex-direction: column;
-  display: flex;
-  justify-content: space-between;
-  cursor: pointer;
-  padding: 20px;
-  color: ${props => props.theme.text.primary};
-  position: relative;
-  border-radius: 5px;
-  background: ${props => props.theme.clickable.bg};
-  border: 1px solid #494b4f;
-  :hover {
-    border: 1px solid #7a7b80;
+              height: 110px;
+              flex-direction: column;
+              display: flex;
+              justify-content: space-between;
+              cursor: pointer;
+              padding: 20px;
+              color: ${props => props.theme.text.primary};
+              position: relative;
+              border-radius: 5px;
+              background: ${props => props.theme.clickable.bg};
+              border: 1px solid #494b4f;
+              :hover {
+                border: 1px solid #7a7b80;
   }
 
-  animation: fadeIn 0.3s 0s;
-  @keyframes fadeIn {
-    from {
-      opacity: 0;
+              animation: fadeIn 0.3s 0s;
+              @keyframes fadeIn {
+                from {
+                opacity: 0;
     }
-    to {
-      opacity: 1;
+              to {
+                opacity: 1;
     }
   }
-`;
+              `;
 
 const GridList = styled.div`
-  display: grid;
-  grid-column-gap: 25px;
-  grid-row-gap: 25px;
-  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
-`;
+              display: grid;
+              grid-column-gap: 25px;
+              grid-row-gap: 25px;
+              grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+              `;
 
 const PlaceholderIcon = styled.img`
-  height: 13px;
-  margin-right: 12px;
-  opacity: 0.65;
-`;
+              height: 13px;
+              margin-right: 12px;
+              opacity: 0.65;
+              `;
 
 const ToggleIcon = styled.img`
-  height: 12px;
-  margin: 0 5px;
-  min-width: 12px;
-`;
+              height: 12px;
+              margin: 0 5px;
+              min-width: 12px;
+              `;
 
 const I = styled.i`
-  color: white;
-  font-size: 14px;
+              color: white;
+              font-size: 14px;
+              display: flex;
+              align-items: center;
+              margin-right: 5px;
+              justify-content: center;
+              `;
+
+const StyledAppDashboard = styled.div`
+              width: 100%;
+              height: 100%;
+              `;
+
+const StatusText = styled.div`
+  position: absolute;
+  top: 20px;
+  right: 20px;
   display: flex;
   align-items: center;
-  margin-right: 5px;
   justify-content: center;
 `;
 
-const StyledAppDashboard = styled.div`
-  width: 100%;
-  height: 100%;
-`;
-
-const CentralContainer = styled.div`
+const StatusWrapper = styled.div<{
+  success?: boolean;
+}>`
   display: flex;
-  flex-direction: column;
-  justify-content: left;
-  align-items: left;
+  line-height: 1.5;
+  align-items: center;
+  font-family: "Work Sans", sans-serif;
+  font-size: 13px;
+  color: #ffffff55;
+  margin-left: 15px;
+  text-overflow: ellipsis;
+  animation-fill-mode: forwards;
+  > i {
+    font-size: 18px;
+    margin-right: 10px;
+    float: left;
+    color: ${(props) => (props.success ? "#4797ff" : "#fcba03")};
+  }
 `;
+const Loading = styled.img`
+  width: 15px;
+  height: 15px;
+  margin-right: 9px;
+  margin-bottom: 0px;
+`;

+ 59 - 50
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -148,21 +148,21 @@ class Sidebar extends Component<PropsType, StateType> {
             "update",
             "delete",
           ]) && (
-            <NavButton path={"/integrations"}>
-              <Img src={integrations} />
-              Integrations
-            </NavButton>
-          )}
+              <NavButton path={"/integrations"}>
+                <Img src={integrations} />
+                Integrations
+              </NavButton>
+            )}
           {this.props.isAuthorized("settings", "", [
             "get",
             "update",
             "delete",
           ]) && (
-            <NavButton path={"/project-settings"}>
-              <Img src={settings} />
-              Project settings
-            </NavButton>
-          )}
+              <NavButton path={"/project-settings"}>
+                <Img src={settings} />
+                Project settings
+              </NavButton>
+            )}
 
           <br />
 
@@ -189,22 +189,22 @@ class Sidebar extends Component<PropsType, StateType> {
               "update",
               "delete",
             ]) && (
-              <NavButton path={"/project-settings"}>
-                <Img src={settings} />
-                Project settings
-              </NavButton>
-            )}
+                <NavButton path={"/project-settings"}>
+                  <Img src={settings} />
+                  Project settings
+                </NavButton>
+              )}
             {this.props.isAuthorized("integrations", "", [
               "get",
               "create",
               "update",
               "delete",
             ]) && (
-              <NavButton path={"/integrations"}>
-                <Img src={integrations} />
-                Integrations
-              </NavButton>
-            )}
+                <NavButton path={"/integrations"}>
+                  <Img src={integrations} />
+                  Integrations
+                </NavButton>
+              )}
             {currentCluster && (
               <>
                 <Spacer y={0.5} />
@@ -218,6 +218,15 @@ class Sidebar extends Component<PropsType, StateType> {
               <Img src={applications} />
               Applications
             </NavButton>
+            {currentProject.db_enabled && (
+              <NavButton
+                path="/databases"
+                active={window.location.pathname.startsWith("/apps")}
+              >
+                <Img src={database} />
+                Databases
+              </NavButton>
+            )}
             <NavButton
               path="/addons"
               active={window.location.pathname.startsWith("/addons")}
@@ -237,16 +246,16 @@ class Sidebar extends Component<PropsType, StateType> {
               "update",
               "delete",
             ]) && (
-              <NavButton
-                path={"/cluster-dashboard"}
-                active={window.location.pathname.startsWith(
-                  "/cluster-dashboard"
-                )}
-              >
-                <Img src={settings} />
-                Infrastructure
-              </NavButton>
-            )}
+                <NavButton
+                  path={"/cluster-dashboard"}
+                  active={window.location.pathname.startsWith(
+                    "/cluster-dashboard"
+                  )}
+                >
+                  <Img src={settings} />
+                  Infrastructure
+                </NavButton>
+              )}
 
             {currentProject.preview_envs_enabled && (
               <NavButton path="/preview-environments">
@@ -304,16 +313,16 @@ class Sidebar extends Component<PropsType, StateType> {
               "update",
               "delete",
             ]) && (
-              <NavButton
-                path={"/cluster-dashboard"}
-                active={window.location.pathname.startsWith(
-                  "/cluster-dashboard"
-                )}
-              >
-                <Img src={infra} />
-                Infrastructure
-              </NavButton>
-            )}
+                <NavButton
+                  path={"/cluster-dashboard"}
+                  active={window.location.pathname.startsWith(
+                    "/cluster-dashboard"
+                  )}
+                >
+                  <Img src={infra} />
+                  Infrastructure
+                </NavButton>
+              )}
 
             {currentProject.preview_envs_enabled && (
               <NavButton path="/preview-environments">
@@ -328,22 +337,22 @@ class Sidebar extends Component<PropsType, StateType> {
               "update",
               "delete",
             ]) && (
-              <NavButton path={"/integrations"}>
-                <Img src={integrations} />
-                Integrations
-              </NavButton>
-            )}
+                <NavButton path={"/integrations"}>
+                  <Img src={integrations} />
+                  Integrations
+                </NavButton>
+              )}
 
             {this.props.isAuthorized("settings", "", [
               "get",
               "update",
               "delete",
             ]) && (
-              <NavButton path={"/project-settings"}>
-                <Img src={settings} />
-                Project settings
-              </NavButton>
-            )}
+                <NavButton path={"/project-settings"}>
+                  <Img src={settings} />
+                  Project settings
+                </NavButton>
+              )}
 
             {/* Hacky workaround for setting currentCluster with legacy method */}
             <Clusters

+ 63 - 61
dashboard/src/shared/api.tsx

@@ -371,9 +371,8 @@ const getFeedEvents = baseApi<
   }
 >("GET", (pathParams) => {
   const { project_id, cluster_id, stack_name, page } = pathParams;
-  return `/api/projects/${project_id}/clusters/${cluster_id}/applications/${stack_name}/events?page=${
-    page || 1
-  }`;
+  return `/api/projects/${project_id}/clusters/${cluster_id}/applications/${stack_name}/events?page=${page || 1
+    }`;
 });
 
 const createEnvironment = baseApi<
@@ -798,11 +797,9 @@ const detectBuildpack = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${
-    pathParams.git_repo_id
-  }/repos/${pathParams.kind}/${pathParams.owner}/${
-    pathParams.name
-  }/${encodeURIComponent(pathParams.branch)}/buildpack/detect`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
+    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
+    }/${encodeURIComponent(pathParams.branch)}/buildpack/detect`;
 });
 
 const detectGitlabBuildpack = baseApi<
@@ -833,11 +830,9 @@ const getBranchContents = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${
-    pathParams.git_repo_id
-  }/repos/${pathParams.kind}/${pathParams.owner}/${
-    pathParams.name
-  }/${encodeURIComponent(pathParams.branch)}/contents`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
+    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
+    }/${encodeURIComponent(pathParams.branch)}/contents`;
 });
 
 const getProcfileContents = baseApi<
@@ -853,11 +848,9 @@ const getProcfileContents = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${
-    pathParams.git_repo_id
-  }/repos/${pathParams.kind}/${pathParams.owner}/${
-    pathParams.name
-  }/${encodeURIComponent(pathParams.branch)}/procfile`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
+    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
+    }/${encodeURIComponent(pathParams.branch)}/procfile`;
 });
 
 const getPorterYamlContents = baseApi<
@@ -873,11 +866,9 @@ const getPorterYamlContents = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${
-    pathParams.git_repo_id
-  }/repos/${pathParams.kind}/${pathParams.owner}/${
-    pathParams.name
-  }/${encodeURIComponent(pathParams.branch)}/porteryaml`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
+    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
+    }/${encodeURIComponent(pathParams.branch)}/porteryaml`;
 });
 
 const parsePorterYaml = baseApi<
@@ -925,11 +916,9 @@ const getBranchHead = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${
-    pathParams.git_repo_id
-  }/repos/${pathParams.kind}/${pathParams.owner}/${
-    pathParams.name
-  }/${encodeURIComponent(pathParams.branch)}/head`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
+    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
+    }/${encodeURIComponent(pathParams.branch)}/head`;
 });
 
 const validatePorterApp = baseApi<
@@ -961,23 +950,23 @@ const validatePorterApp = baseApi<
 
 const createApp = baseApi<
   | {
-      name: string;
-      deployment_target_id: string;
-      type: "github";
-      git_repo_id: number;
-      git_branch: string;
-      git_repo_name: string;
-      porter_yaml_path: string;
-    }
+    name: string;
+    deployment_target_id: string;
+    type: "github";
+    git_repo_id: number;
+    git_branch: string;
+    git_repo_name: string;
+    porter_yaml_path: string;
+  }
   | {
-      name: string;
-      deployment_target_id: string;
-      type: "docker-registry";
-      image: {
-        repository: string;
-        tag: string;
-      };
-    },
+    name: string;
+    deployment_target_id: string;
+    type: "docker-registry";
+    image: {
+      repository: string;
+      tag: string;
+    };
+  },
   {
     project_id: number;
     cluster_id: number;
@@ -1038,17 +1027,17 @@ const updateApp = baseApi<
 });
 
 const appRun = baseApi<
-    {
-        deployment_target_id: string;
-        service_name: string;
-    },
-    {
-        project_id: number;
-        cluster_id: number;
-        porter_app_name: string;
-    }
+  {
+    deployment_target_id: string;
+    service_name: string;
+  },
+  {
+    project_id: number;
+    cluster_id: number;
+    porter_app_name: string;
+  }
 >("POST", (pathParams) => {
-    return `/api/projects/${pathParams.project_id}/clusters/${pathParams.cluster_id}/apps/${pathParams.porter_app_name}/run`;
+  return `/api/projects/${pathParams.project_id}/clusters/${pathParams.cluster_id}/apps/${pathParams.porter_app_name}/run`;
 });
 
 const updateBuildSettings = baseApi<
@@ -1142,7 +1131,7 @@ const getRevision = baseApi<
 
 const porterYamlFromRevision = baseApi<
   {
-      should_format_for_export: boolean;
+    should_format_for_export: boolean;
   },
   {
     project_id: number;
@@ -1880,6 +1869,20 @@ const updateDatabaseStatus = baseApi<
 >("POST", (pathParams) => {
   return `/api/projects/${pathParams.project_id}/infras/${pathParams.infra_id}/database`;
 });
+// GET /api/projects/{project_id}/clusters/{cluster_id}/datastore/status
+const getDatabaseStatus = baseApi<
+  {
+    name: string;
+    type: string
+  },
+  {
+    project_id: number;
+    cluster_id: number;
+
+  }
+>("GET", (pathParams) => {
+  return `/api/projects/${pathParams.project_id}/clusters/${pathParams.cluster_id}/datastore/status`;
+});
 
 const getRepoIntegrations = baseApi("GET", "/api/integrations/repo");
 
@@ -2139,11 +2142,9 @@ const getEnvGroup = baseApi<
     version?: number;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.id}/clusters/${
-    pathParams.cluster_id
-  }/namespaces/${pathParams.namespace}/envgroup?name=${pathParams.name}${
-    pathParams.version ? "&version=" + pathParams.version : ""
-  }`;
+  return `/api/projects/${pathParams.id}/clusters/${pathParams.cluster_id
+    }/namespaces/${pathParams.namespace}/envgroup?name=${pathParams.name}${pathParams.version ? "&version=" + pathParams.version : ""
+    }`;
 });
 
 const getConfigMap = baseApi<
@@ -3201,7 +3202,7 @@ const removeStackEnvGroup = baseApi<
     `/api/v1/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/stacks/${stack_id}/remove_env_group/${env_group_name}`
 );
 
-const getGithubStatus = baseApi<{}, {}>("GET", ({}) => `/api/status/github`);
+const getGithubStatus = baseApi<{}, {}>("GET", ({ }) => `/api/status/github`);
 
 const createSecretAndOpenGitHubPullRequest = baseApi<
   {
@@ -3498,4 +3499,5 @@ export default {
 
   // STATUS
   getGithubStatus,
+  getDatabaseStatus
 };

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

@@ -32,6 +32,9 @@ export type DetailedIngressError = {
   message: string;
   error: string;
 };
+export type Annotations = {
+  category: string;
+}
 
 export type ChartType = {
   stack_id: string;
@@ -54,6 +57,7 @@ export type ChartType = {
       description: string;
       icon: string;
       apiVersion: string;
+      annotations?: Annotations;
     };
     files?: Array<{
       data: string;