mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 08:41:28 +00:00
Merge branch 'master' of https://github.com/totalvoidness/cryptomator
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@
|
||||
.project
|
||||
.classpath
|
||||
target/
|
||||
test-output/
|
||||
|
||||
@@ -3,7 +3,7 @@ Cryptomator
|
||||
|
||||
Multiplatform transparent client-side encryption of your files in the cloud. You need Java 8 in order to run the application. Get the runtime environment here: http://www.oracle.com/technetwork/java/javase/downloads/index.html
|
||||
|
||||
If you run OS X and want to take a look at the current alpha version, go ahead and [download Cryptomator.dmg](https://github.com/totalvoidness/cryptomator/releases/download/v0.1.0/Cryptomator.dmg).
|
||||
If you want to take a look at the current beta version, go ahead and download [Cryptomator.dmg](https://github.com/totalvoidness/cryptomator/releases/download/v0.2.0/Cryptomator.dmg), [Cryptomator.exe](https://github.com/totalvoidness/cryptomator/releases/download/v0.2.0/Cryptomator.exe) or [Cryptomator.jar](https://github.com/totalvoidness/cryptomator/releases/download/v0.2.0/Cryptomator.jar).
|
||||
|
||||
## Features
|
||||
- Totally transparent: Just work on the encrypted volume, as if it was an USB drive
|
||||
@@ -30,7 +30,7 @@ If you run OS X and want to take a look at the current alpha version, go ahead a
|
||||
- *NEW:* No Metadata at all. Encrypted files can be decrypted even on completely shuffled file systems (if their contents are undamaged).
|
||||
|
||||
## Dependencies
|
||||
- Java 8 (for UI only - runs headless on Java 7)
|
||||
- Java 8
|
||||
- Maven
|
||||
- Awesome 3rd party open source libraries (Apache Commons, Apache Jackrabbit, Jetty, Jackson, ...)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.2.0</version>
|
||||
<version>0.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>core</artifactId>
|
||||
<name>Cryptomator core I/O module</name>
|
||||
@@ -63,18 +63,4 @@
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -139,8 +139,8 @@ public class EncryptedDir extends AbstractEncryptedNode {
|
||||
if (Files.exists(path)) {
|
||||
try {
|
||||
final BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
|
||||
properties.add(new DefaultDavProperty<Long>(DavPropertyName.CREATIONDATE, attrs.creationTime().toMillis()));
|
||||
properties.add(new DefaultDavProperty<Long>(DavPropertyName.GETLASTMODIFIED, attrs.lastModifiedTime().toMillis()));
|
||||
properties.add(new DefaultDavProperty<String>(DavPropertyName.CREATIONDATE, FileTimeUtils.toRfc1123String(attrs.creationTime())));
|
||||
properties.add(new DefaultDavProperty<String>(DavPropertyName.GETLASTMODIFIED, FileTimeUtils.toRfc1123String(attrs.lastModifiedTime())));
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error determining metadata " + path.toString(), e);
|
||||
// don't add any further properties
|
||||
|
||||
@@ -96,8 +96,8 @@ public class EncryptedFile extends AbstractEncryptedNode {
|
||||
properties.add(new DefaultDavProperty<Long>(DavPropertyName.GETCONTENTLENGTH, contentLength));
|
||||
|
||||
final BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
|
||||
properties.add(new DefaultDavProperty<Long>(DavPropertyName.CREATIONDATE, attrs.creationTime().toMillis()));
|
||||
properties.add(new DefaultDavProperty<Long>(DavPropertyName.GETLASTMODIFIED, attrs.lastModifiedTime().toMillis()));
|
||||
properties.add(new DefaultDavProperty<String>(DavPropertyName.CREATIONDATE, FileTimeUtils.toRfc1123String(attrs.creationTime())));
|
||||
properties.add(new DefaultDavProperty<String>(DavPropertyName.GETLASTMODIFIED, FileTimeUtils.toRfc1123String(attrs.lastModifiedTime())));
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error determining metadata " + path.toString(), e);
|
||||
throw new IORuntimeException(e);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package org.cryptomator.webdav.jackrabbit.resources;
|
||||
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.Temporal;
|
||||
|
||||
public final class FileTimeUtils {
|
||||
|
||||
private FileTimeUtils() {
|
||||
throw new IllegalStateException("not instantiable");
|
||||
}
|
||||
|
||||
public static String toRfc1123String(FileTime time) {
|
||||
final Temporal date = OffsetDateTime.ofInstant(time.toInstant(), ZoneOffset.UTC);
|
||||
return DateTimeFormatter.RFC_1123_DATE_TIME.format(date);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.2.0</version>
|
||||
<version>0.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>crypto-aes</artifactId>
|
||||
<name>Cryptomator cryptographic module (AES)</name>
|
||||
@@ -48,18 +48,4 @@
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -42,7 +42,6 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.apache.commons.io.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.AbstractCryptor;
|
||||
import org.cryptomator.crypto.CryptorIOSupport;
|
||||
@@ -78,11 +77,6 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
||||
*/
|
||||
private static final int AES_KEY_LENGTH;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final byte[] EMPTY_MASTER_KEY = new byte[MASTER_KEY_LENGTH];
|
||||
|
||||
/**
|
||||
* Jackson JSON-Mapper.
|
||||
*/
|
||||
@@ -92,7 +86,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
||||
* The decrypted master key. Its lifecycle starts with {@link #randomData(int)} or {@link #encryptMasterKey(Path, CharSequence)}. Its
|
||||
* lifecycle ends with {@link #swipeSensitiveData()}.
|
||||
*/
|
||||
private final byte[] masterKey = Arrays.copyOf(EMPTY_MASTER_KEY, MASTER_KEY_LENGTH);
|
||||
private final byte[] masterKey = new byte[MASTER_KEY_LENGTH];
|
||||
|
||||
private static final int SIZE_OF_LONG = Long.SIZE / Byte.SIZE;
|
||||
private static final int SIZE_OF_INT = Integer.SIZE / Byte.SIZE;
|
||||
@@ -108,10 +102,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the masterkey with new random bytes.
|
||||
*/
|
||||
public void randomizeMasterKey() {
|
||||
public Aes256Cryptor() {
|
||||
SECURE_PRNG.setSeed(SECURE_PRNG.generateSeed(PRNG_SEED_LENGTH));
|
||||
SECURE_PRNG.nextBytes(this.masterKey);
|
||||
}
|
||||
@@ -119,10 +110,8 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
||||
/**
|
||||
* Encrypts the current masterKey with the given password and writes the result to the given output stream.
|
||||
*/
|
||||
@Override
|
||||
public void encryptMasterKey(OutputStream out, CharSequence password) throws IOException {
|
||||
if (ArrayUtils.isEquals(this.masterKey, EMPTY_MASTER_KEY)) {
|
||||
throw new IllegalStateException("Masterkey not yet initialized.");
|
||||
}
|
||||
try {
|
||||
// derive key:
|
||||
final byte[] userSalt = randomData(SALT_LENGTH);
|
||||
@@ -157,6 +146,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
|
||||
* @throws UnsupportedKeyLengthException If the masterkey has been encrypted with a higher key length than supported by the system. In
|
||||
* this case Java JCE needs to be installed.
|
||||
*/
|
||||
@Override
|
||||
public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException {
|
||||
byte[] decrypted = new byte[0];
|
||||
try {
|
||||
|
||||
@@ -54,20 +54,11 @@ public class Aes256CryptorTest {
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testUninitializedMasterKey() throws IOException {
|
||||
final String pw = "asd";
|
||||
final Aes256Cryptor cryptor = new Aes256Cryptor();
|
||||
final OutputStream out = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
cryptor.encryptMasterKey(out, pw);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectPassword() throws IOException, WrongPasswordException, DecryptFailedException, UnsupportedKeyLengthException {
|
||||
final String pw = "asd";
|
||||
final Aes256Cryptor cryptor = new Aes256Cryptor();
|
||||
final OutputStream out = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
cryptor.randomizeMasterKey();
|
||||
cryptor.encryptMasterKey(out, pw);
|
||||
cryptor.swipeSensitiveData();
|
||||
|
||||
@@ -81,7 +72,6 @@ public class Aes256CryptorTest {
|
||||
final String pw = "asd";
|
||||
final Aes256Cryptor cryptor = new Aes256Cryptor();
|
||||
final OutputStream out = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
cryptor.randomizeMasterKey();
|
||||
cryptor.encryptMasterKey(out, pw);
|
||||
cryptor.swipeSensitiveData();
|
||||
|
||||
@@ -96,7 +86,6 @@ public class Aes256CryptorTest {
|
||||
final String pw = "asd";
|
||||
final Aes256Cryptor cryptor = new Aes256Cryptor();
|
||||
final OutputStream out = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
cryptor.randomizeMasterKey();
|
||||
cryptor.encryptMasterKey(out, pw);
|
||||
cryptor.swipeSensitiveData();
|
||||
|
||||
@@ -111,7 +100,6 @@ public class Aes256CryptorTest {
|
||||
final String pw = "asd";
|
||||
final Aes256Cryptor cryptor = new Aes256Cryptor();
|
||||
final OutputStream out = Files.newOutputStream(masterKey, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
cryptor.randomizeMasterKey();
|
||||
cryptor.encryptMasterKey(out, pw);
|
||||
cryptor.swipeSensitiveData();
|
||||
|
||||
@@ -124,7 +112,6 @@ public class Aes256CryptorTest {
|
||||
public void testEncryptionOfFilenames() throws IOException {
|
||||
final CryptorIOSupport ioSupportMock = new CryptoIOSupportMock();
|
||||
final Aes256Cryptor cryptor = new Aes256Cryptor();
|
||||
cryptor.randomizeMasterKey();
|
||||
|
||||
// short path components
|
||||
final String originalPath1 = "foo/bar/baz";
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>TestNG: Default test</title>
|
||||
<link href="../testng.css" rel="stylesheet" type="text/css" />
|
||||
<link href="../my-testng.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<style type="text/css">
|
||||
.log { display: none;}
|
||||
.stack-trace { display: none;}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
<!--
|
||||
function flip(e) {
|
||||
current = e.style.display;
|
||||
if (current == 'block') {
|
||||
e.style.display = 'none';
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
e.style.display = 'block';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleBox(szDivId, elem, msg1, msg2)
|
||||
{
|
||||
var res = -1; if (document.getElementById) {
|
||||
res = flip(document.getElementById(szDivId));
|
||||
}
|
||||
else if (document.all) {
|
||||
// this is the way old msie versions work
|
||||
res = flip(document.all[szDivId]);
|
||||
}
|
||||
if(elem) {
|
||||
if(res == 0) elem.innerHTML = msg1; else elem.innerHTML = msg2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function toggleAllBoxes() {
|
||||
if (document.getElementsByTagName) {
|
||||
d = document.getElementsByTagName('div');
|
||||
for (i = 0; i < d.length; i++) {
|
||||
if (d[i].className == 'log') {
|
||||
flip(d[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -->
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<h2 align='center'>Default test</h2><table border='1' align="center">
|
||||
<tr>
|
||||
<td>Tests passed/Failed/Skipped:</td><td>0/0/0</td>
|
||||
</tr><tr>
|
||||
<td>Started on:</td><td>Mon Dec 08 22:07:11 CET 2014</td>
|
||||
</tr>
|
||||
<tr><td>Total time:</td><td>0 seconds (5 ms)</td>
|
||||
</tr><tr>
|
||||
<td>Included groups:</td><td></td>
|
||||
</tr><tr>
|
||||
<td>Excluded groups:</td><td></td>
|
||||
</tr>
|
||||
</table><p/>
|
||||
<small><i>(Hover the method name to see the test class name)</i></small><p/>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated by org.testng.reporters.JUnitXMLReporter -->
|
||||
<testsuite hostname="Sebastians-iMac.local" tests="0" failures="0" timestamp="8 Dec 2014 21:07:12 GMT" time="0.005" errors="0">
|
||||
</testsuite>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 356 B |
Binary file not shown.
|
Before Width: | Height: | Size: 157 B |
@@ -1,2 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>TestNG Report</title><style type="text/css">table {margin-bottom:10px;border-collapse:collapse;empty-cells:show}th,td {border:1px solid #009;padding:.25em .5em}th {vertical-align:bottom}td {vertical-align:top}table a {font-weight:bold}.stripe td {background-color: #E6EBF9}.num {text-align:right}.passedodd td {background-color: #3F3}.passedeven td {background-color: #0A0}.skippedodd td {background-color: #DDD}.skippedeven td {background-color: #CCC}.failedodd td,.attn {background-color: #F33}.failedeven td,.stripe .attn {background-color: #D00}.stacktrace {white-space:pre;font-family:monospace}.totop {font-size:85%;text-align:center;border-bottom:2px solid #000}</style></head><body><table><tr><th>Test</th><th># Passed</th><th># Skipped</th><th># Failed</th><th>Time (ms)</th><th>Included Groups</th><th>Excluded Groups</th></tr><tr><th colspan="7">Default suite</th></tr><tr><td><a href="#t0">Default test</a></td><td class="num">0</td><td class="num">0</td><td class="num">0</td><td class="num">5</td><td></td><td></td></tr></table><table><thead><tr><th>Class</th><th>Method</th><th>Start</th><th>Time (ms)</th></tr></thead><tbody><tr><th colspan="4">Default suite</th></tr></tbody><tbody id="t0"></tbody></table><h2>Default test</h2></body></html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 977 B |
@@ -1,191 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>TestNG reports</title>
|
||||
|
||||
<link type="text/css" href="testng-reports.css" rel="stylesheet" />
|
||||
<script type="text/javascript" src="jquery-1.7.1.min.js"></script>
|
||||
<script type="text/javascript" src="testng-reports.js"></script>
|
||||
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
|
||||
<script type='text/javascript'>
|
||||
google.load('visualization', '1', {packages:['table']});
|
||||
google.setOnLoadCallback(drawTable);
|
||||
var suiteTableInitFunctions = new Array();
|
||||
var suiteTableData = new Array();
|
||||
</script>
|
||||
<!--
|
||||
<script type="text/javascript" src="jquery-ui/js/jquery-ui-1.8.16.custom.min.js"></script>
|
||||
-->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="top-banner-root">
|
||||
<span class="top-banner-title-font">Test results</span>
|
||||
<br/>
|
||||
<span class="top-banner-font-1">1 suite</span>
|
||||
</div> <!-- top-banner-root -->
|
||||
<div class="navigator-root">
|
||||
<div class="navigator-suite-header">
|
||||
<span>All suites</span>
|
||||
<a href="#" class="collapse-all-link" title="Collapse/expand all the suites">
|
||||
<img class="collapse-all-icon" src="collapseall.gif">
|
||||
</img> <!-- collapse-all-icon -->
|
||||
</a> <!-- collapse-all-link -->
|
||||
</div> <!-- navigator-suite-header -->
|
||||
<div class="suite">
|
||||
<div class="rounded-window">
|
||||
<div class="suite-header light-rounded-window-top">
|
||||
<a href="#" class="navigator-link" panel-name="suite-Default_suite">
|
||||
<span class="suite-name border-passed">Default suite</span>
|
||||
</a> <!-- navigator-link -->
|
||||
</div> <!-- suite-header light-rounded-window-top -->
|
||||
<div class="navigator-suite-content">
|
||||
<div class="suite-section-title">
|
||||
<span>Info</span>
|
||||
</div> <!-- suite-section-title -->
|
||||
<div class="suite-section-content">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" class="navigator-link " panel-name="test-xml-Default_suite">
|
||||
<span>testng-customsuite.xml</span>
|
||||
</a> <!-- navigator-link -->
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="navigator-link " panel-name="testlist-Default_suite">
|
||||
<span class="test-stats">1 test</span>
|
||||
</a> <!-- navigator-link -->
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="navigator-link " panel-name="group-Default_suite">
|
||||
<span>0 groups</span>
|
||||
</a> <!-- navigator-link -->
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="navigator-link " panel-name="times-Default_suite">
|
||||
<span>Times</span>
|
||||
</a> <!-- navigator-link -->
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="navigator-link " panel-name="reporter-Default_suite">
|
||||
<span>Reporter output</span>
|
||||
</a> <!-- navigator-link -->
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="navigator-link " panel-name="ignored-methods-Default_suite">
|
||||
<span>Ignored methods</span>
|
||||
</a> <!-- navigator-link -->
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="navigator-link " panel-name="chronological-Default_suite">
|
||||
<span>Chronological view</span>
|
||||
</a> <!-- navigator-link -->
|
||||
</li>
|
||||
</ul>
|
||||
</div> <!-- suite-section-content -->
|
||||
<div class="result-section">
|
||||
<div class="suite-section-title">
|
||||
<span>Results</span>
|
||||
</div> <!-- suite-section-title -->
|
||||
<div class="suite-section-content">
|
||||
<ul>
|
||||
<li>
|
||||
<span class="method-stats">0 methods, </span>
|
||||
</li>
|
||||
</ul>
|
||||
</div> <!-- suite-section-content -->
|
||||
</div> <!-- result-section -->
|
||||
</div> <!-- navigator-suite-content -->
|
||||
</div> <!-- rounded-window -->
|
||||
</div> <!-- suite -->
|
||||
</div> <!-- navigator-root -->
|
||||
<div class="wrapper">
|
||||
<div class="main-panel-root">
|
||||
<div panel-name="suite-Default_suite" class="panel Default_suite">
|
||||
</div> <!-- panel Default_suite -->
|
||||
<div panel-name="test-xml-Default_suite" class="panel">
|
||||
<div class="main-panel-header rounded-window-top">
|
||||
<span class="header-content">/private/var/folders/t_/sydpw2q97yj_fh3p7jp6jx8w0000gn/T/testng-eclipse--34592626/testng-customsuite.xml</span>
|
||||
</div> <!-- main-panel-header rounded-window-top -->
|
||||
<div class="main-panel-content rounded-window-bottom">
|
||||
<pre>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
|
||||
<suite name="Default suite">
|
||||
<test verbose="2" name="Default test">
|
||||
<classes>
|
||||
<class name="org.cryptomator.crypto.aes256.Aes256CryptorTest"/>
|
||||
</classes>
|
||||
</test> <!-- Default test -->
|
||||
</suite> <!-- Default suite -->
|
||||
</pre>
|
||||
</div> <!-- main-panel-content rounded-window-bottom -->
|
||||
</div> <!-- panel -->
|
||||
<div panel-name="testlist-Default_suite" class="panel">
|
||||
<div class="main-panel-header rounded-window-top">
|
||||
<span class="header-content">Tests for Default suite</span>
|
||||
</div> <!-- main-panel-header rounded-window-top -->
|
||||
<div class="main-panel-content rounded-window-bottom">
|
||||
<ul>
|
||||
<li>
|
||||
<span class="test-name">Default test (1 class)</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div> <!-- main-panel-content rounded-window-bottom -->
|
||||
</div> <!-- panel -->
|
||||
<div panel-name="group-Default_suite" class="panel">
|
||||
<div class="main-panel-header rounded-window-top">
|
||||
<span class="header-content">Groups for Default suite</span>
|
||||
</div> <!-- main-panel-header rounded-window-top -->
|
||||
<div class="main-panel-content rounded-window-bottom">
|
||||
</div> <!-- main-panel-content rounded-window-bottom -->
|
||||
</div> <!-- panel -->
|
||||
<div panel-name="times-Default_suite" class="panel">
|
||||
<div class="main-panel-header rounded-window-top">
|
||||
<span class="header-content">Times for Default suite</span>
|
||||
</div> <!-- main-panel-header rounded-window-top -->
|
||||
<div class="main-panel-content rounded-window-bottom">
|
||||
<div class="times-div">
|
||||
<script type="text/javascript">
|
||||
suiteTableInitFunctions.push('tableData_Default_suite');
|
||||
function tableData_Default_suite() {
|
||||
var data = new google.visualization.DataTable();
|
||||
data.addColumn('number', 'Number');
|
||||
data.addColumn('string', 'Method');
|
||||
data.addColumn('string', 'Class');
|
||||
data.addColumn('number', 'Time (ms)');
|
||||
data.addRows(0);
|
||||
window.suiteTableData['Default_suite']= { tableData: data, tableDiv: 'times-div-Default_suite'}
|
||||
return data;
|
||||
}
|
||||
</script>
|
||||
<div id="times-div-Default_suite">
|
||||
</div> <!-- times-div-Default_suite -->
|
||||
</div> <!-- times-div -->
|
||||
</div> <!-- main-panel-content rounded-window-bottom -->
|
||||
</div> <!-- panel -->
|
||||
<div panel-name="reporter-Default_suite" class="panel">
|
||||
<div class="main-panel-header rounded-window-top">
|
||||
<span class="header-content">Reporter output for Default suite</span>
|
||||
</div> <!-- main-panel-header rounded-window-top -->
|
||||
<div class="main-panel-content rounded-window-bottom">
|
||||
</div> <!-- main-panel-content rounded-window-bottom -->
|
||||
</div> <!-- panel -->
|
||||
<div panel-name="ignored-methods-Default_suite" class="panel">
|
||||
<div class="main-panel-header rounded-window-top">
|
||||
<span class="header-content">0 ignored methods</span>
|
||||
</div> <!-- main-panel-header rounded-window-top -->
|
||||
<div class="main-panel-content rounded-window-bottom">
|
||||
</div> <!-- main-panel-content rounded-window-bottom -->
|
||||
</div> <!-- panel -->
|
||||
<div panel-name="chronological-Default_suite" class="panel">
|
||||
<div class="main-panel-header rounded-window-top">
|
||||
<span class="header-content">Methods in chronological order</span>
|
||||
</div> <!-- main-panel-header rounded-window-top -->
|
||||
<div class="main-panel-content rounded-window-bottom">
|
||||
</div> <!-- main-panel-content rounded-window-bottom -->
|
||||
</div> <!-- panel -->
|
||||
</div> <!-- main-panel-root -->
|
||||
</div> <!-- wrapper -->
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 352 B |
@@ -1 +0,0 @@
|
||||
[SuiteResult context=Default test]
|
||||
@@ -1,6 +0,0 @@
|
||||
<table border='1'>
|
||||
<tr>
|
||||
<th>Class name</th>
|
||||
<th>Method name</th>
|
||||
<th>Groups</th>
|
||||
</tr></table>
|
||||
@@ -1 +0,0 @@
|
||||
<h2>Groups used for this test run</h2>
|
||||
@@ -1,6 +0,0 @@
|
||||
<html><head><title>Results for Default suite</title></head>
|
||||
<frameset cols="26%,74%">
|
||||
<frame src="toc.html" name="navFrame">
|
||||
<frame src="main.html" name="mainFrame">
|
||||
</frameset>
|
||||
</html>
|
||||
@@ -1,2 +0,0 @@
|
||||
<html><head><title>Results for Default suite</title></head>
|
||||
<body>Select a result on the left-hand pane.</body></html>
|
||||
@@ -1,2 +0,0 @@
|
||||
<h2>Methods run, sorted chronologically</h2><h3>>> means before, << means after</h3><p/><br/><em>Default suite</em><p/><small><i>(Hover the method name to see the test class name)</i></small><p/>
|
||||
</table>
|
||||
@@ -1,2 +0,0 @@
|
||||
<h2>Methods that were not run</h2><table>
|
||||
</table>
|
||||
@@ -1,2 +0,0 @@
|
||||
<h2>Methods run, sorted chronologically</h2><h3>>> means before, << means after</h3><p/><br/><em>Default suite</em><p/><small><i>(Hover the method name to see the test class name)</i></small><p/>
|
||||
</table>
|
||||
@@ -1 +0,0 @@
|
||||
<h2>Reporter output</h2><table></table>
|
||||
@@ -1 +0,0 @@
|
||||
<html><head><title>testng.xml for Default suite</title></head><body><tt><?xml version="1.0" encoding="UTF-8"?><br/><!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"><br/><suite name="Default suite"><br/> <test verbose="2" name="Default test"><br/> <classes><br/> <class name="org.cryptomator.crypto.aes256.Aes256CryptorTest"/><br/> </classes><br/> </test> <!-- Default test --><br/></suite> <!-- Default suite --><br/></tt></body></html>
|
||||
@@ -1,30 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Results for Default suite</title>
|
||||
<link href="../testng.css" rel="stylesheet" type="text/css" />
|
||||
<link href="../my-testng.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<h3><p align="center">Results for<br/><em>Default suite</em></p></h3>
|
||||
<table border='1' width='100%'>
|
||||
<tr valign='top'>
|
||||
<td>1 test</td>
|
||||
<td><a target='mainFrame' href='classes.html'>0 class</a></td>
|
||||
<td>0 method:<br/>
|
||||
<a target='mainFrame' href='methods.html'>chronological</a><br/>
|
||||
<a target='mainFrame' href='methods-alphabetical.html'>alphabetical</a><br/>
|
||||
<a target='mainFrame' href='methods-not-run.html'>not run (0)</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a target='mainFrame' href='groups.html'>0 group</a></td>
|
||||
<td><a target='mainFrame' href='reporter-output.html'>reporter output</a></td>
|
||||
<td><a target='mainFrame' href='testng.xml.html'>testng.xml</a></td>
|
||||
</tr></table>
|
||||
<table width='100%' class='test-failed'>
|
||||
<tr><td>
|
||||
<table style='width: 100%'><tr><td valign='top'>Default test (0/0/0)</td><td valign='top' align='right'>
|
||||
<a href='Default test.html' target='mainFrame'>Results</a>
|
||||
</td></tr></table>
|
||||
</td></tr><p/>
|
||||
</table>
|
||||
</body></html>
|
||||
@@ -1,9 +0,0 @@
|
||||
<html>
|
||||
<head><title>Test results</title><link href="./testng.css" rel="stylesheet" type="text/css" />
|
||||
<link href="./my-testng.css" rel="stylesheet" type="text/css" />
|
||||
</head><body>
|
||||
<h2><p align='center'>Test results</p></h2>
|
||||
<table border='1' width='100%' class='main-page'><tr><th>Suite</th><th>Passed</th><th>Failed</th><th>Skipped</th><th>testng.xml</th></tr>
|
||||
<tr align='center' class='invocation-failed'><td><em>Total</em></td><td><em>0</em></td><td><em>0</em></td><td><em>0</em></td><td> </td></tr>
|
||||
<tr align='center' class='invocation-failed'><td><a href='Default suite/index.html'>Default suite</a></td>
|
||||
<td>0</td><td>0</td><td>0</td><td><a href='Default suite/testng.xml.html'>Link</a></td></tr></table></body></html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1019 B |
Binary file not shown.
|
Before Width: | Height: | Size: 967 B |
@@ -1,309 +0,0 @@
|
||||
body {
|
||||
margin: 0px 0px 5px 5px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.navigator-selected {
|
||||
background: #ffa500;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
bottom: 0;
|
||||
left: 400px;
|
||||
right: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.navigator-root {
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.suite {
|
||||
margin: 0px 10px 10px 0px;
|
||||
background-color: #fff8dc;
|
||||
}
|
||||
|
||||
.suite-name {
|
||||
padding-left: 10px;
|
||||
font-size: 25px;
|
||||
font-family: Times;
|
||||
}
|
||||
|
||||
.main-panel-header {
|
||||
padding: 5px;
|
||||
background-color: #9FB4D9; //afeeee;
|
||||
font-family: monospace;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.main-panel-content {
|
||||
padding: 5px;
|
||||
margin-bottom: 10px;
|
||||
background-color: #DEE8FC; //d0ffff;
|
||||
}
|
||||
|
||||
.rounded-window {
|
||||
border-radius: 10px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.rounded-window-top {
|
||||
border-top-right-radius: 10px 10px;
|
||||
border-top-left-radius: 10px 10px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.light-rounded-window-top {
|
||||
border-top-right-radius: 10px 10px;
|
||||
border-top-left-radius: 10px 10px;
|
||||
}
|
||||
|
||||
.rounded-window-bottom {
|
||||
border-style: solid;
|
||||
border-width: 0px 1px 1px 1px;
|
||||
border-bottom-right-radius: 10px 10px;
|
||||
border-bottom-left-radius: 10px 10px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.method-name {
|
||||
font-size: 12px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.method-content {
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 1px 0px;
|
||||
margin-bottom: 10;
|
||||
padding-bottom: 5px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.parameters {
|
||||
font-size: 14px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.stack-trace {
|
||||
white-space: pre;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-top: 0px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.testng-xml {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.method-list-content {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.navigator-suite-content {
|
||||
margin-left: 10px;
|
||||
font: 12px 'Lucida Grande';
|
||||
}
|
||||
|
||||
.suite-section-title {
|
||||
margin-top: 10px;
|
||||
width: 80%;
|
||||
border-style: solid;
|
||||
border-width: 1px 0px 0px 0px;
|
||||
font-family: Times;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.suite-section-content {
|
||||
list-style-image: url(bullet_point.png);
|
||||
}
|
||||
|
||||
.top-banner-root {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 45px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 5px;
|
||||
margin: 0px 0px 5px 0px;
|
||||
background-color: #0066ff;
|
||||
font-family: Times;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.top-banner-title-font {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.test-name {
|
||||
font-family: 'Lucida Grande';
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.suite-icon {
|
||||
padding: 5px;
|
||||
float: right;
|
||||
height: 20;
|
||||
}
|
||||
|
||||
.test-group {
|
||||
font: 20px 'Lucida Grande';
|
||||
margin: 5px 5px 10px 5px;
|
||||
border-width: 0px 0px 1px 0px;
|
||||
border-style: solid;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.test-group-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.method-in-group {
|
||||
font-size: 16px;
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
table.google-visualization-table-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.reporter-method-name {
|
||||
font-size: 14px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.reporter-method-output-div {
|
||||
padding: 5px;
|
||||
margin: 0px 0px 5px 20px;
|
||||
font-size: 12px;
|
||||
font-family: monospace;
|
||||
border-width: 0px 0px 0px 1px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.ignored-class-div {
|
||||
font-size: 14px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.ignored-methods-div {
|
||||
padding: 5px;
|
||||
margin: 0px 0px 5px 20px;
|
||||
font-size: 12px;
|
||||
font-family: monospace;
|
||||
border-width: 0px 0px 0px 1px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.border-failed {
|
||||
border-top-left-radius: 10px 10px;
|
||||
border-bottom-left-radius: 10px 10px;
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 0px 10px;
|
||||
border-color: #f00;
|
||||
}
|
||||
|
||||
.border-skipped {
|
||||
border-top-left-radius: 10px 10px;
|
||||
border-bottom-left-radius: 10px 10px;
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 0px 10px;
|
||||
border-color: #edc600;
|
||||
}
|
||||
|
||||
.border-passed {
|
||||
border-top-left-radius: 10px 10px;
|
||||
border-bottom-left-radius: 10px 10px;
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 0px 10px;
|
||||
border-color: #19f52d;
|
||||
}
|
||||
|
||||
.times-div {
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.suite-total-time {
|
||||
font: 16px 'Lucida Grande';
|
||||
}
|
||||
|
||||
.configuration-suite {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.configuration-test {
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
.configuration-class {
|
||||
margin-left: 60px;
|
||||
}
|
||||
|
||||
.configuration-method {
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
.test-method {
|
||||
margin-left: 100px;
|
||||
}
|
||||
|
||||
.chronological-class {
|
||||
background-color: #0ccff;
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 1px 1px;
|
||||
}
|
||||
|
||||
.method-start {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.chronological-class-name {
|
||||
padding: 0px 0px 0px 5px;
|
||||
color: #008;
|
||||
}
|
||||
|
||||
.after, .before, .test-method {
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.navigator-suite-header {
|
||||
font-size: 22px;
|
||||
margin: 0px 10px 5px 0px;
|
||||
background-color: #deb887;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.collapse-all-icon {
|
||||
padding: 5px;
|
||||
float: right;
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
$(document).ready(function() {
|
||||
$('a.navigator-link').click(function() {
|
||||
// Extract the panel for this link
|
||||
var panel = getPanelName($(this));
|
||||
|
||||
// Mark this link as currently selected
|
||||
$('.navigator-link').parent().removeClass('navigator-selected');
|
||||
$(this).parent().addClass('navigator-selected');
|
||||
|
||||
showPanel(panel);
|
||||
});
|
||||
|
||||
installMethodHandlers('failed');
|
||||
installMethodHandlers('skipped');
|
||||
installMethodHandlers('passed', true); // hide passed methods by default
|
||||
|
||||
$('a.method').click(function() {
|
||||
showMethod($(this));
|
||||
return false;
|
||||
});
|
||||
|
||||
// Hide all the panels and display the first one (do this last
|
||||
// to make sure the click() will invoke the listeners)
|
||||
$('.panel').hide();
|
||||
$('.navigator-link').first().click();
|
||||
|
||||
// Collapse/expand the suites
|
||||
$('a.collapse-all-link').click(function() {
|
||||
var contents = $('.navigator-suite-content');
|
||||
if (contents.css('display') == 'none') {
|
||||
contents.show();
|
||||
} else {
|
||||
contents.hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// The handlers that take care of showing/hiding the methods
|
||||
function installMethodHandlers(name, hide) {
|
||||
function getContent(t) {
|
||||
return $('.method-list-content.' + name + "." + t.attr('panel-name'));
|
||||
}
|
||||
|
||||
function getHideLink(t, name) {
|
||||
var s = 'a.hide-methods.' + name + "." + t.attr('panel-name');
|
||||
return $(s);
|
||||
}
|
||||
|
||||
function getShowLink(t, name) {
|
||||
return $('a.show-methods.' + name + "." + t.attr('panel-name'));
|
||||
}
|
||||
|
||||
function getMethodPanelClassSel(element, name) {
|
||||
var panelName = getPanelName(element);
|
||||
var sel = '.' + panelName + "-class-" + name;
|
||||
return $(sel);
|
||||
}
|
||||
|
||||
$('a.hide-methods.' + name).click(function() {
|
||||
var w = getContent($(this));
|
||||
w.hide();
|
||||
getHideLink($(this), name).hide();
|
||||
getShowLink($(this), name).show();
|
||||
getMethodPanelClassSel($(this), name).hide();
|
||||
});
|
||||
|
||||
$('a.show-methods.' + name).click(function() {
|
||||
var w = getContent($(this));
|
||||
w.show();
|
||||
getHideLink($(this), name).show();
|
||||
getShowLink($(this), name).hide();
|
||||
showPanel(getPanelName($(this)));
|
||||
getMethodPanelClassSel($(this), name).show();
|
||||
});
|
||||
|
||||
if (hide) {
|
||||
$('a.hide-methods.' + name).click();
|
||||
} else {
|
||||
$('a.show-methods.' + name).click();
|
||||
}
|
||||
}
|
||||
|
||||
function getHashForMethod(element) {
|
||||
return element.attr('hash-for-method');
|
||||
}
|
||||
|
||||
function getPanelName(element) {
|
||||
return element.attr('panel-name');
|
||||
}
|
||||
|
||||
function showPanel(panelName) {
|
||||
$('.panel').hide();
|
||||
var panel = $('.panel[panel-name="' + panelName + '"]');
|
||||
panel.show();
|
||||
}
|
||||
|
||||
function showMethod(element) {
|
||||
var hashTag = getHashForMethod(element);
|
||||
var panelName = getPanelName(element);
|
||||
showPanel(panelName);
|
||||
var current = document.location.href;
|
||||
var base = current.substring(0, current.indexOf('#'))
|
||||
document.location.href = base + '#' + hashTag;
|
||||
var newPosition = $(document).scrollTop() - 65;
|
||||
$(document).scrollTop(newPosition);
|
||||
}
|
||||
|
||||
function drawTable() {
|
||||
for (var i = 0; i < suiteTableInitFunctions.length; i++) {
|
||||
window[suiteTableInitFunctions[i]]();
|
||||
}
|
||||
|
||||
for (var k in window.suiteTableData) {
|
||||
var v = window.suiteTableData[k];
|
||||
var div = v.tableDiv;
|
||||
var data = v.tableData
|
||||
var table = new google.visualization.Table(document.getElementById(div));
|
||||
table.draw(data, {
|
||||
showRowNumber : false
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testng-results skipped="0" failed="0" total="0" passed="0">
|
||||
<reporter-output>
|
||||
</reporter-output>
|
||||
<suite name="Default suite" duration-ms="5" started-at="2014-12-08T21:07:11Z" finished-at="2014-12-08T21:07:11Z">
|
||||
<groups>
|
||||
</groups>
|
||||
<test name="Default test" duration-ms="5" started-at="2014-12-08T21:07:11Z" finished-at="2014-12-08T21:07:11Z">
|
||||
</test> <!-- Default test -->
|
||||
</suite> <!-- Default suite -->
|
||||
</testng-results>
|
||||
@@ -1,9 +0,0 @@
|
||||
.invocation-failed, .test-failed { background-color: #DD0000; }
|
||||
.invocation-percent, .test-percent { background-color: #006600; }
|
||||
.invocation-passed, .test-passed { background-color: #00AA00; }
|
||||
.invocation-skipped, .test-skipped { background-color: #CCCC00; }
|
||||
|
||||
.main-page {
|
||||
font-size: x-large;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.2.0</version>
|
||||
<version>0.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>crypto-api</artifactId>
|
||||
<name>Cryptomator cryptographic module API</name>
|
||||
@@ -22,19 +22,9 @@
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,3 +1,11 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package org.cryptomator.crypto;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
@@ -15,11 +15,31 @@ import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.DirectoryStream.Filter;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.cryptomator.crypto.exceptions.DecryptFailedException;
|
||||
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
|
||||
import org.cryptomator.crypto.exceptions.WrongPasswordException;
|
||||
|
||||
/**
|
||||
* Provides access to cryptographic functions. All methods are threadsafe.
|
||||
*/
|
||||
public interface Cryptor extends SensitiveDataSwipeListener {
|
||||
|
||||
/**
|
||||
* Encrypts the current masterKey with the given password and writes the result to the given output stream.
|
||||
*/
|
||||
void encryptMasterKey(OutputStream out, CharSequence password) throws IOException;
|
||||
|
||||
/**
|
||||
* Reads the encrypted masterkey from the given input stream and decrypts it with the given password.
|
||||
*
|
||||
* @throws DecryptFailedException If the decryption failed for various reasons (including wrong password).
|
||||
* @throws WrongPasswordException If the provided password was wrong. Note: Sometimes the algorithm itself fails due to a wrong
|
||||
* password. In this case a DecryptFailedException will be thrown.
|
||||
* @throws UnsupportedKeyLengthException If the masterkey has been encrypted with a higher key length than supported by the system. In
|
||||
* this case Java JCE needs to be installed.
|
||||
*/
|
||||
void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException;
|
||||
|
||||
/**
|
||||
* Encrypts each plaintext path component for its own.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package org.cryptomator.crypto;
|
||||
|
||||
/**
|
||||
* Optional monitoring interface. If a cryptor implements this interface, it counts bytes de- and encrypted in a thread-safe manner.
|
||||
*/
|
||||
public interface CryptorIOSampling {
|
||||
|
||||
/**
|
||||
* @return Number of encrypted bytes since the last reset.
|
||||
*/
|
||||
Long pollEncryptedBytes(boolean resetCounter);
|
||||
|
||||
/**
|
||||
* @return Number of decrypted bytes since the last reset.
|
||||
*/
|
||||
Long pollDecryptedBytes(boolean resetCounter);
|
||||
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package org.cryptomator.crypto;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package org.cryptomator.crypto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.DirectoryStream.Filter;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.exceptions.DecryptFailedException;
|
||||
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
|
||||
import org.cryptomator.crypto.exceptions.WrongPasswordException;
|
||||
|
||||
public class SamplingDecorator implements Cryptor, CryptorIOSampling {
|
||||
|
||||
private final Cryptor cryptor;
|
||||
private final AtomicLong encryptedBytes;
|
||||
private final AtomicLong decryptedBytes;
|
||||
|
||||
private SamplingDecorator(Cryptor cryptor) {
|
||||
this.cryptor = cryptor;
|
||||
encryptedBytes = new AtomicLong();
|
||||
decryptedBytes = new AtomicLong();
|
||||
}
|
||||
|
||||
public static Cryptor decorate(Cryptor cryptor) {
|
||||
return new SamplingDecorator(cryptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swipeSensitiveData() {
|
||||
cryptor.swipeSensitiveData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long pollEncryptedBytes(boolean resetCounter) {
|
||||
if (resetCounter) {
|
||||
return encryptedBytes.getAndSet(0);
|
||||
} else {
|
||||
return encryptedBytes.get();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long pollDecryptedBytes(boolean resetCounter) {
|
||||
if (resetCounter) {
|
||||
return decryptedBytes.getAndSet(0);
|
||||
} else {
|
||||
return decryptedBytes.get();
|
||||
}
|
||||
}
|
||||
|
||||
/* Cryptor */
|
||||
|
||||
@Override
|
||||
public void encryptMasterKey(OutputStream out, CharSequence password) throws IOException {
|
||||
cryptor.encryptMasterKey(out, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException {
|
||||
cryptor.decryptMasterKey(in, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encryptPath(String cleartextPath, char encryptedPathSep, char cleartextPathSep, CryptorIOSupport ioSupport) {
|
||||
encryptedBytes.addAndGet(StringUtils.length(cleartextPath));
|
||||
return cryptor.encryptPath(cleartextPath, encryptedPathSep, cleartextPathSep, ioSupport);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decryptPath(String encryptedPath, char encryptedPathSep, char cleartextPathSep, CryptorIOSupport ioSupport) {
|
||||
decryptedBytes.addAndGet(StringUtils.length(encryptedPath));
|
||||
return cryptor.decryptPath(encryptedPath, encryptedPathSep, cleartextPathSep, ioSupport);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException {
|
||||
return cryptor.decryptedContentLength(encryptedFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException {
|
||||
final OutputStream countingInputStream = new CountingOutputStream(decryptedBytes, plaintextFile);
|
||||
return cryptor.decryptedFile(encryptedFile, countingInputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long encryptFile(InputStream plaintextFile, SeekableByteChannel encryptedFile) throws IOException {
|
||||
final InputStream countingInputStream = new CountingInputStream(encryptedBytes, plaintextFile);
|
||||
return cryptor.encryptFile(countingInputStream, encryptedFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter<Path> getPayloadFilesFilter() {
|
||||
return cryptor.getPayloadFilesFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSensitiveDataSwipeListener(SensitiveDataSwipeListener listener) {
|
||||
cryptor.addSensitiveDataSwipeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSensitiveDataSwipeListener(SensitiveDataSwipeListener listener) {
|
||||
cryptor.removeSensitiveDataSwipeListener(listener);
|
||||
}
|
||||
|
||||
private class CountingInputStream extends InputStream {
|
||||
|
||||
private final InputStream in;
|
||||
private final AtomicLong counter;
|
||||
|
||||
private CountingInputStream(AtomicLong counter, InputStream in) {
|
||||
this.in = in;
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int count = in.read();
|
||||
counter.addAndGet(count);
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int count = in.read(b, off, len);
|
||||
counter.addAndGet(count);
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class CountingOutputStream extends OutputStream {
|
||||
|
||||
private final OutputStream out;
|
||||
private final AtomicLong counter;
|
||||
|
||||
private CountingOutputStream(AtomicLong counter, OutputStream out) {
|
||||
this.out = out;
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
counter.incrementAndGet();
|
||||
out.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
counter.addAndGet(len);
|
||||
out.write(b, off, len);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package org.cryptomator.crypto;
|
||||
|
||||
public interface SensitiveDataSwipeListener {
|
||||
|
||||
16
main/pom.xml
16
main/pom.xml
@@ -7,7 +7,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.2.0</version>
|
||||
<version>0.3.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator</name>
|
||||
|
||||
@@ -141,5 +141,19 @@
|
||||
<module>core</module>
|
||||
<module>ui</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.2.0</version>
|
||||
<version>0.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>ui</artifactId>
|
||||
<name>Cryptomator GUI</name>
|
||||
@@ -60,16 +60,6 @@
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
|
||||
@@ -132,7 +132,6 @@ public class InitializeController implements Initializable {
|
||||
OutputStream masterKeyOutputStream = null;
|
||||
try {
|
||||
masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
|
||||
directory.getCryptor().randomizeMasterKey();
|
||||
directory.getCryptor().encryptMasterKey(masterKeyOutputStream, password);
|
||||
encryptExistingContents();
|
||||
directory.getCryptor().swipeSensitiveData();
|
||||
@@ -152,7 +151,7 @@ public class InitializeController implements Initializable {
|
||||
IOUtils.closeQuietly(masterKeyOutputStream);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean isDirectoryEmpty() {
|
||||
try {
|
||||
final DirectoryStream<Path> dirContents = Files.newDirectoryStream(directory.getPath());
|
||||
@@ -162,22 +161,22 @@ public class InitializeController implements Initializable {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean shouldEncryptExistingFiles() {
|
||||
final Alert alert = new Alert(AlertType.CONFIRMATION);
|
||||
alert.setTitle(localization.getString("initialize.alert.directoryIsNotEmpty.title"));
|
||||
alert.setHeaderText(localization.getString("initialize.alert.directoryIsNotEmpty.header"));
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(localization.getString("initialize.alert.directoryIsNotEmpty.content"));
|
||||
|
||||
final Optional<ButtonType> result = alert.showAndWait();
|
||||
return ButtonType.OK.equals(result.get());
|
||||
}
|
||||
|
||||
|
||||
private void encryptExistingContents() throws IOException {
|
||||
final FileVisitor<Path> visitor = new EncryptingFileVisitor(directory.getPath(), directory.getCryptor(), this::shouldEncryptExistingFile);
|
||||
Files.walkFileTree(directory.getPath(), visitor);
|
||||
}
|
||||
|
||||
|
||||
private boolean shouldEncryptExistingFile(Path path) {
|
||||
final String name = path.getFileName().toString();
|
||||
return !directory.getPath().equals(path) && !name.endsWith(Aes256Cryptor.BASIC_FILE_EXT) && !name.endsWith(Aes256Cryptor.METADATA_FILE_EXT) && !name.endsWith(Aes256Cryptor.MASTERKEY_FILE_EXT);
|
||||
|
||||
@@ -11,26 +11,44 @@ package org.cryptomator.ui;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart.Data;
|
||||
import javafx.scene.chart.XYChart.Series;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import org.cryptomator.crypto.CryptorIOSampling;
|
||||
import org.cryptomator.ui.model.Directory;
|
||||
|
||||
public class UnlockedController implements Initializable {
|
||||
|
||||
private static final int IO_SAMPLING_STEPS = 100;
|
||||
private static final double IO_SAMPLING_INTERVAL = 0.25;
|
||||
private ResourceBundle rb;
|
||||
private LockListener listener;
|
||||
private Directory directory;
|
||||
private Timeline ioAnimation;
|
||||
|
||||
@FXML
|
||||
private Label messageLabel;
|
||||
|
||||
@FXML
|
||||
private LineChart<Number, Number> ioGraph;
|
||||
|
||||
@FXML
|
||||
private NumberAxis xAxis;
|
||||
|
||||
@Override
|
||||
public void initialize(URL url, ResourceBundle rb) {
|
||||
this.rb = rb;
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -43,6 +61,60 @@ public class UnlockedController implements Initializable {
|
||||
}
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// IO Graph
|
||||
// ****************************************
|
||||
|
||||
private void startIoSampling(final CryptorIOSampling sampler) {
|
||||
final Series<Number, Number> decryptedBytes = new Series<>();
|
||||
decryptedBytes.setName("decrypted");
|
||||
final Series<Number, Number> encryptedBytes = new Series<>();
|
||||
encryptedBytes.setName("encrypted");
|
||||
|
||||
ioGraph.getData().add(decryptedBytes);
|
||||
ioGraph.getData().add(encryptedBytes);
|
||||
|
||||
ioAnimation = new Timeline();
|
||||
ioAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(IO_SAMPLING_INTERVAL), new IoSamplingAnimationHandler(sampler, decryptedBytes, encryptedBytes)));
|
||||
ioAnimation.setCycleCount(Animation.INDEFINITE);
|
||||
ioAnimation.play();
|
||||
}
|
||||
|
||||
private class IoSamplingAnimationHandler implements EventHandler<ActionEvent> {
|
||||
|
||||
private static final double BYTES_TO_MEGABYTES_FACTOR = IO_SAMPLING_INTERVAL / 1024.0 / 1024.0;
|
||||
private final CryptorIOSampling sampler;
|
||||
private final Series<Number, Number> decryptedBytes;
|
||||
private final Series<Number, Number> encryptedBytes;
|
||||
private int step = 0;
|
||||
|
||||
public IoSamplingAnimationHandler(CryptorIOSampling sampler, Series<Number, Number> decryptedBytes, Series<Number, Number> encryptedBytes) {
|
||||
this.sampler = sampler;
|
||||
this.decryptedBytes = decryptedBytes;
|
||||
this.encryptedBytes = encryptedBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
step++;
|
||||
|
||||
final double decryptedMb = sampler.pollDecryptedBytes(true) * BYTES_TO_MEGABYTES_FACTOR;
|
||||
decryptedBytes.getData().add(new Data<Number, Number>(step, decryptedMb));
|
||||
if (decryptedBytes.getData().size() > IO_SAMPLING_STEPS) {
|
||||
decryptedBytes.getData().remove(0);
|
||||
}
|
||||
|
||||
final double encrypteddMb = sampler.pollEncryptedBytes(true) * BYTES_TO_MEGABYTES_FACTOR;
|
||||
encryptedBytes.getData().add(new Data<Number, Number>(step, encrypteddMb));
|
||||
if (encryptedBytes.getData().size() > IO_SAMPLING_STEPS) {
|
||||
encryptedBytes.getData().remove(0);
|
||||
}
|
||||
|
||||
xAxis.setLowerBound(step - IO_SAMPLING_STEPS);
|
||||
xAxis.setUpperBound(step);
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Directory getDirectory() {
|
||||
@@ -53,6 +125,12 @@ public class UnlockedController implements Initializable {
|
||||
this.directory = directory;
|
||||
final String msg = String.format(rb.getString("unlocked.messageLabel.runningOnPort"), directory.getServer().getPort());
|
||||
messageLabel.setText(msg);
|
||||
|
||||
if (directory.getCryptor() instanceof CryptorIOSampling) {
|
||||
startIoSampling((CryptorIOSampling) directory.getCryptor());
|
||||
} else {
|
||||
ioGraph.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
public LockListener getListener() {
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.Cryptor;
|
||||
import org.cryptomator.crypto.SamplingDecorator;
|
||||
import org.cryptomator.crypto.aes256.Aes256Cryptor;
|
||||
import org.cryptomator.ui.MainApplication;
|
||||
import org.cryptomator.ui.util.MasterKeyFilter;
|
||||
@@ -26,7 +28,7 @@ public class Directory implements Serializable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Directory.class);
|
||||
|
||||
private final WebDAVServer server = new WebDAVServer();
|
||||
private final Aes256Cryptor cryptor = new Aes256Cryptor();
|
||||
private final Cryptor cryptor = SamplingDecorator.decorate(new Aes256Cryptor());
|
||||
private final Path path;
|
||||
private boolean unlocked;
|
||||
private String unmountCommand;
|
||||
@@ -97,7 +99,7 @@ public class Directory implements Serializable {
|
||||
return path.getFileName().toString();
|
||||
}
|
||||
|
||||
public Aes256Cryptor getCryptor() {
|
||||
public Cryptor getCryptor() {
|
||||
return cryptor;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,7 @@ initialize.label.username=Username
|
||||
initialize.label.password=Password
|
||||
initialize.label.retypePassword=Retype password
|
||||
initialize.button.ok=Create vault
|
||||
initialize.alert.directoryIsNotEmpty.title=Confirm
|
||||
initialize.alert.directoryIsNotEmpty.header=The chosen directory is not empty.
|
||||
initialize.alert.directoryIsNotEmpty.title=The chosen directory is not empty
|
||||
initialize.alert.directoryIsNotEmpty.content=All existing files inside this directory will get encrypted. Continue?
|
||||
|
||||
|
||||
@@ -37,6 +36,7 @@ unlock.messageLabel.startServerFailed=Starting WebDAV server failed.
|
||||
# unlocked.fxml
|
||||
unlocked.messageLabel.runningOnPort=Vault is accessible via WebDAV on local port %d.
|
||||
unlocked.button.lock=Lock vault
|
||||
unlocked.ioGraph.yAxis.label=Throughput (MiB/s)
|
||||
|
||||
|
||||
# tray icon
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import java.lang.String?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.chart.LineChart?>
|
||||
<?import javafx.scene.chart.NumberAxis?>
|
||||
|
||||
|
||||
<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" fx:controller="org.cryptomator.ui.UnlockedController" xmlns:fx="http://javafx.com/fxml">
|
||||
@@ -32,6 +34,12 @@
|
||||
|
||||
<!-- Row 1 -->
|
||||
<Button text="%unlocked.button.lock" defaultButton="true" GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#closeVault" focusTraversable="false"/>
|
||||
|
||||
<!-- Row 2 -->
|
||||
<LineChart fx:id="ioGraph" GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" animated="false" createSymbols="false" prefHeight="300.0" legendVisible="true" legendSide="BOTTOM" verticalZeroLineVisible="false" verticalGridLinesVisible="false" horizontalGridLinesVisible="true">
|
||||
<xAxis><NumberAxis fx:id="xAxis" forceZeroInRange="false" tickMarkVisible="false" minorTickVisible="false" tickLabelsVisible="false" autoRanging="false"/></xAxis>
|
||||
<yAxis><NumberAxis label="%unlocked.ioGraph.yAxis.label" autoRanging="true" forceZeroInRange="true" /></yAxis>
|
||||
</LineChart>
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user