kopia-ui: misc

- launch server on a randomized port
- added support for connecting to remote server
- embedded version info in the app and htmlui
This commit is contained in:
Jarek Kowalski committed 2020-03-05 18:40:03 -08:00
1 parent c5cf95fdf6
commit e6fe1aa7f3
10 files changed
+135 -53

No files matched your search

+3 -3
View File
@@ -26,7 +26,7 @@ let tray = null
let configWindow = null;
let mainWindow = null;
function advancedConfiguration() {
function serverConfiguration() {
if (configWindow) {
configWindow.focus();
return;
@@ -42,7 +42,7 @@ function advancedConfiguration() {
})
if (isDev) {
configWindow.loadURL('http://localhost:3000');
configWindow.loadURL('http://localhost:3000/?ts=' + new Date().valueOf());
} else {
configWindow.loadFile('./build/index.html');
}
@@ -215,7 +215,7 @@ function updateTrayContextMenu() {
console.log('updating tray...');
const contextMenu = Menu.buildFromTemplate([
{ label: 'Show Main Window', click: showMainWindow },
{ label: 'Advanced Configuration...', click: advancedConfiguration },
{ label: 'Server Configuration...', click: serverConfiguration },
{ label: 'Check For Updates Now', click: checkForUpdates },
{ type: 'separator' },
{ label: 'Launch At Startup', type: 'checkbox', click: toggleLaunchAtStartup, checked: willLaunchAtStartup() },
+3 -1
View File
@@ -24,11 +24,13 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Kopia - Advanced Configuration</title>
<title>Kopia v%REACT_APP_SHORT_VERSION_INFO% - Server Configuration</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<hr/>
<p class="version-info">Version %REACT_APP_FULL_VERSION_INFO%</p>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
+52 -25
View File
@@ -5,10 +5,10 @@ const { appendToLog } = require('./logging');
const { defaultServerBinary } = require('./utils');
const { spawn } = require('child_process');
let serverProcess = null;
let serverCertSHA256 = "";
let serverPassword = "";
let serverAddress = "";
let runningServerProcess = null;
let runningServerCertSHA256 = "";
let runningServerPassword = "";
let runningServerAddress = "";
function detectServerParam(data) {
let lines = (data + '').split('\n');
@@ -22,15 +22,15 @@ function detectServerParam(data) {
const value = lines[i].substring(p + 2);
switch (key) {
case "SERVER PASSWORD":
serverPassword = value;
runningServerPassword = value;
break;
case "SERVER CERT SHA256":
serverCertSHA256 = value;
runningServerCertSHA256 = value;
break;
case "SERVER ADDRESS":
serverAddress = value;
runningServerAddress = value;
break;
}
}
@@ -55,30 +55,31 @@ function startServer() {
args.push('--random-password')
args.push('--tls-generate-cert')
args.push('--address=localhost:0')
// args.push('--auto-shutdown=600s')
console.log(`spawning ${kopiaPath} ${args.join(' ')}`);
serverProcess = spawn(kopiaPath, args);
runningServerProcess = spawn(kopiaPath, args);
ipcMain.emit('server-status-updated');
serverProcess.stdout.on('data', appendToLog);
serverProcess.stderr.on('data', detectServerParam);
runningServerProcess.stdout.on('data', appendToLog);
runningServerProcess.stderr.on('data', detectServerParam);
serverProcess.on('close', (code, signal) => {
runningServerProcess.on('close', (code, signal) => {
appendToLog(`child process exited with code ${code} and signal ${signal}`);
serverProcess = null;
runningServerProcess = null;
ipcMain.emit('server-status-updated');
});
}
function stopServer() {
if (!serverProcess) {
if (!runningServerProcess) {
console.log('stopServer: server not started');
return;
}
serverProcess.kill();
serverProcess = null;
runningServerProcess.kill();
runningServerProcess = null;
}
ipcMain.on('subscribe-to-status', (event, arg) => {
@@ -90,21 +91,47 @@ ipcMain.on('subscribe-to-status', (event, arg) => {
});
function getServerStatus() {
if (!serverProcess) {
return "Stopped";
}
if (config.get('remoteServer')) {
return "Remote";
} else {
if (!runningServerProcess) {
return "Stopped";
}
return "Running";
return "Running";
}
};
function getServerAddress() {
return "localhost:51515";
function getActiveServerAddress() {
if (config.get('remoteServer')) {
return config.get('remoteServerAddress');
} else {
return runningServerAddress;
}
};
function getActiveServerCertSHA256() {
if (config.get('remoteServer')) {
return config.get('remoteServerCertificateSHA256');
} else {
return runningServerCertSHA256;
}
};
function getActiveServerPassword() {
if (config.get('remoteServer')) {
return config.get('remoteServerPassword');
} else {
return runningServerPassword;
}
};
function sendStatusUpdate(sender) {
sender.send('status-updated', {
status: getServerStatus(),
serverAddress: getServerAddress(),
serverAddress: getActiveServerAddress() || "<pending>",
serverCertSHA256: getActiveServerCertSHA256() || "<pending>",
});
}
@@ -117,15 +144,15 @@ module.exports = {
},
getServerAddress() {
return serverAddress;
return getActiveServerAddress();
},
getServerCertSHA256() {
return serverCertSHA256;
return getActiveServerCertSHA256();
},
getServerPassword() {
return serverPassword;
return getActiveServerPassword();
},
stopServer: stopServer,
+6
View File
@@ -34,4 +34,10 @@ div.tab-body {
font-weight: bold;
color: #000080;
font-family: Consolas, 'Courier New', Courier, monospace;
}
.version-info {
padding-left: 10px;
color: #aaa;
font-size: 80%;
}
+2 -1
View File
@@ -18,7 +18,6 @@ function App() {
<img src={logo} className="App-logo" alt="logo" />
<span className="title">Kopia</span>
</header>
<ServerStatus />
<hr/>
<Tabs defaultActiveKey="config" transition={false}>
<Tab eventKey="config" title="Server Configuration">
@@ -32,6 +31,8 @@ function App() {
</div>
</Tab>
</Tabs>
<hr/>
<ServerStatus />
</Container>
);
}
+38 -21
View File
@@ -10,14 +10,16 @@ export default class ServerConfig extends Component {
"remoteServer": false,
"kopiaPath": "",
"configFile": "",
"repositoryPassword": "",
'serverUsername': 'kopia',
"serverAddress": "https://localhost:51515",
"serverPassword": "",
"remoteServerAddress": "",
"remoteServerPassword": "",
"remoteServerCertificateSHA256": "",
};
this.updateState = this.updateState.bind(this);
this.saveConfiguration = this.saveConfiguration.bind(this);
this.validateRemoteServerAddress = this.validateRemoteServerAddress.bind(this);
if (window.require) {
const { ipcRenderer } = window.require('electron');
@@ -50,6 +52,24 @@ export default class ServerConfig extends Component {
window.close();
}
validateRemoteServerAddress() {
return false;
}
isValidHttpsURL(v) {
try {
const url = new URL(v);
return url.protocol === "https:";
} catch (_) {
return false;
}
}
isValidSHA256(v) {
const re = /^[0-9A-Fa-f]{64}$/g;
return re.test(v);
}
render() {
return <>
<Form>
@@ -70,34 +90,31 @@ export default class ServerConfig extends Component {
<Form.Control type="text" placeholder="Configuration file" value={this.state.configFile} onChange={this.updateState} />
<Form.Text className="text-muted">Uses default configuration path, if not set.</Form.Text>
</Form.Group>
<Form.Group controlId="repositoryPassword">
<Form.Label>Repository Password</Form.Label>
<Form.Control type="password" placeholder="Password" value={this.state.repositoryPassword} onChange={this.updateState} />
<Form.Text className="text-muted">Uses saved password if not set.</Form.Text>
</Form.Group>
</React.Fragment>
</div>}
<Form.Group controlId="connectToServer">
<Form.Check type="radio" name="serverType" label="Connect To Server" checked={this.state.remoteServer} onChange={() => this.setState({ 'remoteServer': true })} />
<Form.Check type="radio" name="serverType" label="Connect To Remote Server" checked={this.state.remoteServer} onChange={() => this.setState({ 'remoteServer': true })} />
</Form.Group>
{this.state.remoteServer &&
<div className="indented">
<React.Fragment>
<Form.Group controlId="serverAddress">
<Form.Group controlId="remoteServerAddress">
<Form.Label>Server Address</Form.Label>
<Form.Control type="text" placeholder="Enter address" value={this.state.serverAddress} onChange={this.updateState} />
<Form.Text className="text-muted">To connect to local server, use localhost:51515</Form.Text>
<Form.Control type="text" isInvalid={!this.isValidHttpsURL(this.state.remoteServerAddress)} placeholder="Enter address" value={this.state.remoteServerAddress} onChange={this.updateState} />
<Form.Text className="text-muted">Enter server URL https://server:port</Form.Text>
<Form.Control.Feedback type="invalid">Must be valid server url starting with https://</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="serverUsername">
<Form.Label>Server Username</Form.Label>
<Form.Control type="text" placeholder="Enter username" value={this.state.serverUsername} onChange={this.updateState} />
</Form.Group>
<Form.Group controlId="serverPassword">
<Form.Group controlId="remoteServerPassword">
<Form.Label>Server Password</Form.Label>
<Form.Control type="password" placeholder="Password" value={this.state.serverPassword} onChange={this.updateState} />
<Form.Control type="password" isInvalid={!this.state.remoteServerPassword} placeholder="Password" value={this.state.remoteServerPassword} onChange={this.updateState} />
<Form.Control.Feedback type="invalid">Password cannot be empty.</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="remoteServerCertificateSHA256">
<Form.Label>Server Certificate Fingerprint</Form.Label>
<Form.Control type="text" isInvalid={!this.isValidSHA256(this.state.remoteServerCertificateSHA256)} placeholder="server certificate fingerprint (SHA256)" value={this.state.remoteServerCertificateSHA256} onChange={this.updateState} />
<Form.Control.Feedback type="invalid">Must be valid server fingerprint</Form.Control.Feedback>
</Form.Group>
</React.Fragment>
</div>
+1 -1
View File
@@ -19,6 +19,6 @@ export default class ServerStatus extends Component {
}
render() {
return <div>Server: <b>{this.state.status}</b> on <code>{this.state.serverAddress}</code></div>;
return <div>Server: <code>{this.state.status}</code><br/>URL: <code>{this.state.serverAddress}</code><br/>TLS Certificate Fingerprint: <code>{this.state.serverCertSHA256}</code></div>;
}
}
+3 -1
View File
@@ -24,11 +24,13 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Kopia UI</title>
<title>Kopia UI v%REACT_APP_SHORT_VERSION_INFO%</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<hr/>
<p class="version-info">Version %REACT_APP_FULL_VERSION_INFO%</p>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
+6
View File
@@ -53,4 +53,10 @@ div.tab-body {
#snapshot-path {
width: 400px;
}
.version-info {
padding-left: 10px;
color: #aaa;
font-size: 80%;
}
+21
View File
@@ -140,6 +140,27 @@ else
curl -LsS https://github.com/goreleaser/goreleaser/releases/download/$(GORELEASER_VERSION)/goreleaser_$$(uname -s)_$$(uname -m).tar.gz | tar zx -C $(TOOLS_DIR)/goreleaser-$(GORELEASER_VERSION)
endif
ifeq ($(TRAVIS_PULL_REQUEST),false)
ifneq ($(TRAVIS_TAG),)
# travis, tagged release
KOPIA_VERSION:=$(TRAVIS_TAG:v%=%)
else
# travis, non-tagged release
KOPIA_VERSION:=$(shell date +%Y%m%d).0.$(TRAVIS_BUILD_NUMBER)
endif
else
# non-travis, or travis PR
KOPIA_VERSION:=$(shell date +%Y%m%d).0.0-$(shell git rev-parse --short HEAD)
endif
# embedded in the HTML pages
export REACT_APP_SHORT_VERSION_INFO:=$(KOPIA_VERSION)
export REACT_APP_FULL_VERSION_INFO:=$(KOPIA_VERSION) built on $(shell date) $(shell hostname)
clean-tools:
rm -rf $(TOOLS_DIR)