feat: As a user, I want to create reports only in PDF file format

This commit is contained in:
Marcel Haag 2023-05-05 11:32:10 +02:00
parent 6e55e61ce5
commit 2e6f78c1f4
16 changed files with 243 additions and 103 deletions

View File

@ -87,7 +87,6 @@ export class HeaderComponent implements OnInit {
if (menuBag.item.icon) {
// tslint:disable-next-line:no-string-literal
if (menuBag.item.icon['icon'] === this.settingsIcon) {
console.warn('Profile');
this.dialogService.openCustomDialog(
ProfileSettingsComponent,
{
@ -98,7 +97,7 @@ export class HeaderComponent implements OnInit {
untilDestroyed(this)
).subscribe({
next: () => {
console.warn('New Settings confirmed');
console.info('New Settings confirmed');
}
});
}

View File

@ -183,12 +183,12 @@
"title": "Titel",
"description": "Beschreibung",
"impact": "Auswirkung",
"affectedUrls": "Betroffene URL's",
"affectedUrls": "Betroffene URL's / API's",
"reproduction": "Reproduktion",
"mitigation": "Minderung",
"add": "Fund hinzufügen",
"add.url": "Betroffene URL hinzufügen",
"affectedUrls.placeholder": "Betroffene URL hier eingeben..",
"add.url": "Betroffene URL / API hinzufügen",
"affectedUrls.placeholder": "Betroffene URL oder API hier eingeben..",
"create": {
"header": "Neuen Fund erstellen"
},

View File

@ -183,12 +183,12 @@
"title": "Title",
"description": "Description",
"impact": "Impact",
"affectedUrls": "Affected URL's",
"affectedUrls": "Affected URL's / API's",
"reproduction": "Reproduction",
"mitigation": "Mitigation",
"add": "Add finding",
"add.url": "Add affected Url",
"affectedUrls.placeholder": "Enter affected URL here..",
"add.url": "Add affected URL / API",
"affectedUrls.placeholder": "Enter affected URL or API here..",
"create": {
"header": "Create New Finding"
},

View File

@ -13,7 +13,7 @@
<app-version-tag [version]="dialogData.options[0].additionalData?.version"></app-version-tag>
</div>
<!--Chart Objective Component-->
<div fxLayout="column" fxLayoutGap="2rem" fxLayoutAlign="center center">
<div fxLayout="column" fxLayoutGap="2rem" fxLayoutAlign="center center" class="objective-chart">
<app-objective-chart
[projectPentestData]="selectedEvaluatedProject$.getValue().projectPentests"></app-objective-chart>
<span
@ -35,8 +35,8 @@
alt="">
</nb-radio>
</nb-radio-group>
<!--Export Format Radio Selection-->
<label class="export-format-label">
<!--ToDo: Export Format Radio Selection only whne more than pdf is supported-->
<!--<label class="export-format-label">
{{ 'report.dialog.formatLabel' | translate }}
</label>
<nb-radio-group name="format" [formControl]="exportReportFormatControl" class="export-radio-buttons"
@ -47,10 +47,10 @@
<nb-radio disabled value="{{exportFormats.CSV}}">
{{exportFormats.CSV}}
</nb-radio>
<nb-radio disabled value="{{exportFormats.HTML}}">
<nb-radio value="{{exportFormats.HTML}}">
{{exportFormats.HTML}}
</nb-radio>
</nb-radio-group>
</nb-radio-group>-->
</div>
</nb-card-body>
<nb-card-footer fxLayout="row" fxLayoutGap="1.5rem" fxLayoutAlign="end end">

View File

@ -4,13 +4,18 @@
.export-report-dialog {
width: 45.25rem !important;
height: 56.25rem;
height: 48.25rem;
.export-report-header {
height: 8vh;
font-size: 1.5rem;
}
.objective-chart {
padding-top: 1.5rem;
padding-bottom: 1.5rem;
}
.languageContainer {
display: flex;
max-width: 8rem;
@ -22,17 +27,19 @@
}
}
.hint {
padding-bottom: 0.5rem;
color: nb-theme(color-danger-default);
}
.export-radio-buttons {
float: left;
clear: none;
margin-left: 1rem;
}
.hint {
padding-bottom: 0.5rem;
color: nb-theme(color-danger-default);
}
nb-form-field {
padding: 0.5rem 0 0.75rem;
}

View File

@ -81,7 +81,6 @@ export class ExportReportDialogComponent implements OnInit {
}
onClickExport(reportFormat: string, reportLanguage: string): void {
console.warn('ToDo: Use format ', reportFormat);
// Get project id from dialog data
const projectId = this.dialogData.options[0].additionalData.id;
// Loading is true as long as there is a response from the reporting service
@ -89,7 +88,6 @@ export class ExportReportDialogComponent implements OnInit {
// Export pentest in choosen format
switch (reportFormat) {
case ExportFormatOptions.PDF: {
// @ts-ignore
this.downloadPentestReport$ = this.reportingService.getReportPDFforProjectById(projectId, reportLanguage)
.pipe(
shareReplay(),
@ -112,16 +110,6 @@ export class ExportReportDialogComponent implements OnInit {
});
break;
}
case ExportFormatOptions.CSV: {
this.loading$.next(false);
this.notificationService.showPopup('report.popup.generation.failed', PopupType.FAILURE);
break;
}
case ExportFormatOptions.HTML: {
this.loading$.next(false);
this.notificationService.showPopup('report.popup.generation.failed', PopupType.FAILURE);
break;
}
default: {
this.loading$.next(false);
this.notificationService.showPopup('report.popup.generation.failed', PopupType.FAILURE);

View File

@ -77,7 +77,6 @@ export class ProfileSettingsComponent implements OnInit {
this.userNameControl.setValue(user.username);
this.userFirstNameControl.setValue(user.firstName);
this.userLastNameControl.setValue(user.lastName);
console.warn(this.user.getValue());
},
error: err => {
console.error(err);
@ -121,6 +120,9 @@ export class ProfileSettingsComponent implements OnInit {
onClickLanguage(language: string): void {
this.translateService.use(language);
// ToDo: Update userAccount language property
const user = this.store.selectSnapshot(SessionState.userAccount);
this.store.dispatch(new UpdateUserSettings({...user, interfaceLang: language}));
}
onClickConfirm(): void {

View File

@ -1,15 +1,16 @@
import {Pipe, PipeTransform} from '@angular/core';
import {formatDate} from '@angular/common';
import {Store} from '@ngxs/store';
import {SessionState} from '@shared/stores/session-state/session-state';
import {CustomPipe} from '@shared/models/custom-pipe.mode';
import {TranslateService} from '@ngx-translate/core';
@Pipe({
name: 'dateTimeFormat'
name: 'dateTimeFormat',
pure: false
})
export class DateTimeFormatPipe implements PipeTransform {
constructor(private store: Store) {
constructor(private store: Store, private translateService: TranslateService) {
}
/**
@ -22,18 +23,15 @@ export class DateTimeFormatPipe implements PipeTransform {
return '-';
}
const localeDateAndNumberFormat = this.store.selectSnapshot(SessionState.userAccount) ?
// @ts-ignore
this.store.selectSnapshot(SessionState.userAccount.interfaceLang) : 'en-US';
const currentLanguage = this.translateService.currentLang;
if (!localeDateAndNumberFormat) {
if (!currentLanguage) {
return formatDate(value, CustomPipe.DATE_TIME_FMT_EN, 'en-US');
}
if (localeDateAndNumberFormat === 'de-DE') {
return formatDate(value, CustomPipe.DATE_TIME_FMT_DE, localeDateAndNumberFormat) + ' Uhr';
if (currentLanguage === 'de-DE') {
return formatDate(value, CustomPipe.DATE_TIME_FMT_DE, currentLanguage) + ' Uhr';
}
// @ts-ignore
return formatDate(value, CustomPipe.DATE_TIME_FMT_EN, localeDateAndNumberFormat);
return formatDate(value, CustomPipe.DATE_TIME_FMT_EN, currentLanguage);
}
}

View File

@ -23,7 +23,7 @@ export class DialogService {
additionalData: any
): NbDialogRef<T> {
return this.dialog.open<T>(componentOrTemplateRef, {
closeOnEsc: false,
closeOnEsc: true,
hasScroll: false,
autoFocus: true,
closeOnBackdropClick: false,

View File

@ -18,7 +18,6 @@ export class ReportingService {
/**
* Get PDF Report by project id
*/
// ToDo: Add language here
public getReportPDFforProjectById(projectId: string, reportLanguage: string): Observable<Loading<ArrayBuffer>> {
return this.http.get(`${this.reportBaseURL}/${projectId}/pdf/${reportLanguage}`,
{
@ -29,12 +28,6 @@ export class ReportingService {
}
).pipe(loadContent<ArrayBuffer>());
}
/* ToDo: Remove report function without observing report progress
public getReportPDFforProjectById(projectId: string): Observable<ArrayBuffer> {
// @ts-ignore
return this.http.get<ArrayBuffer>(`${this.reportBaseURL}/${projectId}/pdf`, {responseType: 'arraybuffer'})
}*/
}
export interface ReportDownloadConfiguration {

View File

@ -1,11 +1,8 @@
package com.securityc4po.reporting.report
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.securityc4po.reporting.configuration.security.Appuser
import com.securityc4po.reporting.extensions.getLoggerFor
import com.securityc4po.reporting.remote.APIService
import com.securityc4po.reporting.remote.model.ProjectReport
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.http.ResponseEntity.notFound
@ -13,7 +10,6 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.*
import reactor.core.publisher.Mono
import reactor.kotlin.core.publisher.switchIfEmpty
import java.io.File
@RestController
@RequestMapping("/reports")
@ -31,7 +27,6 @@ class ReportController(private val apiService: APIService, private val reportSer
"/{projectId}/pdf/{reportLanguage}",
produces = [MediaType.APPLICATION_PDF_VALUE]
)
// ToDo: Add language here
fun downloadPentestReportPDF(@PathVariable(value = "projectId") projectId: String, @PathVariable(value = "reportLanguage") reportLanguage: String, @AuthenticationPrincipal user: Appuser): Mono<ResponseEntity<ByteArray>> {
return this.apiService.requestProjectReportDataById(projectId, user.token).flatMap {projectReport ->
this.reportService.createReport(projectReport, "pdf", reportLanguage).map { reportClassLoaderFilePath ->
@ -41,35 +36,4 @@ class ReportController(private val apiService: APIService, private val reportSer
}
}
}
// ToDo: Add download API for csv report
/*
@GetMapping(
"/{projectId}/csv",
produces = ["text/csv"]
)
fun downloadPentestReportCSV() {
/* ToDo: remove if jsonProjectReportCollection not needed for report generation */
val jsonProjectReportString: String =
File("./src/test/resources/ProjectReportData.json").readText(Charsets.UTF_8)
val jsonProjectReportCollection: ProjectReport =
jacksonObjectMapper().readValue<ProjectReport>(jsonProjectReportString)
/* jsonProjectReportCollection */
}
*/
// ToDo: Add download API for html report
/*
@GetMapping(
"/{projectId}/html",
produces = ["text/html"]
)
fun downloadPentestReportHTML() {
/* ToDo: remove if jsonProjectReportCollection not needed for report generation */
val jsonProjectReportString: String =
File("./src/test/resources/ProjectReportData.json").readText(Charsets.UTF_8)
val jsonProjectReportCollection: ProjectReport =
jacksonObjectMapper().readValue<ProjectReport>(jsonProjectReportString)
/* jsonProjectReportCollection */
}
*/
}

View File

@ -76,19 +76,18 @@ class ReportService {
lateinit var severityRatingTablePath: String
fun createReport(projectReportCollection: ProjectReport, reportFormat: String, reportLanguage: String): Mono<ByteArray> {
logger.info("Use: " + reportLanguage)
// Setup PDFMergerUtility
val mergedC4POPentestReport: PDFMergerUtility = PDFMergerUtility()
// Setup ByteArrayOutputStream for "on the fly" file generation
val pdfDocOutputstream = ByteArrayOutputStream()
val penetstReportOutputstream = ByteArrayOutputStream()
// Try to create report files & merge them together
return createPentestReportFiles(projectReportCollection, reportFormat, reportLanguage, mergedC4POPentestReport).collectList()
.map {
// Merge report files
mergedC4POPentestReport.destinationStream = pdfDocOutputstream
mergedC4POPentestReport.destinationStream = penetstReportOutputstream
mergedC4POPentestReport.mergeDocuments(MemoryUsageSetting.setupTempFileOnly())
}.flatMap {
return@flatMap Mono.just(pdfDocOutputstream.toByteArray())
return@flatMap Mono.just(penetstReportOutputstream.toByteArray())
}.doOnError {
logger.error("Report generation failed.")
}
@ -113,13 +112,13 @@ class ReportService {
createAppendencies(reportFormat, resourceBundle)
).map { jasperObject ->
if (jasperObject is ByteArray) {
val pdfInputSteam = ByteArrayInputStream(jasperObject)
mergedC4POPentestReport.addSource(pdfInputSteam)
val reportFilesInputSteam = ByteArrayInputStream(jasperObject)
mergedC4POPentestReport.addSource(reportFilesInputSteam)
} else if (jasperObject is List<*>) {
jasperObject.forEach { jasperFile ->
if (jasperFile is ByteArray) {
val pdfInputSteam = ByteArrayInputStream(jasperFile)
mergedC4POPentestReport.addSource(pdfInputSteam)
val reportFilesInputSteam = ByteArrayInputStream(jasperFile)
mergedC4POPentestReport.addSource(reportFilesInputSteam)
}
}
}

View File

@ -2,6 +2,7 @@
# Cover
title.cover_one=Penetrationstest
title.cover_two=Ergebnisbericht
date.format=dd.MM.yyyy
hint=Kein Teil dieses Dokuments darf ohne die ausdrückliche schriftliche Genehmigung des Testers an externe Quellen weitergegeben werden
# Table of contents
@ -26,11 +27,11 @@ title.comment=Kommentar:
title=Titel:
description=Beschreibung:
impact=Auswirkung:
reproduction_steps=Reproduktion:
reproduction_steps=Schritte zur Reproduktion:
mitigation=Minderung:
no_mitigation=Keine Schadensminderung zur Vermeidung, Minimierung oder Kompensation des festgestellten oder erforderlichen Befunds.
affected_urls=Betroffene URL's:
no_affected_urls=Keine spezifischen URLs betroffen.
affected_urls=Betroffene URL's / API's:
no_affected_urls=Keine spezifischen URLs oder API's betroffen.
# Appendencies
title.appendencies=Anhänge
@ -38,7 +39,7 @@ title.findings_severities=Schweregrade der Funde
text.findings_severities=Jedem Befund wurde eine Schweregradbewertung von kritisch hoch, mittel oder niedrig zugewiesen. Die Bewertung basiert auf einer Bewertung der Priorität, mit der jeder Befund betrachtet werden sollte, und der potenziellen Auswirkungen, die jeder auf die Vertraulichkeit, Integrität und Verfügbarkeit hat.
title.risk_matrix=Risiko Matrix
text.risk_matrix=Die Risikomatrix wird verwendet, um den potenziellen Schaden einer Gefahr basierend auf den Faktoren Wahrscheinlichkeit und Schweregrad zu bewerten. Die Wahrscheinlichkeits- und Schweregradbewertungen werden multipliziert, um einen Bewertungswert zu erhalten. Diese Punktzahl wird in den Risikobereichen nachgeschlagen, um das Risikoniveau zu bestimmen. Ein Beispiel für eine Gefahren-Risiko-Matrix ist unten angegeben:
example.risk_matrix=Beispiel: Wenn Wahrscheinlichkeit = Möglich (3) und Schweregrad = Erheblich (4), wird die Risikostufe durch Schweregrad * Wahrscheinlichkeit bestimmt, was 3*4 = 12 ist. Die Punktzahl 12 fällt in den Risikobereich 'Hoch'.
example.risk_matrix=Beispiel: Wenn Wahrscheinlichkeit = Möglich (3) und Schweregrad = Erheblich (4), wird die Risikostufe durch Schweregrad * Wahrscheinlichkeit bestimmt, was 3*4 = 12 ist. Die Punktzahl 12 fällt in den Risikobereich 'High'.
# Risk Matrix Table Properties
risk_score=Risiko-Score
to=bis

View File

@ -2,6 +2,7 @@
# Cover
title.cover_one=Penetration Test
title.cover_two=Report of Findings
date.format=MM/dd/yyyy
hint=No part of this document may be disclosed to outside sources without the explicit written authorization of the tester
# Table of contents
@ -29,8 +30,8 @@ impact=Impact:
reproduction_steps=Reproduction Steps:
mitigation=Mitigation:
no_mitigation=No mitigation to avoid, minimize or compensate the finding found or needed.
affected_urls=Affected URL's:
no_affected_urls=No specific URL's affected.
affected_urls=Affected URL's / API's:
no_affected_urls=No specific URL's or API's affected.
# Appendencies
title.appendencies=Appendencies

View File

@ -147,7 +147,7 @@
<textElement textAlignment="Right">
<font fontName="SansSerif" size="12" isBold="true" isItalic="true"/>
</textElement>
<textFieldExpression><![CDATA[(new SimpleDateFormat("dd/MM/yyyy").format($F{createdAt}))]]></textFieldExpression>
<textFieldExpression><![CDATA[(new SimpleDateFormat($R{date.format}).format($F{createdAt}))]]></textFieldExpression>
</textField>
</band>
</detail>
@ -177,7 +177,7 @@
<reportElement x="-20" y="-10" width="595" height="20" forecolor="#151B2E" backcolor="#151B2E" uuid="724a02c5-82c8-4a72-bf81-b77baa72c723"/>
</rectangle>
<textField>
<reportElement x="0" y="-7" width="554" height="15" forecolor="#FFFFFF" uuid="32716a0d-4cec-4dc4-b766-f545dea11169"/>
<reportElement x="-14" y="-7" width="584" height="15" forecolor="#FFFFFF" uuid="32716a0d-4cec-4dc4-b766-f545dea11169"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font size="8" isBold="true"/>
</textElement>

View File

@ -0,0 +1,188 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.20.0.final using JasperReports Library version 6.20.0-2bc7ab61c56f459e8176eb05c7705e145cd400ad -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="C4PO_Coverpage" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="1e81cc75-35cb-406c-934f-0bc56dfd965d">
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="ProjectReportJasperData Template JSON Adapter"/>
<!-- Ignores Issue about not finding a specific font -->
<property name="net.sf.jasperreports.awt.ignore.missing.font" value="true"/>
<parameter name="CDATA_WATERMARK" class="java.lang.String"/>
<parameter name="CDATA_C4POCoverBackground" class="java.lang.String"/>
<queryString language="JSON">
<![CDATA[projectReportData]]>
</queryString>
<field name="id" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="id"/>
<fieldDescription><![CDATA[id]]></fieldDescription>
</field>
<field name="client" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="client"/>
<fieldDescription><![CDATA[client]]></fieldDescription>
</field>
<field name="title" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="title"/>
<fieldDescription><![CDATA[title]]></fieldDescription>
</field>
<field name="createdAt" class="java.util.Date">
<property name="net.sf.jasperreports.json.field.expression" value="createdAt"/>
<fieldDescription><![CDATA[createdAt]]></fieldDescription>
</field>
<field name="tester" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="tester"/>
<fieldDescription><![CDATA[tester]]></fieldDescription>
</field>
<field name="summary" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="summary"/>
<fieldDescription><![CDATA[summary]]></fieldDescription>
</field>
<field name="projectPentestReport" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="projectPentestReport"/>
<fieldDescription><![CDATA[projectPentestReport]]></fieldDescription>
</field>
<field name="createdBy" class="java.lang.String">
<property name="net.sf.jasperreports.json.field.expression" value="createdBy"/>
<fieldDescription><![CDATA[createdBy]]></fieldDescription>
</field>
<field name="version" class="java.lang.String"/>
<background>
<band splitType="Stretch"/>
</background>
<title>
<band height="390" splitType="Stretch">
<image>
<reportElement x="-20" y="-20" width="595" height="409" uuid="7b8866c7-8b72-43a8-9428-2404a75e803e"/>
<imageExpression><![CDATA[$P{CDATA_C4POCoverBackground}]]></imageExpression>
</image>
<rectangle>
<reportElement x="-20" y="-20" width="595" height="280" forecolor="#151B2E" backcolor="rgba(35, 43, 68, 0.5882353)" uuid="7412dfc2-c785-4584-b8e9-120df2ef41f2"/>
<graphicElement>
<pen lineWidth="0.0"/>
</graphicElement>
</rectangle>
<rectangle>
<reportElement x="-20" y="241" width="595" height="120" forecolor="#232B44" backcolor="#232B44" uuid="c3646ed4-24af-4969-990a-322ff29697a9"/>
</rectangle>
<textField textAdjust="StretchHeight">
<reportElement x="6" y="280" width="543" height="51" forecolor="#FFFFFF" uuid="563ae593-7ae8-47fc-8728-01da0a717aad"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="SansSerif" size="26" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$F{client}]]></textFieldExpression>
</textField>
<textField>
<reportElement mode="Transparent" x="5" y="0" width="544" height="219" forecolor="#FEFEFF" backcolor="#232B44" uuid="173fc927-62f1-4242-9c7e-638a21a9672f">
<property name="net.sf.jasperreports.export.accessibility.tag" value="h1"/>
<property name="net.sf.jasperreports.export.pdf.tag.table" value="full"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle" markup="none">
<font fontName="SansSerif" size="36" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$F{title}]]></textFieldExpression>
</textField>
<rectangle>
<reportElement x="-2" y="220" width="577" height="40" forecolor="#232B44" backcolor="#151B2E" uuid="2d5d891c-1d0f-4d81-beab-6d6937f08b5b"/>
<graphicElement>
<pen lineWidth="0.0"/>
</graphicElement>
</rectangle>
<ellipse>
<reportElement x="-20" y="220" width="38" height="40" backcolor="#151B2E" uuid="fefe65c4-59db-4810-9539-1865db235814"/>
<graphicElement>
<pen lineWidth="0.0"/>
</graphicElement>
</ellipse>
<image>
<reportElement x="-14" y="224" width="31" height="37" uuid="ae84d484-ee44-436a-a0cd-e94a265ed665"/>
<imageExpression><![CDATA[$P{CDATA_WATERMARK}]]></imageExpression>
</image>
<staticText>
<reportElement mode="Transparent" x="22" y="226" width="82" height="20" forecolor="#FEFEFF" backcolor="#151B2E" uuid="b40755db-f42b-47cf-9e73-57cd092f7bde"/>
<textElement>
<font fontName="SansSerif&#xA;&#xA;" size="12" isBold="true" isItalic="false"/>
</textElement>
<text><![CDATA[C4PO]]></text>
</staticText>
<staticText>
<reportElement mode="Transparent" x="23" y="242" width="82" height="20" forecolor="#FEFEFF" backcolor="#151B2E" uuid="1e37e3b3-b3d2-4621-9928-08497bd4f667"/>
<textElement>
<font fontName="SansSerif&#xA;&#xA;" size="10" isItalic="true"/>
</textElement>
<text><![CDATA[v.0.0.1]]></text>
</staticText>
<rectangle>
<reportElement x="-20" y="350" width="595" height="30" uuid="e6a81d95-840a-42a8-860d-cb1957d1775c"/>
<graphicElement>
<pen lineWidth="0.0"/>
</graphicElement>
</rectangle>
</band>
</title>
<columnHeader>
<band height="190" splitType="Stretch">
<textField>
<reportElement x="107" y="20" width="340" height="40" forecolor="#232B44" uuid="0c2fdc55-5038-49f5-a972-3575837bb8a6"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font size="26" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$R{title.cover_one}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="107" y="61" width="340" height="40" forecolor="#232B44" uuid="edce29e2-8963-43bd-8361-69e579e4a1e1"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font size="26" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$R{title.cover_two}]]></textFieldExpression>
</textField>
</band>
</columnHeader>
<detail>
<band height="91" splitType="Stretch">
<textField>
<reportElement x="0" y="10" width="551" height="30" uuid="54c4a617-82ea-4ec4-aa3c-d52e8fd22406"/>
<textElement textAlignment="Right">
<font fontName="SansSerif" size="20" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$F{tester}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="80" y="50" width="471" height="30" uuid="f13c2ce6-8960-4ac8-ba3a-f79823f07025"/>
<textElement textAlignment="Right">
<font fontName="SansSerif" size="12" isBold="true" isItalic="true"/>
</textElement>
<textFieldExpression><![CDATA[(new SimpleDateFormat("dd/MM/yyyy").format($F{createdAt}))]]></textFieldExpression>
</textField>
</band>
</detail>
<columnFooter>
<band height="76" splitType="Stretch">
<rectangle>
<reportElement x="-20" y="30" width="595" height="30" forecolor="#232B44" backcolor="#232B44" uuid="1ed47e2d-9d46-44b9-bad7-8eeb1143c83c"/>
<graphicElement>
<pen lineWidth="1.0"/>
</graphicElement>
</rectangle>
<rectangle>
<reportElement x="-20" y="20" width="595" height="10" forecolor="#151B2E" backcolor="#151B2E" uuid="b9a5cd43-3460-4177-97f5-a46eac874e7d"/>
</rectangle>
<textField>
<reportElement x="380" y="35" width="174" height="20" forecolor="#FFFFFF" uuid="0aa9c401-c73c-4a7e-b5a2-b650d333093f"/>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font size="12" isItalic="true"/>
</textElement>
<textFieldExpression><![CDATA["Version " + $F{version}]]></textFieldExpression>
</textField>
</band>
</columnFooter>
<pageFooter>
<band height="10" splitType="Stretch">
<rectangle>
<reportElement x="-20" y="-10" width="595" height="20" forecolor="#151B2E" backcolor="#151B2E" uuid="724a02c5-82c8-4a72-bf81-b77baa72c723"/>
</rectangle>
<textField>
<reportElement x="-14" y="-7" width="584" height="15" forecolor="#FFFFFF" uuid="32716a0d-4cec-4dc4-b766-f545dea11169"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font size="8" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$R{hint}]]></textFieldExpression>
</textField>
</band>
</pageFooter>
</jasperReport>