From 1c087fae322a1b07bb7bd554ee10ff473c47c727 Mon Sep 17 00:00:00 2001
From: Fin Reinhard <fin.reinhard@icloud.com>
Date: Tue, 22 Jan 2019 20:51:03 +0000
Subject: [PATCH] Merge branch 'master' into feature/15-archive-view-url

---
 borgbutler-webapp/src/components/views/config/ConfigurationPage.jsx                            |   24 
 borgbutler-webapp/src/components/views/jobs/Job.jsx                                            |   36 
 borgbutler-core/build.gradle                                                                   |    4 
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java                       |    8 
 borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java                        |   50 +
 borgbutler-core/src/main/java/de/micromata/borgbutler/BorgJob.java                             |   12 
 borgbutler-server/src/test/java/de/micromata/borgbutler/server/BorgInstallationTest.java       |   38 +
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/ServerConfiguration.java        |   14 
 borgbutler-core/src/main/java/de/micromata/borgbutler/demo/DemoRepos.java                      |   32 
 borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/JobQueue.java                       |    8 
 borgbutler-webapp/src/components/views/repos/ConfigureRepoPage.jsx                             |  252 +++++++
 borgbutler-core/src/test/java/de/micromata/borgbutler/cache/ArchiveFilelistCacheTest.java      |    2 
 borgbutler-server/build.gradle                                                                 |    2 
 borgbutler-webapp/src/components/general/forms/FormRadioButton.jsx                             |   62 +
 borgbutler-webapp/src/components/views/repos/RepoCard.jsx                                      |    2 
 borgbutler-webapp/src/containers/WebApp.jsx                                                    |   23 
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/VersionRest.java           |    6 
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/SystemInfoRest.java        |   34 +
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/queue/JsonJob.java         |   12 
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgVersion.java                |   54 +
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/BorgRepoConfigsRest.java   |   44 +
 borgbutler-core/src/main/java/de/micromata/borgbutler/BorgQueueExecutor.java                   |    5 
 borgbutler-webapp/src/components/views/repos/RepoListView.jsx                                  |   21 
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/FilesystemBrowserRest.java |   84 ++
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/RunningMode.java                |    4 
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/I18nRest.java              |    6 
 borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractJob.java                    |   32 
 borgbutler-core/src/main/java/de/micromata/borgbutler/config/Configuration.java                |   17 
 borgbutler-core/src/main/java/de/micromata/borgbutler/config/BorgRepoConfig.java               |    9 
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgInstallation.java           |  196 +++++
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ConfigurationRest.java     |   16 
 borgbutler-webapp/src/components/views/config/ConfigurationServerTab.jsx                       |   97 +-
 borgbutler-webapp/src/components/views/repos/RepoConfigPanel.jsx                               |  153 ++++
 borgbutler-webapp/src/components/views/archives/ArchiveView.jsx                                |    4 
 borgbutler-webapp/src/components/general/forms/FormSelect.jsx                                  |    9 
 borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx                           |  202 +++--
 borgbutler-webapp/src/components/general/forms/FormComponents.jsx                              |   20 
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ArchivesRest.java          |   18 
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/JobsRest.java              |   74 +-
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/SystemInfo.java            |   25 
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java             |   23 
 borgbutler-webapp/src/components/views/jobs/JobMonitorPanel.jsx                                |  102 ++
 borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractCommandLineJob.java         |    6 
 borgbutler-server/src/main/resources/log4j.properties                                          |    2 
 borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java                   |   10 
 borgbutler-webapp/src/components/views/jobs/JobQueue.jsx                                       |   58 +
 borgbutler-webapp/src/components/views/jobs/Job.css                                            |    8 
 borgbutler-webapp/src/css/my-style.css                                                         |   17 
 48 files changed, 1,641 insertions(+), 296 deletions(-)

diff --git a/borgbutler-core/build.gradle b/borgbutler-core/build.gradle
index a659115..6e10b34 100644
--- a/borgbutler-core/build.gradle
+++ b/borgbutler-core/build.gradle
@@ -16,8 +16,8 @@
     compile group: 'com.esotericsoftware', name: 'kryo', version: '5.0.0-RC1'
     // Serialization (faster than Java built-in)
 
-    compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.6'
-    compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.6'
+    compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8'
+    compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.8'
 
     compileOnly "org.projectlombok:lombok:1.18.4"
     testCompileOnly "org.projectlombok:lombok:1.18.4"
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java
index f9ed76e..5923dc2 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java
@@ -10,6 +10,7 @@
 import de.micromata.borgbutler.utils.DateUtils;
 import de.micromata.borgbutler.utils.ReplaceUtils;
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.Validate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -35,16 +36,56 @@
         BorgCommand command = new BorgCommand()
                 .setParams("--version")
                 .setDescription("Getting borg version.");
-        JobResult<String> jobResult = getResult(command);
+
+        BorgJob<String> job = new BorgJob<>(command);
+        JobResult<String> jobResult = job.execute();
         if (jobResult == null || jobResult.getStatus() != JobResult.Status.OK) {
             return null;
         }
-        String version = jobResult.getResultObject();
+        String origVersion = jobResult.getResultObject();
+        String version = origVersion;
+        String[] strs = StringUtils.split(origVersion);
+        if (strs != null) {
+            if (!StringUtils.containsIgnoreCase(origVersion, "borg")) {
+                version = "";
+            } else {
+                version = strs[strs.length - 1]; // Work arround: borg returns "borg-macosx64 1.1.8" as version string (command is included).
+            }
+        }
+        if (version.length() == 0 || !Character.isDigit(version.charAt(0))) {
+            log.error("Version string returned by '" + job.getCommandLineAsString() + "' not as expected (not borg?): " + origVersion);
+            return null;
+        }
         log.info("Borg version: " + version);
         return version;
     }
 
     /**
+     * Executes borg init repository.
+     *
+     * @param repoConfig The configuration of the repo config (only repo is required).
+     * @param encryption The encryption value (repokey, repokey-blake2, none, ...).
+     * @return true, if no errors occured, otherwise false.
+     */
+    public static boolean init(BorgRepoConfig repoConfig, String encryption) {
+        BorgCommand command = new BorgCommand()
+                .setRepoConfig(repoConfig)
+                .setCommand("init")
+                //.setParams("--json") // --progress has no effect.
+                .setDescription("Init new repo '" + repoConfig.getDisplayName() + "'.");
+        JobResult<String> jobResult = getResult(command);
+        String result = jobResult != null ? jobResult.getResultObject() : null;
+        // If everything is ok, now String will be returned, result must be blank:
+        if (jobResult == null || jobResult.getStatus() != JobResult.Status.OK || StringUtils.isNotBlank(result)) {
+            log.error("Error while trying to intialize repo '" + repoConfig.getRepo() + "': " + result);
+            return false;
+        }
+        log.error("Error while trying to intialize repo '" + repoConfig.getRepo() + "': " + result);
+        return false;
+    }
+
+
+    /**
      * Executes borg info repository.
      *
      * @param repoConfig
@@ -176,8 +217,9 @@
         // The returned job might be an already queued or running one!
         final ProgressInfo progressInfo = new ProgressInfo()
                 .setMessage("Getting file list...")
-                .setCurrent(0)
-                .setTotal(archive.getStats().getNfiles());
+                .setCurrent(0);
+        if (archive.getStats() != null) // Occurs only for demo repos.
+            progressInfo.setTotal(archive.getStats().getNfiles());
         BorgJob<List<BorgFilesystemItem>> job = BorgQueueExecutor.getInstance().execute(new BorgJob<List<BorgFilesystemItem>>(command) {
             @Override
             public void processStdOutLine(String line, int level) {
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgJob.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgJob.java
index 221d7f8..da71b43 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgJob.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgJob.java
@@ -52,7 +52,12 @@
         if (command == null) {
             return null;
         }
-        CommandLine commandLine = new CommandLine(ConfigurationHandler.getConfiguration().getBorgCommand());
+        String borgCommand = ConfigurationHandler.getConfiguration().getBorgCommand();
+        if (StringUtils.isBlank(borgCommand)) {
+            log.error("Can't run empty borg command.");
+            return null;
+        }
+        CommandLine commandLine = new CommandLine(borgCommand);
         commandLine.addArgument(command.getCommand());
         if (command.getParams() != null) {
             for (String param : command.getParams()) {
@@ -101,7 +106,7 @@
 
     @Override
     public JobResult<String> execute() {
-        if (DemoRepos.isDemo(command.getRepoConfig().getRepo())) {
+        if (command.getRepoConfig() != null && DemoRepos.isDemo(command.getRepoConfig().getRepo())) {
             return DemoRepos.execute(this);
         }
         return super.execute();
@@ -125,6 +130,9 @@
         if (progressInfo != null) {
             clone.setProgressInfo(progressInfo.clone());
         }
+        clone.setCreateTime(getCreateTime());
+        clone.setStartTime(getStartTime());
+        clone.setStopTime(getStopTime());
         return clone;
     }
 
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgQueueExecutor.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgQueueExecutor.java
index 86f2743..ac0eb44 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgQueueExecutor.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgQueueExecutor.java
@@ -78,16 +78,17 @@
      * For displaying purposes.
      *
      * @param repoConfig
+     * @param oldJobs If false, the running and queued jobs are returned, otherwise the done ones.
      * @return A list of all jobs of the queue (as copies).
      */
