| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127 |
- import React, { useCallback, useContext, useEffect, useState } from "react";
- import styled from "styled-components";
- import yaml from "js-yaml";
- import _, { cloneDeep } from "lodash";
- import loadingSrc from "assets/loading.gif";
- import leftArrow from "assets/left-arrow.svg";
- import { ChartType, ClusterType, ResourceType } from "shared/types";
- import { Context } from "shared/Context";
- import api from "shared/api";
- import StatusIndicator from "components/StatusIndicator";
- import PorterFormWrapper from "components/porter-form/PorterFormWrapper";
- import RevisionSection from "./RevisionSection";
- import ValuesYaml from "./ValuesYaml";
- import GraphSection from "./GraphSection";
- import MetricsSection from "./metrics/MetricsSection";
- import LogsSection from "./logs-section/LogsSection";
- import ListSection from "./ListSection";
- import StatusSection from "./status/StatusSection";
- import SettingsSection from "./SettingsSection";
- import Loading from "components/Loading";
- import { useWebsockets } from "shared/hooks/useWebsockets";
- import useAuth from "shared/auth/useAuth";
- import TitleSection from "components/TitleSection";
- import DeploymentType from "./DeploymentType";
- import EventsTab from "./events/EventsTab";
- import BuildSettingsTab from "./build-settings/BuildSettingsTab";
- import { DisabledNamespacesForIncidents } from "./incidents/DisabledNamespaces";
- import { useStackEnvGroups } from "./useStackEnvGroups";
- type Props = {
- namespace: string;
- currentChart: ChartType;
- currentCluster: ClusterType;
- closeChart: () => void;
- setSidebar: (x: boolean) => void;
- isMetricsInstalled: boolean;
- };
- const getReadableDate = (s: string) => {
- let ts = new Date(s);
- let date = ts.toLocaleDateString();
- let time = ts.toLocaleTimeString([], {
- hour: "numeric",
- minute: "2-digit",
- });
- return `${time} on ${date}`;
- };
- const ExpandedChart: React.FC<Props> = (props) => {
- const [currentChart, setCurrentChart] = useState<ChartType>(
- props.currentChart
- );
- const [showRevisions, setShowRevisions] = useState<boolean>(false);
- const [loading, setLoading] = useState<boolean>(false);
- const [components, setComponents] = useState<ResourceType[]>([]);
- const [isPreview, setIsPreview] = useState<boolean>(false);
- const [devOpsMode, setDevOpsMode] = useState<boolean>(
- localStorage.getItem("devOpsMode") === "true"
- );
- const [rightTabOptions, setRightTabOptions] = useState<any[]>([]);
- const [leftTabOptions, setLeftTabOptions] = useState<any[]>([]);
- const [saveValuesStatus, setSaveValueStatus] = useState<string>(null);
- const [forceRefreshRevisions, setForceRefreshRevisions] = useState<boolean>(
- false
- );
- const [controllers, setControllers] = useState<
- Record<string, Record<string, any>>
- >({});
- const [url, setUrl] = useState<string>(null);
- const [deleting, setDeleting] = useState<boolean>(false);
- const [imageIsPlaceholder, setImageIsPlaceholer] = useState<boolean>(false);
- const [newestImage, setNewestImage] = useState<string>(null);
- const [isLoadingChartData, setIsLoadingChartData] = useState<boolean>(true);
- const [showRepoTooltip, setShowRepoTooltip] = useState(false);
- const [isAuthorized] = useAuth();
- const [fullScreenLogs, setFullScreenLogs] = useState<boolean>(false);
- const [isFullscreen, setIsFullscreen] = useState<boolean>(false);
- const {
- isStack,
- stackEnvGroups,
- isLoadingStackEnvGroups,
- } = useStackEnvGroups(currentChart);
- const {
- newWebsocket,
- openWebsocket,
- closeAllWebsockets,
- closeWebsocket,
- } = useWebsockets();
- const {
- currentCluster,
- currentProject,
- setCurrentError,
- setCurrentOverlay,
- } = useContext(Context);
- // Retrieve full chart data (includes form and values)
- const getChartData = async (chart: ChartType) => {
- setIsLoadingChartData(true);
- const res = await api.getChart(
- "<token>",
- {},
- {
- name: chart.name,
- namespace: chart.namespace,
- cluster_id: currentCluster.id,
- revision: chart.version,
- id: currentProject.id,
- }
- );
- const image = res.data?.config?.image?.repository;
- const tag = res.data?.config?.image?.tag?.toString();
- const newNewestImage = tag ? image + ":" + tag : image;
- let imageIsPlaceholder = false;
- if (
- (image === "porterdev/hello-porter" ||
- image === "public.ecr.aws/o1j4x7p4/hello-porter") &&
- !newestImage
- ) {
- imageIsPlaceholder = true;
- }
- setImageIsPlaceholer(imageIsPlaceholder);
- setNewestImage(newNewestImage);
- const updatedChart = res.data;
- setCurrentChart(updatedChart);
- updateComponents(updatedChart).finally(() => setIsLoadingChartData(false));
- };
- const getControllers = async (chart: ChartType) => {
- // don't retrieve controllers for chart that failed to even deploy.
- if (chart.info.status == "failed") return;
- try {
- const { data: chartControllers } = await api.getChartControllers(
- "<token>",
- {},
- {
- name: chart.name,
- namespace: chart.namespace,
- cluster_id: currentCluster.id,
- revision: chart.version,
- id: currentProject.id,
- }
- );
- chartControllers.forEach((c: any) => {
- c.metadata.kind = c.kind;
- setControllers((oldControllers) => ({
- ...oldControllers,
- [c.metadata.uid]: c,
- }));
- });
- return;
- } catch (error) {
- if (typeof error !== "string") {
- setCurrentError(JSON.stringify(error));
- }
- setCurrentError(error);
- }
- };
- const setupWebsocket = (kind: string) => {
- const apiEndpoint = `/api/projects/${currentProject.id}/clusters/${currentCluster.id}/${kind}/status`;
- const wsConfig = {
- onmessage(evt: MessageEvent) {
- const event = JSON.parse(evt.data);
- let object = event.Object;
- object.metadata.kind = event.Kind;
- if (event.event_type != "UPDATE") {
- return;
- }
- setControllers((oldControllers) => {
- if (
- oldControllers &&
- oldControllers[object.metadata.uid]?.status?.conditions ==
- object.status?.conditions
- ) {
- return oldControllers;
- }
- return {
- ...oldControllers,
- [object.metadata.uid]: object,
- };
- });
- },
- onerror() {
- closeWebsocket(kind);
- },
- };
- newWebsocket(kind, apiEndpoint, wsConfig);
- };
- const updateComponents = async (currentChart: ChartType) => {
- setLoading(true);
- try {
- const res = await api.getChartComponents(
- "<token>",
- {},
- {
- id: currentProject.id,
- name: currentChart.name,
- namespace: currentChart.namespace,
- cluster_id: currentCluster.id,
- revision: currentChart.version,
- }
- );
- setComponents(res.data.Objects);
- setLoading(false);
- } catch (error) {
- console.log(error);
- setLoading(false);
- }
- };
- const onSubmit = async (props: any) => {
- const rawValues = props.values;
- // Convert dotted keys to nested objects
- let values: any = {};
- // Weave in preexisting values and convert to yaml
- if (props?.currentChart?.config) {
- values = props.currentChart.config;
- }
- // Override config from currentChart prop if we have it on the current state
- if (currentChart.config) {
- values = currentChart.config;
- }
- for (let key in rawValues) {
- _.set(values, key, rawValues[key]);
- }
- let valuesYaml = yaml.dump({
- ...values,
- });
- const syncedEnvGroups = props?.metadata
- ? props?.metadata["container.env"]
- : {};
- const deletedEnvGroups = syncedEnvGroups?.deleted || [];
- const addedEnvGroups = syncedEnvGroups?.added || [];
- const addApplicationToEnvGroupPromises = addedEnvGroups.map(
- (envGroup: any) => {
- return api.addApplicationToEnvGroup(
- "<token>",
- {
- name: envGroup?.name,
- app_name: currentChart.name,
- },
- {
- project_id: currentProject.id,
- cluster_id: currentCluster.id,
- namespace: currentChart.namespace,
- }
- );
- }
- );
- try {
- await Promise.all(addApplicationToEnvGroupPromises);
- } catch (error) {
- setCurrentError(
- "We coudln't sync the env group to the application, please try again."
- );
- }
- const removeApplicationToEnvGroupPromises = deletedEnvGroups.map(
- (envGroup: any) => {
- return api.removeApplicationFromEnvGroup(
- "<token>",
- {
- name: envGroup?.name,
- app_name: currentChart.name,
- },
- {
- project_id: currentProject.id,
- cluster_id: currentCluster.id,
- namespace: currentChart.namespace,
- }
- );
- }
- );
- try {
- await Promise.all(removeApplicationToEnvGroupPromises);
- } catch (error) {
- setCurrentError(
- "We coudln't remove the synced env group from the application, please try again."
- );
- }
- setSaveValueStatus("loading");
- try {
- await api.upgradeChartValues(
- "<token>",
- {
- values: valuesYaml,
- // this is triggered from the Porter form, so we set the latest revision to ensure that the release is
- // up to date
- latest_revision: currentChart.version,
- },
- {
- id: currentProject.id,
- namespace: currentChart.namespace,
- name: currentChart.name,
- cluster_id: currentCluster.id,
- }
- );
- getChartData(currentChart);
- setSaveValueStatus("successful");
- setForceRefreshRevisions(true);
- window.analytics?.track("Chart Upgraded", {
- chart: currentChart.name,
- values: valuesYaml,
- });
- } catch (err) {
- const parsedErr = err?.response?.data?.error;
- if (parsedErr) {
- err = parsedErr;
- }
- setSaveValueStatus("The api answered with an error");
- setCurrentError(JSON.stringify(parsedErr));
- window.analytics?.track("Failed to Upgrade Chart", {
- chart: currentChart.name,
- values: valuesYaml,
- error: err,
- });
- return;
- }
- };
- const handleUpgradeVersion = useCallback(
- async (version: string, cb: () => void) => {
- // convert current values to yaml
- let values = currentChart.config;
- let valuesYaml = yaml.dump({
- ...values,
- });
- setSaveValueStatus("loading");
- getChartData(currentChart);
- try {
- await api.upgradeChartValues(
- "<token>",
- {
- values: valuesYaml,
- version: version,
- latest_revision: currentChart.version,
- },
- {
- id: currentProject.id,
- namespace: currentChart.namespace,
- name: currentChart.name,
- cluster_id: currentCluster.id,
- }
- );
- setSaveValueStatus("successful");
- setForceRefreshRevisions(true);
- window.analytics?.track("Chart Upgraded", {
- chart: currentChart.name,
- values: valuesYaml,
- });
- cb && cb();
- } catch (err) {
- let parsedErr = err?.response?.data?.error;
- if (parsedErr) {
- err = parsedErr;
- }
- setSaveValueStatus(err);
- setCurrentError(parsedErr);
- window.analytics?.track("Failed to Upgrade Chart", {
- chart: currentChart.name,
- values: valuesYaml,
- error: err,
- });
- }
- },
- [currentChart]
- );
- const renderTabContents = (currentTab: string) => {
- let { setSidebar } = props;
- let chart = currentChart;
- switch (currentTab) {
- case "logs":
- return (
- <LogsSection
- currentChart={chart}
- isFullscreen={isFullscreen}
- setIsFullscreen={setIsFullscreen}
- />
- );
- case "metrics":
- return <MetricsSection currentChart={chart} />;
- case "events":
- if (DisabledNamespacesForIncidents.includes(currentChart.namespace)) {
- return null;
- }
- return <EventsTab currentChart={chart} />;
- case "status":
- if (isLoadingChartData) {
- return (
- <Placeholder>
- <Loading />
- </Placeholder>
- );
- }
- if (imageIsPlaceholder) {
- return (
- <Placeholder>
- <TextWrap>
- <Header>
- <Spinner src={loadingSrc} /> This application is currently
- being deployed
- </Header>
- Navigate to the{" "}
- <A
- href={
- props.currentChart.git_action_config &&
- `https://github.com/${props.currentChart.git_action_config?.git_repo}/actions`
- }
- target={"_blank"}
- >
- Actions
- </A>{" "}
- tab of your GitHub repo to view live build logs.
- </TextWrap>
- </Placeholder>
- );
- } else {
- return (
- <StatusSection
- currentChart={chart}
- setFullScreenLogs={() => setFullScreenLogs(true)}
- />
- );
- }
- case "settings":
- return (
- <SettingsSection
- currentChart={chart}
- refreshChart={() => getChartData(currentChart)}
- setShowDeleteOverlay={(x: boolean) => {
- if (x) {
- setCurrentOverlay({
- message: `Are you sure you want to delete ${currentChart.name}?`,
- onYes: handleUninstallChart,
- onNo: () => setCurrentOverlay(null),
- });
- } else {
- setCurrentOverlay(null);
- }
- }}
- />
- );
- case "graph":
- return (
- <GraphSection
- components={components}
- currentChart={chart}
- setSidebar={setSidebar}
- // Handle resize YAML wrapper
- showRevisions={showRevisions}
- />
- );
- case "list":
- return (
- <ListSection
- currentChart={chart}
- components={components}
- // Handle resize YAML wrapper
- showRevisions={showRevisions}
- />
- );
- case "values":
- return (
- <ValuesYaml
- currentChart={chart}
- refreshChart={() => getChartData(currentChart)}
- disabled={!isAuthorized("application", "", ["get", "update"])}
- />
- );
- case "build-settings":
- return (
- <BuildSettingsTab
- chart={chart}
- isPreviousVersion={isPreview}
- onSave={() => {
- getChartData(currentChart);
- }}
- />
- );
- default:
- }
- };
- const updateTabs = () => {
- // Collate non-form tabs
- let rightTabOptions = [] as any[];
- let leftTabOptions = [] as any[];
- if (
- currentChart.chart.metadata.home === "https://getporter.dev/" &&
- (currentChart.chart.metadata.name === "web" ||
- currentChart.chart.metadata.name === "worker" ||
- currentChart.chart.metadata.name === "job")
- ) {
- leftTabOptions.push({ label: "Events", value: "events" });
- leftTabOptions.push({ label: "Logs", value: "logs" });
- }
- leftTabOptions.push({ label: "Status", value: "status" });
- if (props.isMetricsInstalled) {
- leftTabOptions.push({ label: "Metrics", value: "metrics" });
- }
- rightTabOptions.push({ label: "Chart Overview", value: "graph" });
- if (devOpsMode) {
- rightTabOptions.push(
- { label: "Manifests", value: "list" },
- { label: "Helm Values", value: "values" }
- );
- }
- if (currentChart?.git_action_config?.git_repo && !isStack) {
- rightTabOptions.push({
- label: "Build Settings",
- value: "build-settings",
- });
- }
- // Settings tab is always last
- if (isAuthorized("application", "", ["get", "delete"])) {
- rightTabOptions.push({ label: "Settings", value: "settings" });
- }
- // Filter tabs if previewing an old revision or updating the chart version
- if (isPreview) {
- let liveTabs = ["status", "events", "settings", "deploy", "metrics"];
- rightTabOptions = rightTabOptions.filter(
- (tab: any) => !liveTabs.includes(tab.value)
- );
- leftTabOptions = leftTabOptions.filter(
- (tab: any) => !liveTabs.includes(tab.value)
- );
- }
- setLeftTabOptions(leftTabOptions);
- setRightTabOptions(rightTabOptions);
- };
- const setRevision = (chart: ChartType, isCurrent?: boolean) => {
- setIsPreview(!isCurrent);
- getChartData(chart);
- };
- // TODO: consolidate with pop + push in refreshTabs
- const toggleDevOpsMode = () => {
- setDevOpsMode(!devOpsMode);
- };
- const renderUrl = () => {
- if (url) {
- return (
- <Url href={url} target="_blank">
- <i className="material-icons">link</i>
- {url}
- </Url>
- );
- }
- const service: any = components?.find((c) => {
- return c.Kind === "Service";
- });
- if (loading) {
- return (
- <Url>
- <Bolded>Loading...</Bolded>
- </Url>
- );
- }
- if (!service?.Name || !service?.Namespace) {
- return;
- }
- return (
- <Url>
- <Bolded>Internal URI:</Bolded>
- {`${service.Name}.${service.Namespace}.svc.cluster.local`}
- </Url>
- );
- };
- const handleUninstallChart = async () => {
- setDeleting(true);
- setCurrentOverlay(null);
- const syncedEnvGroups = currentChart.config?.container?.env?.synced || [];
- const removeApplicationToEnvGroupPromises = syncedEnvGroups.map(
- (envGroup: any) => {
- return api.removeApplicationFromEnvGroup(
- "<token>",
- {
- name: envGroup?.name,
- app_name: currentChart.name,
- },
- {
- project_id: currentProject.id,
- cluster_id: currentCluster.id,
- namespace: currentChart.namespace,
- }
- );
- }
- );
- try {
- await Promise.all(removeApplicationToEnvGroupPromises);
- } catch (error) {
- setCurrentError(
- "We coudln't remove the synced env group from the application, please remove it manually before uninstalling the chart, or try again."
- );
- return;
- }
- try {
- if (currentChart.stack_id) {
- await api.removeStackAppResource(
- "<token>",
- {},
- {
- namespace: currentChart.namespace,
- app_resource_name: currentChart.name,
- project_id: currentProject.id,
- cluster_id: currentCluster.id,
- stack_id: currentChart.stack_id,
- }
- );
- } else {
- await api.uninstallTemplate(
- "<token>",
- {},
- {
- namespace: currentChart.namespace,
- name: currentChart.name,
- id: currentProject.id,
- cluster_id: currentCluster.id,
- }
- );
- }
- props.closeChart();
- } catch (error) {
- console.log(error);
- setCurrentError("Couldn't uninstall chart, please try again");
- }
- };
- useEffect(() => {
- window.analytics?.track("Opened Chart", {
- chart: currentChart.name,
- });
- getChartData(currentChart).then(() => {
- getControllers(currentChart).then(() => {
- ["deployment", "statefulset", "daemonset", "replicaset"]
- .map((kind) => {
- setupWebsocket(kind);
- return kind;
- })
- .forEach((kind) => {
- openWebsocket(kind);
- });
- });
- });
- return () => {
- closeAllWebsockets();
- };
- }, []);
- useEffect(() => {
- updateTabs();
- localStorage.setItem("devOpsMode", devOpsMode.toString());
- }, [devOpsMode, currentChart?.form, isPreview]);
- useEffect((): any => {
- let isSubscribed = true;
- const ingressComponent = components?.find((c) => c.Kind === "Ingress");
- const ingressName = ingressComponent?.Name;
- if (!ingressName) return;
- api
- .getIngress(
- "<token>",
- {},
- {
- id: currentProject.id,
- name: ingressName,
- cluster_id: currentCluster.id,
- namespace: `${currentChart.namespace}`,
- }
- )
- .then((res) => {
- if (!isSubscribed) {
- return;
- }
- if (res.data?.spec?.rules && res.data?.spec?.rules[0]?.host) {
- setUrl(`https://${res.data?.spec?.rules[0]?.host}`);
- return;
- }
- if (res.data?.status?.loadBalancer?.ingress) {
- setUrl(
- `http://${res.data?.status?.loadBalancer?.ingress[0]?.hostname}`
- );
- return;
- }
- })
- .catch(console.log);
- return () => (isSubscribed = false);
- }, [components, currentCluster, currentProject, currentChart]);
- return (
- <>
- {fullScreenLogs ? (
- <StatusSection
- fullscreen={true}
- currentChart={currentChart}
- setFullScreenLogs={() => setFullScreenLogs(false)}
- />
- ) : (
- <>
- {isFullscreen ? (
- <LogsSection
- isFullscreen={true}
- setIsFullscreen={setIsFullscreen}
- currentChart={currentChart}
- />
- ) : (
- <StyledExpandedChart>
- <BreadcrumbRow>
- <Breadcrumb onClick={props.closeChart}>
- <ArrowIcon src={leftArrow} />
- <Wrap>Back</Wrap>
- </Breadcrumb>
- </BreadcrumbRow>
- <HeaderWrapper>
- <TitleSection
- icon={currentChart.chart.metadata.icon}
- iconWidth="33px"
- >
- {currentChart.name}
- <DeploymentType currentChart={currentChart} />
- <TagWrapper>
- Namespace{" "}
- <NamespaceTag>{currentChart.namespace}</NamespaceTag>
- </TagWrapper>
- </TitleSection>
- {currentChart.chart.metadata.name != "worker" &&
- currentChart.chart.metadata.name != "job" &&
- renderUrl()}
- <InfoWrapper>
- <StatusIndicator
- controllers={controllers}
- status={currentChart.info.status}
- margin_left={"0px"}
- />
- <LastDeployed>
- <Dot>•</Dot>Last deployed
- {" " + getReadableDate(currentChart.info.last_deployed)}
- </LastDeployed>
- </InfoWrapper>
- </HeaderWrapper>
- {deleting ? (
- <>
- <LineBreak />
- <Placeholder>
- <TextWrap>
- <Header>
- <Spinner src={loadingSrc} /> Deleting "
- {currentChart.name}"
- </Header>
- You will be automatically redirected after deletion is
- complete.
- </TextWrap>
- </Placeholder>
- </>
- ) : (
- <>
- <RevisionSection
- showRevisions={showRevisions}
- toggleShowRevisions={() => {
- setShowRevisions(!showRevisions);
- }}
- chart={currentChart}
- refreshChart={() => getChartData(currentChart)}
- setRevision={setRevision}
- forceRefreshRevisions={forceRefreshRevisions}
- refreshRevisionsOff={() => setForceRefreshRevisions(false)}
- shouldUpdate={
- currentChart.latest_version &&
- currentChart.latest_version !==
- currentChart.chart.metadata.version
- }
- latestVersion={currentChart.latest_version}
- upgradeVersion={handleUpgradeVersion}
- />
- {isStack && isLoadingStackEnvGroups ? (
- <>
- <LineBreak />
- <Placeholder>
- <TextWrap>
- <Header>
- <Spinner src={loadingSrc} />
- </Header>
- </TextWrap>
- </Placeholder>
- </>
- ) : (
- <>
- {(isPreview || leftTabOptions.length > 0) && (
- <BodyWrapper>
- <PorterFormWrapper
- formData={cloneDeep(currentChart.form)}
- valuesToOverride={{
- namespace: props.namespace,
- clusterId: currentCluster.id,
- }}
- renderTabContents={renderTabContents}
- isReadOnly={
- isPreview ||
- imageIsPlaceholder ||
- !isAuthorized("application", "", [
- "get",
- "update",
- ])
- }
- onSubmit={onSubmit}
- includeMetadata
- rightTabOptions={rightTabOptions}
- leftTabOptions={leftTabOptions}
- color={isPreview ? "#f5cb42" : null}
- addendum={
- <TabButton
- onClick={toggleDevOpsMode}
- devOpsMode={devOpsMode}
- >
- <i className="material-icons">offline_bolt</i>{" "}
- DevOps Mode
- </TabButton>
- }
- saveValuesStatus={saveValuesStatus}
- injectedProps={{
- "key-value-array": {
- availableSyncEnvGroups:
- isStack && !isPreview
- ? stackEnvGroups
- : undefined,
- },
- "url-link": {
- chart: currentChart,
- },
- }}
- />
- </BodyWrapper>
- )}
- </>
- )}
- </>
- )}
- </StyledExpandedChart>
- )}
- </>
- )}
- </>
- );
- };
- export default ExpandedChart;
- const ArrowIcon = styled.img`
- width: 15px;
- margin-right: 8px;
- opacity: 50%;
- `;
- const BreadcrumbRow = styled.div`
- width: 100%;
- display: flex;
- justify-content: flex-start;
- z-index: 999;
- `;
- const Breadcrumb = styled.div`
- color: #aaaabb88;
- font-size: 13px;
- margin-bottom: 15px;
- display: flex;
- align-items: center;
- margin-top: -10px;
- z-index: 999;
- padding: 5px;
- padding-right: 7px;
- border-radius: 5px;
- cursor: pointer;
- :hover {
- background: #ffffff11;
- }
- `;
- const Wrap = styled.div`
- z-index: 999;
- `;
- const TextWrap = styled.div``;
- const LineBreak = styled.div`
- width: calc(100% - 0px);
- height: 1px;
- background: #494b4f;
- margin: 35px 0px;
- `;
- const BodyWrapper = styled.div`
- position: relative;
- padding-bottom: 0;
- margin-bottom: 0;
- `;
- const Header = styled.div`
- font-weight: 500;
- color: #aaaabb;
- font-size: 16px;
- margin-bottom: 15px;
- `;
- const Placeholder = styled.div`
- width: 100%;
- min-height: 300px;
- height: 40vh;
- display: flex;
- align-items: center;
- justify-content: center;
- color: #ffffff44;
- font-size: 14px;
- > i {
- font-size: 18px;
- margin-right: 10px;
- }
- `;
- const Spinner = styled.img`
- width: 15px;
- height: 15px;
- margin-right: 12px;
- margin-bottom: -2px;
- `;
- const Bolded = styled.div`
- font-weight: 500;
- color: #ffffff44;
- margin-right: 6px;
- `;
- const Url = styled.a`
- display: block;
- margin-left: 2px;
- font-size: 13px;
- margin-top: 16px;
- user-select: all;
- margin-bottom: -5px;
- user-select: text;
- display: flex;
- align-items: center;
- > i {
- font-size: 15px;
- margin-right: 10px;
- }
- `;
- const TabButton = styled.div`
- position: absolute;
- right: 0px;
- height: 30px;
- background: linear-gradient(to right, #20222700, #202227 20%);
- padding-left: 30px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 13px;
- color: ${(props: { devOpsMode: boolean }) =>
- props.devOpsMode ? "#aaaabb" : "#aaaabb55"};
- margin-left: 35px;
- border-radius: 20px;
- text-shadow: 0px 0px 8px
- ${(props: { devOpsMode: boolean }) =>
- props.devOpsMode ? "#ffffff66" : "none"};
- cursor: pointer;
- :hover {
- color: ${(props: { devOpsMode: boolean }) =>
- props.devOpsMode ? "" : "#aaaabb99"};
- }
- > i {
- font-size: 17px;
- margin-right: 9px;
- }
- `;
- const HeaderWrapper = styled.div`
- position: relative;
- `;
- const Dot = styled.div`
- margin-right: 9px;
- `;
- const InfoWrapper = styled.div`
- display: flex;
- align-items: center;
- margin-left: 3px;
- margin-top: 22px;
- `;
- const LastDeployed = styled.div`
- font-size: 13px;
- margin-left: 10px;
- margin-top: -1px;
- display: flex;
- align-items: center;
- color: #aaaabb66;
- `;
- const TagWrapper = styled.div`
- height: 20px;
- font-size: 12px;
- display: flex;
- margin-left: 15px;
- margin-bottom: -3px;
- align-items: center;
- font-weight: 400;
- justify-content: center;
- color: #ffffff44;
- border: 1px solid #ffffff44;
- border-radius: 3px;
- padding-left: 5px;
- background: #26282e;
- `;
- const NamespaceTag = styled.div`
- height: 20px;
- margin-left: 6px;
- color: #aaaabb;
- background: #43454a;
- border-radius: 3px;
- font-size: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 0px 6px;
- padding-left: 7px;
- border-top-left-radius: 0px;
- border-bottom-left-radius: 0px;
- `;
- const StyledExpandedChart = styled.div`
- width: 100%;
- z-index: 0;
- animation: fadeIn 0.3s;
- animation-timing-function: ease-out;
- animation-fill-mode: forwards;
- display: flex;
- flex-direction: column;
- @keyframes fadeIn {
- from {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
- }
- `;
- const A = styled.a`
- color: #8590ff;
- text-decoration: underline;
- cursor: pointer;
- `;
|