clusterscan detail

This commit is contained in:
Nancy Butler 2020-09-15 08:51:35 -07:00
parent 14767dbf88
commit 2e1c1a76fa
8 changed files with 275 additions and 12 deletions

View File

@ -255,6 +255,16 @@ cis:
profile: Profile
testID: Test ID
testsToSkip: Tests to Skip
scan:
description: Description
failed: Failed
lastScanTime: Last Scan Time
notApplicable: 'N/A'
number: Number
passed: Passed
scanReport: Scan Report
skipped: Skipped
total: Total
cluster:
nodeDriver:

View File

@ -121,17 +121,19 @@ export default {
return null;
},
parent() {
if (this.parentOverride) {
return this.parentOverride;
}
const displayName = this.$store.getters['type-map/labelFor'](this.schema);
const location = {
name: 'c-cluster-product-resource',
params: { resource: this.value.type }
};
return { displayName, location };
const out = { displayName, location };
if (this.parentOverride) {
Object.assign(out, this.parentOverride);
}
return out ;
}
},
methods: {
@ -166,7 +168,7 @@ export default {
<div v-if="mode==='view'" class="subheader">
<span v-if="isNamespace && project">{{ t("resourceDetail.masthead.project") }}: {{ project.nameDisplay }}</span>
<span v-else-if="namespace">{{ t("resourceDetail.masthead.namespace") }}: <nuxt-link :to="namespaceLocation">{{ namespace }}</nuxt-link></span>
<span>{{ t("resourceDetail.masthead.age") }}: <LiveDate class="live-date" :value="get(value, 'metadata.creationTimestamp')" /></span>
<span v-if="!parent.hideAge">{{ t("resourceDetail.masthead.age") }}: <LiveDate class="live-date" :value="get(value, 'metadata.creationTimestamp')" /></span>
</div>
</div>
<slot name="right">
@ -179,7 +181,7 @@ export default {
</button>
</div>
</slot>
<div v-if="banner" class="state-banner">
<div v-if="banner && !parent.hideBanner" class="state-banner">
<Banner class="state-banner" :color="banner.color" :label="banner.message" />
</div>
</header>

View File

@ -536,7 +536,7 @@ export default {
<Checkbox class="selection-checkbox" :data-node-id="get(row,keyField)" :value="tableSelected.includes(row)" />
</td>
<td v-if="subExpandColumn" class="row-expand" align="middle">
<i data-title="Toggle Expand" :class="{icon: true, 'icon-chevron-right': true, 'icon-chevron-down': !!expanded[get(row, keyField)]}" />
<i data-title="Toggle Expand" :class="{icon: true, 'icon-chevron-right': true, 'icon-chevron-down': !!expanded[get(row, keyField)]}" @click="toggleExpand(row)" />
</td>
<template v-for="col in columns">
<slot

View File

@ -1,6 +1,6 @@
<script>
import BadgeState from '@/components/BadgeState';
import { colorForState, stateDisplay } from '@/plugins/steve/resource-instance';
export default {
components: { BadgeState },
props: {
@ -16,10 +16,28 @@ export default {
type: Object,
default: () => {}
},
arbitrary: {
type: Boolean,
default: false
}
},
data() {
const out = {};
if (this.arbitrary) {
const color = colorForState(this.value);
out.stateDisplay = stateDisplay(this.value);
out.stateBackground = color.replace('text-', 'bg-');
}
return out;
}
};
</script>
<template>
<BadgeState :value="row" />
<BadgeState :value="arbitrary ? {stateDisplay, stateBackground} : row" />
</template>

View File

@ -0,0 +1,211 @@
<script>
import Date from '@/components/formatter/Date';
import SortableTable from '@/components/SortableTable';
import { defaultAsyncData } from '@/components/ResourceDetail';
import { CIS } from '@/config/types';
import { STATE } from '@/config/table-headers';
export default {
components: {
Date,
SortableTable
},
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
async fetch() {
this.owned = await this.value.getOwned();
},
asyncData(ctx) {
return defaultAsyncData(ctx, null, { hideBanner: true, hideAge: true });
},
data() {
return { owned: [] };
},
computed: {
clusterReport() {
return this.owned.filter(each => each.type === CIS.REPORT)[0];
},
parsedData() {
if (!this.clusterReport) {
return null;
}
const { spec:{ reportJSON } } = this.clusterReport;
return JSON.parse(reportJSON);
},
reportNodes() {
return this.parsedData ? this.parsedData.nodes : {};
},
results() {
return this.parsedData ? this.parsedData.results.reduce((all, result) => {
if (result.checks) {
result = result.checks.map((check) => {
check.testStateSort = this.testStateSort(check.state);
if (!!check.node_type) {
check.nodes = check.node_type.reduce((names, type) => {
if (this.reportNodes[type]) {
names.push(...this.reportNodes[type]);
}
return names;
}, []);
}
return check;
});
}
all.push(...result);
return all;
}, []) : [];
},
details() {
if (!this.parsedData) {
return [];
}
return [
{
label: this.t('cis.profile'),
value: this.value.status.lastRunScanProfileName,
to: {
name: 'c-cluster-product-resource-id',
params: {
...this.$route.params,
resource: CIS.CLUSTER_SCAN_PROFILE,
id: this.value.status.lastRunScanProfileName
}
}
},
{
label: this.t('cis.scan.total'),
value: this.parsedData.total
},
{
label: this.t('cis.scan.passed'),
value: this.parsedData.pass
},
{
label: this.t('cis.scan.skipped'),
value: this.parsedData.skip
},
{
label: this.t('cis.scan.failed'),
value: this.parsedData.fail
},
{
label: this.t('cis.scan.notApplicable'),
value: this.parsedData.notApplicable
},
{
label: this.t('cis.scan.lastScanTime'),
value: this.value.status.lastRunTimestamp,
component: 'Date'
},
];
},
reportCheckHeaders() {
return [
{
...STATE,
value: 'state',
formatterOpts: { arbitrary: true },
sort: 'testStateSort'
},
{
name: 'number',
label: this.t('cis.scan.number'),
value: 'id',
sort: ['id'],
width: 100
},
{
name: 'description',
label: this.t('cis.scan.description'),
value: 'description'
},
{
name: 'node',
label: 'Nodes',
value: 'nodes',
formatter: 'List',
sort: 'nodes'
}
];
},
},
methods: {
testStateSort(state) {
const SORT_ORDER = {
failed: 1,
skipped: 2,
notApplicable: 3,
passed: 4,
other: 5,
};
return `${ SORT_ORDER[state] || SORT_ORDER['other'] } ${ state }`;
},
}
};
</script>
<template>
<div>
<div class="detail mb-20">
<div v-for="item in details" :key="item.label">
<span class="text-label">{{ item.label }}</span>:
<component :is="item.component" v-if="item.component" :value="item.value" />
<nuxt-link v-else-if="item.to" :to="item.to">
{{ item.value }}
</nuxt-link>
<span v-else>{{ item.value }}</span>
</div>
</div>
<div v-if="parsedData">
<h3>{{ t('cis.scan.scanReport') }}</h3>
<SortableTable
default-sort-by="state"
:search="false"
:row-actions="false"
:table-actions="false"
:rows="results"
:headers="reportCheckHeaders"
key-field="id"
/>
</div>
</div>
</template>
<style lang='scss' scoped>
.detail {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid var(--border);
& .div {
padding: 0px 10px 0px 10px;
}
}
</style>

View File

@ -107,7 +107,7 @@ export default {
</div>
<div class="row">
<div class="col span-6">
<LabeledSelect v-model="value.spec.benchmarkVersion" :label="t('cis.benchmarkVersion')" :options="compatibleBenchmarkNames" />
<LabeledSelect v-model="value.spec.benchmarkVersion" :mode="mode" :label="t('cis.benchmarkVersion')" :options="compatibleBenchmarkNames" />
</div>
</div>
<div class="spacer" />

View File

@ -1,5 +1,6 @@
import { CIS } from '@/config/types';
import { downloadFile } from '@/utils/download';
import { colorForState } from '@/plugins/steve/resource-instance';
export default {
availableActions() {
@ -25,5 +26,15 @@ export default {
downloadFile(`${ reportCRD.id }.json`, JSON, 'application/json');
};
}
},
testState() {
return (state) => {
const color = colorForState.call(this, this.state);
const bgColor = this.color.replace('text-', 'bg-');
return { color, bgColor };
};
},
};

View File

@ -90,6 +90,7 @@ const STATES = {
locked: { color: 'warning', icon: 'adjust' },
migrating: { color: 'info', icon: 'info' },
notapplied: { color: 'warning', icon: 'tag' },
passed: { color: 'success', icon: 'dot-dotfill' },
paused: { color: 'info', icon: 'info' },
pending: { color: 'info', icon: 'tag' },
provisioning: { color: 'info', icon: 'dot' },
@ -146,6 +147,16 @@ export function colorForState(state) {
return `text-${ color }`;
}
export function stateDisplay(state) {
// @TODO use translations
if ( REMAP_STATE[state] ) {
return REMAP_STATE[state];
}
return state.split(/-/).map(ucFirst).join('-');
}
function maybeFn(val) {
if ( isFunction(val) ) {
return val(this);