Ver Fonte

Git logs (#3058)

* Logs

* Log Update

* Address Changes

* Address Changes

* Address Changes

* add modules

* remove package-lock

---------

Co-authored-by: Justin Rhee <jusrhee@Justins-MacBook-Air.local>
Co-authored-by: jusrhee <justin@porter.run>
sdess09 há 3 anos atrás
pai
commit
553a51d3b1

+ 80 - 0
api/server/handlers/gitinstallation/workflow_logs.go

@@ -0,0 +1,80 @@
+package gitinstallation
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+
+	"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/commonutils"
+	"github.com/porter-dev/porter/api/server/shared/config"
+)
+
+type GetWorkflowLogsHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewGetWorkflowLogsHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *GetWorkflowLogsHandler {
+	return &GetWorkflowLogsHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *GetWorkflowLogsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
+
+	fmt.Printf("HSIEOFDSJPGfSDLKGaslkf")
+	if !ok {
+		return
+	}
+
+	releaseName := r.URL.Query().Get("release_name")
+	filename := r.URL.Query().Get("filename")
+
+	if filename == "" && releaseName == "" {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("filename and release name are both empty")))
+		return
+	}
+
+	if filename == "" {
+		if c.Config().ServerConf.InstanceName != "" {
+			filename = fmt.Sprintf("porter_%s_%s.yml", strings.Replace(
+				strings.ToLower(releaseName), "-", "_", -1),
+				strings.ToLower(c.Config().ServerConf.InstanceName),
+			)
+		} else {
+			filename = fmt.Sprintf("porter_%s.yml", strings.Replace(
+				strings.ToLower(releaseName), "-", "_", -1),
+			)
+		}
+	}
+
+	client, err := GetGithubAppClientFromRequest(c.Config(), r)
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	latestWorkflowRun, err := commonutils.GetLatestWorkflowRun(client, owner, name, filename, "")
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	logsURL, _, err := client.Actions.GetWorkflowRunLogs(r.Context(), owner, name, latestWorkflowRun.GetID(), false)
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	fmt.Printf("Fetched workflow logs URL: %v\n", logsURL.String())
+
+	c.WriteResult(w, r, logsURL.String())
+	
+}

+ 33 - 0
api/server/router/git_installation.go

@@ -735,5 +735,38 @@ func getGitInstallationRoutes(
 		Router:   r,
 	})
 
+	getWorkflowLogsEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbGet,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent: basePath,
+				RelativePath: fmt.Sprintf(
+					"%s/{%s}/{%s}/clusters/{cluster_id}/get_logs_workflow",
+					relPath,
+					types.URLParamGitRepoOwner,
+					types.URLParamGitRepoName,
+				),
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.GitInstallationScope,
+				types.ClusterScope,
+			},
+		},
+	)
+
+	getWorkflowLogsHandler := gitinstallation.NewGetWorkflowLogsHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &router.Route{
+		Endpoint: getWorkflowLogsEndpoint,
+		Handler:  getWorkflowLogsHandler,
+		Router:   r,
+	})
 	return routes, newPath
 }

Diff do ficheiro suprimidas por serem muito extensas
+ 525 - 36
dashboard/package-lock.json


+ 2 - 0
dashboard/package.json

@@ -40,6 +40,7 @@
     "ini": ">=1.3.6",
     "js-base64": "^3.6.0",
     "js-yaml": "^4.1.0",
+    "jszip": "^3.10.1",
     "lodash": "^4.17.21",
     "markdown-to-jsx": "^7.0.1",
     "qs": "^6.9.4",
@@ -49,6 +50,7 @@
     "react-animate-height": "^3.1.1",
     "react-color": "^2.19.3",
     "react-datepicker": "^4.8.0",
+    "react-diff-viewer": "^3.1.1",
     "react-dom": "^18.0.0",
     "react-error-boundary": "^3.1.3",
     "react-hot-toast": "^2.4.0",

+ 244 - 70
dashboard/src/main/home/app-dashboard/expanded-app/ExpandedApp.tsx

@@ -11,8 +11,10 @@ import github from "assets/github.png";
 import pr_icon from "assets/pull_request_icon.svg";
 import loadingImg from "assets/loading.gif";
 import refresh from "assets/refresh.png";
+import danger from "assets/danger.svg";
 
 import api from "shared/api";
