// 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 (
)
}
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 &&
))))}
))}
)
}
}
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