|
|
@@ -1,4 +1,4 @@
|
|
|
-import React, { useState } from "react";
|
|
|
+import React, { useEffect, useState } from "react";
|
|
|
import styled from "styled-components";
|
|
|
|
|
|
import app_event from "assets/app_event.png";
|
|
|
@@ -10,6 +10,7 @@ import healthy from "assets/status-healthy.png";
|
|
|
import failure from "assets/failure.png";
|
|
|
import run_for from "assets/run_for.png";
|
|
|
import refresh from "assets/refresh.png";
|
|
|
+import Loading from "components/Loading";
|
|
|
|
|
|
import Text from "components/porter/Text";
|
|
|
import Container from "components/porter/Container";
|
|
|
@@ -17,17 +18,23 @@ import Spacer from "components/porter/Spacer";
|
|
|
import Link from "components/porter/Link";
|
|
|
import Icon from "components/porter/Icon";
|
|
|
import Modal from "components/porter/Modal";
|
|
|
+import api from "shared/api";
|
|
|
+import { Log } from "main/home/cluster-dashboard/expanded-chart/logs-section/useAgentLogs";
|
|
|
+import JSZip from "jszip";
|
|
|
+import Anser, { AnserJsonEntry } from "anser";
|
|
|
+import GHALogsModal from "../status/GHALogsModal";
|
|
|
|
|
|
type Props = {
|
|
|
event: any;
|
|
|
+ appData: any;
|
|
|
};
|
|
|
|
|
|
-const EventCard: React.FC<Props> = ({
|
|
|
- event,
|
|
|
- i,
|
|
|
-}) => {
|
|
|
+const EventCard: React.FC<Props> = ({ event, i, appData }) => {
|
|
|
const [showModal, setShowModal] = useState<boolean>(false);
|
|
|
const [modalContent, setModalContent] = useState<React.ReactNode>(null);
|
|
|
+ const [logModalVisible, setLogModalVisible] = useState(false);
|
|
|
+ const [logs, setLogs] = useState<Log[]>(null);
|
|
|
+ const [loading, setLoading] = useState<boolean>(true);
|
|
|
|
|
|
const getIcon = (eventType: string) => {
|
|
|
switch (eventType) {
|
|
|
@@ -41,7 +48,7 @@ const EventCard: React.FC<Props> = ({
|
|
|
return pre_deploy;
|
|
|
default:
|
|
|
return app_event;
|
|
|
- };
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
const getTitle = (eventType: string) => {
|
|
|
@@ -56,7 +63,7 @@ const EventCard: React.FC<Props> = ({
|
|
|
return "Application pre-deploy";
|
|
|
default:
|
|
|
return "";
|
|
|
- };
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
const getStatusIcon = (status: string) => {
|
|
|
@@ -69,65 +76,164 @@ const EventCard: React.FC<Props> = ({
|
|
|
return loading;
|
|
|
default:
|
|
|
return loading;
|
|
|
- };
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
const renderStatusText = (event: any) => {
|
|
|
if (event.type === "BUILD") {
|
|
|
switch (event.status) {
|
|
|
case "SUCCESS":
|
|
|
- return <Text color="#68BF8B">Build succeeded</Text>
|
|
|
+ return <Text color="#68BF8B">Build succeeded</Text>;
|
|
|
case "FAILED":
|
|
|
- return <Text color="#FF6060">Build failed</Text>
|
|
|
+ return <Text color="#FF6060">Build failed</Text>;
|
|
|
default:
|
|
|
- return <Text color="#aaaabb66">Build in progress . . </Text>
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
+ return <Text color="#aaaabb66">Build in progress . . </Text>;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if (event.type === "DEPLOY") {
|
|
|
switch (event.status) {
|
|
|
case "SUCCESS":
|
|
|
- return <Text color="#68BF8B">Deployed v100</Text>
|
|
|
+ return <Text color="#68BF8B">Deployed v100</Text>;
|
|
|
case "FAILED":
|
|
|
- return <Text color="#FF6060">Deploying v100 failed</Text>
|
|
|
+ return <Text color="#FF6060">Deploying v100 failed</Text>;
|
|
|
default:
|
|
|
- return <Text color="#aaaabb66">Deploying v100 . . .</Text>
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
+ return <Text color="#aaaabb66">Deploying v100 . . .</Text>;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if (event.type === "PRE_DEPLOY") {
|
|
|
switch (event.status) {
|
|
|
case "SUCCESS":
|
|
|
- return <Text color="#68BF8B">Pre-deploy succeeded . . </Text>
|
|
|
+ return <Text color="#68BF8B">Pre-deploy succeeded . . </Text>;
|
|
|
case "FAILED":
|
|
|
- return <Text color="#FF6060">Pre-deploy failed . . </Text>
|
|
|
+ return <Text color="#FF6060">Pre-deploy failed . . </Text>;
|
|
|
default:
|
|
|
- return <Text color="#aaaabb66">Pre-deploy in progress . . </Text>
|
|
|
- };
|
|
|
- };
|
|
|
+ return <Text color="#aaaabb66">Pre-deploy in progress . . </Text>;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ const triggerWorkflow = async () => {
|
|
|
+ try {
|
|
|
+ const res = await api.reRunGHWorkflow(
|
|
|
+ "",
|
|
|
+ {},
|
|
|
+ {
|
|
|
+ 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],
|
|
|
+ branch: appData.app.branch_name,
|
|
|
+ filename: "porter_stack_" + appData.chart.name + ".yml",
|
|
|
+ }
|
|
|
+ );
|
|
|
+ if (res.data != null) {
|
|
|
+ window.open(res.data, "_blank", "noreferrer");
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.log(error);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
const renderInfoCta = (event: any) => {
|
|
|
if (event.type === "APP_EVENT") {
|
|
|
return (
|
|
|
<>
|
|
|
- <Link hasunderline onClick={() => {
|
|
|
- setModalContent(
|
|
|
- <>
|
|
|
- <Container row>
|
|
|
- <Icon height="20px" src={app_event} />
|
|
|
- <Spacer inline width="10px" />
|
|
|
- <Text size={16}>Event details</Text>
|
|
|
- </Container>
|
|
|
- <Spacer y={1} />
|
|
|
- <Text>TODO: display event logs</Text>
|
|
|
- </>
|
|
|
- )
|
|
|
- setShowModal(true);
|
|
|
- }}>View details</Link>
|
|
|
+ <Link
|
|
|
+ hasunderline
|
|
|
+ onClick={() => {
|
|
|
+ setModalContent(
|
|
|
+ <>
|
|
|
+ <Container row>
|
|
|
+ <Icon height="20px" src={app_event} />
|
|
|
+ <Spacer inline width="10px" />
|
|
|
+ <Text size={16}>Event details</Text>
|
|
|
+ </Container>
|
|
|
+ <Spacer y={1} />
|
|
|
+ <Text>TODO: display event logs</Text>
|
|
|
+ </>
|
|
|
+ );
|
|
|
+ setShowModal(true);
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ View details
|
|
|
+ </Link>
|
|
|
<Spacer inline x={1} />
|
|
|
</>
|
|
|
);
|
|
|
+ }
|
|
|
+
|
|
|
+ const getBuildLogs = async () => {
|
|
|
+ try {
|
|
|
+ setLogs([]);
|
|
|
+ setLogModalVisible(true);
|
|
|
+
|
|
|
+ const res = await api.getGHWorkflowLogById(
|
|
|
+ "",
|
|
|
+ {},
|
|
|
+ {
|
|
|
+ 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",
|
|
|
+ run_id: event.metadata.action_run_id,
|
|
|
+ }
|
|
|
+ );
|
|
|
+ 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);
|
|
|
+ const promises: any[] = [];
|
|
|
+
|
|
|
+ zip.forEach(function (relativePath, zipEntry) {
|
|
|
+ promises.push(
|
|
|
+ (async function () {
|
|
|
+ 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);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ })()
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ await Promise.all(promises);
|
|
|
+ setLogs(logs);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.log(appData);
|
|
|
+ console.log(error);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
if (event.type === "BUILD") {
|
|
|
@@ -135,48 +241,88 @@ const EventCard: React.FC<Props> = ({
|
|
|
case "SUCCESS":
|
|
|
return (
|
|
|
<>
|
|
|
- <Link hasunderline onClick={() => alert("TODO: open GHA logs modal")}>View logs</Link>
|
|
|
+ <Link hasunderline onClick={() => getBuildLogs()}>
|
|
|
+ View logs
|
|
|
+ </Link>
|
|
|
+
|
|
|
+ {logModalVisible && (
|
|
|
+ <GHALogsModal
|
|
|
+ appData={appData}
|
|
|
+ logs={logs}
|
|
|
+ modalVisible={logModalVisible}
|
|
|
+ setModalVisible={setLogModalVisible}
|
|
|
+ actionRunId={event.metadata?.action_run_id}
|
|
|
+ />
|
|
|
+ )}
|
|
|
<Spacer inline x={1} />
|
|
|
</>
|
|
|
);
|
|
|
case "FAILED":
|
|
|
return (
|
|
|
<>
|
|
|
- <Link hasunderline onClick={() => alert("TODO: open GHA logs modal")}>View logs</Link>
|
|
|
+ <Link hasunderline onClick={() => getBuildLogs()}>
|
|
|
+ View logs
|
|
|
+ </Link>
|
|
|
+
|
|
|
+ {logModalVisible && (
|
|
|
+ <GHALogsModal
|
|
|
+ appData={appData}
|
|
|
+ logs={logs}
|
|
|
+ modalVisible={logModalVisible}
|
|
|
+ setModalVisible={setLogModalVisible}
|
|
|
+ actionRunId={event.metadata?.action_run_id}
|
|
|
+ />
|
|
|
+ )}
|
|
|
<Spacer inline x={1} />
|
|
|
</>
|
|
|
);
|
|
|
default:
|
|
|
return (
|
|
|
<>
|
|
|
- <Link hasunderline onClick={() => alert("TODO: link to GHA")}>View live logs</Link>
|
|
|
+ <Link
|
|
|
+ hasunderline
|
|
|
+ target="_blank"
|
|
|
+ to={`https://github.com/${appData.app.repo_name}/actions/runs/${event.metadata?.action_run_id}`}
|
|
|
+ >
|
|
|
+ View live logs
|
|
|
+ </Link>
|
|
|
<Spacer inline x={1} />
|
|
|
</>
|
|
|
);
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
+ }
|
|
|
+ }
|
|
|
+ useEffect(() => {
|
|
|
+ getBuildLogs();
|
|
|
+ }, []);
|
|
|
+
|
|
|
if (event.type === "DEPLOY") {
|
|
|
if (event.type === "FAILED") {
|
|
|
return (
|
|
|
<>
|
|
|
- <Link hasunderline onClick={() => alert("TODO: open deploy logs modal")}>View logs</Link>
|
|
|
+ <Link
|
|
|
+ hasunderline
|
|
|
+ onClick={() => alert("TODO: open deploy logs modal")}
|
|
|
+ >
|
|
|
+ View logs
|
|
|
+ </Link>
|
|
|
<Spacer inline x={1} />
|
|
|
</>
|
|
|
);
|
|
|
} else {
|
|
|
return;
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if (event.type === "PRE_DEPLOY") {
|
|
|
return (
|
|
|
<>
|
|
|
- <Link hasunderline onClick={() => alert("TODO: open logs modal")}>View logs</Link>
|
|
|
+ <Link hasunderline onClick={() => alert("TODO: open logs modal")}>
|
|
|
+ View logs
|
|
|
+ </Link>
|
|
|
<Spacer inline x={1} />
|
|
|
</>
|
|
|
);
|
|
|
- };
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
@@ -203,13 +349,11 @@ const EventCard: React.FC<Props> = ({
|
|
|
</>
|
|
|
)}
|
|
|
{renderStatusText(event)}
|
|
|
- {event.type !== "APP_EVENT" && (
|
|
|
- <Spacer inline x={1} />
|
|
|
- )}
|
|
|
+ {event.type !== "APP_EVENT" && <Spacer inline x={1} />}
|
|
|
{renderInfoCta(event)}
|
|
|
{event.status === "FAILED" && event.type !== "APP_EVENT" && (
|
|
|
<>
|
|
|
- <Link hasunderline>
|
|
|
+ <Link hasunderline onClick={() => triggerWorkflow()}>
|
|
|
<Container row>
|
|
|
<Icon height="10px" src={refresh} />
|
|
|
<Spacer inline width="5px" />
|
|
|
@@ -219,14 +363,10 @@ const EventCard: React.FC<Props> = ({
|
|
|
</>
|
|
|
)}
|
|
|
</Container>
|
|
|
- {false && (
|
|
|
- <Text color="helper">user@email.com</Text>
|
|
|
- )}
|
|
|
+ {false && <Text color="helper">user@email.com</Text>}
|
|
|
</Container>
|
|
|
{showModal && (
|
|
|
- <Modal closeModal={() => setShowModal(false)}>
|
|
|
- {modalContent}
|
|
|
- </Modal>
|
|
|
+ <Modal closeModal={() => setShowModal(false)}>{modalContent}</Modal>
|
|
|
)}
|
|
|
</StyledEventCard>
|
|
|
);
|
|
|
@@ -259,4 +399,4 @@ const StyledEventCard = styled.div`
|
|
|
margin-right: 0;
|
|
|
}
|
|
|
}
|
|
|
-`;
|
|
|
+`;
|