Skip to content

Commit

Permalink
Kopia UI improvements for repository management (#592)
Browse files Browse the repository at this point in the history
* cli: added --tls-print-server-cert flag

This prints complete server certificate that is base64 and PEM-encoded.

It is needed for Electron to securely connect to the server outside of
the browser, since there's no way to trust certificate by fingerprint.

* server: added repo/exists API

* server: added ClientOptions to create and connect API

* server: exposed current-user API

* server: API to change description of a repository

* htmlui: refactored connect/create flow

This cleaned up the code a lot and made UX more obvious.

* kopia-ui: simplified repository management UX

Removed repository configuration window which was confusing due to
the notion of 'server'.

Now KopiaUI will automatically launch 'kopia server --ui' for each
config found in the kopia config directory and shut it down every
time repository is disconnected.

See https://youtu.be/P4Ll_LR4UVM for a quick demo.

Fixes #583
  • Loading branch information
jkowalski committed Sep 7, 2020
1 parent 29ce181 commit 3b87902
Show file tree
Hide file tree
Showing 42 changed files with 2,557 additions and 4,027 deletions.
2 changes: 1 addition & 1 deletion app/package.json
Expand Up @@ -130,7 +130,7 @@
"build-electron-linux": "electron-builder -l",
"build-electron-dir": "electron-builder --dir",
"start-electron-prebuilt": "ELECTRON_IS_DEV=0 electron .",
"dev": "concurrently \"react-scripts start\" \"sleep 3s; electron .\""
"dev": "electron ."
},
"eslintConfig": {
"extends": "react-app"
Expand Down
127 changes: 44 additions & 83 deletions app/public/config.js
@@ -1,10 +1,10 @@
const fs = require('fs');
const path = require('path');
const Electron = require('electron');
const log = require("electron-log")
const log = require("electron-log");

let configs = {};
const configFileSuffix = ".repo";
const configFileSuffix = ".config";

let configDir = "";
let isPortable = false;
Expand Down Expand Up @@ -50,96 +50,62 @@ function globalConfigDir() {
});

// still not set, fall back to per-user config dir.
// we use the same directory that is used by Kopia CLI.
if (!configDir) {
configDir = path.join(Electron.app.getPath("userData"), "repositories");
configDir = path.join(Electron.app.getPath("appData"), "kopia");
}
}

return configDir;
}

function newConfigForRepo(repoID) {
const configFile = path.join(globalConfigDir(), repoID + configFileSuffix);

let data = {};

try {
const b = fs.readFileSync(configFile);
data = JSON.parse(b);
} catch (e) {
data = {
}

if (repoID == "default") {
data.description = "My Repository";
} else {
data.description = "Unnamed Repository";
}
function allConfigs() {
let result = [];

if (repoID == "default" && !isPortable) {
data.configFile = "";
} else {
data.configFile = "./" + repoID + ".config";
}
for (let k in configs) {
result.push(k);
}

return {
get(key) {
return data[key];
},

setBulk(dict) {
for (let k in dict) {
data[k] = dict[k];
}

fs.writeFileSync(configFile, JSON.stringify(data));

emitConfigListUpdated();
},
return result;
}

all() {
return data;
}
}
};
function addNewConfig() {
let id;

Electron.ipcMain.on('config-get', (event, arg) => {
const c = configs[arg.repoID];
if (c) {
event.returnValue = c.all();
if (!configs) {
// first repository is always named "repository" to match Kopia CLI.
id = "repository";
} else {
event.returnValue = {};
id = "repository-" + new Date().valueOf();
}
});

Electron.ipcMain.on('config-add', (event, arg) => {
const c = newConfigForRepo(arg.repoID);
configs[arg.repoID] = c
emitConfigListUpdated();
c.setBulk(arg.data);

event.returnValue = true;
});

function currentConfigSummary() {
let result = [];

for (let k in configs) {
result.push({ repoID: k, desc: configs[k].get('description') });
}

return result;
configs[id] = true;
return id;
}

Electron.ipcMain.on('config-list-fetch', (event, arg) => {
emitConfigListUpdated();
});

function emitConfigListUpdated() {
Electron.ipcMain.emit('config-list-updated-event', currentConfigSummary());
Electron.ipcMain.emit('config-list-updated-event', allConfigs());
};

function deleteConfigIfDisconnected(repoID) {
if (repoID === "repository") {
// never delete default repository config
return false;
}

if (!fs.existsSync(path.join(globalConfigDir(), repoID + configFileSuffix))) {
delete (configs[repoID]);
emitConfigListUpdated();
return true;
}

return false;
}

module.exports = {
loadConfigs() {
fs.mkdirSync(globalConfigDir(), { recursive: true, mode: 0700 });
Expand All @@ -148,15 +114,13 @@ module.exports = {
let count = 0;
entries.filter(e => path.extname(e) == configFileSuffix).forEach(v => {
const repoID = v.replace(configFileSuffix, "");
configs[repoID] = newConfigForRepo(repoID);
configs[repoID] = true;
count++;
});

if (!count) {
if (!configs["repository"]) {
configs["repository"] = true;
firstRun = true;
const c = newConfigForRepo('default');
c.setBulk({});
configs['default'] = c;
}
},

Expand All @@ -173,23 +137,20 @@ module.exports = {
return globalConfigDir();
},

currentConfigSummary,
deleteConfigIfDisconnected,

addNewConfig,

allConfigs,

configForRepo(repoID) {
let c = configs[repoID];
if (c) {
return c;
}

c = newConfigForRepo(repoID);
configs[repoID] = c
configs[repoID] = true;
emitConfigListUpdated();
return c;
},

deleteConfigForRepo(repoID) {
delete (configs[repoID]);
fs.unlinkSync(path.join(globalConfigDir(), repoID + configFileSuffix));
emitConfigListUpdated();
},
}
}
49 changes: 0 additions & 49 deletions app/public/config_window.js

This file was deleted.

0 comments on commit 3b87902

Please sign in to comment.