|
@@ -1,4 +1,4 @@
|
|
|
-import React, { useEffect, useState, useContext, useCallback } from "react";
|
|
|
|
|
|
|
+import React, { useEffect, useState, useContext } from "react";
|
|
|
import { RouteComponentProps, useParams, withRouter } from "react-router";
|
|
import { RouteComponentProps, useParams, withRouter } from "react-router";
|
|
|
import styled from "styled-components";
|
|
import styled from "styled-components";
|
|
|
import yaml from "js-yaml";
|
|
import yaml from "js-yaml";
|
|
@@ -13,7 +13,6 @@ import refresh from "assets/refresh.png";
|
|
|
import save from "assets/save-01.svg";
|
|
import save from "assets/save-01.svg";
|
|
|
|
|
|
|
|
import api from "shared/api";
|
|
import api from "shared/api";
|
|
|
-import JSZip from "jszip";
|
|
|
|
|
import { Context } from "shared/Context";
|
|
import { Context } from "shared/Context";
|
|
|
import Error from "components/porter/Error";
|
|
import Error from "components/porter/Error";
|
|
|
|
|
|
|
@@ -26,7 +25,7 @@ import Link from "components/porter/Link";
|
|
|
import Back from "components/porter/Back";
|
|
import Back from "components/porter/Back";
|
|
|
import TabSelector from "components/TabSelector";
|
|
import TabSelector from "components/TabSelector";
|
|
|
import Icon from "components/porter/Icon";
|
|
import Icon from "components/porter/Icon";
|
|
|
-import { ChartType, PorterAppOptions } from "shared/types";
|
|
|
|
|
|
|
+import { ChartType, CreateUpdatePorterAppOptions } from "shared/types";
|
|
|
import RevisionSection from "main/home/cluster-dashboard/expanded-chart/RevisionSection";
|
|
import RevisionSection from "main/home/cluster-dashboard/expanded-chart/RevisionSection";
|
|
|
import BuildSettingsTab from "../build-settings/BuildSettingsTab";
|
|
import BuildSettingsTab from "../build-settings/BuildSettingsTab";
|
|
|
import Button from "components/porter/Button";
|
|
import Button from "components/porter/Button";
|
|
@@ -37,20 +36,19 @@ import Fieldset from "components/porter/Fieldset";
|
|
|
import { PorterJson, createFinalPorterYaml } from "../new-app-flow/schema";
|
|
import { PorterJson, createFinalPorterYaml } from "../new-app-flow/schema";
|
|
|
import { KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArray";
|
|
import { KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArray";
|
|
|
import { PorterYamlSchema } from "../new-app-flow/schema";
|
|
import { PorterYamlSchema } from "../new-app-flow/schema";
|
|
|
-import { EnvVariablesTab } from "./EnvVariablesTab";
|
|
|
|
|
|
|
+import { EnvVariablesTab } from "./env-vars/EnvVariablesTab";
|
|
|
import GHABanner from "./GHABanner";
|
|
import GHABanner from "./GHABanner";
|
|
|
import LogSection from "./logs/LogSection";
|
|
import LogSection from "./logs/LogSection";
|
|
|
import ActivityFeed from "./activity-feed/ActivityFeed";
|
|
import ActivityFeed from "./activity-feed/ActivityFeed";
|
|
|
import MetricsSection from "./MetricsSection";
|
|
import MetricsSection from "./MetricsSection";
|
|
|
import StatusSectionFC from "./status/StatusSection";
|
|
import StatusSectionFC from "./status/StatusSection";
|
|
|
import ExpandedJob from "./expanded-job/ExpandedJob";
|
|
import ExpandedJob from "./expanded-job/ExpandedJob";
|
|
|
-import { Log } from "main/home/cluster-dashboard/expanded-chart/logs-section/useAgentLogs";
|
|
|
|
|
-import Anser, { AnserJsonEntry } from "anser";
|
|
|
|
|
import _ from "lodash";
|
|
import _ from "lodash";
|
|
|
import AnimateHeight from "react-animate-height";
|
|
import AnimateHeight from "react-animate-height";
|
|
|
import { PartialEnvGroup, PopulatedEnvGroup } from "../../../../components/porter-form/types";
|
|
import { PartialEnvGroup, PopulatedEnvGroup } from "../../../../components/porter-form/types";
|
|
|
import { BuildMethod, PorterApp } from "../types/porterApp";
|
|
import { BuildMethod, PorterApp } from "../types/porterApp";
|
|
|
import EventFocusView from "./activity-feed/events/focus-views/EventFocusView";
|
|
import EventFocusView from "./activity-feed/events/focus-views/EventFocusView";
|
|
|
|
|
+import HelmValuesTab from "./HelmValuesTab";
|
|
|
|
|
|
|
|
type Props = RouteComponentProps & {};
|
|
type Props = RouteComponentProps & {};
|
|
|
|
|
|
|
@@ -72,6 +70,7 @@ const validTabs = [
|
|
|
"build-settings",
|
|
"build-settings",
|
|
|
"settings",
|
|
"settings",
|
|
|
"events",
|
|
"events",
|
|
|
|
|
+ "helm-values",
|
|
|
] as const;
|
|
] as const;
|
|
|
const DEFAULT_TAB = "activity";
|
|
const DEFAULT_TAB = "activity";
|
|
|
type ValidTab = typeof validTabs[number];
|
|
type ValidTab = typeof validTabs[number];
|
|
@@ -85,7 +84,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
currentCluster,
|
|
currentCluster,
|
|
|
currentProject,
|
|
currentProject,
|
|
|
setCurrentError,
|
|
setCurrentError,
|
|
|
- featurePreview,
|
|
|
|
|
|
|
+ user,
|
|
|
} = useContext(Context);
|
|
} = useContext(Context);
|
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
const [deleting, setDeleting] = useState(false);
|
|
const [deleting, setDeleting] = useState(false);
|
|
@@ -95,7 +94,6 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
);
|
|
);
|
|
|
const [hasBuiltImage, setHasBuiltImage] = useState<boolean>(false);
|
|
const [hasBuiltImage, setHasBuiltImage] = useState<boolean>(false);
|
|
|
|
|
|
|
|
- const [error, setError] = useState(null);
|
|
|
|
|
const [forceRefreshRevisions, setForceRefreshRevisions] = useState<boolean>(
|
|
const [forceRefreshRevisions, setForceRefreshRevisions] = useState<boolean>(
|
|
|
false
|
|
false
|
|
|
);
|
|
);
|
|
@@ -112,8 +110,6 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
const [showUnsavedChangesBanner, setShowUnsavedChangesBanner] = useState<boolean>(false);
|
|
const [showUnsavedChangesBanner, setShowUnsavedChangesBanner] = useState<boolean>(false);
|
|
|
|
|
|
|
|
const [expandedJob, setExpandedJob] = useState(null);
|
|
const [expandedJob, setExpandedJob] = useState(null);
|
|
|
- const [logs, setLogs] = useState<Log[]>([]);
|
|
|
|
|
-
|
|
|
|
|
const [services, setServices] = useState<Service[]>([]);
|
|
const [services, setServices] = useState<Service[]>([]);
|
|
|
const [envVars, setEnvVars] = useState<KeyValueType[]>([]);
|
|
const [envVars, setEnvVars] = useState<KeyValueType[]>([]);
|
|
|
const [buttonStatus, setButtonStatus] = useState<React.ReactNode>("");
|
|
const [buttonStatus, setButtonStatus] = useState<React.ReactNode>("");
|
|
@@ -128,13 +124,6 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
const { eventId, tab } = useParams<Params>();
|
|
const { eventId, tab } = useParams<Params>();
|
|
|
const selectedTab: ValidTab = tab != null && validTabs.includes(tab) ? tab : DEFAULT_TAB;
|
|
const selectedTab: ValidTab = tab != null && validTabs.includes(tab) ? tab : DEFAULT_TAB;
|
|
|
|
|
|
|
|
- useEffect(() => {
|
|
|
|
|
- setBannerLoading(true);
|
|
|
|
|
- getBuildLogs().then(() => {
|
|
|
|
|
- setBannerLoading(false);
|
|
|
|
|
- });
|
|
|
|
|
- }, [appData]);
|
|
|
|
|
-
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
if (!_.isEqual(_.omitBy(porterApp, _.isEmpty), _.omitBy(tempPorterApp, _.isEmpty))) {
|
|
if (!_.isEqual(_.omitBy(porterApp, _.isEmpty), _.omitBy(tempPorterApp, _.isEmpty))) {
|
|
|
setButtonStatus("");
|
|
setButtonStatus("");
|
|
@@ -197,7 +186,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- setError(err)
|
|
|
|
|
|
|
+ // that's ok if there's an error, just means there is no pre-deploy chart
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// update apps and release
|
|
// update apps and release
|
|
@@ -313,7 +302,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- setError(err);
|
|
|
|
|
|
|
+ // TODO: handle error
|
|
|
} finally {
|
|
} finally {
|
|
|
setIsLoading(false);
|
|
setIsLoading(false);
|
|
|
}
|
|
}
|
|
@@ -342,7 +331,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
try {
|
|
try {
|
|
|
await Promise.all(removeApplicationToEnvGroupPromises);
|
|
await Promise.all(removeApplicationToEnvGroupPromises);
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
- setError(error);
|
|
|
|
|
|
|
+ // TODO: Handle error
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
try {
|
|
try {
|
|
@@ -378,13 +367,13 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
);
|
|
);
|
|
|
props.history.push("/apps");
|
|
props.history.push("/apps");
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- setError(err);
|
|
|
|
|
|
|
+ // TODO: handle error
|
|
|
} finally {
|
|
} finally {
|
|
|
setDeleting(false);
|
|
setDeleting(false);
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- const updatePorterApp = async (options: Partial<PorterAppOptions>) => {
|
|
|
|
|
|
|
+ const updatePorterApp = async (options: Partial<CreateUpdatePorterAppOptions>) => {
|
|
|
//setting the EnvGroups Config Maps
|
|
//setting the EnvGroups Config Maps
|
|
|
const filteredEnvGroups = deletedEnvGroups.filter((deletedEnvGroup) => {
|
|
const filteredEnvGroups = deletedEnvGroups.filter((deletedEnvGroup) => {
|
|
|
return !syncedEnvGroups.some((syncedEnvGroup) => {
|
|
return !syncedEnvGroups.some((syncedEnvGroup) => {
|
|
@@ -436,7 +425,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
try {
|
|
try {
|
|
|
await Promise.all(addApplicationToEnvGroupPromises);
|
|
await Promise.all(addApplicationToEnvGroupPromises);
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
- setError(error);
|
|
|
|
|
|
|
+ // TODO: handle error
|
|
|
}
|
|
}
|
|
|
try {
|
|
try {
|
|
|
setButtonStatus("loading");
|
|
setButtonStatus("loading");
|
|
@@ -464,9 +453,9 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
repo_name: tempPorterApp.repo_name,
|
|
repo_name: tempPorterApp.repo_name,
|
|
|
git_branch: tempPorterApp.git_branch,
|
|
git_branch: tempPorterApp.git_branch,
|
|
|
buildpacks: "",
|
|
buildpacks: "",
|
|
|
- ...options,
|
|
|
|
|
env_groups: syncedEnvGroups?.map((env) => env.name),
|
|
env_groups: syncedEnvGroups?.map((env) => env.name),
|
|
|
user_update: true,
|
|
user_update: true,
|
|
|
|
|
+ ...options,
|
|
|
}
|
|
}
|
|
|
if (buildView === "docker") {
|
|
if (buildView === "docker") {
|
|
|
updatedPorterApp.dockerfile = tempPorterApp.dockerfile;
|
|
updatedPorterApp.dockerfile = tempPorterApp.dockerfile;
|
|
@@ -481,7 +470,6 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
await api.createPorterApp(
|
|
await api.createPorterApp(
|
|
|
"<token>",
|
|
"<token>",
|
|
|
updatedPorterApp,
|
|
updatedPorterApp,
|
|
|
-
|
|
|
|
|
{
|
|
{
|
|
|
cluster_id: currentCluster.id,
|
|
cluster_id: currentCluster.id,
|
|
|
project_id: currentProject.id,
|
|
project_id: currentProject.id,
|
|
@@ -494,13 +482,12 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
setPorterApp(tempPorterApp);
|
|
setPorterApp(tempPorterApp);
|
|
|
setButtonStatus("success");
|
|
setButtonStatus("success");
|
|
|
setShowUnsavedChangesBanner(false);
|
|
setShowUnsavedChangesBanner(false);
|
|
|
|
|
+ getPorterApp({ revision: 0 });
|
|
|
} else {
|
|
} else {
|
|
|
setButtonStatus(<Error message="Unable to update app" />);
|
|
setButtonStatus(<Error message="Unable to update app" />);
|
|
|
}
|
|
}
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
// TODO: better error handling
|
|
// TODO: better error handling
|
|
|
-
|
|
|
|
|
- console.log(err);
|
|
|
|
|
const errMessage =
|
|
const errMessage =
|
|
|
err?.response?.data?.error ??
|
|
err?.response?.data?.error ??
|
|
|
err?.toString() ??
|
|
err?.toString() ??
|
|
@@ -509,70 +496,6 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- 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");
|
|
|
|
|
- const timestampPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+Z/;
|
|
|
|
|
-
|
|
|
|
|
- lines.forEach((line, index) => {
|
|
|
|
|
- const lineWithoutTimestamp = line
|
|
|
|
|
- .replace(timestampPattern, "")
|
|
|
|
|
- .trimStart();
|
|
|
|
|
- const anserLine: AnserJsonEntry[] = Anser.ansiToJson(
|
|
|
|
|
- lineWithoutTimestamp
|
|
|
|
|
- );
|
|
|
|
|
- if (lineWithoutTimestamp.toLowerCase().includes("error")) {
|
|
|
|
|
- anserLine[0].fg = "238,75,43";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const log: Log = {
|
|
|
|
|
- line: anserLine,
|
|
|
|
|
- lineNumber: index + 1,
|
|
|
|
|
- timestamp: line.match(timestampPattern)?.[0],
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- logs.push(log);
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- setLogs(logs);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- setError(error);
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
const fetchPorterYamlContent = async (
|
|
const fetchPorterYamlContent = async (
|
|
|
porterYaml: string,
|
|
porterYaml: string,
|
|
|
appData: any
|
|
appData: any
|
|
@@ -668,7 +591,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const setRevision = (chart: ChartType, isCurrent?: boolean) => {
|
|
const setRevision = (chart: ChartType, isCurrent?: boolean) => {
|
|
|
- getPorterApp({ revision: chart.version });
|
|
|
|
|
|
|
+ getPorterApp({ revision: isCurrent ? 0 : chart.version });
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const getReadableDate = (s: string) => {
|
|
const getReadableDate = (s: string) => {
|
|
@@ -699,6 +622,12 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
|
|
|
|
|
const renderTabContents = () => {
|
|
const renderTabContents = () => {
|
|
|
switch (selectedTab) {
|
|
switch (selectedTab) {
|
|
|
|
|
+ case "activity":
|
|
|
|
|
+ return <ActivityFeed
|
|
|
|
|
+ chart={appData.chart}
|
|
|
|
|
+ stackName={appData?.app?.name}
|
|
|
|
|
+ appData={appData}
|
|
|
|
|
+ />;
|
|
|
case "overview":
|
|
case "overview":
|
|
|
return (
|
|
return (
|
|
|
<>
|
|
<>
|
|
@@ -797,12 +726,6 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
</Button>
|
|
</Button>
|
|
|
</>
|
|
</>
|
|
|
);
|
|
);
|
|
|
- case "activity":
|
|
|
|
|
- return <ActivityFeed
|
|
|
|
|
- chart={appData.chart}
|
|
|
|
|
- stackName={appData?.app?.name}
|
|
|
|
|
- appData={appData}
|
|
|
|
|
- />;
|
|
|
|
|
case "events":
|
|
case "events":
|
|
|
if (eventId != null) {
|
|
if (eventId != null) {
|
|
|
return <EventFocusView
|
|
return <EventFocusView
|
|
@@ -840,6 +763,12 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
setDeletedEnvGroups={setDeleteEnvGroups}
|
|
setDeletedEnvGroups={setDeleteEnvGroups}
|
|
|
/>
|
|
/>
|
|
|
);
|
|
);
|
|
|
|
|
+ case "helm-values":
|
|
|
|
|
+ return <HelmValuesTab
|
|
|
|
|
+ currentChart={appData.chart}
|
|
|
|
|
+ updatePorterApp={updatePorterApp}
|
|
|
|
|
+ buttonStatus={buttonStatus}
|
|
|
|
|
+ />
|
|
|
default:
|
|
default:
|
|
|
return <ActivityFeed
|
|
return <ActivityFeed
|
|
|
chart={appData.chart}
|
|
chart={appData.chart}
|
|
@@ -1058,6 +987,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
|
|
|
value: "build-settings",
|
|
value: "build-settings",
|
|
|
},
|
|
},
|
|
|
{ label: "Settings", value: "settings" },
|
|
{ label: "Settings", value: "settings" },
|
|
|
|
|
+ user.email.endsWith("porter.run") && { label: "Helm values", value: "helm-values" },
|
|
|
].filter((x) => x)}
|
|
].filter((x) => x)}
|
|
|
currentTab={selectedTab}
|
|
currentTab={selectedTab}
|
|
|
setCurrentTab={(tab: string) => {
|
|
setCurrentTab={(tab: string) => {
|