// Copyright 2018 Red Hat, Inc // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. import * as React from 'react' import PropTypes from 'prop-types' import ReactJson from 'react-json-view' import { Icon, ListView, Row, Col, Modal, } from 'patternfly-react' const INTERESTING_KEYS = ['msg', 'stdout', 'stderr'] function didTaskFail(task) { if (task.failed) { return true } if ('failed_when_result' in task && !task.failed_when_result) { return false } if ('rc' in task && task.rc) { return true } return false } function hostTaskStats (state, host) { if (didTaskFail(host)) { state.failed += 1} else if (host.changed) { state.changed += 1} else if (host.skip_reason) { state.skipped += 1} else { state.ok += 1} } function hasInterestingKeys (obj, keys) { let ret = false Object.entries(obj).forEach(([k, v]) => { if (keys.includes(k)) { if (v !== '') { ret = true } } }) return ret } function makeTaskPath (path) { return path.join('/') } function taskPathMatches (ref, test) { if (test.length < ref.length) return false for (let i=0; i < ref.length; i++) { if (ref[i] !== test[i]) return false } return true } class TaskOutput extends React.Component { static propTypes = { data: PropTypes.object, include: PropTypes.array, } findLoopLabel(item) { const label = item._ansible_item_label if (typeof(label) === 'string') { return label } return '' } shouldIncludeKey(key, value, ignore_underscore) { if (ignore_underscore && key[0] === '_') { return false } if (this.props.include) { if (!this.props.include.includes(key)) { return false } if (value === '') { return false } } return true } renderResults(value) { const interesting_results = [] value.forEach((result, idx) => { const keys = Object.entries(result).filter(([key, value]) => this.shouldIncludeKey(key, value, true)) if (keys.length) { interesting_results.push(idx) } }) return (
{interesting_results.length>0 &&
results
{interesting_results.map((idx) => (

{idx}: {this.findLoopLabel(value[idx])}

{Object.entries(value[idx]).map(([key, value]) => ( this.renderData(key, value, true) ))}
))}
}
) } renderData(key, value, ignore_underscore) { let ret if (!this.shouldIncludeKey(key, value, ignore_underscore)) { return () } if (value === null) { ret = (
          null
        
) } else if (typeof(value) === 'string') { ret = (
          {value}
        
) } else if (typeof(value) === 'object') { ret = (
          
        
) } else { ret = (
          {value.toString()}
        
) } return (
{ret &&
{key}
} {ret && ret}
) } render () { const { data } = this.props return ( {Object.entries(data).map(([key, value]) => ( key==='results'?this.renderResults(value):this.renderData(key, value) ))} ) } } class HostTask extends React.Component { static propTypes = { hostname: PropTypes.string, task: PropTypes.object, host: PropTypes.object, errorIds: PropTypes.object, taskPath: PropTypes.array, displayPath: PropTypes.array, } state = { showModal: false, failed: 0, changed: 0, skipped: 0, ok: 0 } open = () => { this.setState({ showModal: true}) } close = () => { this.setState({ showModal: false}) } constructor (props) { super(props) const { host, taskPath, displayPath } = this.props hostTaskStats(this.state, host) if (taskPathMatches(taskPath, displayPath)) this.state.showModal = true } render () { const { hostname, task, host, taskPath, errorIds } = this.props const ai = [] if (this.state.skipped) { ai.push( SKIPPED ) } if (this.state.changed) { ai.push( CHANGED ) } if (this.state.failed) { ai.push( FAILED ) } if (this.state.ok) { ai.push( OK ) } ai.push( {hostname} ) const expand = errorIds.has(task.task.id) let name = task.task.name if (!name) { name = host.action } if (task.role) { name = task.role.name + ': ' + name } const has_interesting_keys = hasInterestingKeys(this.props.host, INTERESTING_KEYS) let lc = undefined if (!has_interesting_keys) { lc = [] } return ( {has_interesting_keys &&
                 
               
}
{hostname}
) } } class PlayBook extends React.Component { static propTypes = { playbook: PropTypes.object, errorIds: PropTypes.object, taskPath: PropTypes.array, displayPath: PropTypes.array, } render () { const { playbook, errorIds, taskPath, displayPath } = this.props const expandAll = (playbook.phase === 'run') const expand = (expandAll || errorIds.has(playbook.phase + playbook.index) || taskPathMatches(taskPath, displayPath)) const ai = [] if (playbook.trusted) { ai.push( Trusted ) } return ( {playbook.plays.map((play, idx) => ( Play: {play.play.name} {play.tasks.map((task, idx2) => ( Object.entries(task.hosts).map(([hostname, host]) => ( ))))} ))} ) } } class Console extends React.Component { static propTypes = { output: PropTypes.array, displayPath: PropTypes.array, } constructor (props) { super(props) const { output } = this.props const errorIds = new Set() this.errorIds = errorIds // Identify all of the hosttasks (and therefore tasks, plays, and // playbooks) which have failed. The errorIds are either task or // play uuids, or the phase+index for the playbook. Since they are // different formats, we can store them in the same set without // collisions. output.forEach(playbook => { playbook.plays.forEach(play => { play.tasks.forEach(task => { Object.entries(task.hosts).forEach(([, host]) => { if (host.results) { host.results.forEach(result => { if (didTaskFail(result)) { errorIds.add(task.task.id) errorIds.add(play.play.id) errorIds.add(playbook.phase + playbook.index) } }) } if (didTaskFail(host)) { errorIds.add(task.task.id) errorIds.add(play.play.id) errorIds.add(playbook.phase + playbook.index) } }) }) }) }) } render () { const { output, displayPath } = this.props return ( {output.map((playbook, idx) => ( ))} ) } } export default Console