silverstripe /
deploynaut
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | /* global Q */ |
||
| 2 | |||
| 3 | var React = require("react"); |
||
| 4 | var ReactDOM = require("react-dom"); |
||
| 5 | |||
| 6 | var Events = require('./events.js'); |
||
| 7 | var Helpers = require('./helpers.js'); |
||
| 8 | var DeployPlan = require('./DeployPlan.jsx'); |
||
| 9 | |||
| 10 | var DeploymentDialog = React.createClass({ |
||
| 11 | |||
| 12 | getInitialState: function() { |
||
| 13 | return { |
||
| 14 | loading: false, |
||
| 15 | loadingText: "", |
||
| 16 | errorText: "", |
||
| 17 | fetched: true, |
||
| 18 | last_fetched: "" |
||
| 19 | }; |
||
| 20 | }, |
||
| 21 | componentDidMount: function() { |
||
| 22 | this.subscriptions.push(Events.subscribe('loading', function(text) { |
||
| 23 | this.setState({ |
||
| 24 | loading: true, |
||
| 25 | success: false, |
||
| 26 | loadingText: text |
||
| 27 | }); |
||
| 28 | }.bind(this))); |
||
| 29 | this.subscriptions.push(Events.subscribe('loading/done', function() { |
||
| 30 | this.setState({ |
||
| 31 | loading: false, |
||
| 32 | loadingText: '', |
||
| 33 | success: true |
||
| 34 | }); |
||
| 35 | }.bind(this))); |
||
| 36 | this.subscriptions.push(Events.subscribe('error', function(text) { |
||
| 37 | this.setState({ |
||
| 38 | errorText: text, |
||
| 39 | loading: false, |
||
| 40 | loadingText: '', |
||
| 41 | success: false |
||
| 42 | }); |
||
| 43 | }.bind(this))); |
||
| 44 | }, |
||
| 45 | componentWillUnmount: function() { |
||
| 46 | // remove subscribers |
||
| 47 | for (var idx = 0; idx < this.subscriptions.length; idx++) { |
||
|
0 ignored issues
–
show
Coding Style
introduced
by
Loading history...
|
|||
| 48 | this.subscriptions[idx].remove(); |
||
| 49 | } |
||
| 50 | }, |
||
| 51 | |||
| 52 | // subscribers to Events so we can unsubscribe on componentWillUnmount |
||
| 53 | subscriptions: [], |
||
| 54 | |||
| 55 | handleClick: function(e) { |
||
| 56 | e.preventDefault(); |
||
| 57 | Events.publish('loading', "Fetching latest code…"); |
||
| 58 | this.setState({ |
||
| 59 | fetched: false |
||
| 60 | }); |
||
| 61 | |||
| 62 | Q($.ajax({ |
||
| 63 | type: "POST", |
||
| 64 | dataType: 'json', |
||
| 65 | url: this.props.context.projectUrl + '/fetch' |
||
| 66 | })) |
||
| 67 | .then(this.waitForFetchToComplete, this.fetchStatusError) |
||
| 68 | .then(function() { |
||
| 69 | Events.publish('loading/done'); |
||
| 70 | this.setState({ |
||
| 71 | fetched: true |
||
| 72 | }); |
||
| 73 | }.bind(this)) |
||
| 74 | .catch(this.fetchStatusError) |
||
| 75 | .done(); |
||
| 76 | }, |
||
| 77 | getFetchStatus: function (fetchData) { |
||
| 78 | return Q($.ajax({ |
||
| 79 | type: "GET", |
||
| 80 | url: fetchData.href, |
||
| 81 | dataType: 'json' |
||
| 82 | })); |
||
| 83 | }, |
||
| 84 | waitForFetchToComplete:function (fetchData) { |
||
| 85 | return this.getFetchStatus(fetchData).then(function (data) { |
||
| 86 | if (data.status === "Complete") { |
||
| 87 | return data; |
||
| 88 | } |
||
| 89 | if (data.status === "Failed") { |
||
| 90 | return $.Deferred(function (d) { |
||
| 91 | return d.reject(data); |
||
| 92 | }).promise(); |
||
| 93 | } |
||
| 94 | return this.waitForFetchToComplete(fetchData); |
||
| 95 | }.bind(this)); |
||
| 96 | }, |
||
| 97 | |||
| 98 | fetchStatusError: function(data) { |
||
| 99 | var message = 'Unknown error'; |
||
| 100 | if (typeof data.responseText !== 'undefined') { |
||
| 101 | message = data.responseText; |
||
| 102 | } else if (typeof data.message !== 'undefined') { |
||
| 103 | message = data.message; |
||
| 104 | } |
||
| 105 | Events.publish('error', message); |
||
| 106 | }, |
||
| 107 | lastFetchedHandler: function(time_ago) { |
||
| 108 | this.setState({last_fetched: time_ago}); |
||
| 109 | }, |
||
| 110 | render: function() { |
||
| 111 | var classes = Helpers.classNames({ |
||
| 112 | "deploy-dropdown": true, |
||
| 113 | loading: this.state.loading, |
||
| 114 | success: this.state.success |
||
| 115 | }); |
||
| 116 | |||
| 117 | var form; |
||
| 118 | |||
| 119 | if (this.state.errorText !== "") { |
||
| 120 | form = <ErrorMessages message={this.state.errorText} />; |
||
| 121 | } else if (this.state.fetched) { |
||
| 122 | form = ( |
||
| 123 | <DeployForm |
||
| 124 | context={this.props.context} |
||
| 125 | data={this.props.data} |
||
| 126 | lastFetchedHandler={this.lastFetchedHandler} |
||
| 127 | /> |
||
| 128 | ); |
||
| 129 | } else if (this.state.loading) { |
||
| 130 | form = <LoadingDeployForm message="Fetching latest code…" />; |
||
| 131 | } |
||
| 132 | |||
| 133 | return ( |
||
| 134 | <div> |
||
| 135 | <div className={classes} onClick={this.handleClick}> |
||
| 136 | <span className="status-icon" aria-hidden="true"></span> |
||
| 137 | <span className="time">last updated {this.state.last_fetched}</span> |
||
| 138 | <EnvironmentName environmentName={this.props.context.envName} /> |
||
| 139 | </div> |
||
| 140 | {form} |
||
| 141 | </div> |
||
| 142 | ); |
||
| 143 | } |
||
| 144 | }); |
||
| 145 | |||
| 146 | function LoadingDeployForm(props) { |
||
| 147 | return ( |
||
| 148 | <div className="deploy-form-loading"> |
||
| 149 | <div className="icon-holder"> |
||
| 150 | <i className="fa fa-cog fa-spin"></i> |
||
| 151 | <span>{props.message}</span> |
||
| 152 | </div> |
||
| 153 | </div> |
||
| 154 | ); |
||
| 155 | } |
||
| 156 | |||
| 157 | function ErrorMessages(props) { |
||
| 158 | return ( |
||
| 159 | <div className="deploy-dropdown-errors"> |
||
| 160 | {props.message} |
||
| 161 | </div> |
||
| 162 | ); |
||
| 163 | } |
||
| 164 | |||
| 165 | function EnvironmentName(props) { |
||
| 166 | return ( |
||
| 167 | <span className="environment-name"> |
||
| 168 | <i className="fa fa-rocket"> </i> |
||
| 169 | Deployment options <span className="hidden-xs">for {props.environmentName}</span> |
||
| 170 | </span> |
||
| 171 | ); |
||
| 172 | } |
||
| 173 | |||
| 174 | var DeployForm = React.createClass({ |
||
| 175 | getInitialState: function() { |
||
| 176 | return { |
||
| 177 | selectedTab: 1, |
||
| 178 | data: [], |
||
| 179 | preselectSha: null |
||
| 180 | }; |
||
| 181 | }, |
||
| 182 | componentDidMount: function() { |
||
| 183 | this.gitData(); |
||
| 184 | }, |
||
| 185 | |||
| 186 | gitData: function() { |
||
| 187 | this.setState({ |
||
| 188 | loading: true |
||
| 189 | }); |
||
| 190 | Q($.ajax({ |
||
| 191 | type: "POST", |
||
| 192 | dataType: 'json', |
||
| 193 | url: this.props.context.gitRevisionsUrl |
||
| 194 | })).then(function(data) { |
||
| 195 | this.setState({ |
||
| 196 | loading: false, |
||
| 197 | data: data.Tabs, |
||
| 198 | selectedTab: data.preselect_tab ? parseInt(data.preselect_tab, 10) : 1, |
||
| 199 | preselectSha: data.preselect_sha |
||
| 200 | }); |
||
| 201 | this.props.lastFetchedHandler(data.last_fetched); |
||
| 202 | }.bind(this), function(data) { |
||
| 203 | Events.publish('error', data); |
||
| 204 | }); |
||
| 205 | }, |
||
| 206 | |||
| 207 | selectHandler: function(id) { |
||
| 208 | this.setState({selectedTab: id}); |
||
| 209 | }, |
||
| 210 | render: function () { |
||
| 211 | if (this.state.loading) { |
||
| 212 | return ( |
||
| 213 | <LoadingDeployForm message="Loading…" /> |
||
| 214 | ); |
||
| 215 | } |
||
| 216 | |||
| 217 | return ( |
||
| 218 | <div className="deploy-form-outer clearfix"> |
||
| 219 | <form className="form-inline deploy-form" action="POST" action="#"> |
||
| 220 | <DeployTabSelector |
||
| 221 | data={this.state.data} |
||
| 222 | onSelect={this.selectHandler} |
||
| 223 | selectedTab={this.state.selectedTab} |
||
| 224 | /> |
||
| 225 | <DeployTabs |
||
| 226 | context={this.props.context} |
||
| 227 | data={this.state.data} |
||
| 228 | selectedTab={this.state.selectedTab} |
||
| 229 | preselectSha={this.state.preselectSha} |
||
| 230 | SecurityToken={this.state.SecurityToken} |
||
| 231 | /> |
||
| 232 | </form> |
||
| 233 | </div> |
||
| 234 | ); |
||
| 235 | } |
||
| 236 | }); |
||
| 237 | |||
| 238 | function DeployTabSelector(props) { |
||
| 239 | var selectors = props.data.map(function(tab) { |
||
| 240 | return ( |
||
| 241 | <DeployTabSelect |
||
| 242 | key={tab.id} |
||
| 243 | tab={tab} |
||
| 244 | onSelect={props.onSelect} |
||
| 245 | selectedTab={props.selectedTab} |
||
| 246 | /> |
||
| 247 | ); |
||
| 248 | }); |
||
| 249 | return ( |
||
| 250 | <ul className="SelectionGroup tabbedselectiongroup nolabel"> |
||
| 251 | {selectors} |
||
| 252 | </ul> |
||
| 253 | ); |
||
| 254 | } |
||
| 255 | |||
| 256 | var DeployTabSelect = React.createClass({ |
||
| 257 | handleClick: function(e) { |
||
| 258 | e.preventDefault(); |
||
| 259 | this.props.onSelect(this.props.tab.id); |
||
| 260 | }, |
||
| 261 | render: function () { |
||
| 262 | var classes = Helpers.classNames({ |
||
| 263 | active : (this.props.selectedTab === this.props.tab.id) |
||
| 264 | }); |
||
| 265 | return ( |
||
| 266 | <li className={classes}> |
||
| 267 | <a |
||
| 268 | onClick={this.handleClick} |
||
| 269 | href={"#deploy-tab-" + this.props.tab.id} |
||
| 270 | > |
||
| 271 | {this.props.tab.name} |
||
| 272 | </a> |
||
| 273 | </li> |
||
| 274 | ); |
||
| 275 | } |
||
| 276 | |||
| 277 | }); |
||
| 278 | |||
| 279 | function DeployTabs(props) { |
||
| 280 | var tabs = props.data.map(function(tab) { |
||
| 281 | return ( |
||
| 282 | <DeployTab |
||
| 283 | context={props.context} |
||
| 284 | key={tab.id} |
||
| 285 | tab={tab} |
||
| 286 | selectedTab={props.selectedTab} |
||
| 287 | preselectSha={props.selectedTab === tab.id ? props.preselectSha : null} |
||
| 288 | SecurityToken={props.SecurityToken} |
||
| 289 | /> |
||
| 290 | ); |
||
| 291 | }); |
||
| 292 | |||
| 293 | return ( |
||
| 294 | <div className="tab-content"> |
||
| 295 | {tabs} |
||
| 296 | </div> |
||
| 297 | ); |
||
| 298 | } |
||
| 299 | |||
| 300 | var DeployTab = React.createClass({ |
||
| 301 | getInitialState: function() { |
||
| 302 | var defaultSelectedOptions = []; |
||
| 303 | for (var i in this.props.tab.options) { |
||
| 304 | var option = this.props.tab.options[i]; |
||
| 305 | defaultSelectedOptions[option.name] = option.defaultValue; |
||
| 306 | } |
||
| 307 | |||
| 308 | return { |
||
| 309 | summary: this.getInitialSummaryState(), |
||
| 310 | selectedOptions: defaultSelectedOptions, |
||
| 311 | sha: this.props.preselectSha ? this.props.preselectSha : '' |
||
| 312 | }; |
||
| 313 | }, |
||
| 314 | getInitialSummaryState: function() { |
||
| 315 | return { |
||
| 316 | changes: {}, |
||
| 317 | messages: [], |
||
| 318 | validationCode: '', |
||
| 319 | estimatedTime: null, |
||
| 320 | actionCode: null, |
||
| 321 | initialState: true, |
||
| 322 | backupChecked: true |
||
| 323 | }; |
||
| 324 | }, |
||
| 325 | componentDidMount: function() { |
||
| 326 | if (this.shaChosen()) { |
||
| 327 | this.changeSha(this.state.sha); |
||
| 328 | } |
||
| 329 | }, |
||
| 330 | OptionChangeHandler: function(event) { |
||
| 331 | var selectedOptions = this.state.selectedOptions; |
||
| 332 | selectedOptions[event.target.name] = event.target.checked; |
||
| 333 | this.setState({ |
||
| 334 | selectedOptions: selectedOptions |
||
| 335 | }); |
||
| 336 | }, |
||
| 337 | SHAChangeHandler: function(event) { |
||
| 338 | this.setState({ |
||
| 339 | sha: event.target.value |
||
| 340 | }); |
||
| 341 | }, |
||
| 342 | changeSha: function(sha) { |
||
| 343 | this.setState({ |
||
| 344 | summary: this.getInitialSummaryState() |
||
| 345 | }); |
||
| 346 | |||
| 347 | Events.publish('change_loading'); |
||
| 348 | |||
| 349 | var branch = null; |
||
| 350 | |||
| 351 | for (var i in this.props.tab.field_data) { |
||
| 352 | if (this.props.tab.field_data[i].id === sha) { |
||
| 353 | branch = this.props.tab.field_data[i].branch_name; |
||
| 354 | } |
||
| 355 | } |
||
| 356 | |||
| 357 | var summaryData = { |
||
| 358 | sha: sha, |
||
| 359 | branch: branch, |
||
| 360 | DispatcherSecurityID: this.props.SecurityToken |
||
| 361 | }; |
||
| 362 | // merge the 'advanced' options if they are set |
||
| 363 | for (var attrname in this.state.selectedOptions) { |
||
| 364 | if (this.state.selectedOptions.hasOwnProperty(attrname)) { |
||
| 365 | summaryData[attrname] = this.state.selectedOptions[attrname]; |
||
| 366 | } |
||
| 367 | } |
||
| 368 | |||
| 369 | Q($.ajax({ |
||
| 370 | type: "POST", |
||
| 371 | dataType: 'json', |
||
| 372 | url: this.props.context.envUrl + '/deploy_summary', |
||
| 373 | data: summaryData |
||
| 374 | })).then(function(data) { |
||
| 375 | this.setState({ |
||
| 376 | summary: data |
||
| 377 | }); |
||
| 378 | Events.publish('change_loading/done'); |
||
| 379 | }.bind(this), function() { |
||
| 380 | Events.publish('change_loading/done'); |
||
| 381 | }); |
||
| 382 | }, |
||
| 383 | |||
| 384 | changeHandler: function(event) { |
||
| 385 | event.preventDefault(); |
||
| 386 | if (event.target.value === "") { |
||
| 387 | return; |
||
| 388 | } |
||
| 389 | var sha = ReactDOM.findDOMNode(this.refs.sha_selector.refs.sha).value; |
||
| 390 | return this.changeSha(sha); |
||
| 391 | }, |
||
| 392 | |||
| 393 | shaChosen: function() { |
||
| 394 | return (this.state.sha !== ''); |
||
| 395 | }, |
||
| 396 | |||
| 397 | render: function () { |
||
| 398 | var classes = Helpers.classNames({ |
||
| 399 | "tab-pane": true, |
||
| 400 | clearfix: true, |
||
| 401 | active: (this.props.selectedTab === this.props.tab.id) |
||
| 402 | }); |
||
| 403 | |||
| 404 | // setup the dropdown or the text input for selecting a SHA |
||
| 405 | var selector; |
||
| 406 | if (this.props.tab.field_type === 'dropdown') { |
||
| 407 | selector = ( |
||
| 408 | <SelectorDropdown |
||
| 409 | ref="sha_selector" |
||
| 410 | tab={this.props.tab} |
||
| 411 | changeHandler={this.SHAChangeHandler} |
||
| 412 | defaultValue={this.state.sha} |
||
| 413 | /> |
||
| 414 | ); |
||
| 415 | } else if (this.props.tab.field_type === 'textfield') { |
||
| 416 | selector = ( |
||
| 417 | <SelectorText |
||
| 418 | ref="sha_selector" |
||
| 419 | tab={this.props.tab} |
||
| 420 | changeHandler={this.SHAChangeHandler} |
||
| 421 | defaultValue={this.state.sha} |
||
| 422 | /> |
||
| 423 | ); |
||
| 424 | } |
||
| 425 | |||
| 426 | return ( |
||
| 427 | <div id={"deploy-tab-" + this.props.tab.id} className={classes}> |
||
| 428 | <div className="section"> |
||
| 429 | <div htmlFor={this.props.tab.field_id} className="header"> |
||
| 430 | <span className="numberCircle">1</span> {this.props.tab.field_label} |
||
| 431 | </div> |
||
| 432 | {selector} |
||
| 433 | <DeployOptions |
||
| 434 | tab={this.props.tab} |
||
| 435 | changeHandler={this.OptionChangeHandler} |
||
| 436 | options={this.props.tab.options} |
||
| 437 | selectedOptions={this.state.selectedOptions} |
||
| 438 | /> |
||
| 439 | <VerifyButton disabled={!this.shaChosen()} changeHandler={this.changeHandler} /> |
||
| 440 | </div> |
||
| 441 | <DeployPlan context={this.props.context} summary={this.state.summary} /> |
||
| 442 | </div> |
||
| 443 | ); |
||
| 444 | } |
||
| 445 | }); |
||
| 446 | |||
| 447 | var SelectorDropdown = React.createClass({ |
||
| 448 | componentDidMount: function() { |
||
| 449 | $(ReactDOM.findDOMNode(this.refs.sha)).select2({ |
||
| 450 | // Load data into the select2. |
||
| 451 | // The format supports optgroups, and looks like this: |
||
| 452 | // [{text: 'optgroup text', children: [{id: '<sha>', text: '<inner text>'}]}] |
||
| 453 | data: this.props.tab.field_data |
||
| 454 | }).val(this.props.defaultValue); |
||
| 455 | |||
| 456 | if (this.props.changeHandler) { |
||
| 457 | $(ReactDOM.findDOMNode(this.refs.sha)).select2().on("change", this.props.changeHandler); |
||
| 458 | } |
||
| 459 | }, |
||
| 460 | |||
| 461 | render: function() { |
||
| 462 | // From https://select2.github.io/examples.html "The best way to ensure that Select2 is using a percent based |
||
| 463 | // width is to inline the style declaration into the tag". |
||
| 464 | var style = {width: '100%'}; |
||
| 465 | |||
| 466 | return ( |
||
| 467 | <div> |
||
| 468 | <div className="field"> |
||
| 469 | <select |
||
| 470 | ref="sha" |
||
| 471 | id={this.props.tab.field_id} |
||
| 472 | name="sha" |
||
| 473 | className="dropdown" |
||
| 474 | onChange={this.props.changeHandler} |
||
| 475 | style={style} |
||
| 476 | > |
||
| 477 | <option value="">Select {this.props.tab.field_id}</option> |
||
| 478 | </select> |
||
| 479 | </div> |
||
| 480 | </div> |
||
| 481 | ); |
||
| 482 | } |
||
| 483 | }); |
||
| 484 | |||
| 485 | var SelectorText = React.createClass({ |
||
| 486 | render: function() { |
||
| 487 | return ( |
||
| 488 | <div className="field"> |
||
| 489 | <input |
||
| 490 | type="text" |
||
| 491 | ref="sha" |
||
| 492 | id={this.props.tab.field_id} |
||
| 493 | name="sha" |
||
| 494 | className="text" |
||
| 495 | defaultValue={this.props.defaultValue} |
||
| 496 | onChange={this.props.changeHandler} |
||
| 497 | /> |
||
| 498 | </div> |
||
| 499 | ); |
||
| 500 | } |
||
| 501 | }); |
||
| 502 | |||
| 503 | function VerifyButton(props) { |
||
| 504 | return ( |
||
| 505 | <div> |
||
| 506 | <button |
||
| 507 | disabled={props.disabled} |
||
| 508 | value="Verify deployment" |
||
| 509 | className="btn btn-default" |
||
| 510 | onClick={props.changeHandler} |
||
| 511 | > |
||
| 512 | Verify deployment |
||
| 513 | </button> |
||
| 514 | </div> |
||
| 515 | ); |
||
| 516 | } |
||
| 517 | |||
| 518 | function DeployOptions(props) { |
||
| 519 | var options = []; |
||
| 520 | for (var i in props.options) { |
||
| 521 | var name = props.options[i].name; |
||
| 522 | var title = props.options[i].title; |
||
| 523 | var checked = false; |
||
| 524 | |||
| 525 | for (var optionName in props.selectedOptions) { |
||
| 526 | if (optionName === name) { |
||
| 527 | checked = props.selectedOptions[optionName]; |
||
| 528 | } |
||
| 529 | } |
||
| 530 | |||
| 531 | options.push( |
||
| 532 | <DeployOption |
||
| 533 | key={i} |
||
| 534 | changeHandler={props.changeHandler} |
||
| 535 | name={name} |
||
| 536 | title={title} |
||
| 537 | checked={checked} |
||
| 538 | /> |
||
| 539 | ); |
||
| 540 | } |
||
| 541 | |||
| 542 | return ( |
||
| 543 | <div className="deploy-options"> |
||
| 544 | {options} |
||
| 545 | </div> |
||
| 546 | ); |
||
| 547 | } |
||
| 548 | |||
| 549 | function DeployOption(props) { |
||
| 550 | return ( |
||
| 551 | <div className="fieldcheckbox"> |
||
| 552 | <label htmlFor={props.name}> |
||
| 553 | <input |
||
| 554 | type="checkbox" |
||
| 555 | name={props.name} |
||
| 556 | id={props.name} |
||
| 557 | checked={props.checked} |
||
| 558 | onChange={props.changeHandler} |
||
| 559 | /> |
||
| 560 | {props.title} |
||
| 561 | </label> |
||
| 562 | </div> |
||
| 563 | ); |
||
| 564 | } |
||
| 565 | |||
| 566 | module.exports = DeploymentDialog; |
||
| 567 |