+import JSZip from "jszip";
 import { Context } from "shared/Context";
 import useAuth from "shared/auth/useAuth";
 import Error from "components/porter/Error";
@@ -47,6 +49,12 @@ import JobRuns from "./JobRuns";
 import MetricsSection from "./MetricsSection";
 import StatusSectionFC from "./status/StatusSection";
 import ExpandedJob from "./expanded-job/ExpandedJob";
+import { Log } from "main/home/cluster-dashboard/expanded-chart/logs-section/useAgentLogs";
+import Anser, { AnserJsonEntry } from "anser";
+import dayjs from "dayjs";
+import Modal from "components/porter/Modal";
+import TitleSection from "components/TitleSection";
+import GHALogsModal from "./status/GHALogsModal";
 
 type Props = RouteComponentProps & {};
 
@@ -79,6 +87,8 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
   const [tab, setTab] = useState("overview");
   const [saveValuesStatus, setSaveValueStatus] = useState<string>(null);
   const [loading, setLoading] = useState<boolean>(false);
+  const [bannerLoading, setBannerLoading] = useState<boolean>(false);
+
   const [components, setComponents] = useState<ResourceType[]>([]);
 
   const [showRevisions, setShowRevisions] = useState<boolean>(false);
@@ -87,6 +97,8 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
     z.infer<typeof PorterYamlSchema> | undefined
   >(undefined);
   const [expandedJob, setExpandedJob] = useState(null);
+  const [logs, setLogs] = useState<Log[]>(null);
+  const [modalVisible, setModalVisible] = useState(false);
 
   const [services, setServices] = useState<Service[]>([]);
   const [releaseJob, setReleaseJob] = useState<ReleaseService[]>([]);