-    public List<BorgJob<?>> getJobListCopy(BorgRepoConfig repoConfig) {
+    public List<BorgJob<?>> getJobListCopy(BorgRepoConfig repoConfig, boolean oldJobs) {
         JobQueue<String> origQueue = getQueue(repoConfig);
         List<BorgJob<?>> jobList = new ArrayList<>();
         if (origQueue == null) {
             return jobList;
         }
         synchronized (origQueue) {
-            Iterator<AbstractJob<String>> it = origQueue.getQueueIterator();
+            Iterator<AbstractJob<String>> it = oldJobs ? origQueue.getOldJobsIterator() : origQueue.getQueueIterator();
             while (it.hasNext()) {
                 AbstractJob<String> origJob = it.next();
                 if (!(origJob instanceof BorgJob)) {
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java
index 2c45d9c..a25e288 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java
@@ -124,9 +124,15 @@
         this.repoCacheAccess.clear();
     }
 
+    public void clearRepoCacheAccess(String repo) {
+        if (this.repoCacheAccess.get(repo) != null) {
+            log.info("Clearing repository cache '" + repo + "'...");
+            this.repoCacheAccess.remove(repo);
+        }
+    }
+
     public void clearRepoCacheAccess(Repository repository) {
-        log.info("Clearing repository cache '" + repository.getName() + "'...");
-        this.repoCacheAccess.remove(repository.getName());
+        clearRepoCacheAccess(repository.getName());
     }
 
     /**
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/config/BorgRepoConfig.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/config/BorgRepoConfig.java
index 0ffeecd..52ac43a 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/config/BorgRepoConfig.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/config/BorgRepoConfig.java
@@ -1,6 +1,5 @@
 package de.micromata.borgbutler.config;
 
-import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.Getter;
 import lombok.Setter;
 import org.apache.commons.lang3.StringUtils;
@@ -29,7 +28,6 @@
     private String passwordCommand;
     @Getter
     @Setter
-    @JsonIgnore
     private String id;
 
     public String[] getEnvironmentVariables() {
@@ -51,4 +49,11 @@
         if (StringUtils.isBlank(value)) return;
         list.add(variable + "=" + value);
     }
+
+   public void copyFrom(BorgRepoConfig other) {
+        this.displayName = other.displayName;
+        this.repo = other.repo;
+        this.passphrase = other.passphrase;
+        this.passwordCommand = other.passwordCommand;
+   }
 }
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/config/Configuration.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/config/Configuration.java
index d06e30b..5ba55a6 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/config/Configuration.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/config/Configuration.java
@@ -24,9 +24,13 @@
     @JsonIgnore
     @Setter(AccessLevel.PACKAGE)
     private File workingDir;
-
+    /**
+     * The path of the borg command to use.
+     */
     @Getter
-    private String borgCommand = "borg";
+    @Setter
+    private String borgCommand;
+
     /**
      * Default is 100 MB (approximately).
      */
@@ -85,13 +89,8 @@
     }
 
     public List<BorgRepoConfig> getRepoConfigs() {
-        if (!ConfigurationHandler.getConfiguration().isShowDemoRepos()) {
-            return repoConfigs;
-        }
-        List<BorgRepoConfig> result = new ArrayList<>();
-        result.addAll(repoConfigs);
-        DemoRepos.addDemoRepos(result);
-        return result;
+        DemoRepos.handleDemoRepos(repoConfigs);
+        return repoConfigs;
     }
 
     List<BorgRepoConfig> _getRepoConfigs() {
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/demo/DemoRepos.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/demo/DemoRepos.java
index cc0bd61..7ba4fa5 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/demo/DemoRepos.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/demo/DemoRepos.java
@@ -18,9 +18,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Scanner;
+import java.util.*;
 
 public class DemoRepos {
     private enum Type {FAST, SLOW, VERY_SLOW}
@@ -36,13 +34,37 @@
      *
      * @param repositoryList
      */
-    public static void addDemoRepos(List<BorgRepoConfig> repositoryList) {
+    public static void handleDemoRepos(List<BorgRepoConfig> repositoryList) {
         if (!ConfigurationHandler.getConfiguration().isShowDemoRepos()) {
+            // Remove any demo repository if exist due to former settings:
+            Iterator<BorgRepoConfig> it = repositoryList.iterator();
+            while(it.hasNext()) {
+                BorgRepoConfig repoConfig = it.next();
+                if (!StringUtils.startsWith(repoConfig.getRepo(), DEMO_IDENTIFIER)) {
+                    continue;
+                }
+                it.remove();
+            }
             return;
         }
         init();
         for (BorgRepoConfig repo : demoRepos) {
-            repositoryList.add(repo);
+            if (!repositoryList.contains(repo))
+                repositoryList.add(repo);
+        }
+        // Remove duplicate entries (produced by former versions of BorgButler:
+        Set<String> set = new HashSet<>();
+        Iterator<BorgRepoConfig> it = repositoryList.iterator();
+        while(it.hasNext()) {
+            BorgRepoConfig repoConfig = it.next();
+            if (!StringUtils.startsWith(repoConfig.getRepo(), DEMO_IDENTIFIER)) {
+                continue;
+            }
+            if (set.contains(repoConfig.getRepo())) {
+                it.remove();
+            } else {
+                set.add(repoConfig.getRepo());
+            }
         }
     }
 
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractCommandLineJob.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractCommandLineJob.java
index 0c800e6..fa2c938 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractCommandLineJob.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractCommandLineJob.java
@@ -52,6 +52,9 @@
         if (commandLine == null) {
             commandLine = buildCommandLine();
         }
+        if (commandLine == null) {
+            return null;
+        }
         if (commandLineAsString == null) {
             commandLineAsString = commandLine.getExecutable() + " " + StringUtils.join(commandLine.getArguments(), " ");
         }
@@ -61,6 +64,9 @@
     @Override
     public JobResult<String> execute() {
         getCommandLineAsString();
+        if (commandLine == null) {
+            return null;
+        }
         DefaultExecutor executor = new DefaultExecutor();
         if (workingDirectory != null) {
             executor.setWorkingDirectory(workingDirectory);
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractJob.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractJob.java
index df97791..fc8d054 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractJob.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractJob.java
@@ -1,11 +1,13 @@
 package de.micromata.borgbutler.jobs;
 
+import de.micromata.borgbutler.utils.DateUtils;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.Setter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.time.LocalDateTime;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 
@@ -18,7 +20,6 @@
     @Setter
     private boolean cancellationRequested;
     @Getter
-    @Setter(AccessLevel.PROTECTED)
     private Status status;
     @Getter
     @Setter
@@ -29,17 +30,36 @@
     @Getter
     @Setter(AccessLevel.PROTECTED)
     private long uniqueJobNumber = -1;
+    @Getter
+    @Setter
+    private String createTime;
+    @Getter
+    @Setter
+    private String startTime;
+    @Getter
+    @Setter
+    private String stopTime;
+
+    protected AbstractJob<T> setStatus(Status status) {
+        if (status == Status.RUNNING && this.status != Status.RUNNING) {
+            this.startTime = DateUtils.format(LocalDateTime.now());
+        } else if (status != Status.RUNNING && this.status == Status.RUNNING) {
+            this.stopTime = DateUtils.format(LocalDateTime.now());
+        }
+        this.status = status;
+        return this;
+    }
 
     public void cancel() {
         if (this.getStatus() == Status.QUEUED) {
-            this.status = Status.CANCELLED;
+            setStatus(Status.CANCELLED);
         }
         this.cancellationRequested = true;
         cancelRunningProcess();
     }
 
     protected void setCancelled() {
-        this.status = Status.CANCELLED;
+        setStatus(Status.CANCELLED);
     }
 
     /**
@@ -74,7 +94,7 @@
         if (this.status != Status.RUNNING) {
             logger.error("Internal error, illegal state! You shouldn't set the job status to FAILED if not in status RUNNING: " + this.status);
         }
-        this.status = Status.FAILED;
+        setStatus(Status.FAILED);
     }
 
     /**
@@ -96,4 +116,8 @@
      * @return
      */
     public abstract Object getId();
+
+    protected AbstractJob() {
+        this.createTime = DateUtils.format(LocalDateTime.now());
+    }
 }
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/JobQueue.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/JobQueue.java
index e040676..439acff 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/JobQueue.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/JobQueue.java
@@ -9,7 +9,7 @@
 import java.util.concurrent.Executors;
 
 public class JobQueue<T> {
-    private static final int MAX_OLD_JOBS_SIZE = 50;
+    private static final int MAX_OLD_JOBS_SIZE = 10;
     private static long jobSequence = 0;
     private Logger log = LoggerFactory.getLogger(JobQueue.class);
     private List<AbstractJob<T>> queue = new ArrayList<>();
@@ -43,6 +43,12 @@
         }
     }
 
+    public Iterator<AbstractJob<T>> getOldJobsIterator() {
+        synchronized (oldJobs) {
+            return Collections.unmodifiableList(oldJobs).iterator();
+        }
+    }
+
     /**
      * Searches only for queued jobs (not done jobs).
      *
diff --git a/borgbutler-core/src/test/java/de/micromata/borgbutler/cache/ArchiveFilelistCacheTest.java b/borgbutler-core/src/test/java/de/micromata/borgbutler/cache/ArchiveFilelistCacheTest.java
index 7568e77..1397be9 100644
--- a/borgbutler-core/src/test/java/de/micromata/borgbutler/cache/ArchiveFilelistCacheTest.java
+++ b/borgbutler-core/src/test/java/de/micromata/borgbutler/cache/ArchiveFilelistCacheTest.java
@@ -63,7 +63,7 @@
     @Test
     void cleanUpMaximumSizeTest() throws Exception {
         List<BorgFilesystemItem> list = createList(1000000);
-        ArchiveFilelistCache cache = new ArchiveFilelistCache(new File("out"), 3);
+        ArchiveFilelistCache cache = new ArchiveFilelistCache(new File("out"), 5);
         cache.removeAllCacheFiles();
         BorgRepoConfig repoConfig = new BorgRepoConfig();
         repoConfig.setRepo("repo");
diff --git a/borgbutler-server/build.gradle b/borgbutler-server/build.gradle
index 1cf9b43..99af8c8 100644
--- a/borgbutler-server/build.gradle
+++ b/borgbutler-server/build.gradle
@@ -24,8 +24,6 @@
     compile group: 'org.glassfish.jersey.media', name: 'jersey-media-multipart', version: '2.27'
     compile group: 'org.glassfish.jersey.media', name: 'jersey-media-json-jackson', version: '2.27'
     compile group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: '2.27'
-    compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.6'
-    compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.6'
     compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1'
     compile group: 'javax.xml.ws', name: 'jaxws-api', version: '2.3.1'
     compile group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.25'
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgInstallation.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgInstallation.java
new file mode 100644
index 0000000..4f8196d
--- /dev/null
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgInstallation.java
@@ -0,0 +1,196 @@
+package de.micromata.borgbutler.server;
+
+import de.micromata.borgbutler.BorgCommands;
+import de.micromata.borgbutler.config.Configuration;
+import de.micromata.borgbutler.config.ConfigurationHandler;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.config.CookieSpecs;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+
+public class BorgInstallation {
+    private Logger log = LoggerFactory.getLogger(BorgInstallation.class);
+    private static final BorgInstallation instance = new BorgInstallation();
+
+    public static BorgInstallation getInstance() {
+        return instance;
+    }
+
+    public void initialize() {
+        Configuration configuration = ConfigurationHandler.getConfiguration();
+        if (StringUtils.isNotBlank(configuration.getBorgCommand())) {
+            if (version(configuration)) {
+                return;
+            }
+        }
+        initialize(getBinary(RunningMode.getOSType()));
+        if (version(configuration)) {
+            return;
+        }
+        BorgVersion borgVersion = ServerConfiguration.get()._getBorgVersion();
+        log.warn("No working borg version found. Please configure a borg version with minimal version '" + borgVersion.getMinimumRequiredBorgVersion() + "'.");
+    }
+
+    /**
+     * Configures a new borg configuration if modifications was done.
+     * @param oldVersion The old version before this current modification.
+     */
+    public void configure(BorgVersion oldVersion) {
+        Configuration configuration = ConfigurationHandler.getConfiguration();
+        BorgVersion newVersion = ServerConfiguration.get()._getBorgVersion();
+        boolean borgBinaryChanged = !StringUtils.equals(oldVersion.getBorgBinary(), newVersion.getBorgBinary());
+        boolean manualBorgCommand = "manual".equals(newVersion.getBorgBinary());
+        if (manualBorgCommand) {
+            boolean borgCommandChanged = !StringUtils.equals(oldVersion.getBorgCommand(), newVersion.getBorgCommand());
+            if (borgCommandChanged) {
+                configuration.setBorgCommand(newVersion.getBorgCommand());
+                version(configuration);
+            } else {
+                newVersion.copyFrom(oldVersion); // Restore all old settings.
+            }
+        } else {
+            if (borgBinaryChanged) {
+                newVersion.setBorgCommand(oldVersion.getBorgCommand()); // Don't modify borg command, if not manual.
+                initialize(getBinary(newVersion.getBorgBinary()));
+            } else {
+                newVersion.copyFrom(oldVersion); // Restore all old settings.
+            }
+        }
+    }
+
+    private boolean initialize(String[] binary) {
+        if (binary == null) {
+            return false;
+        }
+        Configuration configuration = ConfigurationHandler.getConfiguration();
+        File file = download(binary);
+        BorgVersion borgVersion = ServerConfiguration.get()._getBorgVersion();
+        if (file != null) {
+            configuration.setBorgCommand(file.getAbsolutePath());
+            borgVersion.setBorgCommand(file.getAbsolutePath());
+            borgVersion.setBorgBinary(binary[0]);
+        }
+        return version(configuration);
+    }
+
+    private boolean version(Configuration configuration) {
+        String versionString = BorgCommands.version();
+        boolean versionOK = false;
+        BorgVersion borgVersion = ServerConfiguration.get()._getBorgVersion();
+        String msg = null;
+        if (versionString != null) {
+            borgVersion.setVersion(versionString);
+            int cmp = versionString.compareTo(borgVersion.getMinimumRequiredBorgVersion());
+            if (cmp < 0) {
+                msg = "Found borg version '" + versionString + "' is less than minimum required version '" + borgVersion.getMinimumRequiredBorgVersion() + "'.";
+                log.info(msg);
+            } else {
+                versionOK = true;
+                msg = "Found borg '" + configuration.getBorgCommand() + "', version: " + versionString + " (equals to or newer than '" + borgVersion.getMinimumRequiredBorgVersion()
+                        + "', OK).";
+                log.info(msg);
+            }
+        } else {
+            msg = "Couldn't execute borg command '" + configuration.getBorgCommand() + "'.";
+        }
+        borgVersion.setVersionOK(versionOK);
+        borgVersion.setStatusMessage(msg);
+        return versionOK;
+    }
+
+    private String[] getBinary(RunningMode.OSType osType) {
+        String os = null;
+        switch (osType) {
+            case MAC_OS:
+                os = "mac";
+                break;
+            case LINUX:
+                os = "linux64";
+                break;
+            case FREEBSD:
+                os = "freebsd64";
+                break;
+        }
+        return getBinary(os);
+    }
+
+    private String[] getBinary(String os) {
+        if (os == null) {
+            return null;
+        }
+        BorgVersion borgVersion = ServerConfiguration.get()._getBorgVersion();
+        for (String[] binary : borgVersion.getBorgBinaries()) {
+            if (binary[0].contains(os)) {
+                return binary;
+            }
+        }
+        return null;
+    }
+
+    File download(RunningMode.OSType osType) {
+        String[] binary = getBinary(osType);
+        if (binary == null) {
+            log.info("Can't download binary (no binary found for OS '" + osType + "'.");
+            return null;
+        }
+        return download(binary);
+    }
+
+    private File download(String[] binary) {
+        File file = getBinaryFile(binary);
+        if (file.exists()) {
+            // File already downloaded, nothing to do.
+            return file;
+        }
+        BorgVersion borgVersion = ServerConfiguration.get()._getBorgVersion();
+        String url = borgVersion.getBinariesDownloadUrl() + getDownloadFilename(binary);
+        log.info("Trying to download borg binary '" + binary[0] + "' (" + binary[1] + ") from url: " + url + "...");
+        HttpClientBuilder builder = HttpClients.custom()
+                .setDefaultRequestConfig(RequestConfig.custom()
+                        .setCookieSpec(CookieSpecs.STANDARD).build());
+        try (CloseableHttpClient httpClient = builder.build()) {
+            HttpGet getRequest = new HttpGet(url);
+
+            HttpResponse response = httpClient.execute(getRequest);
+
+            if (response.getStatusLine().getStatusCode() != 200) {
+                throw new RuntimeException("Failed : HTTP error code : "
+                        + response.getStatusLine().getStatusCode());
+            }
+            FileUtils.copyInputStreamToFile(response.getEntity().getContent(), file);
+            log.info("Downloaded: " + file.getAbsolutePath());
+            file.setExecutable(true, false);
+            return file;
+        } catch (IOException ex) {
+            log.error(ex.getMessage(), ex);
+            return null;
+        }
+    }
+
+    private File getBinaryFile(String[] binary) {
+        File dir = new File(ConfigurationHandler.getInstance().getWorkingDir(), "bin");
+        if (!dir.exists()) {
+            log.info("Creating binary directory: " + dir.getAbsolutePath());
+            dir.mkdirs();
+        }
+        BorgVersion borgVersion = ServerConfiguration.get()._getBorgVersion();
+        return new File(dir, getDownloadFilename(binary) + "-" + borgVersion.getBinariesDownloadVersion());
+    }
+
+    private String getDownloadFilename(String[] binary) {
+        return "borg-" + binary[0];
+    }
+
+    private BorgInstallation() {
+    }
+}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgVersion.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgVersion.java
new file mode 100644
index 0000000..2f856d4
--- /dev/null
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgVersion.java
@@ -0,0 +1,54 @@
+package de.micromata.borgbutler.server;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.Setter;
+
+public class BorgVersion {
+    @Getter
+    private String binariesDownloadVersion = "1.1.8";
+    @Getter
+    private String binariesDownloadUrl = "https://github.com/borgbackup/borg/releases/download/" + binariesDownloadVersion + "/";
+    @Getter
+    private String[][] borgBinaries = {
+            {"freebsd64", "FreeBSD 64"},
+            {"linux32", "Linux 32"},
+            {"linux64", "Linux 64"},
+            {"macosx64", "MacOS X 64"}};
+
+    @Getter
+    private String minimumRequiredBorgVersion = "1.1.8";
+
+    /**
+     * One of the values "macosx64", "linux64" etc. for using a binary provided by BorgButler or null / "manual" for
+     * using a manual installed borg version.
+     */
+    @Getter
+    @Setter(AccessLevel.PACKAGE)
+    private String borgBinary;
+    /**
+     * The path of the borg command to use.
+     */
+    @Getter
+    @Setter
+    private String borgCommand;
+
+    @Getter
+    @Setter(AccessLevel.PACKAGE)
+    private boolean versionOK = false;
+    @Getter
+    @Setter(AccessLevel.PACKAGE)
+    private String version;
+    @Getter
+    @Setter(AccessLevel.PACKAGE)
+    private String statusMessage;
+
+    public BorgVersion copyFrom(BorgVersion other) {
+        this.borgCommand = other.borgCommand;
+        this.borgBinary = other.borgBinary;
+        this.versionOK = other.versionOK;
+        this.version = other.version;
+        this.statusMessage = other.statusMessage;
+        return this;
+    }
+}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java
index 07f1b63..93f77ce 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java
@@ -14,6 +14,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.awt.*;
 import java.io.*;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
@@ -81,7 +82,11 @@
                     return;
                 }
             }
-            RunningMode.setServerType(RunningMode.ServerType.SERVER);
+            if (Desktop.isDesktopSupported()) {
+                RunningMode.setServerType(RunningMode.ServerType.DESKTOP);
+            } else {
+                RunningMode.setServerType(RunningMode.ServerType.SERVER);
+            }
             RunningMode.logMode();
             Runtime.getRuntime().addShutdownHook(new Thread() {
                 @Override
@@ -91,6 +96,7 @@
             });
 
             JettyServer server = startUp();
+            BorgInstallation.getInstance().initialize();
             if (!line.hasOption('q')) {
 
                 try {
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/RunningMode.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/RunningMode.java
index a3940ef..3da4739 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/RunningMode.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/RunningMode.java
@@ -16,7 +16,7 @@
 
     public enum UserManagement {SINGLE}
 
-    public enum OSType {MAC_OS, WINDOWS, LINUX, OTHER}
+    public enum OSType {MAC_OS, WINDOWS, LINUX, FREEBSD, OTHER}
 
     private static boolean running;
     private static File baseDir;
@@ -49,6 +49,8 @@
                 osType = OSType.WINDOWS;
             } else if (osTypeString.toLowerCase().contains("linux")) {
                 osType = OSType.LINUX;
+            } else if (osTypeString.toLowerCase().contains("freebsd")) {
+                osType = OSType.FREEBSD;
             } else {
                 osType = OSType.OTHER;
             }
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/ServerConfiguration.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/ServerConfiguration.java
index ce67240..8c91bf2 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/ServerConfiguration.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/ServerConfiguration.java
@@ -18,6 +18,7 @@
 
     private int port = WEBSERVER_PORT_DEFAULT;
     private boolean webDevelopmentMode = WEB_DEVELOPMENT_MODE_PREF_DEFAULT;
+    private BorgVersion borgVersion = new BorgVersion();
     @JsonProperty
     public String getCacheDir() {
         return ButlerCache.getInstance().getCacheDir().getAbsolutePath();
@@ -31,6 +32,17 @@
         return SUPPORTED_LANGUAGES;
     }
 
+    /**
+     * @return a clone of this.borgVersion.
+     */
+    public BorgVersion getBorgVersion() {
+        return new BorgVersion().copyFrom(borgVersion);
+    }
+
+    BorgVersion _getBorgVersion() {
+        return this.borgVersion;
+    }
+
     public static String getApplicationHome() {
         if (applicationHome == null) {
             applicationHome = System.getProperty("applicationHome");
@@ -65,5 +77,7 @@
         super.copyFrom(other);
         this.port = other.port;
         this.webDevelopmentMode = other.webDevelopmentMode;
+        this.borgVersion.copyFrom(other.borgVersion);
+        this.setBorgCommand(this.borgVersion.getBorgCommand());
     }
 }
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ArchivesRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ArchivesRest.java
index 582715e..abf41ea 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ArchivesRest.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ArchivesRest.java
@@ -33,16 +33,16 @@
 public class ArchivesRest {
     private static Logger log = LoggerFactory.getLogger(ArchivesRest.class);
 
-    @GET
-    @Produces(MediaType.APPLICATION_JSON)
     /**
      *
-     * @param repo Name of repository ({@link Repository#getName()}.
+     * @param repoName Name of repository ({@link Repository#getName()}.
      * @param archiveId Id or name of archive.
      * @param prettyPrinter If true then the json output will be in pretty format.
      * @return Repository (including list of archives) as json string.
      * @see JsonUtils#toJson(Object, boolean)
      */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
     public String getArchive(@QueryParam("repo") String repoName,
                              @QueryParam("archiveId") String archiveId, @QueryParam("force") boolean force,
                              @QueryParam("prettyPrinter") boolean prettyPrinter) {
@@ -53,9 +53,6 @@
         return JsonUtils.toJson(archive, prettyPrinter);
     }
 
-    @GET
-    @Path("filelist")
-    @Produces(MediaType.APPLICATION_JSON)
     /**
      *
      * @param archiveId Id or name of archive.
@@ -69,6 +66,9 @@
      * @return Repository (including list of archives) as json string.
      * @see JsonUtils#toJson(Object, boolean)
      */
+    @GET
+    @Path("filelist")
+    @Produces(MediaType.APPLICATION_JSON)
     public String getArchiveFileList(@QueryParam("archiveId") String archiveId,
                                      @QueryParam("searchString") String searchString,
                                      @QueryParam("mode") String mode,
@@ -105,13 +105,13 @@
         return JsonUtils.toJson(items, prettyPrinter);
     }
 
-    @GET
-    @Path("/restore")
-    @Produces(MediaType.APPLICATION_OCTET_STREAM)
     /**
      * @param archiveId
      * @param fileNumber The fileNumber of the file or directory in the archive served by BorgButler's
      */
+    @GET
+    @Path("/restore")
+    @Produces(MediaType.APPLICATION_OCTET_STREAM)
     public Response restore(@QueryParam("archiveId") String archiveId, @QueryParam("fileNumber") int fileNumber) {
         log.info("Requesting file #" + fileNumber + " of archive '" + archiveId + "'.");
         FileSystemFilter filter = new FileSystemFilter().setFileNumber(fileNumber);
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/BorgRepoConfigsRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/BorgRepoConfigsRest.java
new file mode 100644
index 0000000..b2c0875
--- /dev/null
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/BorgRepoConfigsRest.java
@@ -0,0 +1,44 @@
+package de.micromata.borgbutler.server.rest;
+
+import de.micromata.borgbutler.cache.ButlerCache;
+import de.micromata.borgbutler.config.BorgRepoConfig;
+import de.micromata.borgbutler.config.ConfigurationHandler;
+import de.micromata.borgbutler.json.JsonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+
+@Path("/repoConfig")
+public class BorgRepoConfigsRest {
+    private static Logger log = LoggerFactory.getLogger(BorgRepoConfigsRest.class);
+
+    /**
+     * @param id            id or name of repo.
+     * @param prettyPrinter If true then the json output will be in pretty format.
+     * @return {@link BorgRepoConfig} as json string.
+     * @see JsonUtils#toJson(Object, boolean)
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getRepoConfig(@QueryParam("id") String id, @QueryParam("prettyPrinter") boolean prettyPrinter) {
+        BorgRepoConfig repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(id);
+        return JsonUtils.toJson(repoConfig, prettyPrinter);
+    }
+
+    @POST
+    @Produces(MediaType.TEXT_PLAIN)
+    public void setRepoConfig(String jsonConfig) {
+        BorgRepoConfig newRepoConfig = JsonUtils.fromJson(BorgRepoConfig.class, jsonConfig);
+        BorgRepoConfig repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(newRepoConfig.getId());
+        if (repoConfig == null) {
+            log.error("Can't find repo config '" + newRepoConfig.getId() + "'. Can't save new settings.");
+            return;
+        }
+        ButlerCache.getInstance().clearRepoCacheAccess(repoConfig.getRepo());
+        ButlerCache.getInstance().clearRepoCacheAccess(newRepoConfig.getRepo());
+        repoConfig.copyFrom(newRepoConfig);
+        ConfigurationHandler.getInstance().save();
+    }
+}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ConfigurationRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ConfigurationRest.java
index 06abb81..a0c5c08 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ConfigurationRest.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ConfigurationRest.java
@@ -3,6 +3,8 @@
 import de.micromata.borgbutler.cache.ButlerCache;
 import de.micromata.borgbutler.config.ConfigurationHandler;
 import de.micromata.borgbutler.json.JsonUtils;
+import de.micromata.borgbutler.server.BorgInstallation;
+import de.micromata.borgbutler.server.BorgVersion;
 import de.micromata.borgbutler.server.ServerConfiguration;
 import de.micromata.borgbutler.server.user.UserData;
 import de.micromata.borgbutler.server.user.UserManager;
@@ -17,14 +19,14 @@
 public class ConfigurationRest {
     private Logger log = LoggerFactory.getLogger(ConfigurationRest.class);
 
-    @GET
-    @Path("config")
-    @Produces(MediaType.APPLICATION_JSON)
     /**
      *
      * @param prettyPrinter If true then the json output will be in pretty format.
      * @see JsonUtils#toJson(Object, boolean)
      */
+    @GET
+    @Path("config")
+    @Produces(MediaType.APPLICATION_JSON)
     public String getConfig(@QueryParam("prettyPrinter") boolean prettyPrinter) {
         String json = JsonUtils.toJson(ServerConfiguration.get(), prettyPrinter);
         return json;
@@ -37,18 +39,20 @@
         ConfigurationHandler configurationHandler = ConfigurationHandler.getInstance();
         ServerConfiguration config = (ServerConfiguration)configurationHandler.getConfiguration();
         ServerConfiguration srcConfig = JsonUtils.fromJson(ServerConfiguration.class, jsonConfig);
+        BorgVersion oldBorgVersion = config.getBorgVersion();
         config.copyFrom(srcConfig);
+        BorgInstallation.getInstance().configure(oldBorgVersion);
         configurationHandler.save();
     }
 
-    @GET
-    @Path("user")
-    @Produces(MediaType.APPLICATION_JSON)
     /**
      *
      * @param prettyPrinter If true then the json output will be in pretty format.
      * @see JsonUtils#toJson(Object, boolean)
      */
+    @GET
+    @Path("user")
+    @Produces(MediaType.APPLICATION_JSON)
     public String getUser(@QueryParam("prettyPrinter") boolean prettyPrinter) {
         UserData user = RestUtils.getUser();
         String json = JsonUtils.toJson(user, prettyPrinter);
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/FilesystemBrowserRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/FilesystemBrowserRest.java
new file mode 100644
index 0000000..150e913
--- /dev/null
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/FilesystemBrowserRest.java
@@ -0,0 +1,84 @@
+package de.micromata.borgbutler.server.rest;
+
+import de.micromata.borgbutler.json.JsonUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.swing.*;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import java.io.File;
+
+@Path("/files")
+public class FilesystemBrowserRest {
+    private Logger log = LoggerFactory.getLogger(FilesystemBrowserRest.class);
+
+    /**
+     * Opens a directory browser or file browser on the desktop app and returns the chosen dir/file. Works only if Browser and Desktop app are running
+     * on the same host.
+     *
+     * @param current The current path of file. If not given the directory/file browser starts with the last used directory or user.home.
+     * @return The chosen directory path (absolute path).
+     */
+    @GET
+    @Path("/browse-local-filesystem")
+    @Produces(MediaType.APPLICATION_JSON)
+    public String browseLocalFilesystem(@Context HttpServletRequest requestContext, @QueryParam("current") String current) {
+        String msg = RestUtils.checkLocalDesktopAvailable(requestContext);
+        if (msg != null) {
+            log.info(msg);
+            return msg;
+        }
+        JFileChooser chooser;
+        if (StringUtils.isNotBlank(current)) {
+            chooser = new JFileChooser(current);
+        } else {
+            chooser = new JFileChooser();
+        }
+        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+        synchronized (FilesystemBrowserRest.class) {
+            if (frame == null) {
+                frame = new JFrame("BorgButler");
+                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+                JLabel label = new JLabel("Hello World");
+                frame.getContentPane().add(label);
+                frame.pack();
+            }
+        }
+        frame.setVisible(true);
+        frame.setAlwaysOnTop(true);
+        int returnCode = chooser.showDialog(frame, "Choose");
+        frame.setVisible(false);
+        frame.setAlwaysOnTop(false);
+        File file = null;
+        if (returnCode == JFileChooser.APPROVE_OPTION) {
+            file = chooser.getSelectedFile();
+        }
+        String filename = file != null ? JsonUtils.toJson(file.getAbsolutePath()) : "";
+        String result = "{\"directory\":" + filename + "}";
+        return result;
+    }
+
+    /**
+     * @return OK, if the local desktop services such as open file browser etc. are available.
+     */
+    @GET
+    @Path("/local-fileservices-available")
+    @Produces(MediaType.TEXT_PLAIN)
+    public String browseLocalFilesystem(@Context HttpServletRequest requestContext) {
+        String msg = RestUtils.checkLocalDesktopAvailable(requestContext);
+        if (msg != null) {
+            log.info(msg);
+            return msg;
+        }
+        return "OK";
+    }
+
+    private static JFrame frame;
+}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/I18nRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/I18nRest.java
index 7e060d6..b17c855 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/I18nRest.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/I18nRest.java
@@ -20,9 +20,6 @@
 public class I18nRest {
     private Logger log = LoggerFactory.getLogger(I18nRest.class);
 
-    @GET
-    @Path("list")
-    @Produces(MediaType.APPLICATION_JSON)
     /**
      *
      * @param requestContext For detecting the user's client locale.
@@ -31,6 +28,9 @@
      * @param prettyPrinter If true then the json output will be in pretty format.
      * @see JsonUtils#toJson(Object, boolean)
      */
+    @GET
+    @Path("list")
+    @Produces(MediaType.APPLICATION_JSON)
     public String getList(@Context HttpServletRequest requestContext, @QueryParam("prettyPrinter") boolean prettyPrinter,
                           @QueryParam("keysOnly") boolean keysOnly, @QueryParam("locale") String locale) {
         Locale localeObject;
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/JobsRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/JobsRest.java
index 14f1525..be744f0 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/JobsRest.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/JobsRest.java
@@ -26,10 +26,8 @@
 public class JobsRest {
     private static Logger log = LoggerFactory.getLogger(JobsRest.class);
 
-    private static List<JsonJobQueue> testList;
+    private static List<JsonJobQueue> testList, oldJobsTestList;
 
-    @GET
-    @Produces(MediaType.APPLICATION_JSON)
     /**
      * @param repo If given, only the job queue of the given repo will be returned.
      * @param testMode If true, then a test job list is created.
@@ -37,12 +35,16 @@
      * @return Job queues as json string.
      * @see JsonUtils#toJson(Object, boolean)
      */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
     public String getJobs(@QueryParam("repo") String repo,
                           @QueryParam("testMode") boolean testMode,
+                          @QueryParam("oldJobs") boolean oldJobs,
                           @QueryParam("prettyPrinter") boolean prettyPrinter) {
+        log.debug("getJobs repo=" + repo + ", oldJobs=" + oldJobs);
         if (testMode) {
             // Return dynamic test queue:
-            return returnTestList(prettyPrinter);
+            return returnTestList(oldJobs, prettyPrinter);
         }
         boolean validRepo = false;
         if (StringUtils.isNotBlank(repo) && !"null".equals(repo) && !"undefined".equals(repo)) {
@@ -51,13 +53,13 @@
         BorgQueueExecutor borgQueueExecutor = BorgQueueExecutor.getInstance();
         List<JsonJobQueue> queueList = new ArrayList<>();
         if (validRepo) { // Get only the queue of the given repo:
-            JsonJobQueue queue = getQueue(repo);
+            JsonJobQueue queue = getQueue(repo, oldJobs);
             if (queue != null) {
                 queueList.add(queue);
             }
         } else { // Get all the queues (of all repos).
             for (String rep : borgQueueExecutor.getRepos()) {
-                JsonJobQueue queue = getQueue(rep);
+                JsonJobQueue queue = getQueue(rep, oldJobs);
                 if (queue != null) {
                     queueList.add(queue);
                 }
@@ -66,24 +68,13 @@
         return JsonUtils.toJson(queueList, prettyPrinter);
     }
 
-    @GET
-    @Produces(MediaType.APPLICATION_JSON)
-    @Path("/statistics")
-    /**
-     * @return The total number of jobs queued or running (and other statistics): {@link de.micromata.borgbutler.BorgQueueStatistics}.
-     * @see JsonUtils#toJson(Object, boolean)
-     */
-    public String getStatistics() {
-        return JsonUtils.toJson(BorgQueueExecutor.getInstance().getStatistics());
-    }
-
-    private JsonJobQueue getQueue(String repo) {
+    private JsonJobQueue getQueue(String repo, boolean oldJobs) {
         BorgQueueExecutor borgQueueExecutor = BorgQueueExecutor.getInstance();
         BorgRepoConfig repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(repo);
         if (repoConfig == null) {
             return null;
         }
-        List<BorgJob<?>> borgJobList = borgQueueExecutor.getJobListCopy(repoConfig);
+        List<BorgJob<?>> borgJobList = borgQueueExecutor.getJobListCopy(repoConfig, oldJobs);
         if (CollectionUtils.isEmpty(borgJobList))
             return null;
         JsonJobQueue queue = new JsonJobQueue().setRepo(repoConfig.getDisplayName());
@@ -95,11 +86,11 @@
         return queue;
     }
 
-    @Path("/cancel")
-    @GET
     /**
      * @param uniqueJobNumberString The id of the job to cancel.
      */
+    @Path("/cancel")
+    @GET
     public void cancelJob(@QueryParam("uniqueJobNumber") String uniqueJobNumberString) {
         Long uniqueJobNumber = null;
         try {
@@ -114,28 +105,31 @@
     /**
      * Only for test purposes and development.
      *
+     * @param oldJobs
      * @param prettyPrinter
      * @return
      */
-    private String returnTestList(boolean prettyPrinter) {
-        if (testList == null) {
-            testList = new ArrayList<>();
+    private String returnTestList(boolean oldJobs, boolean prettyPrinter) {
+        List<JsonJobQueue> list = oldJobs ? oldJobsTestList : testList;
+        if (list == null) {
+            list = new ArrayList<>();
             long uniqueJobNumber = 100000;
             JsonJobQueue queue = new JsonJobQueue().setRepo("My Computer");
-            addTestJob(queue, "info", "my-macbook", 0, 2342)
-                    .setUniqueJobNumber(uniqueJobNumber++);
-            addTestJob(queue, "list", "my-macbook", -1, -1)
-                    .setUniqueJobNumber(uniqueJobNumber++);
-            testList.add(queue);
+            addTestJob(queue, "info", "my-macbook", 0, 2342, uniqueJobNumber++, oldJobs);
+            addTestJob(queue, "list", "my-macbook", -1, -1, uniqueJobNumber++, oldJobs);
+            list.add(queue);
 
             queue = new JsonJobQueue().setRepo("My Server");
-            addTestJob(queue, "list", "my-server", 0, 1135821)
-                    .setUniqueJobNumber(uniqueJobNumber++);
-            addTestJob(queue, "info", "my-server", -1, -1)
-                    .setUniqueJobNumber(uniqueJobNumber++);
-            testList.add(queue);
-        } else {
-            for (JsonJobQueue jobQueue : testList) {
+            addTestJob(queue, "list", "my-server", 0, 1135821, uniqueJobNumber++, oldJobs);
+            addTestJob(queue, "info", "my-server", -1, -1, uniqueJobNumber++, oldJobs);
+            list.add(queue);
+            if (oldJobs) {
+                oldJobsTestList = list;
+            } else {
+                testList = list;
+            }
+        } else if (!oldJobs) {
+            for (JsonJobQueue jobQueue : list) {
                 for (JsonJob job : jobQueue.getJobs()) {
                     if (job.getStatus() != AbstractJob.Status.RUNNING) continue;
                     long current = job.getProgressInfo().getCurrent();
@@ -158,7 +152,7 @@
                 }
             }
         }
-        return JsonUtils.toJson(testList, prettyPrinter);
+        return JsonUtils.toJson(list, prettyPrinter);
     }
 
     /**
@@ -171,7 +165,7 @@
      * @param total
      * @return
      */
-    private JsonJob addTestJob(JsonJobQueue queue, String operation, String host, long current, long total) {
+    private JsonJob addTestJob(JsonJobQueue queue, String operation, String host, long current, long total, long uniqueNumber, boolean oldJobs) {
         ProgressInfo progressInfo = new ProgressInfo()
                 .setCurrent(current)
                 .setTotal(total);
@@ -196,6 +190,10 @@
         if (queue.getJobs() == null) {
             queue.setJobs(new ArrayList<>());
         }
+        job.setUniqueJobNumber(uniqueNumber);
+        if (oldJobs) {
+            job.setStatus(uniqueNumber % 2 == 0 ? AbstractJob.Status.CANCELLED : AbstractJob.Status.DONE);
+        }
         queue.getJobs().add(job);
         return job;
     }
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java
index 3152f1c..337dfb6 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java
@@ -19,15 +19,15 @@
 public class ReposRest {
     private static Logger log = LoggerFactory.getLogger(ReposRest.class);
 
-    @GET
-    @Path("list")
-    @Produces(MediaType.APPLICATION_JSON)
     /**
      *
      * @param prettyPrinter If true then the json output will be in pretty format.
      * @return A list of repositories of type {@link BorgRepository}.
      * @see JsonUtils#toJson(Object, boolean)
      */
+    @GET
+    @Path("list")
+    @Produces(MediaType.APPLICATION_JSON)
     public String getList(@QueryParam("prettyPrinter") boolean prettyPrinter) {
         List<Repository> repositories = ButlerCache.getInstance().getAllRepositories();
         if (CollectionUtils.isEmpty(repositories)) {
@@ -36,32 +36,31 @@
         return JsonUtils.toJson(repositories, prettyPrinter);
     }
 
-    @GET
-    @Path("repo")
-    @Produces(MediaType.APPLICATION_JSON)
     /**
      *
      * @param id id or name of repo.
-     * @param force If true, a reload of all repositories is forced.
      * @param prettyPrinter If true then the json output will be in pretty format.
-     * @return Repository (without list of archives) as json string.
+     * @return {@link Repository} (without list of archives) as json string.
      * @see JsonUtils#toJson(Object, boolean)
      */
+    @GET
+    @Path("repo")
+    @Produces(MediaType.APPLICATION_JSON)
     public String getRepo(@QueryParam("id") String id, @QueryParam("prettyPrinter") boolean prettyPrinter) {
         Repository repository = ButlerCache.getInstance().getRepository(id);
         return JsonUtils.toJson(repository, prettyPrinter);
     }
 
-    @GET
-    @Path("repoArchiveList")
-    @Produces(MediaType.APPLICATION_JSON)
     /**
      *
      * @param id id or name of repo.
      * @param prettyPrinter If true then the json output will be in pretty format.
-     * @return Repository (including list of archives) as json string.
+     * @return {@link Repository} (including list of archives) as json string.
      * @see JsonUtils#toJson(Object, boolean)
      */
+    @GET
+    @Path("repoArchiveList")
+    @Produces(MediaType.APPLICATION_JSON)
     public String getRepoArchiveList(@QueryParam("id") String id, @QueryParam("force") boolean force,
                                      @QueryParam("prettyPrinter") boolean prettyPrinter) {
         if (force) {
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/SystemInfo.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/SystemInfo.java
new file mode 100644
index 0000000..eaf6495
--- /dev/null
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/SystemInfo.java
@@ -0,0 +1,25 @@
+package de.micromata.borgbutler.server.rest;
+
+import de.micromata.borgbutler.BorgQueueStatistics;
+import de.micromata.borgbutler.server.BorgVersion;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Statistics of all the job queues, especially the number of total queued and running jobs.
+ * This is used e. g. by the client for showing a badge near to the menu entry "job monitor" with the number
+ * of Jobs in the queues.
+ */
+public class SystemInfo {
+    @Getter
+    @Setter
+    private BorgQueueStatistics queueStatistics;
+
+    @Getter
+    @Setter
+    private boolean configurationOK;
+
+    @Getter
+    @Setter
+    private BorgVersion borgVersion;
+}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/SystemInfoRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/SystemInfoRest.java
new file mode 100644
index 0000000..ce585cf
--- /dev/null
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/SystemInfoRest.java
@@ -0,0 +1,34 @@
+package de.micromata.borgbutler.server.rest;
+
+import de.micromata.borgbutler.BorgQueueExecutor;
+import de.micromata.borgbutler.json.JsonUtils;
+import de.micromata.borgbutler.server.BorgVersion;
+import de.micromata.borgbutler.server.ServerConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+@Path("/system")
+public class SystemInfoRest {
+    private static Logger log = LoggerFactory.getLogger(SystemInfoRest.class);
+
+    /**
+     * @return The total number of jobs queued or running (and other statistics): {@link de.micromata.borgbutler.BorgQueueStatistics}.
+     * @see JsonUtils#toJson(Object, boolean)
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("info")
+    public String getStatistics() {
+        BorgVersion borgVersion = ServerConfiguration.get().getBorgVersion();
+        SystemInfo systemInfonfo = new SystemInfo()
+                .setQueueStatistics(BorgQueueExecutor.getInstance().getStatistics())
+                .setConfigurationOK(borgVersion.isVersionOK())
+                .setBorgVersion(borgVersion);
+        return JsonUtils.toJson(systemInfonfo);
+    }
+}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/VersionRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/VersionRest.java
index efd277f..82f033f 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/VersionRest.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/VersionRest.java
@@ -22,15 +22,15 @@
 public class VersionRest {
     private Logger log = LoggerFactory.getLogger(VersionRest.class);
 
-    @GET
-    @Path("version")
-    @Produces(MediaType.APPLICATION_JSON)
     /**
      *
      * @param requestContext For detecting the user's client locale.
      * @param prettyPrinter If true then the json output will be in pretty format.
      * @see JsonUtils#toJson(Object, boolean)
      */
+    @GET
+    @Path("version")
+    @Produces(MediaType.APPLICATION_JSON)
     public String getVersion(@Context HttpServletRequest requestContext, @QueryParam("prettyPrinter") boolean prettyPrinter) {
         UserData user = RestUtils.getUser();
         String language = Languages.asString(user.getLocale());
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/queue/JsonJob.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/queue/JsonJob.java
index 119aa9a..0169269 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/queue/JsonJob.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/queue/JsonJob.java
@@ -38,6 +38,15 @@
     @Getter
     @Setter
     private String[] environmentVariables;
+    @Getter
+    @Setter
+    private String createTime;
+    @Getter
+    @Setter
+    private String startTime;
+    @Getter
+    @Setter
+    private String stopTime;
 
     public JsonJob() {
     }
@@ -55,6 +64,9 @@
         this.commandLineAsString = borgJob.getCommandLineAsString();
         this.description = borgJob.getDescription();
         environmentVariables = borgJob.getCommand().getRepoConfig().getEnvironmentVariables();
+        this.createTime = borgJob.getCreateTime();
+        this.startTime = borgJob.getStartTime();
+        this.stopTime = borgJob.getStopTime();
     }
 
     /**
diff --git a/borgbutler-server/src/main/resources/log4j.properties b/borgbutler-server/src/main/resources/log4j.properties
index 141c0c5..03a0e14 100644
--- a/borgbutler-server/src/main/resources/log4j.properties
+++ b/borgbutler-server/src/main/resources/log4j.properties
@@ -14,7 +14,7 @@
 
 log4j.appender.file=org.apache.log4j.RollingFileAppender
 
-log4j.appender.file.File=merlin.log
+log4j.appender.file.File=borgbutler.log
 log4j.appender.file.MaxFileSize=10MB
 log4j.appender.file.MaxBackupIndex=5
 log4j.appender.file.layout=org.apache.log4j.PatternLayout
diff --git a/borgbutler-server/src/test/java/de/micromata/borgbutler/server/BorgInstallationTest.java b/borgbutler-server/src/test/java/de/micromata/borgbutler/server/BorgInstallationTest.java
new file mode 100644
index 0000000..dc97e98
--- /dev/null
+++ b/borgbutler-server/src/test/java/de/micromata/borgbutler/server/BorgInstallationTest.java
@@ -0,0 +1,38 @@
+package de.micromata.borgbutler.server;
+
+import de.micromata.borgbutler.config.ConfigurationHandler;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class BorgInstallationTest {
+    private Logger log = LoggerFactory.getLogger(BorgInstallationTest.class);
+
+    @Test
+    void foo() throws Exception {
+        ConfigurationHandler.getConfiguration().setBorgCommand(null);
+        BorgInstallation borgInstallation = BorgInstallation.getInstance();
+        borgInstallation.initialize();
+        ConfigurationHandler.getConfiguration().setBorgCommand("borg");
+        borgInstallation.initialize();
+    }
+
+    @Test
+    void downloadTest() {
+        String version = new BorgVersion().getBinariesDownloadVersion();
+        checkDownload(RunningMode.OSType.LINUX, "borg-linux64-" + version);
+        checkDownload(RunningMode.OSType.MAC_OS, "borg-macosx64-" + version);
+        checkDownload(RunningMode.OSType.FREEBSD, "borg-freebsd64-" + version);
+        assertNull(BorgInstallation.getInstance().download(RunningMode.OSType.WINDOWS));
+    }
+
+    private void checkDownload(RunningMode.OSType osType, String expectedName) {
+        File file = BorgInstallation.getInstance().download(osType);
+        assertEquals(expectedName, file.getName());
+        assertTrue(file.canExecute());
+    }
+}
diff --git a/borgbutler-webapp/src/components/general/forms/FormComponents.jsx b/borgbutler-webapp/src/components/general/forms/FormComponents.jsx
index 4d8f4d0..32a84ff 100644
--- a/borgbutler-webapp/src/components/general/forms/FormComponents.jsx
+++ b/borgbutler-webapp/src/components/general/forms/FormComponents.jsx
@@ -3,8 +3,9 @@
 import classNames from 'classnames';
 import {FormFeedback, Input, UncontrolledTooltip} from 'reactstrap';
 import {FormCheckbox} from "./FormCheckbox";
+import {FormRadioButton} from "./FormRadioButton";
 import {FormButton} from "./FormButton";
-import {FormSelect, FormOption} from "./FormSelect";
+import {FormOption, FormSelect} from "./FormSelect";
 import {revisedRandId} from "../../../utilities/global";
 import I18n from "../translation/I18n";
 
@@ -80,10 +81,14 @@
         </UncontrolledTooltip>;
     }
     const {fieldLength, className, hint, hintPlacement, id, ...other} = props;
+    let myClassName = className;
+    if (fieldLength > 0) {
+        myClassName = classNames(`col-sm-${props.fieldLength}`, className);
+    }
     return (
         <React.Fragment>
             <Input id={targetId}
-                   className={classNames(`col-sm-${props.fieldLength}`, className)}
+                   className={myClassName}
                    {...other}
             />
             {tooltip}
@@ -113,7 +118,6 @@
     name: '',
     hint: null,
     hintPlacement: 'top',
-    fieldLength: 10,
     value: '',
     min: null,
     max: null,
@@ -158,9 +162,9 @@
 
 
 const FormLabelField = (props) => {
-    const forId = props.children.props.id || props.htmlFor || revisedRandId();
+    const forId = props.children.props.id || props.children.props.name || props.htmlFor || revisedRandId();
     return (
-        <FormGroup>
+        <FormGroup className={props.className}>
             <FormLabel length={props.labelLength} htmlFor={forId}>
                 {props.label}
             </FormLabel>
@@ -173,6 +177,7 @@
 
 FormLabelField.propTypes = {
     id: PropTypes.string,
+    className: PropTypes.string,
     htmlFor: PropTypes.string,
     validationMessage: PropTypes.string,
     labelLength: PropTypes.number,
@@ -184,6 +189,7 @@
 
 FormLabelField.defaultProps = {
     id: null,
+    className: null,
     htmlFor: null,
     validationMessage: null,
     labelLength: 2,
@@ -202,6 +208,7 @@
             label={props.label}
             hint={props.hint}
             validationState={props.validationState}
+            className={props.className}
         >
             <FormInput
                 id={props.id}
@@ -224,6 +231,7 @@
 FormLabelInputField.propTypes = {
     id: PropTypes.string,
     label: PropTypes.node,
+    className: PropTypes.node,
     labelLength: PropTypes.number,
     fieldLength: PropTypes.number,
     hint: PropTypes.string,
@@ -241,6 +249,7 @@
 
 FormLabelInputField.defaultProps = {
     id: null,
+    className: null,
     label: '',
     labelLength: 2,
     fieldLength: 10,
@@ -291,6 +300,7 @@
     FormSelect,
     FormOption,
     FormCheckbox,
+    FormRadioButton,
     FormLabelInputField,
     FormFieldset,
     FormButton,
diff --git a/borgbutler-webapp/src/components/general/forms/FormRadioButton.jsx b/borgbutler-webapp/src/components/general/forms/FormRadioButton.jsx
new file mode 100644
index 0000000..e92f6e5
--- /dev/null
+++ b/borgbutler-webapp/src/components/general/forms/FormRadioButton.jsx
@@ -0,0 +1,62 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {UncontrolledTooltip} from 'reactstrap';
+import {revisedRandId} from "../../../utilities/global";
+import classNames from 'classnames';
+import I18n from "../translation/I18n";
+
+class FormRadioButton extends React.Component {
+
+    _id = this.props.id || revisedRandId();
+
+    render() {
+        const {id, className, labelKey, label, hint, hintKey, ...other} = this.props;
+        let tooltip = null;
+        let hintId = null;
+        if (hint || hintKey) {
+            hintId = `hint-${this._id}`;
+            tooltip =
+                <UncontrolledTooltip placement="right" target={hintId}>
+                    {hintKey ? <I18n name={hintKey}/> : hint}
+                </UncontrolledTooltip>;
+        }
+        let labelNode = <label
+            className={'custom-control-label'}
+            htmlFor={this._id}>
+            {labelKey ? <I18n name={labelKey}/> : this.props.label}
+        </label>;
+        return (
+            <React.Fragment>
+                <div className="custom-control custom-radio custom-control-inline" id={hintId}>
+                    <input type="radio"
+                           id={this._id}
+                           className={classNames('custom-control-input', className)}
+                           {...other}
+                    />
+                    {labelNode}
+                </div>
+                {tooltip}
+            </React.Fragment>
+        );
+    }
+}
+
+FormRadioButton.propTypes = {
+    id: PropTypes.string,
+    name: PropTypes.string,
+    checked: PropTypes.bool,
+    onChange: PropTypes.func,
+    hint: PropTypes.string,
+    label: PropTypes.node,
+    labelKey: PropTypes.string
+};
+
+FormRadioButton.defaultProps = {
+    checked: false,
+    onChange: null
+};
+
+
+export {
+    FormRadioButton
+};
diff --git a/borgbutler-webapp/src/components/general/forms/FormSelect.jsx b/borgbutler-webapp/src/components/general/forms/FormSelect.jsx
index 6b0bd21..906fcae 100644
--- a/borgbutler-webapp/src/components/general/forms/FormSelect.jsx
+++ b/borgbutler-webapp/src/components/general/forms/FormSelect.jsx
@@ -12,11 +12,15 @@
             {props.hint}
         </UncontrolledTooltip>;
     }
-    const {className, hint, hintPlacement, id, ...other} = props;
+    const {fieldLength, className, hint, hintPlacement, id, ...other} = props;
+    let myClassName = className;
+    if (fieldLength > 0) {
+        myClassName = classNames(`col-sm-${props.fieldLength}`, className);
+    }
     return (
         <React.Fragment>
             <select id={targetId}
-                    className={classNames('custom-select form-control form-control-sm mr-1', className)}
+                    className={classNames('custom-select form-control form-control-sm mr-1', myClassName)}
                     {...other}
             >
                 {props.children}
@@ -31,6 +35,7 @@
     value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
     name: PropTypes.string,
     onChange: PropTypes.func,
+    fieldLength: PropTypes.number,
     hint: PropTypes.string,
     hintPlacement: PropTypes.oneOf(['right', 'top']),
     children: PropTypes.node,
diff --git a/borgbutler-webapp/src/components/views/archives/ArchiveView.jsx b/borgbutler-webapp/src/components/views/archives/ArchiveView.jsx
index 3a32ffc..8a2a060 100644
--- a/borgbutler-webapp/src/components/views/archives/ArchiveView.jsx
+++ b/borgbutler-webapp/src/components/views/archives/ArchiveView.jsx
@@ -72,7 +72,9 @@
             />;
         } else if (this.state.archive) {
             pageHeader = <React.Fragment>
-                <Link to={`/repoArchives/${this.state.repoId}`}> {archive.repoDisplayName}</Link> - {archive.name}
+                <Link to={'/repos'}> Repositories</Link> -
+                <Link
+                    to={`/repoArchives/${this.state.repoId}/${archive.repoDisplayName}`}> {archive.repoDisplayName}</Link> - {archive.name}
                 <div
                     className={'btn btn-outline-primary refresh-button-right'}
                     onClick={this.toggleModal}
diff --git a/borgbutler-webapp/src/components/views/config/ConfigurationPage.jsx b/borgbutler-webapp/src/components/views/config/ConfigurationPage.jsx
index 3a26b4d..715baf8 100644
--- a/borgbutler-webapp/src/components/views/config/ConfigurationPage.jsx
+++ b/borgbutler-webapp/src/components/views/config/ConfigurationPage.jsx
@@ -8,6 +8,7 @@
 import ConfigAccountTab from "./ConfigurationAccountTab";
 import ConfigServerTab from "./ConfigurationServerTab";
 import LoadingOverlay from '../../general/loading/LoadingOverlay';
+import ConfirmModal from '../../general/modal/ConfirmModal';
 
 class ConfigurationPage
     extends React
@@ -20,11 +21,13 @@
         this.state = {
             activeTab: '1',
             loading: false,
-            reload: false
+            reload: false,
+            confirmModal: false
         };
 
         this.onSave = this.onSave.bind(this);
         this.onCancel = this.onCancel.bind(this);
+        this.toggleModal = this.toggleModal.bind(this);
     }
 
     toggleTab = tab => () => {
@@ -58,6 +61,12 @@
         this.setReload();
     }
 
+    toggleModal() {
+        this.setState({
+            confirmModal: !this.state.confirmModal
+        })
+    }
+
     render() {
         // https://codepen.io/_arpy/pen/xYoyPW
         if (this.state.reload) {
@@ -65,6 +74,17 @@
         }
         return (
             <React.Fragment>
+                <ConfirmModal
+                    onConfirm={ConfigServerTab.clearAllCaches}
+                    title={'Are you sure?'}
+                    toggle={this.toggleModal}
+                    open={this.state.confirmModal}
+                >
+                    Do you really want to clear all caches? All Archive file lists and caches for repo and archive
+                    information will be cleared.
+                    <br/>
+                    This is a safe option but it may take some time to re-fill the caches (on demand) again.
+                </ConfirmModal>
                 <PageHeader><I18n name={'configuration'}/></PageHeader>
                 <Nav tabs>
                     <NavLink
@@ -92,6 +112,8 @@
                 </TabContent>
                 <FormGroup>
                     <FormField length={12}>
+                        <FormButton id={'clearAllCaches'} onClick={this.toggleModal}> Clear all caches
+                        </FormButton>
                         <FormButton onClick={this.onCancel}
                                     hintKey="configuration.cancel.hint"><I18n name={'common.cancel'}/>
                         </FormButton>
diff --git a/borgbutler-webapp/src/components/views/config/ConfigurationServerTab.jsx b/borgbutler-webapp/src/components/views/config/ConfigurationServerTab.jsx
index 84f0e2d..eff23c4 100644
--- a/borgbutler-webapp/src/components/views/config/ConfigurationServerTab.jsx
+++ b/borgbutler-webapp/src/components/views/config/ConfigurationServerTab.jsx
@@ -1,10 +1,20 @@
 import React from 'react';
-import {FormButton, FormCheckbox, FormLabelField, FormLabelInputField} from '../../general/forms/FormComponents';
+import {Alert} from 'reactstrap';
+import {
+    FormCheckbox,
+    FormField,
+    FormGroup,
+    FormInput,
+    FormLabel,
+    FormLabelField,
+    FormLabelInputField,
+    FormOption,
+    FormSelect
+} from '../../general/forms/FormComponents';
 import {getRestServiceUrl} from '../../../utilities/global';
 import I18n from '../../general/translation/I18n';
 import ErrorAlertGenericRestFailure from '../../general/ErrorAlertGenericRestFailure';
 import Loading from '../../general/Loading';
-import ConfirmModal from '../../general/modal/ConfirmModal';
 
 class ConfigServerTab extends React.Component {
     loadConfig = () => {
@@ -25,6 +35,8 @@
             .then((data) => {
                 this.setState({
                     loading: false,
+                    borgBinary: data.borgVersion.borgBinary,
+                    borgCommand: data.borgVersion.borgCommand,
                     ...data
                 })
             })
@@ -48,13 +60,13 @@
             showDemoRepos: true,
             maxArchiveContentCacheCapacityMb: 100,
             redirect: false,
-            confirmModal: false
+            borgCommand: null,
+            borgBinary: null
         };
 
         this.handleTextChange = this.handleTextChange.bind(this);
         this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
         this.loadConfig = this.loadConfig.bind(this);
-        this.toggleModal = this.toggleModal.bind(this);
     }
 
     componentDidMount() {
@@ -73,9 +85,13 @@
     save() {
         var config = {
             port: this.state.port,
-            maxArchiveContentCacheCapacityMb : this.state.maxArchiveContentCacheCapacityMb,
+            maxArchiveContentCacheCapacityMb: this.state.maxArchiveContentCacheCapacityMb,
             webDevelopmentMode: this.state.webDevelopmentMode,
-            showDemoRepos: this.state.showDemoRepos
+            showDemoRepos: this.state.showDemoRepos,
+            borgVersion: {
+                borgCommand: this.state.borgCommand,
+                borgBinary: this.state.borgBinary
+            }
         };
         return fetch(getRestServiceUrl("configuration/config"), {
             method: 'POST',
@@ -96,12 +112,6 @@
         })
     }
 
-    toggleModal() {
-        this.setState({
-            confirmModal: !this.state.confirmModal
-        })
-    }
-
     render() {
         if (this.state.loading) {
             return <Loading/>;
@@ -110,30 +120,47 @@
         if (this.state.failed) {
             return <ErrorAlertGenericRestFailure handleClick={this.loadConfig}/>;
         }
-
+        const borgVersion = this.state.borgVersion;
+        let borgInfoColor = 'success';
+        let borgInfoMessage = `Borg version '${borgVersion.version}' is OK.`;
+        if (!borgVersion.versionOK) {
+            borgInfoColor = 'danger';
+            borgInfoMessage = borgVersion.statusMessage;
+        }
         return (
-            <div>
-                <ConfirmModal
-                    onConfirm={ConfigServerTab.clearAllCaches}
-                    title={'Are you sure?'}
-                    toggle={this.toggleModal}
-                    open={this.state.confirmModal}
-                >
-                    Do you really want to clear all caches? All Archive file lists and caches for repo and archive
-                    information will be cleared.
-                    <br/>
-                    This is a safe option but it may take some time to re-fill the caches (on demand) again.
-                </ConfirmModal>
+            <React.Fragment>
                 <form>
-                    <FormLabelField>
-                        <FormButton id={'clearAllCaches'} onClick={this.toggleModal}> Clear all caches
-                        </FormButton>
-                    </FormLabelField>
+                    <FormGroup>
+                        <FormLabel>{'Borg command'}</FormLabel>
+                        <FormField length={2}>
+                            <FormSelect
+                                value={this.state.borgBinary}
+                                name={'borgBinary'}
+                                onChange={this.handleTextChange}
+                                hint={`Choose your OS and BorgButler will download and use a ready to run borg binary from ${borgVersion.binariesDownloadUrl} or choose a manual installed version.`}
+                            >
+                                {borgVersion.borgBinaries
+                                    .map((binary, index) => <FormOption label={binary[1]} value={binary[0]}
+                                                                        key={index}/>)}
+                                <FormOption label={'Manual'} value={'manual'}/>
+                            </FormSelect>
+                        </FormField>
+                        <FormInput fieldLength={8} name={'borgCommand'} value={this.state.borgCommand}
+                                   onChange={this.handleTextChange}
+                                   placeholder="Enter path of borg command"
+                                   disabled={this.state.borgBinary !== "manual"}/>
+                    </FormGroup>
+                    <FormGroup>
+                        <FormField length={4}/>
+                        <Alert className={'col-sm-8'} color={borgInfoColor}>
+                            {borgInfoMessage}
+                        </Alert>
+                    </FormGroup>
                     <FormLabelInputField label={'Port'} fieldLength={2} type="number" min={0} max={65535}
                                          step={1}
                                          name={'port'} value={this.state.port}
                                          onChange={this.handleTextChange}
-                                         placeholder="Enter port" />
+                                         placeholder="Enter port"/>
                     <FormLabelInputField label={'Maximum disc capacity (MB)'} fieldLength={2} type="number" min={50}
                                          max={10000}
                                          step={50}
@@ -141,21 +168,21 @@
                                          value={this.state.maxArchiveContentCacheCapacityMb}
                                          onChange={this.handleTextChange}
                                          placeholder="Enter maximum Capacity"
-                                         hint={`Limits the cache size of archive file lists in the local cache directory: ${this.state.cacheDir}`} />
+                                         hint={`Limits the cache size of archive file lists in the local cache directory: ${this.state.cacheDir}`}/>
                     <FormLabelField label={'Show demo repositories'} fieldLength={2}>
                         <FormCheckbox checked={this.state.showDemoRepos}
                                       hint={'If true, some demo repositories are shown for testing the functionality of BorgButler without any further configuration and running borg backups.'}
                                       name="showDemoRepos"
-                                      onChange={this.handleCheckboxChange} />
+                                      onChange={this.handleCheckboxChange}/>
                     </FormLabelField>
-                    <FormLabelField label={<I18n name={'configuration.webDevelopmentMode'} />} fieldLength={2}>
+                    <FormLabelField label={<I18n name={'configuration.webDevelopmentMode'}/>} fieldLength={2}>
                         <FormCheckbox checked={this.state.webDevelopmentMode}
                                       hintKey={'configuration.webDevelopmentMode.hint'}
                                       name="webDevelopmentMode"
-                                      onChange={this.handleCheckboxChange} />
+                                      onChange={this.handleCheckboxChange}/>
                     </FormLabelField>
                 </form>
-            </div>
+            </React.Fragment>
         );
     }
 }
diff --git a/borgbutler-webapp/src/components/views/jobs/Job.css b/borgbutler-webapp/src/components/views/jobs/Job.css
index b57da22..7650622 100644
--- a/borgbutler-webapp/src/components/views/jobs/Job.css
+++ b/borgbutler-webapp/src/components/views/jobs/Job.css
@@ -7,4 +7,12 @@
 .job-progress {
     width: calc(100% - 3em);
     margin-top: -10px;
+}
+
+.job-progress div.progress {
+    margin: 0 .75rem .375rem;
+}
+
+.onclick {
+    cursor: pointer;
 }
\ No newline at end of file
diff --git a/borgbutler-webapp/src/components/views/jobs/Job.jsx b/borgbutler-webapp/src/components/views/jobs/Job.jsx
index dcf01f4..e9b0a14 100644
--- a/borgbutler-webapp/src/components/views/jobs/Job.jsx
+++ b/borgbutler-webapp/src/components/views/jobs/Job.jsx
@@ -39,6 +39,22 @@
     render() {
         let content = undefined;
         let job = this.props.job;
+        let cancelDisabled = undefined;
+        let times = ' (created: ' + job.createTime;
+        if (job.startTime) {
+            times += ', started: ' + job.startTime;
+        }
+        if (job.stopTime) {
+            times += ', ended: ' + job.stopTime;
+        }
+        times += ')';
+        if ((job.status !== 'RUNNING' && job.status !== 'QUEUED') || job.cancellationRequested) {
+            cancelDisabled = true;
+        }
+        let cancelButton = <div className="job-cancel"><Button color={'danger'}
+                                                               onClick={() => this.cancelJob(job.uniqueJobNumber)}
+                                                               disabled={cancelDisabled}><IconCancel/></Button>
+        </div>;
         if (job.status === 'RUNNING') {
             let progressPercent = 100;
             let color = 'success';
@@ -49,13 +65,18 @@
                 progressPercent = job.progressPercent;
             }
             content = <Progress animated color={color} value={progressPercent}>{job.progressText}</Progress>;
+        } else if (job.status === 'CANCELLED') {
+            content = <Progress color={'warning'} value={100}>{job.status}</Progress>
+            cancelButton = '';
+        } else if (job.status === 'FAILED') {
+            content = <Progress color={'danger'} value={100}>{job.status}</Progress>
+            cancelButton = '';
+        } else if (job.status === 'DONE') {
+            content = <Progress color={'success'} value={100}>{job.status}</Progress>
+            cancelButton = '';
         } else {
             content = <Progress color={'info'} value={100}>{job.status}</Progress>
         }
-        let cancelDisabled = undefined;
-        if ((job.status !== 'RUNNING' && job.status !== 'QUEUED') || job.cancellationRequested) {
-            cancelDisabled = true;
-        }
         let environmentVariables = '';
         if (job.environmentVariables && Array.isArray(job.environmentVariables)) {
             environmentVariables = job.environmentVariables.map((variable, index) => <React.Fragment key={index}>
@@ -70,10 +91,7 @@
                         <Button color="link" onClick={this.toggle}>{job.description}</Button>
                         {content}
                     </div>
-                    <div className="job-cancel"><Button color={'danger'}
-                                                        onClick={() => this.cancelJob(job.uniqueJobNumber)}
-                                                        disabled={cancelDisabled}><IconCancel/></Button>
-                    </div>
+                    {cancelButton}
                 </div>
                 <Collapse isOpen={this.state.collapse}>
                     <Card>
@@ -82,7 +100,7 @@
                                 <tbody>
                                 <tr>
                                     <th>Status</th>
-                                    <td>{job.status}</td>
+                                    <td>{job.status} {times}</td>
                                 </tr>
                                 <tr>
                                     <th>Command line</th>
diff --git a/borgbutler-webapp/src/components/views/jobs/JobMonitorPanel.jsx b/borgbutler-webapp/src/components/views/jobs/JobMonitorPanel.jsx
index 759cab2..1dc5557 100644
--- a/borgbutler-webapp/src/components/views/jobs/JobMonitorPanel.jsx
+++ b/borgbutler-webapp/src/components/views/jobs/JobMonitorPanel.jsx
@@ -1,19 +1,21 @@
 import React from 'react';
-import {Button} from 'reactstrap';
+import {Button, Collapse} from 'reactstrap';
 import {getRestServiceUrl, isDevelopmentMode} from '../../../utilities/global';
 import JobQueue from './JobQueue';
-import ErrorAlert from '../archives/ArchiveView';
+import ErrorAlert from '../../general/ErrorAlert';
 import PropTypes from 'prop-types';
 
 class JobMonitorPanel extends React.Component {
     state = {
         isFetching: false,
-        testMode: false
+        isFetchingOldJobs: false,
+        testMode: false,
+        collapseOldJobs: false
     };
 
     componentDidMount = () => {
-        this.fetchQueues();
-        this.interval = setInterval(() => this.fetchQueues(), 2000);
+        this.fetchQueues(false);
+        this.interval = setInterval(() => this.fetchJobs(), 2000);
     };
 
     componentWillUnmount() {
@@ -26,14 +28,39 @@
         });
     }
 
-    fetchQueues = () => {
+    toggleOldJobs() {
+        if (!this.state.collapseOldJobs) {
+            this.fetchQueues(true);
+        }
+        this.setState({collapseOldJobs: !this.state.collapseOldJobs});
+    }
+
+    fetchJobs() {
+        if (!this.state.isFetching) { // Don't run twice at the same time
+            this.fetchQueues(false);
+        }
+        if (this.state.collapseOldJobs && !this.state.isFetchingOldJobs) {
+            this.fetchQueues(true);
+        }
+    }
+
+    fetchQueues = (oldJobs) => {
+        let queuesVar = 'queues';
+        let isFetchingVar = 'isFetching';
+        let failedVar = 'failed';
+        if (oldJobs) {
+            queuesVar = 'oldJobsQueues';
+            isFetchingVar = 'isFetchingOldJobs';
+            failedVar = 'oldJobsFailed';
+        }
         this.setState({
-            isFetching: true,
-            failed: false
+            [isFetchingVar]: true,
+            [failedVar]: false
         });
         fetch(getRestServiceUrl('jobs', {
             repo: this.props.repo,
-            testMode: this.state.testMode
+            testMode: this.state.testMode,
+            oldJobs: oldJobs
         }), {
             method: 'GET',
             headers: {
@@ -46,11 +73,11 @@
                     return queue;
                 });
                 this.setState({
-                    isFetching: false,
-                    queues
+                    [isFetchingVar]: false,
+                    [queuesVar]: queues
                 });
             })
-            .catch(() => this.setState({isFetching: false, failed: true}));
+            .catch(() => this.setState({[isFetchingVar]: false, [failedVar]: true}));
     };
 
     render() {
@@ -60,7 +87,7 @@
             content = <i>Loading...</i>;
         } else if (this.state.failed) {
             content = <ErrorAlert
-                title={'Cannot load Repositories'}
+                title={'Cannot load job queues'}
                 description={'Something went wrong during contacting the rest api.'}
                 action={{
                     handleClick: this.fetchQueues,
@@ -77,16 +104,50 @@
                             key={queue.repo}
                         />)}
                 </React.Fragment>;
-            } else if (isDevelopmentMode() && !this.props.embedded) {
-                content = <React.Fragment>No jobs are running or queued.<br/><br/>
-                    <Button color="primary" onClick={this.toggleTestMode}>Test mode</Button>
-                </React.Fragment>
             } else {
                 content = <React.Fragment>No jobs are running or queued.</React.Fragment>
             }
         }
+        let oldJobs = '';
+
+        if (this.state.isFetchingOldJobs && !this.state.oldJobsQueues) {
+            oldJobs = <i>Loading...</i>;
+        } else if (this.state.oldJobsFailed) {
+            oldJobs = <ErrorAlert
+                title={'Cannot load old job queues'}
+                description={'Something went wrong during contacting the rest api.'}
+                action={{
+                    handleClick: this.fetchQueues,
+                    title: 'Try again'
+                }}
+            />;
+        } else if (this.state.oldJobsQueues) {
+            if (this.state.oldJobsQueues.length > 0) {
+                oldJobs = <React.Fragment>
+                    {this.state.oldJobsQueues
+                        .map((queue) => <JobQueue
+                            embedded={this.props.embedded}
+                            queue={queue}
+                            key={queue.repo}
+                        />)}
+                </React.Fragment>
+            } else {
+                oldJobs = <React.Fragment>No old jobs available.</React.Fragment>
+            }
+        }
+        let testContent = '';
+        if (isDevelopmentMode() && !this.props.embedded) {
+            testContent = <React.Fragment><br/><br/><br/><Button className="btn-outline-info" onClick={this.toggleTestMode}>Test mode</Button></React.Fragment>;
+        }
+
         return <React.Fragment>
             {content}
+            <h5 className={'onclick'} onClick={this.toggleOldJobs}>Show old jobs
+            </h5>
+            <Collapse isOpen={this.state.collapseOldJobs}>
+                {oldJobs}
+            </Collapse>
+            {testContent}
         </React.Fragment>;
     }
 
@@ -95,15 +156,18 @@
 
         this.fetchQueues = this.fetchQueues.bind(this);
         this.toggleTestMode = this.toggleTestMode.bind(this);
+        this.toggleOldJobs = this.toggleOldJobs.bind(this);
     }
 }
 
-JobMonitorPanel.propTypes = {
+JobMonitorPanel
+    .propTypes = {
     embedded: PropTypes.bool,
     repo: PropTypes.string
 };
 
-JobMonitorPanel.defaultProps = {
+JobMonitorPanel
+    .defaultProps = {
     embedded: true,
     repo: null
 };
diff --git a/borgbutler-webapp/src/components/views/jobs/JobQueue.jsx b/borgbutler-webapp/src/components/views/jobs/JobQueue.jsx
index 20310ab..c04090e 100644
--- a/borgbutler-webapp/src/components/views/jobs/JobQueue.jsx
+++ b/borgbutler-webapp/src/components/views/jobs/JobQueue.jsx
@@ -1,25 +1,45 @@
 import React from 'react';
-import {Card, CardBody, CardHeader, ListGroup} from 'reactstrap';
+import {Card, CardBody, CardHeader, Collapse, ListGroup} from 'reactstrap';
 import Job from "./Job";
 
-function JobQueue({queue, embedded}) {
-    return (
-        <div>
-            <Card>
-                <CardHeader>Job queue: {queue.repo}</CardHeader>
-                <CardBody>
-                    <ListGroup>
-                        {queue.jobs
-                            .map((job, index) => <Job
-                                embedded={embedded}
-                                job={job}
-                                key={job.uniqueJobNumber}
-                            />)}
-                    </ListGroup>
-                </CardBody>
-            </Card>
-        </div>
-    );
+class JobQueue extends React.Component {
+    state = {
+        collapsed: true
+    };
+
+    toggle() {
+        this.setState({collapsed: !this.state.collapsed});
+    }
+
+
+    render() {
+        const queue = this.props.queue;
+        return (
+            <div>
+                <Card>
+                    <CardHeader className={'onclick'} onClick={this.toggle}>Job queue: {queue.repo}</CardHeader>
+                    <Collapse isOpen={this.state.collapsed}>
+                        <CardBody>
+                            <ListGroup>
+                                {queue.jobs
+                                    .map((job, index) => <Job
+                                        embedded={this.props.embedded}
+                                        job={job}
+                                        key={job.uniqueJobNumber}
+                                    />)}
+                            </ListGroup>
+                        </CardBody>
+                    </Collapse>
+                </Card>
+            </div>
+        );
+    }
+
+    constructor(props) {
+        super(props);
+        this.toggle = this.toggle.bind(this);
+
+    }
 }
 
 export default JobQueue;
diff --git a/borgbutler-webapp/src/components/views/repos/ConfigureRepoPage.jsx b/borgbutler-webapp/src/components/views/repos/ConfigureRepoPage.jsx
new file mode 100644
index 0000000..9583c37
--- /dev/null
+++ b/borgbutler-webapp/src/components/views/repos/ConfigureRepoPage.jsx
@@ -0,0 +1,252 @@
+import React from 'react';
+import {Link} from 'react-router-dom'
+import {
+    FormButton,
+    FormField,
+    FormGroup,
+    FormInput,
+    FormLabel,
+    FormLabelInputField,
+    FormOption,
+    FormRadioButton,
+    FormSelect
+} from '../../general/forms/FormComponents';
+import I18n from "../../general/translation/I18n";
+import {PageHeader} from "../../general/BootstrapComponents";
+
+class ConfigureRepoPage extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.handleTextChange = this.handleTextChange.bind(this);
+        this.handleRepoTextChange = this.handleRepoTextChange.bind(this);
+        this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
+        this.state = {
+            repoConfig: {},
+            mode: 'existingRepo',
+            localRemote: 'local',
+            encryption: 'repokey',
+            passwordMethod: 'passwordCommand',
+            passwordCreate: null
+        };
+    }
+
+    handleTextChange = event => {
+        event.preventDefault();
+        this.setState({[event.target.name]: event.target.value});
+        if (event.target.name === 'passwordMethod') {
+            const value = event.target.value;
+            var passwordCommand = null;
+            var passwordCreate = null;
+            if (value === 'passwordFile') {
+                passwordCommand = 'cat ~/.borg-passphrase';
+                passwordCreate = <React.Fragment>
+                    Create a file with a password in it in your home directory and use permissions to keep anyone else
+                    from
+                    reading it:<br/>
+                    <div className="command-line">head -c 1024 /dev/urandom | base64 > ~/.borg-passphrase<br/>
+                        chmod 400 ~/.borg-passphrase
+                    </div>
+                </React.Fragment>;
+            } else if (value === 'macos-keychain') {
+                passwordCommand = 'security find-generic-password -a $USER -s borg-passphrase';
+                passwordCreate = <React.Fragment>
+                    Generate a passphrase and use security to save it to your login (default) keychain:<br/>
+                    <div className="command-line">security add-generic-password -D secret -U -a $USER -s borg-passphrase
+                        -w $(head -c 1024 /dev/urandom | base64)
+                    </div>
+                </React.Fragment>;
+            } else if (value === 'gnome-keyring') {
+                passwordCommand = 'secret-tool lookup borg-repository repo-name';
+                passwordCreate = <React.Fragment>
+                    First ensure libsecret-tools, gnome-keyring and libpam-gnome-keyring are installed. If
+                    libpam-gnome-keyring wasn’t already installed, ensure it runs on login:<br/>
+                    <div className="command-line">sudo sh -c "echo session optional pam_gnome_keyring.so auto_start >>
+                        /etc/pam.d/login"<br/>
+                        sudo sh -c "echo password optional pam_gnome_keyring.so >> /etc/pam.d/passwd"<br/>
+                        # you may need to relogin afterwards to activate the login keyring
+                    </div>
+                    Then add a secret to the login keyring:
+                    <div className="command-line">head -c 1024 /dev/urandom | base64 | secret-tool store borg-repository
+                        repo-name --label="Borg Passphrase"</div>
+                </React.Fragment>;
+            } else if (value === 'kwallet') {
+                passwordCommand = 'kwalletcli -e borg-passphrase -f Passwords';
+                passwordCreate = <React.Fragment>
+                    Ensure kwalletcli is installed, generate a passphrase, and store it in your “wallet”:<br/>
+                    <div className="command-line">head -c 1024 /dev/urandom | base64 | kwalletcli -Pe borg-passphrase -f
+                        Passwords
+                    </div>
+                </React.Fragment>;
+            }
+            if (passwordCommand) {
+                this.setState({repoConfig: {...this.state.repoConfig, 'passwordCommand': passwordCommand}})
+            }
+            this.setState({'passwordCreate': passwordCreate});
+        }
+    }
+
+
+    handleRepoTextChange = event => {
+        event.preventDefault();
+        this.setState({repoConfig: {...this.state.repoConfig, [event.target.name]: event.target.value}});
+    }
+
+    handleCheckboxChange = event => {
+        this.setState({[event.target.name]: event.target.value});
+        /*if (event.target.name === 'mode' && event.target.value === 'existingRepo'
+            && this.state.passwordMethod !== 'passwordCommand' && this.state.passwordMethod !== 'passphrase') {
+            // Other options such as Mac OS key chain isn't available for existing repos:
+            this.setState({passwordMethod: 'passwordCommand'});
+        }*/
+    }
+
+    render() {
+        const repoConfig = this.state.repoConfig;
+        var passwordMethods = [['password-command', 'Password command'],
+            ['passwordFile', 'Password file'],
+            ['macos-keychain', 'Mac OS X keychain'],
+            ['gnome-keyring', 'GNOME keyring'],
+            ['kwallet', 'KWallet'],
+            ['passphrase', 'Passphrase (not recommended)']
+        ];
+        let repoPlaceHolder = 'Enter the repo used by Borg.';
+        if (this.state.mode === 'initNewRepo' && this.state.localRemote === 'remote') {
+            repoPlaceHolder = 'Enter the remote path of the repo, such as user@hostname:backup.';
+        }
+        let repoFieldLength = 10;
+        let browseButton = null;
+        if (this.state.localRemote === 'local') {
+            repoFieldLength = 9;
+            browseButton = <FormButton onClick={null}
+                                       hint={'Browse local backup directory.'}>Browse</FormButton>
+            repoPlaceHolder = 'Enter or browse the local path of the repo home dir used by Borg.';
+        }
+        return <React.Fragment>
+            <PageHeader>
+                Configure repository
+            </PageHeader>
+            <form>
+                <FormGroup>
+                    <FormLabel length={2}>{'Mode'}</FormLabel>
+                    <FormField length={10}>
+                        <FormRadioButton name={'mode'} id={'mode1'} label={'Add existing repository'}
+                                         value={'existingRepo'}
+                                         checked={this.state.mode === 'existingRepo'}
+                                         onChange={this.handleCheckboxChange}
+                                         hint={'Do you want to add an already existing Borg repository?'}/>
+                        <FormRadioButton name={'mode'} id={'mode2'} label={'Init new repository'} value={'initNewRepo'}
+                                         checked={this.state.mode === 'initNewRepo'}
+                                         onChange={this.handleCheckboxChange}
+                                         hint={'Do you want to create and init a new one?'}/>
+                    </FormField>
+                </FormGroup>
+                <FormGroup>
+                    <FormLabel length={2}>{'Local or remote'}</FormLabel>
+                    <FormField length={10}>
+                        <FormRadioButton name={'localRemote'} id={'localRemote1'} label={'Local repository'}
+                                         value={'local'}
+                                         checked={this.state.localRemote === 'local'}
+                                         onChange={this.handleCheckboxChange}/>
+                        <FormRadioButton name={'localRemote'} id={'localRemote2'} label={'Remote repository'}
+                                         value={'remote'}
+                                         checked={this.state.localRemote === 'remote'}
+                                         onChange={this.handleCheckboxChange}/>
+                    </FormField>
+                </FormGroup>
+                <FormLabelInputField label={'Display name'} fieldLength={12}
+                                     name={'displayName'} value={repoConfig.displayName}
+                                     onChange={this.handleRepoTextChange}
+                                     placeholder="Enter display name (only for displaying purposes)."/>
+                <FormGroup>
+                    <FormLabel length={2}>{'Repo'}</FormLabel>
+                    <FormField length={repoFieldLength}>
+                        <FormInput
+                            id={'repo'}
+                            name={'repo'}
+                            type={'text'}
+                            value={repoConfig.repo}
+                            onChange={this.handleRepoTextChange}
+                            placeholder={repoPlaceHolder}
+                        />
+                    </FormField>
+                    {browseButton}
+                </FormGroup>
+                <FormLabelInputField label={'RSH'} fieldLength={12}
+                                     name={'rsh'} value={repoConfig.rsh}
+                                     onChange={this.handleRepoTextChange}
+                                     placeholder="Enter the rsh value (ssh command) for remote repository."
+                                     className={this.state.localRemote === 'local' ? 'hidden' : null}/>
+                <FormGroup className={this.state.mode === 'existingRepo' ? 'hidden' : null}>
+                    <FormLabel length={2}>{'Encryption'}</FormLabel>
+                    <FormField length={2}>
+                        <FormSelect
+                            value={repoConfig.encryption}
+                            name={'encryption'}
+                            onChange={this.handleRepoTextChange}
+                            hint={'Encryption for the new repository (use repokey if you don\'t know what to choose).'}
+                        >
+                            <FormOption label={'repokey (SHA256)'} value={'repokey'}/>
+                            <FormOption label={'repokey-blake2'} value={'repokey-blake2'}/>
+                            <FormOption label={'keyfile (SHA256)'} value={'keyfile'}/>
+                            <FormOption label={'none (not recommended)'} value={'none'}/>
+                        </FormSelect>
+                    </FormField>
+                </FormGroup>
+                <FormGroup>
+                    <FormLabel length={2}>{'Password method'}</FormLabel>
+                    <FormField length={3}>
+                        <FormSelect
+                            value={this.state.passwordMethod}
+                            name={'passwordMethod'}
+                            onChange={this.handleTextChange}
+                        >
+                            {passwordMethods
+                                .map((entry) => <FormOption label={entry[1]} value={entry[0]}
+                                                            key={entry[0]}/>)}
+                        </FormSelect>
+                    </FormField>
+                </FormGroup>
+                <FormGroup className={!this.state.passwordCreate ? 'hidden' : null}>
+                    <FormLabel length={2}>{'Passphrase creation info'}</FormLabel>
+                    <FormField length={10}>
+                        {this.state.passwordCreate}
+                    </FormField>
+                </FormGroup>
+                <FormLabelInputField label={'Password command'} fieldLength={12}
+                                     name={'passwordCommand'} value={repoConfig.passwordCommand}
+                                     onChange={this.handleRepoTextChange}
+                                     placeholder="Enter the password command to get the command from or choose a method above."
+                                     className={this.state.passwordMethod === 'passphrase' ? 'hidden' : null}
+                />
+                <FormLabelInputField label={'Password'} fieldLength={6} type={'password'}
+                                     name={'passphrase'} value={repoConfig.passphrase}
+                                     onChange={this.handleRepoTextChange}
+                                     hint={"It's recommended to use password command instead."}
+                                     className={this.state.passwordMethod !== 'passphrase' ? 'hidden' : null}
+                />
+                <FormField length={12}>
+                    <Link to={'/repos'} className={'btn btn-outline-primary'}><I18n name={'common.cancel'}/>
+                    </Link>
+                    <FormButton onClick={this.onSave} bsStyle="primary"
+                                hintKey="configuration.save.hint"><I18n name={'common.save'}/>
+                    </FormButton>
+                </FormField>
+            </form>
+            <code>
+                <h2>Todo:</h2>
+                <ul>
+                    <li>Implement 'Save' button ;-)</li>
+                    <li>Add own environment variables.</li>
+                    <li>Implement browse button for local repos.</li>
+                    <li>Note (for new backups): Save your password, otherwise your backup will be lost without!</li>
+                    <li>Note (hide password fields): Your backup will not be encrypted!</li>
+                    <li>Remove and clone repo.</li>
+                </ul>
+            </code>
+        </React.Fragment>;
+    }
+}
+
+export default ConfigureRepoPage;
+
diff --git a/borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx b/borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx
index 4fe43bc..12995e8 100644
--- a/borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx
+++ b/borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx
@@ -1,19 +1,22 @@
 import React from 'react'
-import {Nav, NavLink, TabContent, Table, TabPane} from 'reactstrap';
-import { Link } from "react-router-dom";
+import {Badge, Nav, NavLink, TabContent, Table, TabPane} from 'reactstrap';
+import {Link} from "react-router-dom";
 import classNames from 'classnames';
 import {PageHeader} from '../../general/BootstrapComponents';
 import {getRestServiceUrl, humanFileSize} from '../../../utilities/global';
 import ErrorAlert from '../../general/ErrorAlert';
 import {IconCheck, IconRefresh} from '../../general/IconComponents';
 import JobMonitorPanel from '../jobs/JobMonitorPanel';
+import RepoConfigPanel from "./RepoConfigPanel";
 
 class RepoArchiveListView extends React.Component {
 
     state = {
         id: this.props.match.params.id,
+        displayName: this.props.match.params.displayName,
         isFetching: false,
         activeTab: '1',
+        redirectOnError: true
     };
 
     componentDidMount = () => {
@@ -42,7 +45,12 @@
                     repo: json
                 })
             })
-            .catch(() => this.setState({isFetching: false, failed: true}));
+            .catch(() => {
+                this.setState({isFetching: false, failed: true})
+                if (this.state.redirectOnError && this.state.activeTab !== '3') {
+                    this.setState({activeTab: '3', redirectOnError: false});
+                }
+            });
     };
 
     toggleTab = tab => () => {
@@ -51,22 +59,46 @@
         })
     };
 
+    afterSave() {
+        if (!this.state.failed) {
+            this.setState({
+                activeTab: '1'
+            })
+        }
+    }
+
+    afterCancel() {
+        if (!this.state.failed) {
+            this.setState({
+                activeTab: '1'
+            })
+        }
+    }
+
     render = () => {
-        let content = undefined;
+        let errorBadge = '';
+        let content1 = undefined;
+        let content2 = undefined;
         const repo = this.state.repo;
-        let pageHeader = '';
+        const displayName = (this.state.displayName) ? this.state.displayName : `Error: id=${this.state.id}`;
+        let pageHeader = <React.Fragment>
+            {displayName}
+        </React.Fragment>;
 
         if (this.state.isFetching) {
-            content = <JobMonitorPanel repo={this.state.id} />;
+            content1 = <JobMonitorPanel repo={this.state.id}/>;
+            content2 = content1;
         } else if (this.state.failed) {
-            content = <ErrorAlert
+            content1 = <ErrorAlert
                 title={'Cannot load Repositories'}
-                description={'Something went wrong during contacting the rest api.'}
+                description={'Something went wrong, may-be wrong configuration?'}
                 action={{
                     handleClick: this.fetchRepo,
                     title: 'Try again'
                 }}
             />;
+            content2 = content1;
+            errorBadge = <Badge color="danger" pill>!</Badge>;
         } else if (this.state.repo) {
             pageHeader = <React.Fragment>
                 {repo.displayName}
@@ -127,82 +159,92 @@
                     <td>{repo.cache.path}</td>
                 </tr>
             }
-            content = <React.Fragment>
-                <Nav tabs>
-                    <NavLink
-                        className={classNames({active: this.state.activeTab === '1'})}
-                        onClick={this.toggleTab('1')}
-                    >
-                        Archives
-                    </NavLink>
-                    <NavLink
-                        className={classNames({active: this.state.activeTab === '2'})}
-                        onClick={this.toggleTab('2')}
-                    >
-                        Information
-                    </NavLink>
-                </Nav>
-                <TabContent activeTab={this.state.activeTab}>
-                    <TabPane tabId={'1'}>
-                        <Table hover>
-                            <tbody>
-                            <tr>
-                                <th>Archive</th>
-                                <th>Time</th>
-                                <th></th>
-                                <th>Id</th>
-                            </tr>
-                            {repo.archives.map((archive) => {
-                                // Return the element. Also pass key
-                                let loaded = '';
-                                if (archive.fileListAlreadyCached) {
-                                    loaded = <IconCheck />;
-                                }
-                                return (
-                                    <tr key={archive.id}>
-                                        <td><Link to={`/archives/${repo.id}/${archive.id}/`}>{archive.name}</Link></td>
-                                        <td>{archive.time}</td>
-                                        <td>{loaded}</td>
-                                        <td>{archive.id}</td>
-                                    </tr>);
-                            })}
-                            </tbody>
-                        </Table>
-                    </TabPane>
-                    <TabPane tabId={'2'}>
-                        <Table striped bordered hover>
-                            <tbody>
-                            <tr>
-                                <td>Id</td>
-                                <td>{repo.id}</td>
-                            </tr>
-                            <tr>
-                                <td>Name</td>
-                                <td>{repo.name}</td>
-                            </tr>
-                            <tr>
-                                <td>Location</td>
-                                <td>{repo.location}</td>
-                            </tr>
-                            {stats}
-                            <tr>
-                                <td>Security dir</td>
-                                <td>{repo.securityDir}</td>
-                            </tr>
-                            {encryption}
-                            {cachePath}
-                            </tbody>
-                        </Table>
-                    </TabPane>
-                </TabContent>
-            </React.Fragment>;
+
+            content1 = <Table hover>
+                <tbody>
+                <tr>
+                    <th>Archive</th>
+                    <th>Time</th>
+                    <th></th>
+                    <th>Id</th>
+                </tr>
+                {repo.archives.map((archive) => {
+                    // Return the element. Also pass key
+                    let loaded = '';
+                    if (archive.fileListAlreadyCached) {
+                        loaded = <IconCheck/>;
+                    }
+                    return (
+                        <tr key={archive.id}>
+                            <td><Link to={`/archives/${repo.id}/${archive.id}/`}>{archive.name}</Link></td>
+                            <td>{archive.time}</td>
+                            <td>{loaded}</td>
+                            <td>{archive.id}</td>
+                        </tr>);
+                })}
+                </tbody>
+            </Table>;
+            content2 = <Table striped bordered hover>
+                <tbody>
+                <tr>
+                    <td>Id</td>
+                    <td>{repo.id}</td>
+                </tr>
+                <tr>
+                    <td>Name</td>
+                    <td>{repo.name}</td>
+                </tr>
+                <tr>
+                    <td>Location</td>
+                    <td>{repo.location}</td>
+                </tr>
+                {stats}
+                <tr>
+                    <td>Security dir</td>
+                    <td>{repo.securityDir}</td>
+                </tr>
+                {encryption}
+                {cachePath}
+                </tbody>
+            </Table>;
 
         }
         return <React.Fragment>
             <PageHeader>
-                {pageHeader}
+                <Link to={'/repos'}> Repositories</Link> - {pageHeader}
             </PageHeader>
-            {content}
+            <Nav tabs>
+                <NavLink
+                    className={classNames({active: this.state.activeTab === '1'})}
+                    onClick={this.toggleTab('1')}
+                >
+                    Archives {errorBadge}
+                </NavLink>
+                <NavLink
+                    className={classNames({active: this.state.activeTab === '2'})}
+                    onClick={this.toggleTab('2')}
+                >
+                    Information {errorBadge}
+                </NavLink>
+                <NavLink
+                    className={classNames({active: this.state.activeTab === '3'})}
+                    onClick={this.toggleTab('3')}
+                >
+                    Configuration
+                </NavLink>
+            </Nav>
+            <TabContent activeTab={this.state.activeTab}>
+                <TabPane tabId={'1'}>
+                    {content1}
+                </TabPane>
+                <TabPane tabId={'2'}>
+                    {content2}
+                </TabPane>
+                <TabPane tabId={'3'}>
+                    <RepoConfigPanel id={this.state.id} afterCancel={this.afterCancel} afterSave={this.afterSave}
+                                     repoError={this.state.failed}/>
+                </TabPane>
+            </TabContent>
         </React.Fragment>;
     };
 
@@ -211,6 +253,8 @@
 
         this.fetchRepo = this.fetchRepo.bind(this);
         this.toggleTab = this.toggleTab.bind(this);
+        this.afterCancel = this.afterCancel.bind(this);
+        this.afterSave = this.afterSave.bind(this);
     }
 }
 
diff --git a/borgbutler-webapp/src/components/views/repos/RepoCard.jsx b/borgbutler-webapp/src/components/views/repos/RepoCard.jsx
index b04cfad..b7ae760 100644
--- a/borgbutler-webapp/src/components/views/repos/RepoCard.jsx
+++ b/borgbutler-webapp/src/components/views/repos/RepoCard.jsx
@@ -18,7 +18,7 @@
         let repoText = this.buildItem(null, content);
 
         return <React.Fragment>
-            <Card tag={Link} to={`/repoArchives/${repo.id}`} outline color="success" className={'repo'}
+            <Card tag={Link} to={`/repoArchives/${repo.id}/${repo.displayName}`} outline color="success" className={'repo'}
                   style={{backgroundColor: '#fff'}}>
                 <CardHeader>{repo.displayName}</CardHeader>
                 <CardBody>
diff --git a/borgbutler-webapp/src/components/views/repos/RepoConfigPanel.jsx b/borgbutler-webapp/src/components/views/repos/RepoConfigPanel.jsx
new file mode 100644
index 0000000..bb549df
--- /dev/null
+++ b/borgbutler-webapp/src/components/views/repos/RepoConfigPanel.jsx
@@ -0,0 +1,153 @@
+import React from 'react';
+import {FormGroup} from 'reactstrap';
+import {FormButton, FormField, FormLabelInputField} from '../../general/forms/FormComponents';
+import {getRestServiceUrl} from '../../../utilities/global';
+import I18n from "../../general/translation/I18n";
+import LoadingOverlay from '../../general/loading/LoadingOverlay';
+import PropTypes from "prop-types";
+import ErrorAlert from "../../general/ErrorAlert";
+
+class RepoConfigPanel extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.state = {
+            loading: false,
+            repoConfig: undefined
+        };
+
+        this.fetch = this.fetch.bind(this);
+        this.handleTextChange = this.handleTextChange.bind(this);
+        this.onSave = this.onSave.bind(this);
+        this.onCancel = this.onCancel.bind(this);
+    }
+
+    componentDidMount = () => this.fetch();
+
+    fetch = () => {
+        this.setState({
+            isFetching: true,
+            failed: false,
+            repoConfig: undefined
+        });
+        fetch(getRestServiceUrl('repoConfig', {
+            id: this.props.id
+        }), {
+            method: 'GET',
+            headers: {
+                'Accept': 'application/json'
+            }
+        })
+            .then(response => response.json())
+            .then(json => {
+                this.setState({
+                    isFetching: false,
+                    repoConfig: json
+                })
+            })
+            .catch((error) => {
+                console.log("error", error);
+                this.setState({
+                    isFetching: false,
+                    failed: true
+                });
+            })
+    };
+
+    handleTextChange = event => {
+        event.preventDefault();
+        this.setState({repoConfig: {...this.state.repoConfig, [event.target.name]: event.target.value}});
+    }
+
+    async onSave(event) {
+        const response = fetch(getRestServiceUrl("repoConfig"), {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json'
+            },
+            body: JSON.stringify(this.state.repoConfig)
+        });
+        if (response) await response;
+        if (this.props.afterSave) {
+            this.props.afterSave();
+        }
+    }
+
+    async onCancel() {
+        const response = this.fetch();
+        if (response) await response;
+        if (this.props.afterCancel) {
+            this.props.afterCancel();
+        }
+    }
+
+    render() {
+        let content;
+        let repoError = '';
+        if (this.props.repoError) {
+            repoError =<ErrorAlert
+                title={'Cannot access repository'}
+                description={'Repo not available or mis-configured (please refer the log files for more details).'}
+            />
+        }
+        if (this.state.isFetching) {
+            content = <React.Fragment>Loading...</React.Fragment>;
+        } else if (this.state.failed) {
+            content = <ErrorAlert
+                title={'Cannot load config of repository'}
+                description={'Something went wrong during contacting the rest api.'}
+                action={{
+                    handleClick: this.fetchRepo,
+                    title: 'Try again'
+                }}
+            />;
+        } else if (this.state.repoConfig) {
+            const repoConfig = this.state.repoConfig;
+            content = <React.Fragment>
+                <FormGroup>
+                    <FormLabelInputField label={'Display name'} fieldLength={12}
+                                         name={'displayName'} value={repoConfig.displayName}
+                                         onChange={this.handleTextChange}
+                                         placeholder="Enter display name (only for displaying purposes)."/>
+                    <FormLabelInputField label={'Repo'} fieldLength={12}
+                                         name={'repo'} value={repoConfig.repo}
+                                         onChange={this.handleTextChange}
+                                         placeholder="Enter the name of the repo, used by Borg."/>
+                    <FormLabelInputField label={'RSH'} fieldLength={12}
+                                         name={'rsh'} value={repoConfig.rsh}
+                                         onChange={this.handleTextChange}
+                                         placeholder="Enter the rsh value (ssh command) for remote repository."/>
+                    <FormLabelInputField label={'Password command'} fieldLength={12}
+                                         name={'passwordCommand'} value={repoConfig.passwordCommand}
+                                         onChange={this.handleTextChange}
+                                         placeholder="Enter the password command to get the command from."/>
+                    <FormLabelInputField label={'Password'} fieldLength={6} type={'password'}
+                                         name={'passphrase'} value={repoConfig.passphrase}
+                                         onChange={this.handleTextChange}
+                                         hint={"It's recommended to use password command instead."}
+                    />
+                    <FormField length={12}>
+                        <FormButton onClick={this.onCancel}
+                                    hintKey="configuration.cancel.hint"><I18n name={'common.cancel'}/>
+                        </FormButton>
+                        <FormButton onClick={this.onSave} bsStyle="primary"
+                                    hintKey="configuration.save.hint"><I18n name={'common.save'}/>
+                        </FormButton>
+                    </FormField>
+                </FormGroup>
+                <LoadingOverlay active={this.state.loading}/>
+            </React.Fragment>;
+        }
+        return <React.Fragment>{content}{repoError}</React.Fragment>;
+    }
+}
+
+RepoConfigPanel.propTypes = {
+    afterCancel: PropTypes.func.isRequired,
+    afterSave: PropTypes.func.isRequired,
+    id: PropTypes.string,
+    repoError: PropTypes.bool
+};
+
+export default RepoConfigPanel;
+
diff --git a/borgbutler-webapp/src/components/views/repos/RepoListView.jsx b/borgbutler-webapp/src/components/views/repos/RepoListView.jsx
index 7c03a4a..fde2450 100644
--- a/borgbutler-webapp/src/components/views/repos/RepoListView.jsx
+++ b/borgbutler-webapp/src/components/views/repos/RepoListView.jsx
@@ -1,11 +1,12 @@
 import React from 'react'
+import {Link} from 'react-router-dom'
 import './RepoListView.css';
 import {CardDeck} from 'reactstrap';
 import {PageHeader} from '../../general/BootstrapComponents';
 import {getRestServiceUrl} from '../../../utilities/global';
 import ErrorAlert from '../../general/ErrorAlert';
 import RepoCard from './RepoCard';
-import {IconRefresh} from "../../general/IconComponents";
+import {IconAdd, IconRefresh} from "../../general/IconComponents";
 
 class RepoListView extends React.Component {
 
@@ -73,12 +74,12 @@
 
             content = <React.Fragment>
                 <CardDeck>
-                {this.state.repos.map(repo => {
-                    return <RepoCard
-                        key={repo.id}
-                        repo={repo}
-                    />;
-                })}
+                    {this.state.repos.map(repo => {
+                        return <RepoCard
+                            key={repo.id}
+                            repo={repo}
+                        />;
+                    })}
                 </CardDeck>
             </React.Fragment>;
 
@@ -95,6 +96,12 @@
                 </div>
             </PageHeader>
             {content}
+            <br/>
+            <Link to={'/repo/configure'}
+                  className={'btn btn-outline-primary'}
+            >
+                <IconAdd/>
+            </Link>
         </React.Fragment>;
     };
 
diff --git a/borgbutler-webapp/src/containers/WebApp.jsx b/borgbutler-webapp/src/containers/WebApp.jsx
index 73d1b82..1aeed96 100644
--- a/borgbutler-webapp/src/containers/WebApp.jsx
+++ b/borgbutler-webapp/src/containers/WebApp.jsx
@@ -17,6 +17,7 @@
 import Footer from '../components/views/footer/Footer';
 import {loadVersion} from '../actions';
 import {getTranslation} from '../utilities/i18n';
+import ConfigureRepoPage from '../components/views/repos/ConfigureRepoPage';
 
 class WebApp extends React.Component {
 
@@ -24,11 +25,11 @@
 
     componentDidMount = () => {
         this.props.loadVersion();
-        this.interval = setInterval(() => this.fetchJobStatistics(), 5000);
+        this.interval = setInterval(() => this.fetchSystemInfo(), 5000);
     };
 
-    fetchJobStatistics = () => {
-        fetch(getRestServiceUrl('jobs/statistics'), {
+    fetchSystemInfo = () => {
+        fetch(getRestServiceUrl('system/info'), {
             method: 'GET',
             headers: {
                 'Accept': 'application/json'
@@ -37,7 +38,7 @@
             .then(response => response.json())
             .then(json => {
                 this.setState({
-                    statistics: json
+                    systemInfo: json
                 });
             })
             .catch();
@@ -45,15 +46,20 @@
 
     render() {
         let jobsBadge = '';
-        if (this.state && this.state.statistics && this.state.statistics.numberOfRunningAndQueuedJobs > 0) {
-            jobsBadge = <Badge color="danger" pill>{this.state.statistics.numberOfRunningAndQueuedJobs}</Badge>;
+        const statistics = (this.state && this.state.systemInfo) ? this.state.systemInfo.queueStatistics : null;
+        if (statistics && statistics.numberOfRunningAndQueuedJobs > 0) {
+            jobsBadge = <Badge color="danger" pill>{statistics.numberOfRunningAndQueuedJobs}</Badge>;
+        }
+        let configurationBadge = '';
+        if (this.state && this.state.systemInfo && !this.state.systemInfo.configurationOK) {
+            configurationBadge = <Badge color="danger" pill>!</Badge>;
         }
         let routes = [
             ['Start', '/', Start],
             ['Repositories', '/repos', RepoListView],
             ['Job monitor', '/jobmonitor', JobMonitorView, {badge: jobsBadge}],
             [getTranslation('logviewer'), '/logging', LogPage],
-            [getTranslation('configuration'), '/config', ConfigurationPage]
+            [getTranslation('configuration'), '/config', ConfigurationPage, {badge: configurationBadge}]
         ];
 
         if (isDevelopmentMode()) {
@@ -76,8 +82,9 @@
                                     />
                                 ))
                             }
-                            <Route path={'/repoArchives/:id'} component={RepoArchiveListView}/>
+                            <Route path={'/repoArchives/:id/:displayName'} component={RepoArchiveListView} />
                             <Route path={'/archives/:repoId/:archiveId/'} component={ArchiveView} />
+                            <Route path={'/repo/configure'} component={ConfigureRepoPage} />
                         </Switch>
                     </div>
                     <Footer versionInfo={this.props.version}/>
diff --git a/borgbutler-webapp/src/css/my-style.css b/borgbutler-webapp/src/css/my-style.css
index 352ebd6..2b8e86a 100644
--- a/borgbutler-webapp/src/css/my-style.css
+++ b/borgbutler-webapp/src/css/my-style.css
@@ -2,6 +2,19 @@
     color: #50555f;
 }
 
+.hidden {
+    display: none;
+}
+
+.command-line {
+    font-family: monospace;
+    white-space: pre;
+    border-style: solid;
+    background-color: black;
+    color: white;
+    padding-left: 5pt;
+}
+
 a:hover {
     color: #47a7eb;
 }
@@ -175,6 +188,10 @@
     color: #47a7eb;
 }
 
+ul.nav-tabs a.active {
+    font-weight: bold;
+}
+
 .tab-pane.active {
     padding: 20px;
 }

--
Gitblit v1.10.0