Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0355520
work in progress
georgedias Mar 1, 2024
ee70c3e
Completed filtered html report
georgedias Mar 1, 2024
11933bf
SonarCloud fix
georgedias Mar 1, 2024
15fcf8c
Merge branch 'master' into htmlFilteredReports
georgedias Mar 1, 2024
1716018
Removed copy package file
georgedias Mar 1, 2024
4f3d77f
Merge branch 'htmlFilteredReports' of github.com:mitre/heimdall2 into…
georgedias Mar 1, 2024
ebb6630
remove ssl certs from pr
em-c-rod Mar 4, 2024
ee2d83c
Revert "remove ssl certs from pr"
em-c-rod Mar 4, 2024
fb5cae2
try removing the certs again because the test may have errored for a …
em-c-rod Mar 4, 2024
ad7e902
reset yarn.lock to what master has and set the package.json setting b…
em-c-rod Mar 4, 2024
6fcd9f7
Merge branch 'master' into htmlFilteredReports
aaronlippold Mar 10, 2024
6d0d602
Added capability to filter reports on status and severity
georgedias Mar 14, 2024
c9da6b9
Sonar cloud fixes
georgedias Mar 14, 2024
16debd8
Update apps/frontend/src/components/global/ExportHTMLModal.vue
em-c-rod Mar 14, 2024
c685143
Update apps/frontend/src/components/global/ExportHTMLModal.vue
em-c-rod Mar 14, 2024
2eb1c5f
Update apps/frontend/src/components/global/ExportHTMLModal.vue
em-c-rod Mar 14, 2024
27d54f7
linting
em-c-rod Mar 14, 2024
ef73c42
Only display files that have selected controls
georgedias Mar 15, 2024
942b798
Merge branch 'htmlFilteredReports' of github.com:mitre/heimdall2 into…
georgedias Mar 15, 2024
9876542
Only display files that have selected controls
georgedias Mar 15, 2024
22455aa
Merge branch 'master' into htmlFilteredReports
georgedias Mar 15, 2024
a490685
Added wait cursor while loading files and generating HTML report
georgedias Mar 16, 2024
bccfbbc
Merge branch 'master' into htmlFilteredReports
em-c-rod Mar 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions apps/frontend/public/static/export/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -110,28 +110,8 @@
{{/showResultSets}}
</nav>