@@ -96,6 +108,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
 
   const getPorterApp = async () => {
     // setIsLoading(true);
+    setBannerLoading(true);
     const { appName } = props.match.params as any;
     try {
       if (!currentCluster || !currentProject) {
@@ -154,10 +167,15 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
 
       setPorterJson(porterJson);
       setAppData(newAppData);
-      updateServicesAndEnvVariables(resChartData?.data, releaseChartData?.data, porterJson);
+      updateServicesAndEnvVariables(
+        resChartData?.data,
+        releaseChartData?.data,
+        porterJson
+      );
 
       // Only check GHA status if no built image is set
-      const hasBuiltImage = !!resChartData.data.config?.global?.image?.repository;
+      const hasBuiltImage = !!resChartData.data.config?.global?.image
+        ?.repository;
       if (hasBuiltImage || !resPorterApp.data.repo_name) {
         setWorkflowCheckPassed(true);
         setHasBuiltImage(true);
@@ -288,6 +306,70 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
     }
   };
 
+  useEffect(() => {
+    setBannerLoading(true);
+    getBuildLogs().then(() => {
+      setBannerLoading(false);
+    });
+  }, [appData]);
+
+  const getBuildLogs = async () => {
+    try {
+      const res = await api.getGHWorkflowLogs(
+        "",
+        {},
+        {
+          project_id: appData.app.project_id,
+          cluster_id: appData.app.cluster_id,
+          git_installation_id: appData.app.git_repo_id,
+          owner: appData.app.repo_name?.split("/")[0],
+          name: appData.app.repo_name?.split("/")[1],
+          filename: "porter_stack_" + appData.chart.name + ".yml",
+        }
+      );
+      let logs: Log[] = [];
+      if (res.data != null) {
+        // Fetch the logs
+        const logsResponse = await fetch(res.data);
+
+        // Ensure that the response body is only read once
+        const logsBlob = await logsResponse.blob();
+
+        if (logsResponse.headers.get("Content-Type") === "application/zip") {
+          const zip = await JSZip.loadAsync(logsBlob);
+
+          zip.forEach(async function (relativePath, zipEntry) {
+            const fileData = await zip.file(relativePath)?.async("string");
+
+            if (
+              fileData &&
+              fileData.includes("Run porter-dev/porter-cli-action@v0.1.0")
+            ) {
+              const lines = fileData.split("\n");
+
+              lines.forEach((line, index) => {
+                const anserLine: AnserJsonEntry[] = Anser.ansiToJson(line);
+                const log: Log = {
+                  line: anserLine,
+                  lineNumber: index + 1,
+                  timestamp: line.match(
+                    /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+Z/
+                  )?.[0],
+                };
+
+                logs.push(log);
+              });
+            }
+          });
+          console.log(logs);
+          setLogs(logs);
+        }
+      }
+    } catch (error) {
+      console.log(error);
+    }
+  };
+
   const fetchPorterYamlContent = async (
     porterYaml: string,
     appData: any
@@ -371,7 +453,9 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
 
     // handle release chart
     if (releaseChart?.config || porterJson?.release) {
-      setReleaseJob([Service.deserializeRelease(releaseChart?.config, porterJson)]);
+      setReleaseJob([
+        Service.deserializeRelease(releaseChart?.config, porterJson),
+      ]);
     }
   };
 
@@ -631,7 +715,11 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                 <Fieldset>
                   <Container row>
                     <PlaceholderIcon src={notFound} />
-                    <Text color="helper">No pre-deploy jobs were found. Add a pre-deploy job to perform an operation before your application services deploy, like a database migration.</Text>
+                    <Text color="helper">
+                      No pre-deploy jobs were found. Add a pre-deploy job to
+                      perform an operation before your application services
+                      deploy, like a database migration.
+                    </Text>
                   </Container>
                 </Fieldset>
                 <Spacer y={0.5} />
@@ -648,11 +736,13 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
               services={releaseJob}
               limitOne={true}
               customOnClick={() => {
-                setReleaseJob([Service.default(
-                  "pre-deploy",
-                  "release",
-                  porterJson
-                ) as ReleaseService]);
+                setReleaseJob([
+                  Service.default(
+                    "pre-deploy",
+                    "release",
+                    porterJson
+                  ) as ReleaseService,
+                ]);
               }}
               addNewText={"Add a new pre-deploy job"}
               defaultExpanded={true}
@@ -666,13 +756,14 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
               Update pre-deploy job
             </Button>
             <Spacer y={0.5} />
-            {releaseJob.length > 0 && <JobRuns
-              lastRunStatus="all"
-              namespace={appData.chart?.namespace}
-              sortType="Newest"
-              releaseName={appData.app.name + "-r"}
-            />
-            }
+            {releaseJob.length > 0 && (
+              <JobRuns
+                lastRunStatus="all"
+                namespace={appData.chart?.namespace}
+                sortType="Newest"
+                releaseName={appData.app.name + "-r"}
+              />
+            )}
           </>
         );
       default:
@@ -682,12 +773,12 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
 
   if (expandedJob) {
     return (
-      <ExpandedJob 
+      <ExpandedJob
         appName={appData.app.name}
         jobName={expandedJob}
         goBack={() => setExpandedJob(null)}
       />
-    )
+    );
   }
 
   return (
@@ -785,32 +876,95 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
           ) : (
             <>
               {!workflowCheckPassed ? (
-                <GHABanner
-                  repoName={appData.app.repo_name}
-                  branchName={appData.app.git_branch}
-                  pullRequestUrl={appData.app.pull_request_url}
-                  stackName={appData.app.name}
-                  gitRepoId={appData.app.git_repo_id}
-                  porterYamlPath={appData.app.porter_yaml_path}
-                />
+                bannerLoading ? (
+                  <Banner>
+                    <Loading />
+                  </Banner>
+                ) : (
+                  <GHABanner
+                    repoName={appData.app.repo_name}
+                    branchName={appData.app.git_branch}
+                    pullRequestUrl={appData.app.pull_request_url}
+                    stackName={appData.app.name}
+                    gitRepoId={appData.app.git_repo_id}
+                    porterYamlPath={appData.app.porter_yaml_path}
+                  />
+                )
               ) : !hasBuiltImage ? (
-                <Banner
-                  suffix={
-                    <RefreshButton onClick={() => window.location.reload()}>
-                      <img src={refresh} /> Refresh
-                    </RefreshButton>
-                  }
-                >
-                  Your GitHub repo has not been built yet.
-                  <Spacer inline width="5px" />
-                  <Link
-                    hasunderline
-                    target="_blank"
-                    to={`https://github.com/${appData.app.repo_name}/actions`}
-                  >
-                    Check status
-                  </Link>
-                </Banner>
+                <>
+                  {logs ? (
+                    <Banner
+                      type="error"
+                      suffix={
+                        <>
+                          <>
+                            <RefreshButton
+                              onClick={() => window.location.reload()}
+                            >
+                              <img src={refresh} /> Refresh
+                            </RefreshButton>
+                          </>
+                        </>
+                      }
+                    >
+                      <div
+                        style={{
+                          display: "flex",
+                          alignItems: "center",
+                          marginBottom: "-20px",
+                        }}
+                      >
+                        Your build was not successful
+                        <Spacer inline width="15px" />
+                        <>
+                          <Link
+                            hasunderline
+                            target="_blank"
+                            onClick={() => setModalVisible(true)}
+                          >
+                            View Logs
+                          </Link>
+                          {modalVisible && (
+                            <GHALogsModal
+                              appData={appData}
+                              logs={logs}
+                              modalVisible={false}
+                              setModalVisible={setModalVisible}
+                            />
+                          )}
+                        </>
+                      </div>
+
+                      <Spacer inline width="5px" />
+                    </Banner>
+                  ) : bannerLoading ? (
+                    <Banner>
+                      <Loading />
+                    </Banner>
+                  ) : (
+                    <Banner
+                      suffix={
+                        <>
+                          <RefreshButton
+                            onClick={() => window.location.reload()}
+                          >
+                            <img src={refresh} /> Refresh
+                          </RefreshButton>
+                        </>
+                      }
+                    >
+                      Your GitHub repo has not been built yet.
+                      <Spacer inline width="5px" />
+                      <Link
+                        hasunderline
+                        target="_blank"
+                        to={`https://github.com/${appData.app.repo_name}/actions`}
+                      >
+                        Check status
+                      </Link>
+                    </Banner>
+                  )}
+                </>
               ) : (
                 <>
                   <DarkMatter />
@@ -827,7 +981,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                     shouldUpdate={
                       appData.chart.latest_version &&
                       appData.chart.latest_version !==
-                      appData.chart.chart.metadata.version
+                        appData.chart.chart.metadata.version
                     }
                     latestVersion={appData.chart.latest_version}
                     upgradeVersion={appUpgradeVersion}
@@ -841,6 +995,30 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                   appData.app.git_repo_id
                     ? hasBuiltImage
                       ? [
+                          { label: "Overview", value: "overview" },
+                          { label: "Events", value: "events" },
+                          { label: "Logs", value: "logs" },
+                          { label: "Metrics", value: "metrics" },
+                          { label: "Debug", value: "status" },
+                          { label: "Pre-deploy", value: "pre-deploy" },
+                          {
+                            label: "Environment variables",
+                            value: "environment-variables",
+                          },
+                          { label: "Build settings", value: "build-settings" },
+                          { label: "Settings", value: "settings" },
+                        ]
+                      : [
+                          { label: "Overview", value: "overview" },
+                          { label: "Pre-deploy", value: "pre-deploy" },
+                          {
+                            label: "Environment variables",
+                            value: "environment-variables",
+                          },
+                          { label: "Build settings", value: "build-settings" },
+                          { label: "Settings", value: "settings" },
+                        ]
+                    : [
                         { label: "Overview", value: "overview" },
                         // { label: "Activity", value: "activity" },
                         { label: "Events", value: "events" },
@@ -935,6 +1113,28 @@ const RefreshButton = styled.div`
   }
 `;
 
+const LogsButton = styled.div`
+  color: white;
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+  :hover {
+    color: red;
+    > img {
+      opacity: 1;
+    }
+  }
+
+  > img {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 5px;
+    margin-right: 10px;
+    opacity: 0.8;
+  }
+`;
+
 const Spinner = styled.img`
   width: 15px;
   height: 15px;
@@ -978,11 +1178,6 @@ const BranchTag = styled.div`
   text-overflow: ellipsis;
 `;
 
-const BranchSection = styled.div`
-  background: ${(props) => props.theme.fg};
-  border: 1px solid #494b4f;
-`;
-
 const SmallIcon = styled.img<{ opacity?: string; height?: string }>`
   height: ${(props) => props.height || "15px"};
   opacity: ${(props) => props.opacity || 1};
@@ -1030,24 +1225,3 @@ const StyledExpandedApp = styled.div`
     }
   }
 `;
-
-const HeaderWrapper = styled.div`
-  position: relative;
-`;
-const LastDeployed = styled.div`
-  font-size: 13px;
-  margin-left: 8px;
-  margin-top: -1px;
-  display: flex;
-  align-items: center;
-  color: #aaaabb66;
-`;
-const Dot = styled.div`
-  margin-right: 16px;
-`;
-const InfoWrapper = styled.div`
-  display: flex;
-  align-items: center;
-  margin-left: 3px;
-  margin-top: 22px;
-`;

+ 226 - 0
dashboard/src/main/home/app-dashboard/expanded-app/status/GHALogsModal.tsx

@@ -0,0 +1,226 @@
+import React, { useEffect, useRef, useState } from "react";
+import styled from "styled-components";
+import Modal from "components/porter/Modal";
+import TitleSection from "components/TitleSection";
+import { Log } from "../useAgentLogs";
+import Loading from "components/Loading";
+import Text from "components/porter/Text";
+import danger from "assets/danger.svg";
+import Anser, { AnserJsonEntry } from "anser";
+
+import dayjs from "dayjs";
+import Link from "components/porter/Link";
+import Spacer from "components/porter/Spacer";
+import Checkbox from "components/porter/Checkbox";
+type Props = {
+  appData: any;
+  logs: Log[];
+  modalVisible: boolean;
+  setModalVisible: (x: boolean) => void;
+};
+
+interface ExpandedIncidentLogsProps {
+  logs: Log[];
+}
+
+const GHALogsModal: React.FC<Props> = ({
+  appData,
+  logs,
+  modalVisible,
+  setModalVisible,
+}) => {
+  const [scrollToBottomEnabled, setScrollToBottomEnabled] = useState(true);
+  const scrollToBottomRef = useRef<HTMLDivElement | undefined>(undefined);
+  const ExpandedIncidentLogs = ({ logs }: ExpandedIncidentLogsProps) => {
+    if (!logs.length) {
+      return (
+        <LogsLoadWrapper>
+          <Loading />
+        </LogsLoadWrapper>
+      );
+    }
+
+    return (
+      <LogsSectionWrapper>
+        <StyledLogsSection>
+          {logs?.map((log, i) => {
+            return (
+              <LogSpan key={[log.lineNumber, i].join(".")}>
+                <span className="line-number">{log.lineNumber}.</span>
+                <span className="line-timestamp">
+                  {dayjs(log.timestamp).format("MMM D, YYYY HH:mm:ss")}
+                </span>
+                <LogOuter key={[log.lineNumber, i].join(".")}>
+                  {log.line?.map((ansi, j) => {
+                    if (ansi.clearLine) {
+                      return null;
+                    }
+
+                    return (
+                      <LogInnerSpan
+                        key={[log.lineNumber, i, j].join(".")}
+                        ansi={ansi}
+                      >
+                        {ansi.content.replace(/ /g, "\u00a0")}
+                      </LogInnerSpan>
+                    );
+                  })}
+                </LogOuter>
+              </LogSpan>
+            );
+          })}
+          <div ref={scrollToBottomRef} />
+        </StyledLogsSection>
+      </LogsSectionWrapper>
+    );
+  };
+  useEffect(() => {
+    if (scrollToBottomRef.current && scrollToBottomEnabled) {
+      scrollToBottomRef.current.scrollIntoView({
+        behavior: "smooth",
+        block: "end",
+      });
+    }
+  }, [logs, scrollToBottomRef, scrollToBottomEnabled]);
+  const renderExpandedEventMessage = () => {
+    if (!logs) {
+      return <Loading />;
+    }
+    return (
+      <>
+        <ExpandedIncidentLogs logs={logs} />
+      </>
+    );
+  };
+
+  return (
+    <Modal closeModal={() => setModalVisible(false)} width={"800px"}>
+      <TitleSection icon={danger}>
+        <Text size={16}>Logs for {appData.app.name}</Text>
+      </TitleSection>
+
+      {renderExpandedEventMessage()}
+      <Spacer y={0.5} />
+      <Link
+        hasunderline
+        target="_blank"
+        to={`https://github.com/${appData.app.repo_name}/actions`}
+      >
+        Check Full Build Logs
+      </Link>
+    </Modal>
+  );
+};
+
+export default GHALogsModal;
+
+const LogsSectionWrapper = styled.div`
+  position: relative;
+`;
+
+const StyledLogsSection = styled.div`
+  margin-top: 20px;
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  position: relative;
+  font-size: 13px;
+  max-height: 400px;
+  border-radius: 8px;
+  border: 1px solid #ffffff33;
+  border-top: none;
+  background: #101420;
+  animation: floatIn 0.3s;
+  animation-timing-function: ease-out;
+  animation-fill-mode: forwards;
+  overflow-y: auto;
+  overflow-wrap: break-word;
+  position: relative;
+  @keyframes floatIn {
+    from {
+      opacity: 0;
+      transform: translateY(10px);
+    }
+    to {
+      opacity: 1;
+      transform: translateY(0px);
+    }
+  }
+`;
+
+const LogSpan = styled.div`
+  font-family: monospace;
+  user-select: text;
+  display: flex;
+  align-items: flex-end;
+  gap: 8px;
+  width: 100%;
+  & > * {
+    padding-block: 5px;
+  }
+  & > .line-timestamp {
+    height: 100%;
+    color: #949effff;
+    opacity: 0.5;
+    font-family: monospace;
+    min-width: fit-content;
+    padding-inline-end: 5px;
+  }
+  & > .line-number {
+    height: 100%;
+    background: #202538;
+    display: inline-block;
+    text-align: right;
+    min-width: 45px;
+    padding-inline-end: 5px;
+    opacity: 0.3;
+    font-family: monospace;
+  }
+`;
+
+const LogOuter = styled.div`
+  display: inline-block;
+  word-wrap: anywhere;
+  flex-grow: 1;
+  font-family: monospace, sans-serif;
+  font-size: 12px;
+`;
+
+const LogInnerSpan = styled.span`
+  font-family: monospace, sans-serif;
+  font-size: 12px;
+  font-weight: ${(props: { ansi: Anser.AnserJsonEntry }) =>
+    props.ansi?.decoration && props.ansi?.decoration == "bold" ? "700" : "400"};
+  color: ${(props: { ansi: Anser.AnserJsonEntry }) =>
+    props.ansi?.fg ? `rgb(${props.ansi?.fg})` : "white"};
+  background-color: ${(props: { ansi: Anser.AnserJsonEntry }) =>
+    props.ansi?.bg ? `rgb(${props.ansi?.bg})` : "transparent"};
+`;
+
+export const ViewLogsWrapper = styled.div`
+  margin-bottom: -15px;
+  margin-top: 15px;
+`;
+const LogsLoadWrapper = styled.div`
+  height: 50px;
+`;
+
+const ScrollButton = styled.div`
+  background: #26292e;
+  border-radius: 5px;
+  height: 30px;
+  font-size: 13px;
+  display: flex;
+  cursor: pointer;
+  align-items: center;
+  padding: 10px;
+  padding-left: 8px;
+  > i {
+    font-size: 16px;
+    margin-right: 5px;
+  }
+  border: 1px solid #494b4f;
+  :hover {
+    border: 1px solid #7a7b80;
+  }
+`;

+ 60 - 50
dashboard/src/shared/api.tsx

@@ -493,16 +493,6 @@ const deleteRegistryIntegration = baseApi<
   return `/api/projects/${pathParams.project_id}/registries/${pathParams.registry_id}`;
 });
 
-const deleteGitlabIntegration = baseApi<
-  {},
-  {
-    project_id: number;
-    integration_id: number;
-  }
->("DELETE", ({ project_id, integration_id }) => {
-  return `/api/projects/${project_id}/integrations/gitlab/${integration_id}`;
-});
-
 const deleteSlackIntegration = baseApi<
   {},
   {
@@ -664,19 +654,18 @@ const detectBuildpack = baseApi<
 });
 
 const detectGitlabBuildpack = baseApi<
-  {
-    repo_path: string;
-    branch: string;
-    dir: string;
-  },
+  { dir: string },
   {
     project_id: number;
     integration_id: number;
+    repo_owner: string;
+    repo_name: string;
+    branch: string;
   }
 >(
   "GET",
-  ({ project_id, integration_id }) =>
-    `/api/projects/${project_id}/integrations/gitlab/${integration_id}/repos/buildpack/detect`
+  ({ project_id, integration_id, repo_name, repo_owner, branch }) =>
+    `/api/projects/${project_id}/integrations/gitlab/${integration_id}/repos/${repo_owner}/${repo_name}/${branch}/buildpack/detect`
 );
 
 const getBranchContents = baseApi<
@@ -739,34 +728,23 @@ const getPorterYamlContents = baseApi<
   }/${encodeURIComponent(pathParams.branch)}/porteryaml`;
 });
 
-const getGitlabPorterYamlContents = baseApi<
-  {
-    repo_path: string;
-    branch: string;
-    path: string;
-  },
-  {
-    project_id: number;
-    integration_id: number;
-  }
->("GET", ({ project_id, integration_id }) => {
-  return `/api/projects/${project_id}/integrations/gitlab/${integration_id}/repos/porteryaml`;
-});
-
 const getGitlabProcfileContents = baseApi<
   {
-    repo_path: string;
-    branch: string;
     path: string;
   },
   {
     project_id: number;
     integration_id: number;
+    owner: string;
+    name: string;
+    branch: string;
   }
 >(
   "GET",
-  ({ project_id, integration_id }) =>
-    `/api/projects/${project_id}/integrations/gitlab/${integration_id}/repos/procfile`
+  ({ project_id, integration_id, owner, name, branch }) =>
+    `/api/projects/${project_id}/integrations/gitlab/${integration_id}/repos/${owner}/${name}/${encodeURIComponent(
+      branch
+    )}/procfile`
 );
 
 const getBranches = baseApi<
@@ -2122,6 +2100,41 @@ const reRunGHWorkflow = baseApi<
   }
 );
 
+const getGHWorkflowLogs = baseApi<
+  {},
+  {
+    project_id: number;
+    cluster_id: number;
+    git_installation_id: number;
+    owner: string;
+    name: string;
+    filename?: string;
+    release_name?: string;
+  }
+>(
+  "GET",
+  ({
+    project_id,
+    git_installation_id,
+    owner,
+    name,
+    cluster_id,
+    filename,
+    release_name,
+  }) => {
+    const queryParams = new URLSearchParams();
+
+    if (release_name) {
+      queryParams.set("release_name", release_name);
+    }
+    if (filename) {
+      queryParams.set("filename", filename);
+    }
+
+    return `/api/projects/${project_id}/gitrepos/${git_installation_id}/${owner}/${name}/clusters/${cluster_id}/get_logs_workflow?${queryParams.toString()}`;
+  }
+);
+
 const triggerPreviewEnvWorkflow = baseApi<
   {},
   { project_id: number; cluster_id: number; deployment_id: number }
@@ -2179,9 +2192,7 @@ const getGitProviders = baseApi<{}, { project_id: number }>(
 );
 
 const getGitlabRepos = baseApi<
-  {
-    search_term: string;
-  },
+  {},
   { project_id: number; integration_id: number }
 >(
   "GET",
@@ -2190,34 +2201,34 @@ const getGitlabRepos = baseApi<
 );
 
 const getGitlabBranches = baseApi<
-  {
-    repo_path: string;
-    search_term: string;
-  },
+  {},
   {
     project_id: number;
     integration_id: number;
+    repo_owner: string;
+    repo_name: string;
   }
 >(
   "GET",
-  ({ project_id, integration_id }) =>
-    `/api/projects/${project_id}/integrations/gitlab/${integration_id}/repos/branches`
+  ({ project_id, integration_id, repo_owner, repo_name }) =>
+    `/api/projects/${project_id}/integrations/gitlab/${integration_id}/repos/${repo_owner}/${repo_name}/branches`
 );
 
 const getGitlabFolderContent = baseApi<
   {
-    repo_path: string;
-    branch: string;
     dir: string;
   },
   {
     project_id: number;
     integration_id: number;
+    repo_owner: string;
+    repo_name: string;
+    branch: string;
   }
 >(
   "GET",
-  ({ project_id, integration_id }) =>
-    `/api/projects/${project_id}/integrations/gitlab/${integration_id}/repos/contents`
+  ({ project_id, integration_id, repo_owner, repo_name, branch }) =>
+    `/api/projects/${project_id}/integrations/gitlab/${integration_id}/repos/${repo_owner}/${repo_name}/${branch}/contents`
 );
 
 const getLogPodValues = baseApi<
@@ -2601,7 +2612,6 @@ export default {
   deletePod,
   deleteProject,
   deleteRegistryIntegration,
-  deleteGitlabIntegration,
   deleteSlackIntegration,
   updateNotificationConfig,
   getNotificationConfig,
@@ -2746,6 +2756,7 @@ export default {
   updateBuildConfig,
   updateGitActionConfig,
   reRunGHWorkflow,
+  getGHWorkflowLogs,
   triggerPreviewEnvWorkflow,
   getTagsByProjectId,
   createTag,
@@ -2755,7 +2766,6 @@ export default {
   getGitlabRepos,
   getGitlabBranches,
   getGitlabFolderContent,
-  getGitlabPorterYamlContents,
   getLogPodValues,
   getLogs,
   listPorterEvents,

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff