| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555 |
- import React, { Component } from "react";
- import styled from "styled-components";
- import _ from "lodash";
- import randomWords from "random-words";
- import { RouteComponentProps, withRouter } from "react-router";
- import api from "shared/api";
- import { Context } from "shared/Context";
- import hardcodedNames from "../hardcodedNameDict";
- import SourcePage from "./SourcePage";
- import SettingsPage from "./SettingsPage";
- import {
- PorterTemplate,
- ActionConfigType,
- ChoiceType,
- ClusterType,
- StorageType,
- } from "shared/types";
- type PropsType = RouteComponentProps & {
- currentTab?: string;
- currentTemplate: PorterTemplate;
- hideLaunchFlow: () => void;
- form: any;
- };
- type StateType = {
- currentPage: string;
- templateName: string;
- sourceType: string;
- valuesToOverride: any;
- imageUrl: string;
- imageTag: string;
- actionConfig: ActionConfigType;
- procfileProcess: string;
- branch: string;
- repoType: string;
- dockerfilePath: string | null;
- procfilePath: string | null;
- folderPath: string | null;
- selectedRegistry: any;
- selectedNamespace: string;
- saveValuesStatus: string;
- };
- const defaultActionConfig: ActionConfigType = {
- git_repo: "",
- image_repo_uri: "",
- branch: "",
- git_repo_id: 0,
- };
- class LaunchFlow extends Component<PropsType, StateType> {
- state = {
- currentPage: "source",
- templateName: "",
- saveValuesStatus: "",
- sourceType: "",
- selectedNamespace: "default",
- valuesToOverride: {} as any,
- imageUrl: "",
- imageTag: "",
- actionConfig: { ...defaultActionConfig },
- procfileProcess: "",
- branch: "",
- repoType: "",
- dockerfilePath: null as string | null,
- procfilePath: null as string | null,
- folderPath: null as string | null,
- selectedRegistry: null as any,
- };
- createGHAction = (chartName: string, chartNamespace: string, env?: any) => {
- let { currentProject, currentCluster, setCurrentError } = this.context;
- let {
- actionConfig,
- branch,
- selectedRegistry,
- dockerfilePath,
- folderPath,
- } = this.state;
- let imageRepoUri = `${selectedRegistry.url}/${chartName}-${chartNamespace}`;
- // DockerHub registry integration is per repo
- if (selectedRegistry.service === "dockerhub") {
- imageRepoUri = selectedRegistry.url;
- }
- api
- .createGHAction(
- "<token>",
- {
- git_repo: actionConfig.git_repo,
- git_branch: branch,
- registry_id: selectedRegistry.id,
- dockerfile_path: dockerfilePath,
- folder_path: folderPath,
- image_repo_uri: imageRepoUri,
- git_repo_id: actionConfig.git_repo_id,
- env: env,
- },
- {
- project_id: currentProject.id,
- CLUSTER_ID: currentCluster.id,
- RELEASE_NAME: chartName,
- RELEASE_NAMESPACE: chartNamespace,
- }
- )
- .then((res) => console.log(""))
- .catch((err) => {
- let parsedErr =
- err?.response?.data?.errors && err.response.data.errors[0];
- if (parsedErr) {
- err = parsedErr;
- }
- this.setState({
- saveValuesStatus: `Could not create GitHub Action: ${err}`,
- });
- setCurrentError(err);
- });
- };
- onSubmitAddon = (wildcard?: any) => {
- let { selectedNamespace } = this.state;
- let { currentCluster, currentProject, setCurrentError } = this.context;
- let name =
- this.state.templateName || randomWords({ exactly: 3, join: "-" });
- this.setState({ saveValuesStatus: "loading" });
- let values = {};
- for (let key in wildcard) {
- _.set(values, key, wildcard[key]);
- }
- api
- .deployTemplate(
- "<token>",
- {
- templateName: this.props.currentTemplate.name,
- storage: StorageType.Secret,
- formValues: values,
- namespace: selectedNamespace,
- name,
- },
- {
- id: currentProject.id,
- cluster_id: currentCluster.id,
- name: this.props.currentTemplate.name.toLowerCase().trim(),
- version: this.props.currentTemplate?.currentVersion || "latest",
- repo_url: process.env.ADDON_CHART_REPO_URL,
- }
- )
- .then((_) => {
- // this.props.setCurrentView('cluster-dashboard');
- this.setState({ saveValuesStatus: "successful" }, () => {
- // redirect to dashboard
- let dst =
- this.props.currentTemplate.name === "job" ? "jobs" : "applications";
- setTimeout(() => {
- this.props.history.push(dst);
- }, 500);
- window.analytics.track("Deployed Add-on", {
- name: this.props.currentTemplate.name,
- namespace: selectedNamespace,
- values: values,
- });
- });
- })
- .catch((err) => {
- let parsedErr =
- err?.response?.data?.errors && err.response.data.errors[0];
- if (parsedErr) {
- err = parsedErr;
- }
- this.setState({
- saveValuesStatus: parsedErr,
- });
- setCurrentError(err.response.data.errors[0]);
- window.analytics.track("Failed to Deploy Add-on", {
- name: this.props.currentTemplate.name,
- namespace: selectedNamespace,
- values: values,
- error: err,
- });
- });
- };
- onSubmit = async (rawValues: any) => {
- let { currentCluster, currentProject, setCurrentError } = this.context;
- let {
- selectedNamespace,
- templateName,
- imageUrl,
- imageTag,
- sourceType,
- } = this.state;
- let name = templateName || randomWords({ exactly: 3, join: "-" });
- this.setState({ saveValuesStatus: "loading" });
- // Convert dotted keys to nested objects
- let values: any = {};
- for (let key in rawValues) {
- _.set(values, key, rawValues[key]);
- }
- let tag = imageTag;
- if (imageUrl.includes(":")) {
- let splits = imageUrl.split(":");
- imageUrl = splits[0];
- tag = splits[1];
- } else if (!tag) {
- tag = "latest";
- }
- if (sourceType === "repo") {
- if (this.props.currentTemplate?.name == "job") {
- imageUrl = "public.ecr.aws/o1j4x7p4/hello-porter-job";
- tag = "latest";
- } else {
- imageUrl = "public.ecr.aws/o1j4x7p4/hello-porter";
- tag = "latest";
- }
- }
- let provider;
- switch (currentCluster.service) {
- case "eks":
- provider = "aws";
- break;
- case "gke":
- provider = "gcp";
- break;
- case "doks":
- provider = "digitalocean";
- break;
- default:
- provider = "";
- }
- // don't overwrite for templates that already have a source (i.e. non-Docker templates)
- if (imageUrl && tag) {
- _.set(values, "image.repository", imageUrl);
- _.set(values, "image.tag", tag);
- }
- _.set(values, "ingress.provider", provider);
- var url: string;
- // check if template is docker and create external domain if necessary
- if (this.props.currentTemplate.name == "web") {
- if (values?.ingress?.enabled && !values?.ingress?.custom_domain) {
- url = await new Promise((resolve, reject) => {
- api
- .createSubdomain(
- "<token>",
- {
- release_name: name,
- },
- {
- id: currentProject.id,
- cluster_id: currentCluster.id,
- }
- )
- .then((res) => {
- resolve(res.data?.external_url);
- })
- .catch((err) => {
- let parsedErr =
- err?.response?.data?.errors && err.response.data.errors[0];
- if (parsedErr) {
- err = parsedErr;
- }
- this.setState({
- saveValuesStatus: `Could not create subdomain: ${err}`,
- });
- setCurrentError(err);
- });
- });
- values.ingress.porter_hosts = [url];
- }
- }
- api
- .deployTemplate(
- "<token>",
- {
- templateName: this.props.currentTemplate.name,
- imageURL: imageUrl,
- storage: StorageType.Secret,
- formValues: values,
- namespace: selectedNamespace,
- name,
- },
- {
- id: currentProject.id,
- cluster_id: currentCluster.id,
- name: this.props.currentTemplate.name.toLowerCase().trim(),
- version: this.props.currentTemplate?.currentVersion || "latest",
- repo_url: process.env.APPLICATION_CHART_REPO_URL,
- }
- )
- .then((res: any) => {
- if (sourceType === "repo") {
- let env = rawValues["container.env.normal"];
- console.log(env);
- this.createGHAction(name, selectedNamespace, env);
- }
- // this.props.setCurrentView('cluster-dashboard');
- this.setState({ saveValuesStatus: "successful" }, () => {
- // redirect to dashboard with namespace
- setTimeout(() => {
- let dst =
- this.props.currentTemplate.name === "job"
- ? "jobs"
- : "applications";
- this.props.history.push(dst);
- }, 1000);
- });
- })
- .catch((err: any) => {
- let parsedErr =
- err?.response?.data?.errors && err.response.data.errors[0];
- console.log(parsedErr);
- if (parsedErr) {
- err = parsedErr;
- }
- this.setState({
- saveValuesStatus: `Could not deploy template: ${err}`,
- });
- setCurrentError(err);
- });
- };
- renderCurrentPage = () => {
- let { form, currentTab } = this.props;
- let {
- currentPage,
- valuesToOverride,
- templateName,
- imageUrl,
- imageTag,
- actionConfig,
- branch,
- repoType,
- dockerfilePath,
- procfileProcess,
- procfilePath,
- folderPath,
- selectedNamespace,
- selectedRegistry,
- saveValuesStatus,
- sourceType,
- } = this.state;
- if (currentPage === "source" && currentTab === "porter") {
- return (
- <SourcePage
- sourceType={sourceType}
- setSourceType={(x: string) => this.setState({ sourceType: x })}
- templateName={templateName}
- setPage={(x: string) => {
- this.setState({ currentPage: x });
- }}
- setTemplateName={(x: string) => this.setState({ templateName: x })}
- setValuesToOverride={(x: any) =>
- this.setState({ valuesToOverride: x })
- }
- imageUrl={imageUrl}
- setImageUrl={(x: string) => this.setState({ imageUrl: x })}
- imageTag={imageTag}
- setImageTag={(x: string) => this.setState({ imageTag: x })}
- actionConfig={actionConfig}
- setActionConfig={(x: ActionConfigType) =>
- this.setState({ actionConfig: x })
- }
- branch={branch}
- setBranch={(x: string) => this.setState({ branch: x })}
- procfileProcess={procfileProcess}
- setProcfileProcess={(x: string) =>
- this.setState({ procfileProcess: x })
- }
- repoType={repoType}
- setRepoType={(x: string) => this.setState({ repoType: x })}
- dockerfilePath={dockerfilePath}
- setDockerfilePath={(x: string) =>
- this.setState({ dockerfilePath: x })
- }
- folderPath={folderPath}
- setFolderPath={(x: string) => this.setState({ folderPath: x })}
- procfilePath={procfilePath}
- setProcfilePath={(x: string) => this.setState({ procfilePath: x })}
- selectedRegistry={selectedRegistry}
- setSelectedRegistry={(x: string) =>
- this.setState({ selectedRegistry: x })
- }
- />
- );
- }
- // Display main (non-source) settings page
- return (
- <SettingsPage
- onSubmit={currentTab === "porter" ? this.onSubmit : this.onSubmitAddon}
- saveValuesStatus={saveValuesStatus}
- selectedNamespace={selectedNamespace}
- setSelectedNamespace={(x: string) =>
- this.setState({ selectedNamespace: x })
- }
- templateName={templateName}
- setTemplateName={(x: string) => this.setState({ templateName: x })}
- hasSource={currentTab === "porter"}
- setPage={(x: string) => this.setState({ currentPage: x })}
- form={form}
- valuesToOverride={valuesToOverride}
- clearValuesToOverride={() => this.setState({ valuesToOverride: null })}
- />
- );
- };
- renderIcon = () => {
- let icon = this.props.currentTemplate?.icon;
- if (icon) {
- return <Icon src={icon} />;
- }
- return (
- <Polymer>
- <i className="material-icons">layers</i>
- </Polymer>
- );
- };
- render() {
- let { currentTab } = this.props;
- let { name } = this.props.currentTemplate;
- if (hardcodedNames[name]) {
- name = hardcodedNames[name];
- }
- return (
- <StyledLaunchFlow>
- <TitleSection>
- <i className="material-icons" onClick={this.props.hideLaunchFlow}>
- keyboard_backspace
- </i>
- {this.renderIcon()}
- <Title>
- New {name} {currentTab === "porter" ? null : "Instance"}
- </Title>
- </TitleSection>
- {this.renderCurrentPage()}
- <Br />
- </StyledLaunchFlow>
- );
- }
- }
- LaunchFlow.contextType = Context;
- export default withRouter(LaunchFlow);
- const Br = styled.div`
- width: 100%;
- height: 120px;
- `;
- const Icon = styled.img`
- width: 40px;
- margin-right: 14px;
- opacity: 0;
- animation: floatIn 0.5s 0.2s;
- animation-fill-mode: forwards;
- @keyframes floatIn {
- from {
- opacity: 0;
- transform: translateY(10px);
- }
- to {
- opacity: 1;
- transform: translateY(0px);
- }
- }
- `;
- const Polymer = styled.div`
- margin-bottom: -3px;
- > i {
- color: ${(props) => props.theme.containerIcon};
- font-size: 24px;
- margin-left: 12px;
- margin-right: 3px;
- }
- `;
- const Title = styled.div`
- font-size: 24px;
- font-weight: 600;
- font-family: "Work Sans", sans-serif;
- color: #ffffff;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- `;
- const TitleSection = styled.div`
- margin-bottom: 20px;
- display: flex;
- flex-direction: row;
- align-items: center;
- > i {
- cursor: pointer;
- font-size 24px;
- color: #969Fbbaa;
- margin-right: 10px;
- padding: 3px;
- margin-left: 0px;
- border-radius: 100px;
- :hover {
- background: #ffffff11;
- }
- }
- > a {
- > i {
- display: flex;
- align-items: center;
- margin-bottom: -2px;
- font-size: 18px;
- margin-left: 18px;
- color: #858faaaa;
- cursor: pointer;
- :hover {
- color: #aaaabb;
- }
- }
- }
- `;
- const StyledLaunchFlow = styled.div`
- width: calc(90% - 130px);
- min-width: 300px;
- padding-top: 20px;
- margin-top: calc(50vh - 340px);
- `;
|