Skip to content

Commit

Permalink
feat: initial progress bar implementation
Browse files Browse the repository at this point in the history
resolves #193
  • Loading branch information
jeremylong committed Jul 18, 2024
1 parent 2025b6c commit 7388b5d
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 82 deletions.
1 change: 1 addition & 0 deletions vulnz/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter:2.7.18'
}
implementation 'com.diogonunes:JColor:5.5.1'
implementation 'org.jline:jline:3.26.2'
implementation 'commons-io:commons-io:2.16.1'
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
import io.github.jeremylong.vulnz.cli.cache.CacheException;
import io.github.jeremylong.vulnz.cli.cache.CacheProperties;
import io.github.jeremylong.vulnz.cli.model.BasicOutput;
import io.github.jeremylong.vulnz.cli.ui.IProgressMonitor;
import io.github.jeremylong.vulnz.cli.ui.JlineShutdownHook;
import io.github.jeremylong.vulnz.cli.ui.ProgressMonitor;
import org.apache.commons.io.output.CountingOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -110,6 +113,9 @@ public class CveCommand extends AbstractNvdCommand {
@CommandLine.Option(names = {"--cvssV3Severity"}, description = "")
private NvdCveClientBuilder.CvssV3Severity cvssV3Severity;

@CommandLine.Option(names = {"--interactive"}, description = "Displays a progress bar")
private boolean interactive;

@Override
public Integer timedCall() throws Exception {
if (isDebug()) {
Expand Down Expand Up @@ -265,12 +271,16 @@ private Integer processRequest(NvdCveClientBuilder builder, CacheProperties prop
}
}
ZonedDateTime lastModified = null;
int count = 0;
// retrieve from NVD API
try (NvdCveClient api = builder.build()) {
try (NvdCveClient api = builder.build(); IProgressMonitor monitor = new ProgressMonitor(interactive, "NVD")) {
Runtime.getRuntime().addShutdownHook(new JlineShutdownHook());
while (api.hasNext()) {
Collection<DefCveItem> data = api.next();
collectCves(cves, data);
lastModified = api.getLastUpdated();
count += data.size();
monitor.updateProgress("NVD", count, api.getTotalAvailable());
}
} catch (Exception ex) {
LOG.debug("\nERROR", ex);
Expand Down Expand Up @@ -385,9 +395,15 @@ private int processRequest(NvdCveClientBuilder builder) throws IOException {
jsonOut.writeFieldName("cves");
jsonOut.writeStartArray();
BasicOutput output = new BasicOutput();
try (NvdCveClient api = builder.build()) {
int count = 0;
try (NvdCveClient api = builder.build(); IProgressMonitor monitor = new ProgressMonitor(interactive, "NVD")) {
Runtime.getRuntime().addShutdownHook(new JlineShutdownHook());
while (api.hasNext()) {
Collection<DefCveItem> list = api.next();
if (list != null) {
count += list.size();
}
monitor.updateProgress("NVD", count, api.getTotalAvailable());
if (list != null) {
output.setSuccess(true);
output.addCount(list.size());
Expand Down Expand Up @@ -415,7 +431,7 @@ private int processRequest(NvdCveClientBuilder builder) throws IOException {
}
LOG.info(colorize("\nSUCCESS", Attribute.GREEN_TEXT()));
status = 0;
} catch (Exception ex) {
} catch (Throwable ex) {
LOG.error("\nERROR", ex);
}
return status;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) 2024 Jeremy Long. All Rights Reserved.
*/
package io.github.jeremylong.vulnz.cli.ui;

public interface IProgressMonitor extends AutoCloseable {

public void addMonitor(String name);

public void updateProgress(String name, int current, int max);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) 2024 Jeremy Long. All Rights Reserved.
*/
package io.github.jeremylong.vulnz.cli.ui;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.layout.TTLLLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.Layout;
import org.jline.terminal.Terminal;

public class JLineAppender extends AppenderBase<ILoggingEvent> {

private Layout<ILoggingEvent> layout;

public void setLayout(Layout<ILoggingEvent> layout) {
this.layout = layout;
}

@Override
protected void append(ILoggingEvent event) {
Terminal terminal = ProgressMonitor.getTerminal();
if (terminal != null) {
terminal.writer().println(layout.doLayout(event));
terminal.flush();
} else {
if (event.getLevel() == Level.TRACE || event.getLevel() == Level.DEBUG) {
System.err.println(layout.doLayout(event));
} else {
System.out.println(layout.doLayout(event));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) 2024 Jeremy Long. All Rights Reserved.
*/
package io.github.jeremylong.vulnz.cli.ui;

import org.jline.terminal.Terminal;

import java.io.IOException;

public class JlineShutdownHook extends Thread {

public void run() {
try {
ProgressMonitor.closeTerminal();
} catch (IOException e) {
// ignore
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) 2023-2024 Jeremy Long. All Rights Reserved.
*/
package io.github.jeremylong.vulnz.cli.ui;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.jline.utils.AttributedString;
import org.jline.utils.Status;

public class ProgressMonitor implements IProgressMonitor {

boolean enabled;
Map<String, Integer> rows = new HashMap<>();

private static Terminal terminal = null;
private Status status;

static Terminal getTerminal() {
return terminal;
}

@SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
public ProgressMonitor(boolean enabled) throws IOException {

}

@SuppressFBWarnings({"CT_CONSTRUCTOR_THROW", "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD"})
public ProgressMonitor(boolean enabled, String name) throws IOException {
this.enabled = enabled;
if (enabled) {
addMonitor(name);
terminal = TerminalBuilder.terminal();
status = new Status(terminal);
}
}

@Override
public void addMonitor(String name) {
this.rows.put(name, 0);
}

private List<AttributedString> determineStatusBar() {
int maxNameWidth = rows.keySet().stream().mapToInt(String::length).max().orElse(0);
return rows.entrySet().stream().map(entry -> {
String name = entry.getKey();
int percent = entry.getValue();
int remaining = terminal.getWidth();
remaining = Math.min(remaining, 100);
StringBuilder string = new StringBuilder(remaining);
remaining -= maxNameWidth;
string.append(name);
int spaces = maxNameWidth - name.length();
if (spaces > 0) {
string.append(String.join("", Collections.nCopies(spaces, " ")));
}
if (percent >= 100) {
string.append(" complete");
} else {
String spacer = percent < 10 ? " " : "";
string.append(spacer).append(String.format(" %d%% [", percent));
remaining -= 10;
int completed = remaining * percent / 100;
int filler = remaining - completed;
System.out.println("completed: " + completed + " filler: " + filler + " remaining: " + remaining);
string.append(String.join("", Collections.nCopies(completed, "="))).append('>')
.append(String.join("", Collections.nCopies(filler, " "))).append(']');
}
String s = string.toString();
return new AttributedString(s);
}).sorted().collect(Collectors.toList());
}

@Override
public void updateProgress(String name, int current, int max) {
int percent = (int) (current * 100 / max);
rows.put(name, percent);
if (enabled) {

status.update(new ArrayList<AttributedString>());
status.resize();
List<AttributedString> displayedRows = determineStatusBar();
status.update(displayedRows, true);
}
}

@Override
public void close() throws Exception {
if (enabled) {
if (status != null) {
status.close();
}
closeTerminal();
enabled = false;
}
}

static void closeTerminal() throws IOException {
if (terminal != null) {
terminal.close();
terminal = null;
}
}
}
77 changes: 0 additions & 77 deletions vulnz/src/main/java/io/github/jeremylong/vulnz/cli/ui/Screen.java

This file was deleted.

10 changes: 8 additions & 2 deletions vulnz/src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<configuration>
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
<target>System.err</target>

<!-- <appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">-->
<!-- <target>System.err</target>-->
<!-- <layout class="ch.qos.logback.classic.PatternLayout">-->
<!-- <Pattern>%msg%n%throwable</Pattern>-->
<!-- </layout>-->
<!-- </appender>-->
<appender name="STDERR" class="io.github.jeremylong.vulnz.cli.ui.JLineAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%msg%n%throwable</Pattern>
</layout>
Expand Down
Loading

0 comments on commit 7388b5d

Please sign in to comment.