diff --git a/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/reports/IntegrityReportConstants.java b/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/reports/IntegrityReportConstants.java index 65cb2a533..ed0e106d1 100644 --- a/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/reports/IntegrityReportConstants.java +++ b/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/reports/IntegrityReportConstants.java @@ -31,7 +31,7 @@ public enum ReportPart { public String getPartName() {return "deletedFile";} public String getHumanString() {return "deleted files";} - }, CHECKSUM_ISSUE { + }, CHECKSUM_ERROR { public String getPartName() {return "checksumIssue";} public String getHumanString() {return "inconsistent checksums";} diff --git a/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/reports/IntegrityReportWriter.java b/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/reports/IntegrityReportWriter.java index 23503d7e5..cf7895ca6 100644 --- a/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/reports/IntegrityReportWriter.java +++ b/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/reports/IntegrityReportWriter.java @@ -53,7 +53,7 @@ public class IntegrityReportWriter { public IntegrityReportWriter(File reportDir) { this.reportDir = reportDir; missingFilesWriter = new IntegrityReportPartWriter(ReportPart.MISSING_FILE, reportDir); - checksumIssuesWriter = new IntegrityReportPartWriter(ReportPart.CHECKSUM_ISSUE, reportDir); + checksumIssuesWriter = new IntegrityReportPartWriter(ReportPart.CHECKSUM_ERROR, reportDir); missingChecksumsWriter = new IntegrityReportPartWriter(ReportPart.MISSING_CHECKSUM, reportDir); obsoleteChecksumsWriter = new IntegrityReportPartWriter(ReportPart.OBSOLETE_CHECKSUM, reportDir); deletedFilesWriter2 = new IntegrityReportPartWriter(ReportPart.DELETED_FILE, reportDir); diff --git a/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/web/RestIntegrityService.java b/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/web/RestIntegrityService.java index 4d06a3022..5eb7b31eb 100644 --- a/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/web/RestIntegrityService.java +++ b/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/web/RestIntegrityService.java @@ -66,8 +66,10 @@ import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; @Path("/IntegrityService") public class RestIntegrityService { @@ -83,153 +85,166 @@ public RestIntegrityService() { } /** - * Method to get the checksum errors per pillar in a given collection. + * REST endpoint to get the list of present files on a pillar in a given collection. * - * @param collectionID, the collectionID from which to return checksum errors - * @param pillarID, the ID of the pillar in the collection from which to return checksum errors - * @param pageNumber, the page number for calculating offsets (@see pageSize) - * @param pageSize, the number of checksum errors per page. + * @param collectionID The collection ID from which to return present file list. + * @param pillarID The ID of the pillar in the collection from which to return present file list + * @return Returns a {@link HashMap} containing a key-pair of pillarID and its missing files as a {@link String} and + * {@link List}. */ @GET - @Path("/getChecksumErrorFileIDs/") + @Path("/getTotalFileIDs") @Produces(MediaType.APPLICATION_JSON) - public StreamingOutput getChecksumErrors( + public HashMap> getTotalFileIDs( @QueryParam("collectionID") String collectionID, @QueryParam("pillarID") String pillarID, - @QueryParam("pageNumber") - int pageNumber, + @QueryParam("page") + int page, @DefaultValue("100") @QueryParam("pageSize") int pageSize) { - int firstID = (pageNumber - 1) * pageSize; + IntegrityIssueIterator it = model.getFilesOnPillar(pillarID, getOffset(page, pageSize), pageSize, collectionID); - return streamPartFromLatestReport(ReportPart.CHECKSUM_ISSUE, collectionID, pillarID, firstID, pageSize); + if (it == null) { + throw new WebApplicationException( + Response.status(Response.Status.NO_CONTENT).entity("Failed to get missing files from database") + .type(MediaType.TEXT_PLAIN).build()); + } + + List iteratorAsList = StreamingTools.iteratorToList(it); + if (iteratorAsList.isEmpty()) { + throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND) + .entity(String.format(Locale.ROOT, "No fileIDs found for collection: '%s' and pillar: '%s'", collectionID, pillarID)) + .type(MediaType.TEXT_PLAIN).build()); + } + + return new HashMap<>(Map.of(pillarID, iteratorAsList)); } /** - * Method to get the list of missing files per pillar in a given collection. + * REST endpoint to get the list of missing files for a pillar in a given collection. * - * @param collectionID, the collectionID from which to return missing files - * @param pillarID, the ID of the pillar in the collection from which to return missing files - * @param pageNumber, the page number for calculating offsets (@see pageSize) - * @param pageSize, the number of checksum errors per page. + * @param collectionID The collection ID from which to return missing files. + * @return Returns a {@link HashMap} mapping the given pillar to its missing files. */ @GET - @Path("/getMissingFileIDs/") + @Path("/getMissingFileIDs") @Produces(MediaType.APPLICATION_JSON) - public StreamingOutput getMissingFileIDs( + public HashMap> getMissingFileIDs( @QueryParam("collectionID") String collectionID, @QueryParam("pillarID") String pillarID, - @QueryParam("pageNumber") - int pageNumber, + @QueryParam("page") + int page, @DefaultValue("100") @QueryParam("pageSize") int pageSize) { + HashMap> output = new HashMap<>(); + ReportPart part = ReportPart.MISSING_FILE; + List missingOnPillar; + List missingOnOtherPillar; - int firstID = (pageNumber - 1) * pageSize; + List otherPillars = SettingsUtils.getPillarIDsForCollection(collectionID).stream() + .filter(pillar -> !pillar.equals(pillarID)).collect(Collectors.toList()); + + try { + missingOnPillar = getReportPart(part, collectionID, pillarID, page, pageSize); + output.put(pillarID, missingOnPillar); + } catch (FileNotFoundException e) { + throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND) + .entity(String.format(Locale.ROOT, "No integrity '%s' report part for collection: '%s' and pillar: '%s' found!", + part.getHumanString(), collectionID, pillarID)).type(MediaType.TEXT_PLAIN).build()); + } + + for (String otherPillar : otherPillars) { + missingOnOtherPillar = compareMissingFiles(missingOnPillar, collectionID, otherPillar, pageSize); + output.put(otherPillar, missingOnOtherPillar); + } - return streamPartFromLatestReport(ReportPart.MISSING_FILE, collectionID, pillarID, firstID, pageSize); + return output; } /** - * Method to get the list of missing checksums per pillar in a given collection. + * REST endpoint to get the list of missing checksums for a pillar in a given collection. * - * @param collectionID, the collectionID - * @param pillarID, the ID of the pillar in the collection - * @param pageNumber, the page number for calculating offsets (@see pageSize) - * @param pageSize, the maximum number of results per page. + * @param collectionID, The collection ID. + * @param pillarID, The ID of the pillar in the collection + * @return Returns a {@link HashMap} mapping the given pillar to its missing checksums. */ @GET - @Path("/getMissingChecksumsFileIDs/") + @Path("/getMissingChecksumsFileIDs") @Produces(MediaType.APPLICATION_JSON) - public StreamingOutput getMissingChecksums( + public HashMap> getMissingChecksums( @QueryParam("collectionID") String collectionID, @QueryParam("pillarID") String pillarID, - @QueryParam("pageNumber") - int pageNumber, + @QueryParam("page") + int page, @DefaultValue("100") @QueryParam("pageSize") int pageSize) { - - int firstID = (pageNumber - 1) * pageSize; - - return streamPartFromLatestReport(ReportPart.MISSING_CHECKSUM, collectionID, pillarID, firstID, pageSize); + List streamingOutput = getReportPartForPillar(ReportPart.MISSING_CHECKSUM, collectionID, pillarID, page, pageSize); + return new HashMap<>(Map.of(pillarID, streamingOutput)); } /** - * Method to get the list of obsolete checksums per pillar in a given collection. + * REST endpoint to get the list of obsolete checksums for a pillar in a given collection. * - * @param collectionID, the collectionID - * @param pillarID, the ID of the pillar in the collection - * @param pageNumber, the page number for calculating offsets (@see pageSize) - * @param pageSize, the maximum number of results per page. + * @param collectionID The collection ID. + * @param pillarID The ID of the pillar in the collection + * @return Returns a {@link HashMap} mapping the given pillar to its obsolete checksums. */ @GET - @Path("/getObsoleteChecksumsFileIDs/") + @Path("/getObsoleteChecksumsFileIDs") @Produces(MediaType.APPLICATION_JSON) - public StreamingOutput geObsoleteChecksums( + public HashMap> geObsoleteChecksums( @QueryParam("collectionID") String collectionID, @QueryParam("pillarID") String pillarID, - @QueryParam("pageNumber") - int pageNumber, + @QueryParam("page") + int page, @DefaultValue("100") @QueryParam("pageSize") int pageSize) { - - int firstID = (pageNumber - 1) * pageSize; - - return streamPartFromLatestReport(ReportPart.OBSOLETE_CHECKSUM, collectionID, pillarID, firstID, pageSize); + List streamingOutput = getReportPartForPillar(ReportPart.OBSOLETE_CHECKSUM, collectionID, pillarID, page, pageSize); + return new HashMap<>(Map.of(pillarID, streamingOutput)); } /** - * Method to get the list of present files on a pillar in a given collection. + * REST endpoint that fetches the checksum errors, that are inconsistent, for a given pillar in a collection. * - * @param collectionID, the collectionID from which to return present file list - * @param pillarID, the ID of the pillar in the collection from which to return present file list - * @param pageNumber, the page number for calculating offsets (@see pageSize) - * @param pageSize, the number of checksum errors per page. + * @param collectionID The collectionID from which to return checksum errors + * @param pillarID The ID of the pillar in the collection from which to return checksum errors + * @return Returns a {@link HashMap} mapping the given pillar to the checksum errors that are inconsistent. */ @GET - @Path("/getAllFileIDs/") + @Path("/getChecksumErrorFileIDs") @Produces(MediaType.APPLICATION_JSON) - public StreamingOutput getAllFileIDs( + public HashMap> getChecksumErrors( @QueryParam("collectionID") String collectionID, @QueryParam("pillarID") String pillarID, - @QueryParam("pageNumber") - int pageNumber, + @QueryParam("page") + int page, @DefaultValue("100") @QueryParam("pageSize") int pageSize) { - - int firstID = (pageNumber - 1) * pageSize; - - IntegrityIssueIterator it = model.getFilesOnPillar(pillarID, firstID, pageSize, collectionID); - - if (it != null) { - return JSONStreamingTools.StreamIntegrityIssues(it); - } else { - throw new WebApplicationException( - Response.status(Response.Status.NO_CONTENT).entity("Failed to get missing files from database") - .type(MediaType.TEXT_PLAIN).build()); - } + List streamingOutput = getReportPartForPillar(ReportPart.CHECKSUM_ERROR, collectionID, pillarID, page, pageSize); + return new HashMap<>(Map.of(pillarID, streamingOutput)); } /** * Get the listing of integrity status as a JSON array */ @GET - @Path("/getIntegrityStatus/") + @Path("/getIntegrityStatus") @Produces(MediaType.APPLICATION_JSON) public String getIntegrityStatus( @QueryParam("collectionID") @@ -249,15 +264,16 @@ public String getIntegrityStatus( String pillarName = Objects.requireNonNullElse(SettingsUtils.getPillarName(pillar), "N/A"); PillarType pillarTypeObject = SettingsUtils.getPillarType(pillar); String pillarType = pillarTypeObject != null ? pillarTypeObject.value() : null; - PillarCollectionStat emptyStat = new PillarCollectionStat(pillar, collectionID, pillarName, pillarType, - 0L, 0L, 0L, 0L, 0L, 0L, new Date(0), new Date(0)); + PillarCollectionStat emptyStat = new PillarCollectionStat(pillar, collectionID, pillarName, pillarType, 0L, 0L, 0L, 0L, 0L, + 0L, new Date(0), new Date(0)); stats.put(pillar, emptyStat); } } jg.writeStartArray(); for (PillarCollectionStat stat : stats.values()) { writeIntegrityStatusObject(stat, jg); - log.debug("IntegrityStatus: Wrote pillar name: " + stat.getPillarName() + " to pillar" + stat.getPillarID()); + log.debug(String.format(Locale.ROOT, "IntegrityStatus: Wrote pillar name: '%s' to pillar '%s'", stat.getPillarName(), + stat.getPillarID())); } jg.writeEndArray(); jg.flush(); @@ -266,10 +282,10 @@ public String getIntegrityStatus( } /*** - * Get the current workflows setup as a JSON array + * Get the current workflow's setup as a JSON array */ @GET - @Path("/getWorkflowSetup/") + @Path("/getWorkflowSetup") @Produces(MediaType.APPLICATION_JSON) public String getWorkflowSetup( @QueryParam("collectionID") @@ -296,7 +312,7 @@ public String getWorkflowSetup( * Get the list of possible workflows as a JSON array */ @GET - @Path("/getWorkflowList/") + @Path("/getWorkflowList") @Produces(MediaType.APPLICATION_JSON) public List getWorkflowList( @QueryParam("collectionID") @@ -312,7 +328,7 @@ public List getWorkflowList( * Get the latest integrity report, or an error message telling no such report found. */ @GET - @Path("/getLatestIntegrityReport/") + @Path("/getLatestIntegrityReport") @Produces(MediaType.TEXT_PLAIN) public StreamingOutput getLatestIntegrityReport( @QueryParam("collectionID") @@ -321,9 +337,9 @@ public StreamingOutput getLatestIntegrityReport( try { fullReport = integrityReportProvider.getLatestIntegrityReportReader(collectionID).getFullReport(); } catch (FileNotFoundException e) { - throw new WebApplicationException( - Response.status(Response.Status.NOT_FOUND).entity("No integrity report for collection: " + collectionID + " found!") - .type(MediaType.TEXT_PLAIN).build()); + throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND) + .entity(String.format(Locale.ROOT, "No integrity report for collection: '%s' found!", collectionID)) + .type(MediaType.TEXT_PLAIN).build()); } return output -> { try { @@ -344,7 +360,7 @@ public StreamingOutput getLatestIntegrityReport( * Start a named workflow. */ @POST - @Path("/startWorkflow/") + @Path("/startWorkflow") @Consumes("application/x-www-form-urlencoded") @Produces("text/html") public String startWorkflow( @@ -360,7 +376,7 @@ public String startWorkflow( * Start a named workflow. */ @GET - @Path("/getCollectionInformation/") + @Path("/getCollectionInformation") @Produces(MediaType.APPLICATION_JSON) public String getCollectionInformation( @QueryParam("collectionID") @@ -392,24 +408,100 @@ public String getCollectionInformation( } /** - * Private method to help stream parts from a given ReportPart, collection and pillar. + * Private method to help stream parts from a given {@link ReportPart}, collection and pillar. * - * @param part The part to stream issues from - * @param collectionID The ID of the collection - * @param pillarID The ID of the pillar - * @param firstID Index of the first result - * @param maxLines The maximum number of lines to stream + * @param part The part to stream issues from. + * @param collectionID The ID of the collection. + * @param pillarID The ID of the pillar. + * @return Returns a {@link List} of fileIDs from {@link ReportPart} of the given collection and pillar. + * @throws FileNotFoundException If there is no integrity report for the given {@link ReportPart}. */ - private StreamingOutput streamPartFromLatestReport(ReportPart part, String collectionID, String pillarID, int firstID, int maxLines) { + private List getReportPart(ReportPart part, String collectionID, String pillarID, int page, int pageSize) + throws FileNotFoundException { + List reportPartContent; + int offset = getOffset(page, pageSize); + + IntegrityReportReader reader = integrityReportProvider.getLatestIntegrityReportReader(collectionID); + File reportPart = reader.getReportPart(part.getPartName(), pillarID); + reportPartContent = StreamingTools.filePartToList(reportPart, offset, pageSize); + + return reportPartContent; + } + + /** + * Overloaded method calling {@link RestIntegrityService#getReportPart} but instead of returning an empty list, it will throw a + * {@link WebApplicationException}. + * + * @return Returns either a {@link List} of fileIDs or throws a {@link WebApplicationException}. + */ + private List getReportPartForPillar(ReportPart part, String collectionID, String pillarID, int page, int pageSize) { + List reportPartContent; try { - IntegrityReportReader reader = integrityReportProvider.getLatestIntegrityReportReader(collectionID); - File reportPart = reader.getReportPart(part.getPartName(), pillarID); - return JSONStreamingTools.StreamFileParts(reportPart, firstID, maxLines); + reportPartContent = getReportPart(part, collectionID, pillarID, page, pageSize); } catch (FileNotFoundException e) { throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND) - .entity("No integrity '" + part.getHumanString() + "' report part for collection: " + collectionID + " and pillar: " + - pillarID + " found!").type(MediaType.TEXT_PLAIN).build()); + .entity(String.format(Locale.ROOT, "No integrity '%s' report part for collection: '%s' and pillar: '%s' found!", + part.getHumanString(), collectionID, pillarID)).type(MediaType.TEXT_PLAIN).build()); + } + return reportPartContent; + } + + /** + * Compares the missing fileIDs to that of the missing fileIDs of the given pillar. + *
+ * It is important to notice, that in order for this to work, it expects all the contents of the + * integrity reports to have been sorted using
+ * {@link String#compareTo(String)}. + * + * @param missingOnPillar The list of files that are missing on the pillar in focus. + * @param collectionID The collection ID. + * @param pillar The pillar to compare to. + * @param pageSize The paging size. + * @return Returns a {@link List} containing the files that were also missing on the given pillar. + */ + private List compareMissingFiles(List missingOnPillar, String collectionID, String pillar, int pageSize) { + List batchToCheck; + List agreedMissingFileIDs = new ArrayList<>(); + for (String missingFileID : missingOnPillar) { + try { + batchToCheck = findBatchRecursively(missingFileID, collectionID, pillar, 1, pageSize); + if (batchToCheck.contains(missingFileID)) { + agreedMissingFileIDs.add(missingFileID); + } + } catch (FileNotFoundException ignored) { + break; // If there is no integrity report for the given pillar, we break the for-loop. + } } + + return agreedMissingFileIDs; + } + + /** + * Helper method to recursively find the batch in which the {@code fileID} we're looking for would be found if it exists. + * + * @return A {@link List} of size {@code pageSize} with the files in which the {@code fileID} could be found. + * @throws FileNotFoundException If there's no integrity report, a {@link FileNotFoundException} will be thrown. + */ + private List findBatchRecursively(String fileID, String collectionID, String pillar, int i, int pageSize) + throws FileNotFoundException { + List batchToCheck = getReportPart(ReportPart.MISSING_FILE, collectionID, pillar, i, pageSize); + + if (!batchToCheck.isEmpty()) { + String lastFileInBatch = batchToCheck.get(batchToCheck.size() - 1); + if (fileID.compareTo(lastFileInBatch) > 0) { + // If the last index of the current batch is lexicographically lower than the fileID, then skip to next batch + batchToCheck = findBatchRecursively(fileID, collectionID, pillar, i + 1, pageSize); + } + } + + return batchToCheck; + } + + /** + * Helper method to compute start index. + */ + private int getOffset(int page, int pageSize) { + return (page - 1) * pageSize; } private void writeIntegrityStatusObject(PillarCollectionStat stat, JsonGenerator jg) throws IOException { diff --git a/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/web/JSONStreamingTools.java b/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/web/StreamingTools.java similarity index 64% rename from bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/web/JSONStreamingTools.java rename to bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/web/StreamingTools.java index c1a01c993..e3cf4eeab 100644 --- a/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/web/JSONStreamingTools.java +++ b/bitrepository-integrity-service/src/main/java/org/bitrepository/integrityservice/web/StreamingTools.java @@ -35,19 +35,21 @@ import java.io.FileInputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; /** * Class to handle streaming of different kinds of JSON data */ -public class JSONStreamingTools { - private final static Logger log = LoggerFactory.getLogger(JSONStreamingTools.class); +public class StreamingTools { + private final static Logger log = LoggerFactory.getLogger(StreamingTools.class); /** * Helper method to stream integrity issues as JSON for webservices. * * @param iterator The IntegrityIssueIterator with integrity issues that is to be streamed out. */ - public static StreamingOutput StreamIntegrityIssues(IntegrityIssueIterator iterator) { + public static StreamingOutput streamIntegrityIssues(IntegrityIssueIterator iterator) { final IntegrityIssueIterator it = iterator; return output -> { JsonFactory jf = new JsonFactory(); @@ -69,7 +71,7 @@ public static StreamingOutput StreamIntegrityIssues(IntegrityIssueIterator itera it.close(); } } catch (Exception e) { - log.error("Caught execption when closing IntegrityIssueIterator", e); + log.error("Caught exception when closing IntegrityIssueIterator", e); throw new WebApplicationException(e); } } @@ -82,9 +84,9 @@ public static StreamingOutput StreamIntegrityIssues(IntegrityIssueIterator itera * * @param source The source file * @param offset The number of lines to skip - * @param maxlines The maximum number of lines to output + * @param maxLines The maximum number of lines to output */ - public static StreamingOutput StreamFileParts(File source, int offset, int maxlines) { + public static StreamingOutput streamFileParts(File source, int offset, int maxLines) { final File input = source; return output -> { JsonFactory jf = new JsonFactory(); @@ -99,7 +101,7 @@ public static StreamingOutput StreamFileParts(File source, int offset, int maxli continue; } jg.writeString(line); - if (linesRead - offset >= maxlines) { + if (linesRead - offset >= maxLines) { break; } } @@ -111,4 +113,50 @@ public static StreamingOutput StreamFileParts(File source, int offset, int maxli } }; } + + /** + * Iterates over the elements in an {@link IntegrityIssueIterator} and returns the items as a list. + * + * @param iterator The {@link IntegrityIssueIterator}. + * @return Returns a {@link List }. + */ + public static List iteratorToList(IntegrityIssueIterator iterator) { + List output = new ArrayList<>(); + String issue; + while ((issue = iterator.getNextIntegrityIssue()) != null) { + output.add(issue); + } + iterator.close(); + + return output; + } + + /** + * Helper method to create a {@link List} of all or parts of a files' content. + * @param source The source file + * @param offset The number of lines to skip + * @param maxLines The number of lines to read + * @return Returns a {@link List} of the found content. + */ + public static List filePartToList(File source, int offset, int maxLines) { + List output = new ArrayList<>(); + + try (BufferedReader b = new BufferedReader(new InputStreamReader(new FileInputStream(source), StandardCharsets.UTF_8))) { + int linesRead = 0; + String line; + while ((line = b.readLine()) != null) { + if (linesRead++ < offset) { + continue; + } + output.add(line); + if (linesRead - offset >= maxLines) { + break; + } + } + } catch (Exception e) { + throw new WebApplicationException(e); + } + + return output; + } } diff --git a/bitrepository-webclient/src/main/webapp/bootstrap/css/bootstrap.css b/bitrepository-webclient/src/main/webapp/bootstrap/css/bootstrap.css index 9e7dd809a..4e8b6d5c6 100644 --- a/bitrepository-webclient/src/main/webapp/bootstrap/css/bootstrap.css +++ b/bitrepository-webclient/src/main/webapp/bootstrap/css/bootstrap.css @@ -1982,7 +1982,6 @@ legend + .control-group { } table { - table-layout: fixed; max-width: 100%; background-color: transparent; border-collapse: collapse; @@ -5183,7 +5182,7 @@ input[type="submit"].btn.btn-mini { margin-bottom: 0; text-align: right; background-color: #f5f5f5; - border-top: 1px solid #ddd; + /*border-top: 1px solid #ddd;*/ -webkit-border-radius: 0 0 6px 6px; -moz-border-radius: 0 0 6px 6px; border-radius: 0 0 6px 6px; @@ -6048,4 +6047,4 @@ a.badge:hover { .affix { position: fixed; -} \ No newline at end of file +} diff --git a/bitrepository-webclient/src/main/webapp/css/modal.css b/bitrepository-webclient/src/main/webapp/css/modal.css new file mode 100644 index 000000000..c0d013936 --- /dev/null +++ b/bitrepository-webclient/src/main/webapp/css/modal.css @@ -0,0 +1,102 @@ +.table th { + font-weight: bold; +} + +.modal-table tr th { + border-bottom: 1px solid black; +} + +.modal-table td { + border-bottom: 1px solid #9996; +} + +.table thead th { + vertical-align: bottom; + padding: 15px; +} + +.modal-table-head { + padding: 5px; + position: sticky; + top: 2%; + height: 35px; + background-color: #fff; +} + +.modal-body { + position: relative; + margin-top: 20px; + height: inherit; + max-height: 86%; + padding: 15px; + overflow-y: auto; +} + +.modal-dialog { + position: fixed; + z-index: 1050; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.3); + border-radius: 6px; + outline: 0; + box-shadow: 0 3px 7px rgb(0 0 0 / 30%); + -webkit-box-shadow: 0 3px 7px rgb(0 0 0 / 30%); + -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + background-clip: padding-box; + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + left: 50%; + top: 10%; + bottom: 2%; + transform: translate(-50%, 0); + min-width: 70%; + /*height: 86%;*/ +} + +.fixed-div { + position: fixed; + width: 95%; + height: 6%; + background-color: #fff; + top: 6%; +} + +.search-bar { + width: 33%; + margin: 10px; +} + +.file-id { + color: #000 !important; + cursor: copy !important; + text-decoration: none !important; +} + +.inline-block { + white-space: nowrap; + display: inline-flex; +} + +.current-page-p { + margin: 0 0 0 10px; +} + +.prev-button, .next-button { + display: inline-block; + margin-right: 5px; + margin-left: 5px; + background-color: #4fa7dc; + border: 1px solid #367def; + color: #000; +} + +.prev-button:disabled, .next-button:disabled { + display: inline-block; + margin-right: 5px; + margin-left: 5px; + background-color: rgba(128, 128, 128, 0.5); + border: 1px solid rgba(89, 89, 89, 0.5); + color: rgba(0, 0, 0, 0.5); + cursor: not-allowed; +} + diff --git a/bitrepository-webclient/src/main/webapp/integrity-service.html b/bitrepository-webclient/src/main/webapp/integrity-service.html index 0401e7be1..1887a489f 100644 --- a/bitrepository-webclient/src/main/webapp/integrity-service.html +++ b/bitrepository-webclient/src/main/webapp/integrity-service.html @@ -24,6 +24,7 @@ Bitrepository integrity service + @@ -34,14 +35,14 @@

Integrity service

- - - Integrity information - - - Change collection: - + + + Integrity information + + Change collection: + +
@@ -69,22 +70,22 @@

Integrity service

- - Integrity status - - + + Integrity status + + - +
- + - - - - + + + + @@ -93,50 +94,47 @@

Integrity service

-
Pillar ID Pillar Name Pillar Type Total number of filesNumber of missing filesNumber of missing checksumsNumber of obsolete checksumsNumber of inconsistent checksumsNumber of missing filesNumber of missing checksumsNumber of obsolete checksumsNumber of inconsistent checksums
`; + + // Populate the header of the table. + let header = ``; + header += ``; + header += ``; + header += ``; + + // Color header yellow for the pillar that is in 'focus' + for (let i = 0; i < pillars.length; i++) { + if (pillars[i] === pillarID) { + header += ``; + } else { + header += ``; + } + } + + header += ``; + header += ``; + html += header + `` + + // Populate the file status rows of the table. This only happens if there are more than 0 files. + let idxIncrement = (page - 1) * pageSize; + html += getTableBody(operation, json, idxIncrement); + + html += ``; + html += ``; + html += ``; + html += `
`; + + // Assign html and activate searchbar filtering and copy to clipboard. + $(contentElement).html(html); + activateSearchbar(); + enableCopyToClipboard(); + + // Enable button functionality + if (page > 1) { + $(".prev-button").on("click", () => self.getModal(page - 1)); + } + if (page < maxPages) { + $(".next-button").on("click", () => self.getModal(page + 1)); + } + }).fail(function () { + let html = "
" + html += "Failed to load page"; + html += "
" + $(contentElement).html(html); + }); + }; + + function getTableBody(operation, json, startIdx) { + let html = ``; + + for (let i = 0; i < files.length; i++) { + html += ``; + html += `${startIdx + i + 1}`; + html += `${files[i]}`; + for (let j = 0; j < pillars.length; j++) { + if (!json[pillars[j]].includes(files[i]) || operation === "Total files") { + // File is NOT missing + html += `✓`; + } else { + // File IS missing + html += `x`; + } + } + html += ``; + } + return html; + } + + function activateSearchbar() { + $(".search-bar").on("keyup", function () { + let filter = $(this).val().toUpperCase(); + $("#files-table tr").filter(function () { + $(this).toggle($(this).text().toUpperCase().indexOf(filter) > -1); + }); + noSearchResult(); + }); + } + + function noSearchResult() { + let noResultText = $(".no-result-p"); + let hasResult = false; + $("#files-table tr").each(function () { + if (!$(this).is(":hidden")) { + hasResult = true; + return false; + } + }); + hasResult ? noResultText.hide() : noResultText.show(); + } + + function enableCopyToClipboard() { + $(".file-id").each(function () { + $(this).on("click", function () { + let text = $(this).text(); + navigator.clipboard.writeText(text).then(); + }); + }); + } +}