Create a capsule for Node Explorer, and allow login via command line parameters to bypass login screen.

This commit is contained in:
Chris Rankin 2017-01-31 17:54:26 +00:00
parent 30f9cc8fcd
commit f9ac03287c
5 changed files with 142 additions and 4 deletions

View File

@ -13,6 +13,7 @@ include 'experimental:sandbox'
include 'test-utils'
include 'tools:demobench'
include 'tools:explorer'
include 'tools:explorer:capsule'
include 'tools:loadtest'
include 'docs/source/example-code' // Note that we are deliberately choosing to use '/' here. With ':' gradle would treat the directories as actual projects.
include 'samples:attachment-demo'

View File

@ -0,0 +1,54 @@
/**
* This build.gradle exists to package Node Explorer as an executable fat jar.
*/
apply plugin: 'us.kirchmeier.capsule'
description 'Node Explorer'
repositories {
mavenLocal()
mavenCentral()
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
jcenter()
maven {
url 'https://dl.bintray.com/kotlin/exposed'
}
}
// Force the Caplet to target Java 6. This ensures that running 'java -jar explorer.jar' on any Java 6 VM upwards
// will get as far as the Capsule version checks, meaning that if your JVM is too old, you will at least get
// a sensible error message telling you what to do rather than a bytecode version exception that doesn't.
// If we introduce .java files into this module that need Java 8+ then we will have to push the caplet into
// its own module so its target can be controlled individually, but for now this suffices.
sourceCompatibility = 1.6
targetCompatibility = 1.6
dependencies {
compile project(':tools:explorer')
}
task buildExplorerJAR(type: FatCapsule) {
applicationClass 'net.corda.explorer.Main'
archiveName "node-explorer-${corda_version}.jar"
applicationSource = files(project.tasks.findByName('jar'), '../build/classes/main/ExplorerCaplet.class')
classifier 'fat'
capsuleManifest {
systemProperties['visualvm.display.name'] = 'Node Explorer'
minJavaVersion = '1.8.0'
// This version is known to work and avoids earlier 8u versions that have bugs.
minUpdateVersion['1.8'] = '102'
caplets = ['ExplorerCaplet']
// JVM configuration:
// - Constrain to small heap sizes to ease development on low end devices.
// - Switch to the G1 GC which is going to be the default in Java 9 and gives low pause times/string dedup.
//
// If you change these flags, please also update Driver.kt
jvmArgs = ['-Xmx200m', '-XX:+UseG1GC']
}
}
build.dependsOn buildExplorerJAR

View File

@ -0,0 +1,49 @@
import java.io.File;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
public class ExplorerCaplet extends Capsule {
protected ExplorerCaplet(Capsule pred) {
super(pred);
}
/**
* Overriding the Caplet classpath generation via the intended interface in Capsule.
*/
@Override
@SuppressWarnings("unchecked")
protected <T> T attribute(Map.Entry<String, T> attr) {
// Equality is used here because Capsule never instantiates these attributes but instead reuses the ones
// defined as public static final fields on the Capsule class, therefore referential equality is safe.
if (ATTR_APP_CLASS_PATH == attr) {
T cp = super.attribute(attr);
List<Path> classpath = augmentClasspath((List<Path>) cp, "plugins");
return (T) augmentClasspath(classpath, "dependencies");
}
return super.attribute(attr);
}
// TODO: Make directory configurable via the capsule manifest.
// TODO: Add working directory variable to capsules string replacement variables.
private List<Path> augmentClasspath(List<Path> classpath, String dirName) {
File dir = new File(dirName);
if (!dir.exists()) {
dir.mkdir();
}
File[] files = dir.listFiles();
for (File file : files) {
if (file.isFile() && isJAR(file)) {
classpath.add(file.toPath().toAbsolutePath());
}
}
return classpath;
}
private Boolean isJAR(File file) {
return file.getName().toLowerCase().endsWith(".jar");
}
}

View File

@ -55,9 +55,39 @@ class Main : App(MainView::class) {
}.showAndWait().get()
if (button != ButtonType.OK) it.consume()
}
stage.hide()
loginView.login()
stage.show()
val hostname = parameters.named["host"]
val port = asInteger(parameters.named["port"])
val username = parameters.named["username"]
val password = parameters.named["password"]
var isLoggedIn = false
if ((hostname != null) && (port != null) && (username != null) && (password != null)) {
try {
loginView.login(hostname, port, username, password)
isLoggedIn = true
} catch (e: Exception) {
ExceptionDialog(e).apply { initOwner(stage.scene.window) }.showAndWait()
}
}
if (!isLoggedIn) {
stage.hide()
loginView.login()
stage.show()
}
}
private fun asInteger(s: String?): Int? {
if (s == null) {
return null;
}
try {
return s.toInt();
} catch (e: NumberFormatException) {
return null
}
}
init {

View File

@ -38,6 +38,10 @@ class LoginView : View() {
private val keyStorePasswordProperty by objectProperty(SettingsModel::keyStorePasswordProperty)
private val trustStorePasswordProperty by objectProperty(SettingsModel::trustStorePasswordProperty)
fun login(host: String?, port: Int, username: String, password: String) {
getModel<NodeMonitorModel>().register(HostAndPort.fromParts(host, port), configureSSL(), username, password)
}
fun login() {
val status = Dialog<LoginStatus>().apply {
dialogPane = root
@ -46,7 +50,7 @@ class LoginView : View() {
ButtonBar.ButtonData.OK_DONE -> try {
root.isDisable = true
// TODO : Run this async to avoid UI lockup.
getModel<NodeMonitorModel>().register(HostAndPort.fromParts(hostTextField.text, portProperty.value), configureSSL(), usernameTextField.text, passwordTextField.text)
login(hostTextField.text, portProperty.value, usernameTextField.text, passwordTextField.text)
if (!rememberMe.value) {
username.value = ""
host.value = ""