From c69f90c7de1f06bb4607cdfb11b13e0aa2d3c8b8 Mon Sep 17 00:00:00 2001 From: "kirill.labutin" Date: Thu, 6 Feb 2025 01:29:58 +0300 Subject: [PATCH] Console ui refactor --- pom.xml | 2 +- src/main/java/ru/kirillius/mktbk/App.java | 266 +++++++++--------- src/main/java/ru/kirillius/mktbk/Console.java | 94 +++++++ src/main/resources/icon.ico | Bin 0 -> 76150 bytes 4 files changed, 225 insertions(+), 137 deletions(-) create mode 100644 src/main/java/ru/kirillius/mktbk/Console.java create mode 100644 src/main/resources/icon.ico diff --git a/pom.xml b/pom.xml index 3e16ee4..6d0049e 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ false anything - + src/main/resources/icon.ico ./data ${maven.compiler.source} diff --git a/src/main/java/ru/kirillius/mktbk/App.java b/src/main/java/ru/kirillius/mktbk/App.java index f3d8461..e3d89d5 100644 --- a/src/main/java/ru/kirillius/mktbk/App.java +++ b/src/main/java/ru/kirillius/mktbk/App.java @@ -14,7 +14,7 @@ import java.util.logging.Level; public class App { private static final String LOG_CONTEXT = App.class.getSimpleName(); private List containers; - + private Console console; public static void main(String[] args) throws IOException, MikrotikApiException, InterruptedException { SystemLogger.initializeLogging(Level.INFO, Collections.emptyList()); @@ -22,116 +22,110 @@ public class App { new App(); } - private String cin() { - try { - return reader.readLine(); - } catch (IOException e) { - return ""; - } - } - - private void pause() { - System.out.println("Press any key to continue..."); - cin(); - } - - private BufferedReader reader; - private ContainerMetadata selectContainer() { - System.out.println("Found containers:"); - var i = 0; - for (var container : containers) { - System.out.println(String.valueOf(++i) + ' ' + container); - } - do { - - var index = -1; - - if (containers.size() > 1) { - do { - index = -1; - System.out.print("Select container you want to backup:"); - try { - index = Integer.parseInt(cin()) - 1; - } catch (NumberFormatException e) { - SystemLogger.error("Invalid number", LOG_CONTEXT, e); - } - } while (index >= containers.size() || index < 0); - } - - if (containers.isEmpty()) { - return null; - } - - var container = containers.get(index); - System.out.print("Do you really want to backup container " + container + "? [y/n]"); - - if (cin().equals("y")) { - return container; - } - } while (true); +// System.out.println("Found containers:"); +// var i = 0; +// for (var container : containers) { +// System.out.println(String.valueOf(++i) + ' ' + container); +// } +// do { +// +// var index = -1; +// +// if (containers.size() > 1) { +// do { +// index = -1; +// System.out.print("Select container you want to backup:"); +// try { +// index = Integer.parseInt(cin()) - 1; +// } catch (NumberFormatException e) { +// SystemLogger.error("Invalid number", LOG_CONTEXT, e); +// } +// } while (index >= containers.size() || index < 0); +// } +// +// if (containers.isEmpty()) { +// return null; +// } +// +// var container = containers.get(index); +// System.out.print("Do you really want to backup container " + container + "? [y/n]"); +// +// if (cin().equals("y")) { +// return container; +// } +// } while (true); + return null; } public App() throws IOException, MikrotikApiException, InterruptedException { - try { - reader = new BufferedReader(new InputStreamReader(System.in)); - auth(); - containers = deviceContext.getContainers(); - - ContainerMetadata container; - do { - container = selectContainer(); - if (container == null) { - return; - } - - if (!container.isRunning()) { - SystemLogger.warning("The selected container is not running. There is a limitation that requires the container to be running.", LOG_CONTEXT); - SystemLogger.message("Starting container " + container, LOG_CONTEXT); - deviceContext.getApiConnection().execute("/container/start .id=" + container.getId()); - try { - Thread.sleep(10000L); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - containers = deviceContext.getContainers(); - var id = container.getId(); - container = containers.stream().filter(containerMetadata -> containerMetadata.getId().equals(id)).findFirst().orElse(null); - if (container == null || !container.isRunning()) { - SystemLogger.error("Something went wrong. Unable to start container.", LOG_CONTEXT); - pause(); - } - } - } while (container == null); - - SystemLogger.warning("/!\\ We are going to make a backup. You have to stop all services in this container to prevent data corruption.", LOG_CONTEXT); - System.out.print("Type yes to continue or Ctrl+C to abort: "); - while (!"yes".equals(cin())) ; - - - var layerFile = new File(container.getGuid()); - var outputFile = new File("backup_of_" + container.getComment() + ".tar"); - - - var files = findFiles(container); - downloadFiles(container, files, layerFile); - - - SystemLogger.message("Building docker image " + outputFile.getName(), LOG_CONTEXT); - var builder = new DockerImageBuilder(container); - builder.build(layerFile, outputFile); - SystemLogger.message("Backup is done ", LOG_CONTEXT); - pause(); - - } finally { - if (reader != null) { - reader.close(); - } - if (deviceContext != null) { - deviceContext.close(); - } - } +// try { +// console = new Console(); +// auth(); +// var mode = console.select("What do you want to do?", List.of("Backup containers", "Restore backup", "Exit application")); +// if(mode == 0){ +// selectWhatToBackup(); +// }else if(mode == 1){ +// selectWhatToRestore(); +// } +// +// containers = deviceContext.getContainers(); +// +// +// ContainerMetadata container; +// do { +// container = selectContainer(); +// if (container == null) { +// return; +// } +// +// if (!container.isRunning()) { +// SystemLogger.warning("The selected container is not running. There is a limitation that requires the container to be running.", LOG_CONTEXT); +// SystemLogger.message("Starting container " + container, LOG_CONTEXT); +// deviceContext.getApiConnection().execute("/container/start .id=" + container.getId()); +// try { +// Thread.sleep(10000L); +// } catch (InterruptedException e) { +// throw new RuntimeException(e); +// } +// +// containers = deviceContext.getContainers(); +// var id = container.getId(); +// container = containers.stream().filter(containerMetadata -> containerMetadata.getId().equals(id)).findFirst().orElse(null); +// if (container == null || !container.isRunning()) { +// SystemLogger.error("Something went wrong. Unable to start container.", LOG_CONTEXT); +// pause(); +// } +// } +// } while (container == null); +// +// SystemLogger.warning("/!\\ We are going to make a backup. You have to stop all services in this container to prevent data corruption.", LOG_CONTEXT); +// System.out.print("Type yes to continue or Ctrl+C to abort: "); +// while (!"yes".equals(cin())) ; +// +// +// var layerFile = new File(container.getGuid()); +// var outputFile = new File("backup_of_" + container.getComment() + ".tar"); +// +// +// var files = findFiles(container); +// downloadFiles(container, files, layerFile); +// +// +// SystemLogger.message("Building docker image " + outputFile.getName(), LOG_CONTEXT); +// var builder = new DockerImageBuilder(container); +// builder.build(layerFile, outputFile); +// SystemLogger.message("Backup is done ", LOG_CONTEXT); +// pause(); +// +// } finally { +// if (reader != null) { +// reader.close(); +// } +// if (deviceContext != null) { +// deviceContext.close(); +// } +// } // openSFTP(); @@ -177,7 +171,7 @@ public class App { } catch (IOException e) { status.clear(); SystemLogger.error("Unable to copy file " + file.getPath(), LOG_CONTEXT, e); - pause(); + console.pause(); } } tar.finish(); @@ -202,34 +196,34 @@ public class App { private void auth() { do { - try { - - System.out.print("Enter remote host:"); - var host = cin(); - if (host.trim().isEmpty()) { - throw new RuntimeException("Remote host is empty"); - } - - System.out.print("Enter username:"); - var username = cin(); - if (username.trim().isEmpty()) { - throw new RuntimeException("Username is empty"); - } - System.out.print("Enter password:"); - var password = cin(); - if (password.trim().isEmpty()) { - throw new RuntimeException("Password is empty"); - } - - deviceContext = new DeviceContext(host, username, password); - deviceContext.getApiConnection(); - } catch (Exception e) { - SystemLogger.error("Unable to connect", LOG_CONTEXT, e); - if (deviceContext != null) { - deviceContext.close(); - } - deviceContext = null; - } +// try { +// +// System.out.print("Enter remote host:"); +// var host = cin(); +// if (host.trim().isEmpty()) { +// throw new RuntimeException("Remote host is empty"); +// } +// +// System.out.print("Enter username:"); +// var username = cin(); +// if (username.trim().isEmpty()) { +// throw new RuntimeException("Username is empty"); +// } +// System.out.print("Enter password:"); +// var password = cin(); +// if (password.trim().isEmpty()) { +// throw new RuntimeException("Password is empty"); +// } +// +// deviceContext = new DeviceContext(host, username, password); +// deviceContext.getApiConnection(); +// } catch (Exception e) { +// SystemLogger.error("Unable to connect", LOG_CONTEXT, e); +// if (deviceContext != null) { +// deviceContext.close(); +// } +// deviceContext = null; +// } } while (deviceContext == null); SystemLogger.message("Connected", LOG_CONTEXT); diff --git a/src/main/java/ru/kirillius/mktbk/Console.java b/src/main/java/ru/kirillius/mktbk/Console.java new file mode 100644 index 0000000..79c54e9 --- /dev/null +++ b/src/main/java/ru/kirillius/mktbk/Console.java @@ -0,0 +1,94 @@ +package ru.kirillius.mktbk; + +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; + +public class Console implements Closeable { + private final BufferedReader reader; + + public Console() { + reader = new BufferedReader(new InputStreamReader(System.in)); + } + + public void clear() { + var os = System.getProperty("os.name").toLowerCase(); + if (os.contains("win")) { + try { + new ProcessBuilder("cmd", "/c", "cls").inheritIO().start().waitFor(); + } catch (InterruptedException | IOException e) { + throw new RuntimeException(e); + } + } else { + System.out.print("\033[H\033[2J"); + System.out.flush(); + } + } + + @Override + public void close() throws IOException { + reader.close(); + } + + public String read() { + try { + return reader.readLine(); + } catch (IOException e) { + return ""; + } + } + + public boolean confirm(String question) { + do { + System.out.print(question + " [y/n]"); + var result = read().trim(); + if (result.equals("y")) { + return true; + } else if (result.equals("n")) { + return false; + } + } while (true); + } + + public String prompt(String question, boolean canBeEmpty) { + do { + System.out.print(question + ": "); + var result = read().trim(); + if (!canBeEmpty && result.trim().isEmpty()) { + continue; + } + return result; + } while (true); + } + + public int select(String caption, List values) { + var index = -1; + + if (values.size() > 1) { + do { + clear(); + System.out.println(caption + ":"); + var i = 0; + for (var row : values) { + System.out.println(String.valueOf(++i) + ' ' + row); + } + index = -1; + + try { + index = Integer.parseInt(read()) - 1; + } catch (NumberFormatException ignored) { + } + } while (index >= values.size() || index < 0); + return (index); + } else { + return -1; + } + } + + public void pause() { + System.out.println("Press enter to continue..."); + read(); + } +} diff --git a/src/main/resources/icon.ico b/src/main/resources/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4b75886ccd82b31d87a174d41a8950e9bb7d9fdd GIT binary patch literal 76150 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 literal 0 HcmV?d00001