|
|
@@ -1,26 +1,22 @@
|
|
|
-import React, { Component } from "react";
|
|
|
-import styled from "styled-components";
|
|
|
-import randomWords from "random-words";
|
|
|
-import posthog from "posthog-js";
|
|
|
-import _ from "lodash";
|
|
|
-import { Context } from "shared/Context";
|
|
|
-import api from "shared/api";
|
|
|
-
|
|
|
-import {
|
|
|
- PorterTemplate,
|
|
|
- ChoiceType,
|
|
|
- ClusterType,
|
|
|
- StorageType,
|
|
|
-} from "shared/types";
|
|
|
-import Selector from "components/Selector";
|
|
|
-import ImageSelector from "components/image-selector/ImageSelector";
|
|
|
-import TabRegion from "components/TabRegion";
|
|
|
-import InputRow from "components/values-form/InputRow";
|
|
|
-import SaveButton from "components/SaveButton";
|
|
|
-import ValuesWrapper from "components/values-form/ValuesWrapper";
|
|
|
-import ValuesForm from "components/values-form/ValuesForm";
|
|
|
-import { isAlphanumeric } from "shared/common";
|
|
|
-import { safeDump } from "js-yaml";
|
|
|
+import React, { Component } from 'react';
|
|
|
+import styled from 'styled-components';
|
|
|
+import randomWords from 'random-words';
|
|
|
+import posthog from 'posthog-js';
|
|
|
+import _ from 'lodash';
|
|
|
+import { Context } from 'shared/Context';
|
|
|
+import api from 'shared/api';
|
|
|
+
|
|
|
+import { ActionConfigType, ChoiceType, ClusterType, StorageType } from 'shared/types';
|
|
|
+import Selector from 'components/Selector';
|
|
|
+import ImageSelector from 'components/image-selector/ImageSelector';
|
|
|
+import TabRegion from 'components/TabRegion';
|
|
|
+import InputRow from 'components/values-form/InputRow';
|
|
|
+import SaveButton from 'components/SaveButton';
|
|
|
+import ActionConfEditor from 'components/repo-selector/ActionConfEditor';
|
|
|
+import ValuesWrapper from 'components/values-form/ValuesWrapper';
|
|
|
+import ValuesForm from 'components/values-form/ValuesForm';
|
|
|
+import { isAlphanumeric } from 'shared/common';
|
|
|
+import { safeDump } from 'js-yaml';
|
|
|
|
|
|
type PropsType = {
|
|
|
currentTemplate: any;
|
|
|
@@ -30,36 +26,75 @@ type PropsType = {
|
|
|
};
|
|
|
|
|
|
type StateType = {
|
|
|
- currentView: string;
|
|
|
- clusterOptions: { label: string; value: string }[];
|
|
|
- saveValuesStatus: string | null;
|
|
|
- selectedNamespace: string;
|
|
|
- selectedCluster: string;
|
|
|
- selectedImageUrl: string | null;
|
|
|
- selectedTag: string | null;
|
|
|
- templateName: string;
|
|
|
- tabOptions: ChoiceType[];
|
|
|
- currentTab: string | null;
|
|
|
- tabContents: any;
|
|
|
- namespaceOptions: { label: string; value: string }[];
|
|
|
+ currentView: string,
|
|
|
+ clusterOptions: { label: string, value: string }[],
|
|
|
+ clusterMap: { [clusterId: string]: ClusterType },
|
|
|
+ saveValuesStatus: string | null
|
|
|
+ selectedNamespace: string,
|
|
|
+ selectedCluster: string,
|
|
|
+ selectedImageUrl: string | null,
|
|
|
+ sourceType: string,
|
|
|
+ selectedTag: string | null,
|
|
|
+ templateName: string,
|
|
|
+ tabOptions: ChoiceType[],
|
|
|
+ currentTab: string | null,
|
|
|
+ tabContents: any
|
|
|
+ namespaceOptions: { label: string, value: string }[],
|
|
|
+ actionConfig: ActionConfigType,
|
|
|
+ branch: string,
|
|
|
+ pathIsSet: boolean,
|
|
|
};
|
|
|
|
|
|
export default class LaunchTemplate extends Component<PropsType, StateType> {
|
|
|
state = {
|
|
|
- currentView: "repo",
|
|
|
- clusterOptions: [] as { label: string; value: string }[],
|
|
|
- saveValuesStatus: "No container image specified" as string | null,
|
|
|
+ currentView: 'repo',
|
|
|
+ clusterOptions: [] as { label: string, value: string }[],
|
|
|
+ clusterMap: {} as { [clusterId: string]: ClusterType },
|
|
|
+ saveValuesStatus: 'No container image specified' as (string | null),
|
|
|
selectedCluster: this.context.currentCluster.name,
|
|
|
selectedNamespace: "default",
|
|
|
- selectedImageUrl: "" as string | null,
|
|
|
- templateName: "",
|
|
|
- selectedTag: "" as string | null,
|
|
|
+ selectedImageUrl: '' as string | null,
|
|
|
+ sourceType: 'registry',
|
|
|
+ templateName: '',
|
|
|
+ selectedTag: '' as string | null,
|
|
|
tabOptions: [] as ChoiceType[],
|
|
|
currentTab: null as string | null,
|
|
|
tabContents: [] as any,
|
|
|
- namespaceOptions: [] as { label: string; value: string }[],
|
|
|
+ namespaceOptions: [] as { label: string, value: string }[],
|
|
|
+ actionConfig: {
|
|
|
+ git_repo: '',
|
|
|
+ image_repo_uri: '',
|
|
|
+ git_repo_id: 0,
|
|
|
+ dockerfile_path: '',
|
|
|
+ } as ActionConfigType,
|
|
|
+ branch: '',
|
|
|
+ pathIsSet: false,
|
|
|
};
|
|
|
|
|
|
+ createGHAction = (chartName: string, chartNamespace: string) => {
|
|
|
+ let { currentProject, currentCluster } = this.context;
|
|
|
+ let { actionConfig } = this.state;
|
|
|
+
|
|
|
+ api.createGHAction('<token>', {
|
|
|
+ git_repo: actionConfig.git_repo,
|
|
|
+ image_repo_uri: actionConfig.image_repo_uri,
|
|
|
+ dockerfile_path: actionConfig.dockerfile_path,
|
|
|
+ git_repo_id: actionConfig.git_repo_id,
|
|
|
+ }, {
|
|
|
+ project_id: currentProject.id,
|
|
|
+ CLUSTER_ID: currentCluster.id,
|
|
|
+ RELEASE_NAME: chartName,
|
|
|
+ RELEASE_NAMESPACE: chartNamespace,
|
|
|
+ }, (err: any, res: any) => {
|
|
|
+ if (err) {
|
|
|
+ console.log(err);
|
|
|
+ } else {
|
|
|
+ // Exit to initial settings tab
|
|
|
+ console.log(res.data);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
onSubmitAddon = (wildcard?: any) => {
|
|
|
let { currentCluster, currentProject } = this.context;
|
|
|
let name =
|
|
|
@@ -71,41 +106,41 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
|
|
|
_.set(values, key, wildcard[key]);
|
|
|
}
|
|
|
|
|
|
- api.deployTemplate(
|
|
|
- "<token>",
|
|
|
- {
|
|
|
- templateName: this.props.currentTemplate.name,
|
|
|
- storage: StorageType.Secret,
|
|
|
- formValues: values,
|
|
|
- namespace: this.state.selectedNamespace,
|
|
|
- name,
|
|
|
- },
|
|
|
- {
|
|
|
- id: currentProject.id,
|
|
|
- cluster_id: currentCluster.id,
|
|
|
- name: this.props.currentTemplate.name.toLowerCase().trim(),
|
|
|
- version: "latest",
|
|
|
- },
|
|
|
- (err: any, res: any) => {
|
|
|
- if (err) {
|
|
|
- this.setState({ saveValuesStatus: "error" });
|
|
|
- posthog.capture("Failed to deploy template", {
|
|
|
- name: this.props.currentTemplate.name,
|
|
|
- namespace: this.state.selectedNamespace,
|
|
|
- values: values,
|
|
|
- error: err,
|
|
|
- });
|
|
|
- } else {
|
|
|
- // this.props.setCurrentView('cluster-dashboard');
|
|
|
- this.setState({ saveValuesStatus: "successful" });
|
|
|
- posthog.capture("Deployed template", {
|
|
|
- name: this.props.currentTemplate.name,
|
|
|
- namespace: this.state.selectedNamespace,
|
|
|
- values: values,
|
|
|
- });
|
|
|
+ api.deployTemplate('<token>', {
|
|
|
+ templateName: this.props.currentTemplate.name,
|
|
|
+ storage: StorageType.Secret,
|
|
|
+ formValues: values,
|
|
|
+ namespace: this.state.selectedNamespace,
|
|
|
+ name,
|
|
|
+ }, {
|
|
|
+ id: currentProject.id,
|
|
|
+ cluster_id: currentCluster.id,
|
|
|
+ name: this.props.currentTemplate.name.toLowerCase().trim(),
|
|
|
+ version: 'latest',
|
|
|
+ }, (err: any, res: any) => {
|
|
|
+ if (err) {
|
|
|
+ this.setState({ saveValuesStatus: 'error' });
|
|
|
+ posthog.capture('Failed to deploy template', {
|
|
|
+ name: this.props.currentTemplate.name,
|
|
|
+ namespace: this.state.selectedNamespace,
|
|
|
+ values: values,
|
|
|
+ error: err
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ if (this.state.sourceType === 'repo') {
|
|
|
+ this.createGHAction(name, this.state.selectedNamespace);
|
|
|
}
|
|
|
+ // this.props.setCurrentView('cluster-dashboard');
|
|
|
+ this.setState({ saveValuesStatus: 'successful' }, () => {
|
|
|
+ // redirect to dashboard
|
|
|
+ });
|
|
|
+ posthog.capture('Deployed template', {
|
|
|
+ name: this.props.currentTemplate.name,
|
|
|
+ namespace: this.state.selectedNamespace,
|
|
|
+ values: values,
|
|
|
+ })
|
|
|
}
|
|
|
- );
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
onSubmit = (rawValues: any) => {
|
|
|
@@ -131,45 +166,60 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
|
|
|
tag = "latest";
|
|
|
}
|
|
|
|
|
|
- _.set(values, "image.repository", imageUrl);
|
|
|
- _.set(values, "image.tag", tag);
|
|
|
-
|
|
|
- api.deployTemplate(
|
|
|
- "<token>",
|
|
|
- {
|
|
|
- templateName: this.props.currentTemplate.name,
|
|
|
- imageURL: this.state.selectedImageUrl,
|
|
|
- storage: StorageType.Secret,
|
|
|
- formValues: values,
|
|
|
- namespace: this.state.selectedNamespace,
|
|
|
- name,
|
|
|
- },
|
|
|
- {
|
|
|
- id: currentProject.id,
|
|
|
- cluster_id: currentCluster.id,
|
|
|
- name: this.props.currentTemplate.name.toLowerCase().trim(),
|
|
|
- version: "latest",
|
|
|
- },
|
|
|
- (err: any, res: any) => {
|
|
|
- if (err) {
|
|
|
- this.setState({ saveValuesStatus: "error" });
|
|
|
- posthog.capture("Failed to deploy template", {
|
|
|
- name: this.props.currentTemplate.name,
|
|
|
- namespace: this.state.selectedNamespace,
|
|
|
- values: values,
|
|
|
- error: err,
|
|
|
- });
|
|
|
- } else {
|
|
|
- // this.props.setCurrentView('cluster-dashboard');
|
|
|
- this.setState({ saveValuesStatus: "successful" });
|
|
|
- posthog.capture("Deployed template", {
|
|
|
- name: this.props.currentTemplate.name,
|
|
|
- namespace: this.state.selectedNamespace,
|
|
|
- values: values,
|
|
|
- });
|
|
|
+ if (this.state.sourceType === 'repo') {
|
|
|
+ imageUrl = 'hello-world';
|
|
|
+ tag = 'latest';
|
|
|
+ }
|
|
|
+
|
|
|
+ _.set(values, "image.repository", imageUrl)
|
|
|
+ _.set(values, "image.tag", tag)
|
|
|
+
|
|
|
+ console.log(`
|
|
|
+ ${this.props.currentTemplate.name}\n
|
|
|
+ ${this.state.selectedImageUrl}\n
|
|
|
+ ${values}\n
|
|
|
+ ${this.state.selectedNamespace}\n
|
|
|
+ ${name}\n
|
|
|
+ ${currentProject.id}\n
|
|
|
+ ${currentCluster.id}\n}
|
|
|
+ `)
|
|
|
+
|
|
|
+ api.deployTemplate('<token>', {
|
|
|
+ templateName: this.props.currentTemplate.name,
|
|
|
+ imageURL: this.state.selectedImageUrl,
|
|
|
+ storage: StorageType.Secret,
|
|
|
+ formValues: values,
|
|
|
+ namespace: this.state.selectedNamespace,
|
|
|
+ name,
|
|
|
+ }, {
|
|
|
+ id: currentProject.id,
|
|
|
+ cluster_id: currentCluster.id,
|
|
|
+ name: this.props.currentTemplate.name.toLowerCase().trim(),
|
|
|
+ version: 'latest',
|
|
|
+ }, (err: any, res: any) => {
|
|
|
+ if (err) {
|
|
|
+ this.setState({ saveValuesStatus: 'error' });
|
|
|
+ posthog.capture('Failed to deploy template', {
|
|
|
+ name: this.props.currentTemplate.name,
|
|
|
+ namespace: this.state.selectedNamespace,
|
|
|
+ values: values,
|
|
|
+ error: err
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ if (this.state.sourceType === 'repo') {
|
|
|
+ this.createGHAction(name, this.state.selectedNamespace);
|
|
|
}
|
|
|
+ // this.props.setCurrentView('cluster-dashboard');
|
|
|
+ this.setState({ saveValuesStatus: 'successful' }, () => {
|
|
|
+ // redirect to dashboard with namespace
|
|
|
+ });
|
|
|
+ posthog.capture('Deployed template', {
|
|
|
+ name: this.props.currentTemplate.name,
|
|
|
+ namespace: this.state.selectedNamespace,
|
|
|
+ values: values,
|
|
|
+ })
|
|
|
}
|
|
|
- );
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
renderTabContents = () => {
|
|
|
@@ -223,45 +273,41 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
|
|
|
|
|
|
// TODO: query with selected filter once implemented
|
|
|
let { currentProject, currentCluster } = this.context;
|
|
|
- api.getClusters(
|
|
|
- "<token>",
|
|
|
- {},
|
|
|
- { id: currentProject.id },
|
|
|
- (err: any, res: any) => {
|
|
|
- if (err) {
|
|
|
- // console.log(err)
|
|
|
- } else if (res.data) {
|
|
|
- let clusterOptions = res.data.map((x: ClusterType) => {
|
|
|
- return { label: x.name, value: x.name };
|
|
|
- });
|
|
|
- if (res.data.length > 0) {
|
|
|
- this.setState({ clusterOptions });
|
|
|
- }
|
|
|
+ api.getClusters('<token>', {}, { id: currentProject.id }, (err: any, res: any) => {
|
|
|
+ if (err) {
|
|
|
+ // console.log(err)
|
|
|
+ } else if (res.data) {
|
|
|
+ let clusterOptions: { label: string, value: string }[] = [];
|
|
|
+ let clusterMap: { [clusterId: string]: ClusterType } = {};
|
|
|
+ res.data.forEach((cluster: ClusterType, i: number) => {
|
|
|
+ clusterOptions.push({ label: cluster.name, value: cluster.name });
|
|
|
+ clusterMap[cluster.name] = cluster;
|
|
|
+ })
|
|
|
+ if (res.data.length > 0) {
|
|
|
+ this.setState({ clusterOptions, clusterMap });
|
|
|
}
|
|
|
}
|
|
|
- );
|
|
|
+ });
|
|
|
|
|
|
- api.getNamespaces(
|
|
|
- "<token>",
|
|
|
- {
|
|
|
- cluster_id: currentCluster.id,
|
|
|
- },
|
|
|
- { id: currentProject.id },
|
|
|
- (err: any, res: any) => {
|
|
|
- if (err) {
|
|
|
- console.log(err);
|
|
|
- } else if (res.data) {
|
|
|
- let namespaceOptions = res.data.items.map(
|
|
|
- (x: { metadata: { name: string } }) => {
|
|
|
- return { label: x.metadata.name, value: x.metadata.name };
|
|
|
- }
|
|
|
- );
|
|
|
- if (res.data.items.length > 0) {
|
|
|
- this.setState({ namespaceOptions });
|
|
|
- }
|
|
|
+ this.updateNamespaces(currentCluster.id);
|
|
|
+ }
|
|
|
+
|
|
|
+ updateNamespaces = (id: number) => {
|
|
|
+ let { currentProject } = this.context;
|
|
|
+ api.getNamespaces('<token>', {
|
|
|
+ cluster_id: id,
|
|
|
+ }, { id: currentProject.id }, (err: any, res: any) => {
|
|
|
+ if (err) {
|
|
|
+ console.log(err)
|
|
|
+ } else if (res.data) {
|
|
|
+ let namespaceOptions = res.data.items.map((x: { metadata: {name: string}}) => {
|
|
|
+ return { label: x.metadata.name, value: x.metadata.name }
|
|
|
+ });
|
|
|
+ if (res.data.items.length > 0) {
|
|
|
+ this.setState({ namespaceOptions });
|
|
|
}
|
|
|
}
|
|
|
- );
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
setSelectedImageUrl = (x: string) => {
|
|
|
@@ -327,25 +373,67 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
|
|
|
|
|
|
// Display if current template uses source (image or repo)
|
|
|
renderSourceSelector = () => {
|
|
|
+ let { currentProject } = this.context;
|
|
|
+
|
|
|
if (this.props.form?.hasSource) {
|
|
|
- return (
|
|
|
- <>
|
|
|
- <Subtitle>
|
|
|
- Select the container image you would like to connect to this
|
|
|
- template.
|
|
|
- <Required>*</Required>
|
|
|
- </Subtitle>
|
|
|
- <DarkMatter />
|
|
|
- <ImageSelector
|
|
|
- selectedTag={this.state.selectedTag}
|
|
|
- selectedImageUrl={this.state.selectedImageUrl}
|
|
|
- setSelectedImageUrl={this.setSelectedImageUrl}
|
|
|
- setSelectedTag={(x: string) => this.setState({ selectedTag: x })}
|
|
|
- forceExpanded={true}
|
|
|
- />
|
|
|
- <br />
|
|
|
- </>
|
|
|
- );
|
|
|
+ if (this.state.sourceType === 'registry') {
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <Subtitle>
|
|
|
+ Select the container image you would like to connect to this template or
|
|
|
+ <Highlight onClick={() => this.setState({ sourceType: 'repo' })}>
|
|
|
+ link a git repository
|
|
|
+ </Highlight>.
|
|
|
+ <Required>*</Required>
|
|
|
+ </Subtitle>
|
|
|
+ <DarkMatter />
|
|
|
+ <ImageSelector
|
|
|
+ selectedTag={this.state.selectedTag}
|
|
|
+ selectedImageUrl={this.state.selectedImageUrl}
|
|
|
+ setSelectedImageUrl={this.setSelectedImageUrl}
|
|
|
+ setSelectedTag={(x: string) => this.setState({ selectedTag: x })}
|
|
|
+ forceExpanded={true}
|
|
|
+ />
|
|
|
+ <br />
|
|
|
+ </>
|
|
|
+ )
|
|
|
+ } else {
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <Subtitle>
|
|
|
+ Select a repo to connect to. You can
|
|
|
+ <A padRight={true} href={`/api/oauth/projects/${currentProject.id}/github?redirected=true`}>
|
|
|
+ log in with GitHub
|
|
|
+ </A> or
|
|
|
+ <Highlight
|
|
|
+ onClick={() => this.setState({
|
|
|
+ sourceType: 'registry',
|
|
|
+ actionConfig: {
|
|
|
+ git_repo: '',
|
|
|
+ image_repo_uri: '',
|
|
|
+ git_repo_id: 0,
|
|
|
+ dockerfile_path: '',
|
|
|
+ } as ActionConfigType
|
|
|
+ })}
|
|
|
+ >
|
|
|
+ link an image registry
|
|
|
+ </Highlight>.
|
|
|
+ <Required>*</Required>
|
|
|
+ </Subtitle>
|
|
|
+ <ActionConfEditor
|
|
|
+ actionConfig={this.state.actionConfig}
|
|
|
+ branch={this.state.branch}
|
|
|
+ pathIsSet={this.state.pathIsSet}
|
|
|
+ setActionConfig={(actionConfig: ActionConfigType) => this.setState({ actionConfig }, () => {
|
|
|
+ this.setSelectedImageUrl(this.state.actionConfig.image_repo_uri);
|
|
|
+ })}
|
|
|
+ setBranch={(branch: string) => this.setState({ branch })}
|
|
|
+ setPath={(pathIsSet: boolean) => this.setState({ pathIsSet })}
|
|
|
+ />
|
|
|
+ <br />
|
|
|
+ </>
|
|
|
+ )
|
|
|
+ }
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -376,9 +464,12 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
|
|
|
</ClusterLabel>
|
|
|
<Selector
|
|
|
activeValue={this.state.selectedCluster}
|
|
|
- setActiveValue={(cluster: string) =>
|
|
|
- this.setState({ selectedCluster: cluster })
|
|
|
- }
|
|
|
+ setActiveValue={(cluster: string) => {
|
|
|
+ this.context.setCurrentCluster(this.state.clusterMap[cluster]);
|
|
|
+ this.updateNamespaces(this.state.clusterMap[cluster].id);
|
|
|
+ console.log(this.state.clusterMap[cluster]);
|
|
|
+ this.setState({ selectedCluster: cluster });
|
|
|
+ }}
|
|
|
options={this.state.clusterOptions}
|
|
|
width="250px"
|
|
|
dropdownWidth="335px"
|
|
|
@@ -408,10 +499,9 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
|
|
|
}
|
|
|
>
|
|
|
(lowercase letters, numbers, and "-" only)
|
|
|
- </Warning>
|
|
|
- . (Optional)
|
|
|
+ </Warning>. (Optional)
|
|
|
</Subtitle>
|
|
|
- <DarkMatter antiHeight="-27px" />
|
|
|
+ <DarkMatter antiHeight='-27px' />
|
|
|
<InputRow
|
|
|
type="text"
|
|
|
value={this.state.templateName}
|
|
|
@@ -582,3 +672,19 @@ const StyledLaunchTemplate = styled.div`
|
|
|
width: 100%;
|
|
|
padding-bottom: 150px;
|
|
|
`;
|
|
|
+
|
|
|
+const Highlight = styled.div`
|
|
|
+ color: #8590ff;
|
|
|
+ text-decoration: underline;
|
|
|
+ margin-left: 5px;
|
|
|
+ cursor: pointer;
|
|
|
+ padding-right: ${(props: { padRight?: boolean }) => props.padRight ? '5px' : ''};
|
|
|
+`;
|
|
|
+
|
|
|
+const A = styled.a`
|
|
|
+ color: #8590ff;
|
|
|
+ text-decoration: underline;
|
|
|
+ margin-left: 5px;
|
|
|
+ cursor: pointer;
|
|
|
+ padding-right: ${(props: { padRight?: boolean }) => props.padRight ? '5px' : ''};
|
|
|
+`;
|