github.com/replicatedhq/ship@v0.55.0/web/init/src/components/shared/DetermineComponentForRoute.jsx (about) 1 import React from "react"; 2 import PropTypes from "prop-types"; 3 import { withRouter } from "react-router-dom"; 4 import find from "lodash/find"; 5 import indexOf from "lodash/indexOf"; 6 7 import Loader from "./Loader"; 8 import StepMessage from "./StepMessage"; 9 import { RENDER_PHASE, StepBuildingAssets } from "./StepBuildingAssets"; 10 import StepHelmIntro from "../../containers/HelmChartInfo"; 11 import StepHelmValues from "../kustomize/HelmValuesEditor"; 12 import { TERRAFORM_PHASE, StepTerraform } from "./StepTerraform"; 13 import { KUBECTL_PHASE, StepKubectlApply } from "./StepKubectlApply"; 14 import KustomizeEmpty from "../kustomize/kustomize_overlay/KustomizeEmpty"; 15 import KustomizeOverlay from "../../containers/KustomizeOverlay"; 16 import ConfigOnly from "../../containers/ConfigOnly"; 17 import { fetchContentForStep } from "../../redux/data/appRoutes/actions"; 18 19 export class DetermineComponentForRoute extends React.Component { 20 static propTypes = { 21 /** Callback function to be invoked at the finalization of the Ship Init flow */ 22 onCompletion: PropTypes.func, 23 } 24 25 constructor(props) { 26 super(props); 27 this.state = { 28 maxPollReached: false, 29 }; 30 } 31 32 componentDidMount() { 33 this.getContentForStep(); 34 } 35 36 handleAction = async (action, gotoNext) => { 37 await this.props.finalizeStep({action}); 38 if (gotoNext) { 39 this.gotoRoute(); 40 } 41 } 42 43 getContentForStep = () => { 44 const { getContentForStep, currentRoute } = this.props; 45 const { id: routeId } = currentRoute; 46 getContentForStep(routeId); 47 } 48 49 gotoRoute = async(route) => { 50 let nextRoute = route; 51 const { basePath, routes, currentRoute, history, onCompletion } = this.props; 52 53 if (!nextRoute) { 54 const currRoute = find(routes, ["id", currentRoute.id]); 55 const currIndex = indexOf(routes, currRoute); 56 nextRoute = routes[currIndex + 1]; 57 } 58 59 if (!nextRoute) { 60 if (onCompletion) { 61 await this.handleShutdown(); 62 return onCompletion(); 63 } 64 65 await this.handleShutdown(); 66 return this.gotoDone(); 67 } 68 69 history.push(`${basePath}/${nextRoute.id}`); 70 } 71 72 handleShutdown = async () => { 73 const { apiEndpoint, shutdownApp } = this.props; 74 75 const url = `${apiEndpoint}/shutdown`; 76 await fetch(url, { 77 method: "POST", 78 headers: { 79 "Accept": "application/json", 80 }, 81 }); 82 await shutdownApp(); 83 } 84 85 gotoDone = () => { 86 const { basePath } = this.props; 87 this.props.history.push(`${basePath}/done`); 88 } 89 90 skipKustomize = async () => { 91 const { apiEndpoint } = this.props; 92 const { actions, progress } = await fetchContentForStep(apiEndpoint, "kustomize"); 93 94 let stepValid = true; 95 let kustomizeErrorMessage = ""; 96 if (progress && progress.detail) { 97 const { status, message } = JSON.parse(progress.detail); 98 stepValid = status !== "error"; 99 kustomizeErrorMessage = message; 100 } 101 102 if (stepValid) { 103 const [finalizeKustomize] = actions; 104 await this.props.finalizeStep({ action: finalizeKustomize }); 105 106 this.startPoll("kustomize", this.gotoRoute); 107 } else { 108 // TODO: Handle case where error detected in Kustomize step on skip 109 // This can occur even if no overlays created since kustomize 110 // build is always executed. 111 alert(`Error detected, cancelling kustomize step skip. Error below: \n${kustomizeErrorMessage}`); 112 } 113 } 114 115 startPoll = async (routeId, cb) => { 116 if (!this.props.isPolling) { 117 this.props.pollContentForStep(routeId, cb); 118 } 119 } 120 121 startPollingStep = (routeId) => { 122 const { initializeStep } = this.props; 123 initializeStep(routeId); 124 this.startPoll(routeId, () => { 125 // Timeout to wait a little bit before transitioning to the next step 126 setTimeout(this.gotoRoute, 500); 127 }); 128 } 129 130 renderStep = () => { 131 const { 132 currentStep = {}, 133 progress, 134 actions, 135 location, 136 initializeStep, 137 phase, 138 currentRoute, 139 routes 140 } = this.props; 141 const routeId = currentRoute.id; 142 const firstRouteIdx = indexOf(routes, find(routes, ["id", currentRoute.id])); 143 const firstRoute = firstRouteIdx === 0; 144 145 if (!phase || !phase.length) return null; 146 switch (phase) { 147 case "requirementNotMet": 148 return ( 149 <div className="flex1 flex-column justifyContent--center alignItems--center"> 150 <p className="u-fontSize--large u-fontWeight--medium u-color--tundora u-marginBottom--20">Whoa there, you're getting a little ahead of yourself. There are steps that need to be completed before you can be here.</p> 151 <button className="btn primary" onClick={this.props.history.goBack}>Take me back</button> 152 </div> 153 ) 154 case "message": 155 return ( 156 <StepMessage 157 actions={actions} 158 message={currentStep.message} 159 level={currentStep.level} 160 handleAction={this.handleAction} 161 firstRoute={firstRoute} 162 goBack={this.props.history.goBack} 163 isLoading={this.props.dataLoading.submitActionLoading} 164 /> 165 ); 166 case "config": 167 return ( 168 <ConfigOnly 169 actions={actions} 170 handleAction={this.handleAction} 171 routeId={routeId} 172 firstRoute={firstRoute} 173 goBack={this.props.history.goBack} 174 /> 175 ); 176 case "stream": 177 return ( 178 <StepMessage 179 actions={actions} 180 message={currentStep.message} 181 level={currentStep.level} 182 handleAction={this.handleAction} 183 goBack={this.props.history.goBack} 184 firstRoute={firstRoute} 185 isLoading={this.props.dataLoading.submitActionLoading || !currentStep.message.contents} 186 /> 187 ); 188 case RENDER_PHASE: 189 return ( 190 <StepBuildingAssets 191 startPollingStep={this.startPollingStep} 192 location={location} 193 status={progress || currentStep.status} 194 currentRoute={currentRoute} 195 /> 196 ); 197 case TERRAFORM_PHASE: 198 return ( 199 <StepTerraform 200 startPollingStep={this.startPollingStep} 201 currentRoute={currentRoute} 202 startPoll={this.startPoll} 203 location={location} 204 status={progress || currentStep.status} 205 handleAction={this.handleAction} 206 gotoRoute={this.gotoRoute} 207 goBack={this.props.history.goBack} 208 firstRoute={firstRoute} 209 initializeStep={initializeStep} 210 /> 211 ); 212 case KUBECTL_PHASE: 213 return ( 214 <StepKubectlApply 215 startPollingStep={this.startPollingStep} 216 currentRoute={currentRoute} 217 startPoll={this.startPoll} 218 location={location} 219 status={progress || currentStep.status} 220 handleAction={this.handleAction} 221 gotoRoute={this.gotoRoute} 222 initializeStep={initializeStep} 223 /> 224 ); 225 case "helm-intro": 226 return ( 227 <StepHelmIntro 228 actions={actions} 229 isUpdate={currentStep.helmIntro.isUpdate} 230 shipAppMetadata={this.props.shipAppMetadata} 231 handleAction={this.handleAction} 232 goBack={this.props.history.goBack} 233 firstRoute={firstRoute} 234 isLoading={this.props.dataLoading.submitActionLoading} 235 /> 236 ); 237 case "helm-values": 238 return ( 239 <StepHelmValues 240 saveValues={this.props.saveHelmChartValues} 241 getStep={currentStep.helmValues} 242 shipAppMetadata={this.props.shipAppMetadata} 243 actions={actions} 244 handleAction={this.handleAction} 245 goBack={this.props.history.goBack} 246 firstRoute={firstRoute} 247 isLoading={this.props.dataLoading.submitActionLoading} 248 /> 249 ); 250 case "kustomize-intro": 251 return ( 252 <KustomizeEmpty 253 actions={actions} 254 handleAction={this.handleAction} 255 skipKustomize={this.skipKustomize} 256 goBack={this.props.history.goBack} 257 firstRoute={firstRoute} 258 /> 259 ); 260 case "kustomize": 261 return ( 262 <KustomizeOverlay 263 startPoll={this.startPoll} 264 getCurrentStep={this.getContentForStep} 265 pollCallback={this.gotoRoute} 266 routeId={routeId} 267 actions={actions} 268 isNavcycle={true} 269 finalizeStep={this.props.finalizeStep} 270 handleAction={this.handleAction} 271 currentStep={currentStep} 272 skipKustomize={this.skipKustomize} 273 dataLoading={this.props.dataLoading} 274 goBack={this.props.history.goBack} 275 firstRoute={firstRoute} 276 /> 277 ); 278 default: 279 return ( 280 <div className="flex1 flex-column justifyContent--center alignItems--center"> 281 <Loader size="60" /> 282 </div> 283 ); 284 } 285 } 286 287 render() { 288 const { phase, dataLoading } = this.props; 289 const isLoadingStep = phase === "loading"; 290 return ( 291 <div className="flex-column flex1"> 292 <div className="flex-column flex1 u-overflow--hidden u-position--relative"> 293 <div className="flex-1-auto flex u-overflow--auto"> 294 {(isLoadingStep || dataLoading.getMetadataLoading) && !this.state.maxPollReached ? 295 <div className="flex1 flex-column justifyContent--center alignItems--center"> 296 <Loader size="60" /> 297 </div> 298 : this.state.maxPollReached ? 299 <div className="flex1 flex-column justifyContent--center alignItems--center"> 300 <p className="u-fontSize--large u-fontWeight--medium u-color--tundora">Oops, something isn't quite right. If you continue to experience this problem contact <a href="mailto:support@replicated.com">support@replicated.com</a></p> 301 </div> 302 : 303 this.renderStep() 304 } 305 </div> 306 </div> 307 </div> 308 ); 309 } 310 } 311 312 export default withRouter(DetermineComponentForRoute)