<!-- Profile info card -->
<div class="px-4 py-6" id="profileInfo">
<div
class="min-w-full rounded-xl border border-gray-300 bg-gray-100 px-4 py-2 print:border-black print:bg-white"
>
<h5 class="mb-2 text-xl font-bold">Profile Info</h5>
<div class="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-4">
<!-- Generate an info report for every profile -->
{{#files}}
<div class="mx-2 my-2 break-words">
<div><strong>Filename:</strong> {{filename}}</div>
<div><strong>Tool Version:</strong> {{toolVersion}}</div>
<div><strong>Platform:</strong> {{platform}}</div>
<div><strong>Duration:</strong> {{duration}}</div>
</div>
{{/files}}
</div>
</div>
</div>

<!-- Profile status card -->
<div class="px-4 pb-6">
<div class="px-4 py-6">
<div
class="min-w-full rounded-xl border border-gray-300 bg-gray-100 px-4 py-2 print:border-black print:bg-white"
>
Expand Down Expand Up @@ -217,6 +197,26 @@ <h5 class="mb-2 text-xl font-bold">Profile Status</h5>
</div>
</div>

<!-- Profile info card -->
<div class="px-4 pb-6" id="profileInfo">
<div
class="min-w-full rounded-xl border border-gray-300 bg-gray-100 px-4 py-2 print:border-black print:bg-white"
>
<h5 class="mb-2 text-xl font-bold">Profile Info</h5>
<div class="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-4">
<!-- Generate an info report for every profile -->
{{#files}}
<div class="mx-2 my-2 break-words">
<div><strong>Filename:</strong> {{filename}}</div>
<div><strong>Tool Version:</strong> {{toolVersion}}</div>
<div><strong>Platform:</strong> {{platform}}</div>
<div><strong>Duration:</strong> {{duration}}</div>
</div>
{{/files}}
</div>
</div>
</div>

<div class="overflow-hidden">
<!-- Result section -->
<!-- If empty, then this should be an executive report HTML export type -->
Expand Down
109 changes: 105 additions & 4 deletions apps/frontend/src/components/global/ExportHTMLModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@
<pre class="pt-5" v-text="description" />
</v-col>
</v-row>
<v-row>
<v-col cols="1">
<v-icon color="primary">mdi-information-variant-circle</v-icon>
</v-col>
<v-col>
Both Manager and Administrator Reports can be data intensive, which
can take longer for the browser to render the page.
</v-col>
</v-row>
</v-card-text>
<v-divider />
<v-card-actions>
Expand All @@ -57,6 +66,7 @@ import {Filter} from '../../store/data_filters';
import {InspecDataModule} from '../../store/data_store';
import {SnackbarModule} from '../../store/snackbar';
import {FromHDFToHTMLMapper} from '@mitre/hdf-converters';
import {SourcedContextualizedEvaluation} from '../../store/report_intake';

// All selectable export types for an HTML export
enum FileExportTypes {
Expand Down Expand Up @@ -118,23 +128,38 @@ export default class ExportHTMLModal extends Vue {
return SnackbarModule.failure('No files have been loaded.');
}

document.body.style.cursor = 'wait';
const files = [];
const filteredStatus = this.filter.status!.toString() || '';
const filteredSeverity = this.filter.severity!.toString() || '';

for (const fileId of this.filter.fromFile) {
const file = InspecDataModule.allEvaluationFiles.find(
(f) => f.uniqueId === fileId
);

if (file) {
const data = file.evaluation;
const fileName = file.filename;
const fileID = file.uniqueId;
files.push({data, fileName, fileID});
const filteredControls = this.getFilterControlIds(
data,
filteredStatus,
filteredSeverity
);

// Only show files that have controls that meet
// the status/severity criteria, could be all files
if (filteredControls.length > 0) {
const fileName = file.filename;
const fileID = file.uniqueId;
files.push({data, fileName, fileID, filteredControls});
}
}
}

// Generate and export HTML file
const body = await new FromHDFToHTMLMapper(files, this.exportType).toHTML(
'/static/export/'
);

saveAs(
new Blob([s2ab(body)], {type: 'application/octet-stream'}),
`${this.exportType}_Report_${new Date().toString()}.html`.replace(
Expand All @@ -143,7 +168,83 @@ export default class ExportHTMLModal extends Vue {
)
);

document.body.style.cursor = 'default';
this.closeModal();
}

/**
* Generate an array containing the control identification numbers
* (profiles.controls) selected. The Id is extracted for the
* profiles.controls.id, it can be CIS, STIG (V or SV), CWEID, WASCID,
* or any other control Id.
*
* Possible selection permutations are:
* - Status (pass, failed, etc)
* - Severity ( low, medium, etc)
* - Combination of Status and Severity
*/
getFilterControlIds(
data: SourcedContextualizedEvaluation,
status: string,
severity: string
): string[] {
/**
* NOTE: The filterControls array is used to specify what controls
* are selected based on Status and Severity selection.
* If we use the approach of filtering the content from the data object
* (e.g.
* data.data.profiles[0].controls =
* data.data.profiles[0].controls.filter((control) => {
* if (filteredControls.includes(control.id)) {
* return filteredControls.includes(control.id);
* }
* });
* )
* the contextualize object does not get updated and the results
* in data_store get out of sync, there is, when utilizing the
* ".contains" it returns the results object (child of the controls object)
* where the file.evaluations returns the filtered controls.
*/
let filteredControls: string[] = [];
// Both Status and Severity selection
if (status.length > 0 && severity.length > 0) {
data.contains.map((profile) => {
profile.contains.map((result) => {
if (
status?.includes(result.root.hdf.status) &&
severity?.includes(result.root.hdf.severity)
) {
filteredControls.push(result.data.id);
}
});
});
// Status selection
} else if (status.length > 0) {
data.contains.map((profile) => {
profile.contains.map((result) => {
if (status?.includes(result.root.hdf.status)) {
filteredControls.push(result.data.id);
}
});
});
// Severity selection
} else if (severity.length > 0) {
data.contains.map((profile) => {
profile.contains.map((result) => {
if (severity?.includes(result.root.hdf.severity)) {
filteredControls.push(result.data.id);
}
});
});
// No selection
} else {
data.contains.map((profile) => {
profile.contains.map((result) => {
filteredControls.push(result.data.id);
});
});
}
return filteredControls;
}
}
</script>
22 changes: 17 additions & 5 deletions apps/frontend/src/components/global/upload_tabs/FileReader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,13 @@
<v-progress-circular
indeterminate
color="#ff5600"
:size="80"
:width="20"
/>
:size="120"
:width="15"
>
<template #default>
<b>{{ percent }}% loaded</b>
</template>
</v-progress-circular>
</div>
</div>
</v-col>
Expand Down Expand Up @@ -140,7 +144,7 @@ interface VueFileAgentRecord {
export default class FileReader extends mixins(ServerMixin) {
fileRecords: Array<VueFileAgentRecord> = [];
loading = false;

percent = 0;
isActiveDialog = false;

filesSelected() {
Expand All @@ -151,12 +155,18 @@ export default class FileReader extends mixins(ServerMixin) {

/** Callback for our file reader */
commit_files(files: File[]) {
const totalFiles = files.length;
let index = 1;
document.body.style.cursor = 'wait';
Promise.all(
files.map(async (file) => {
try {
return await InspecIntakeModule.loadFile({file});
const fileId = await InspecIntakeModule.loadFile({file});
this.percent = Math.floor((index++ / totalFiles) * 100);
return fileId;
} catch (err) {
SnackbarModule.failure(String(err));
document.body.style.cursor = 'default';
}
})
)
Expand All @@ -174,6 +184,8 @@ export default class FileReader extends mixins(ServerMixin) {
})
.finally(() => {
this.loading = false;
this.percent = 0;
document.body.style.cursor = 'default';
});
}

Expand Down
3 changes: 3 additions & 0 deletions apps/frontend/src/views/Results.vue
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ export default class Results extends mixins(RouteMixin, ServerMixin) {
treeFilters: TreeMapState = [];
controlSelection: string | null = null;

gotStatus: boolean = false;
gotSeverity: boolean = false;

/** Model for if all-filtered snackbar should be showing */
filterSnackbar = false;

Expand Down
2 changes: 1 addition & 1 deletion libs/hdf-converters/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,4 @@
"^.+\\.ts$": "ts-jest"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ type InputData = {
data: ContextualizedEvaluation | string;
fileName: string;
fileID: string;
filteredControls?: string[];
};

type ProcessedData = {
data: ContextualizedEvaluation;
fileName: string;
fileID: string;
filteredControls?: string[];
};

// All selectable export types for an HTML export
Expand Down Expand Up @@ -174,7 +176,12 @@ export class FromHDFToHTMLMapper {
}

this.addFiledata(
{data: file.data, fileName: file.fileName, fileID: file.fileID},
{
data: file.data,
fileName: file.fileName,
fileID: file.fileID,
filteredControls: file.filteredControls
},
exportType
);
}
Expand All @@ -199,11 +206,24 @@ export class FromHDFToHTMLMapper {

// Pull out results from file
const allResultLevels: ContextualizedControl[] = [];
file.data.contains.map((profile) => {
profile.contains.map((result) => {
allResultLevels.push(result);
if (file.filteredControls === undefined) {
file.data.contains.map((profile) => {
profile.contains.map((result) => {
allResultLevels.push(result);
});
});
});
} else {
file.data.contains.flatMap((profile) => {
profile.contains.flatMap((result) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
for (const element of file.filteredControls!) {
if (element === result.data.id) {
allResultLevels.push(result);
}
}
});
});
}

// Begin filling out outpuData object to pass into HTML template
// Set high level generalized profile details
Expand Down Expand Up @@ -310,7 +330,7 @@ export class FromHDFToHTMLMapper {
};

// Calculate & set compliance level and color from result statuses
// Set default complaince level and color
// Set default compliance level and color
this.outputData.compliance.level = '0.00%';
this.outputData.compliance.color = 'low';

Expand All @@ -324,7 +344,9 @@ export class FromHDFToHTMLMapper {
100
);
// Set compliance level
this.outputData.compliance.level = complianceLevel;
this.outputData.compliance.level = complianceLevel.includes('NaN')
? '0.00%'
: complianceLevel;
// Determine color of compliance level
// High compliance is green, medium is yellow, low is red
this.outputData.compliance.color = translateCompliance(complianceLevel);
Expand Down Expand Up @@ -495,7 +517,7 @@ export class FromHDFToHTMLMapper {
return text;
}

// Prompt HTML generation from data pulled from file during constructor intialization
// Prompt HTML generation from data pulled from file during constructor initialization
// Requires path to prompt location of needed files relative to function call location
async toHTML(path: string): Promise<string> {
// Pull export template + styles and create outputData object containing data to fill template with
Expand All @@ -517,7 +539,6 @@ export class FromHDFToHTMLMapper {
'//# sourceMappingURL=tw-elements.umd.min.js.map',
''
);

// Render template and return generated HTML file
return Mustache.render(template, this.outputData);
}
Expand Down
Loading