Compare commits

..

65 Commits

Author SHA1 Message Date
Zlatin Balevsky
549e8c2d98 Release 0.4.12 2019-09-22 16:55:04 +01:00
Zlatin Balevsky
b54d24db0d new update server destination 2019-09-22 16:47:35 +01:00
Zlatin Balevsky
fa12e84345 stronger sig type 2019-09-22 16:23:01 +01:00
Zlatin Balevsky
6430ff2691 bump i2p libs version 2019-09-22 16:13:12 +01:00
Zlatin Balevsky
591313c81c point to the pkg project 2019-09-20 21:09:53 +01:00
Zlatin Balevsky
ce7b6a0c65 change to gasp AA font table, try metal lnf if the others fail 2019-09-16 15:06:45 +01:00
Zlatin Balevsky
5c4d4c4580 embedded router will not work without reseed certificates, so remove it 2019-09-16 15:04:34 +01:00
Zlatin Balevsky
4cb864ff9f update version 2019-09-16 15:03:20 +01:00
Zlatin Balevsky
417675ad07 update dark_trion's hostcache address 2019-07-22 21:48:29 +01:00
Zlatin Balevsky
9513e5ba3c update todo 2019-07-20 13:15:44 +01:00
Zlatin Balevsky
85610cf169 add new host-cache 2019-07-15 22:05:09 +01:00
Zlatin Balevsky
e8322384b8 Release 0.4.11 2019-07-15 14:28:21 +01:00
Zlatin Balevsky
179279ed30 Merge branch 'master' of https://github.com/zlatinb/muwire 2019-07-14 06:19:18 +01:00
Zlatin Balevsky
ae79f0fded Clear Done button, thanks to Aegon 2019-07-14 06:19:05 +01:00
Zlatin Balevsky
ed878b3762 Merge pull request #11 from zetok/readme
Add info about the default I2CP port to README.md
2019-07-12 09:17:24 +01:00
Zetok Zalbavar
623cca0ef2 Add info about the default I2CP port to README.md
Also:
 - improved formatting a bit
 - removed trailing whitespaces
2019-07-12 07:28:12 +01:00
Zlatin Balevsky
eaa883c3ba count duplicate files towards total in Uploads panel 2019-07-11 23:28:12 +01:00
Zlatin Balevsky
7ae8076865 disable webui for now 2019-07-11 22:29:47 +01:00
Zlatin Balevsky
b1aa92661c do not pack200 some jars because of duplicate entries 2019-07-11 20:42:24 +01:00
Zlatin Balevsky
9ed94c8376 do not include tomcat runtime 2019-07-11 20:41:57 +01:00
Zlatin Balevsky
fa6aea1abe attempt to produce an I2P plugin 2019-07-11 19:49:04 +01:00
Zlatin Balevsky
0de84e704b hello webui 2019-07-11 18:34:27 +01:00
Zlatin Balevsky
a767dda044 add empty grails project for a web ui 2019-07-11 17:56:42 +01:00
Zlatin Balevsky
56e9235d7b avoid FS call to get file length 2019-07-11 15:28:25 +01:00
Zlatin Balevsky
2fba9a74ce persist files.json every minute 2019-07-11 14:32:57 +01:00
Zlatin Balevsky
2bb6826906 canonicalize all files before they enter FileManager and do not look for absolute path on persistence 2019-07-11 14:32:12 +01:00
Zlatin Balevsky
9f339629a9 remove unnecessary canonicalization 2019-07-11 11:58:20 +01:00
Zlatin Balevsky
58d4207f94 Release 0.4.10 2019-07-11 05:09:05 +01:00
Zlatin Balevsky
32577a28dc some download stats 2019-07-11 05:00:25 +01:00
Zlatin Balevsky
f7b43304d4 use split pane in downloads tab as well 2019-07-11 03:57:49 +01:00
Zlatin Balevsky
dcbe09886d split pane instead of gridlayout 2019-07-11 03:48:05 +01:00
Zlatin Balevsky
5a54b2dcda shift focus to search pane on search 2019-07-10 22:33:21 +01:00
Zlatin Balevsky
581293b24f column sizes 2019-07-10 22:27:07 +01:00
Zlatin Balevsky
cd072b9f76 enable/disable download button correctly 2019-07-10 22:23:20 +01:00
Zlatin Balevsky
6b74fc5956 fix trust/distrust buttons 2019-07-10 22:17:32 +01:00
Zlatin Balevsky
3de2f872bb show results per sender 2019-07-10 22:08:18 +01:00
Zlatin Balevsky
fcde917d08 fix context menu and double-click 2019-07-10 21:26:13 +01:00
Zlatin Balevsky
4ded065010 move buttons onto search result tab 2019-07-10 21:23:00 +01:00
Zlatin Balevsky
18a1c7091a move downloads to their own pane 2019-07-10 20:54:45 +01:00
Zlatin Balevsky
46aee19f80 disable the button of the currently open pane 2019-07-10 20:37:09 +01:00
Zlatin Balevsky
92dd7064c6 Release 0.4.9 2019-07-10 12:02:36 +01:00
Zlatin Balevsky
b2e4dda677 rearrange tables 2019-07-10 11:55:06 +01:00
Zlatin Balevsky
e77a2c8961 clear hits table on refresh 2019-07-09 21:42:52 +01:00
Zlatin Balevsky
ee2fd2ef68 single hit per search uuid 2019-07-09 21:22:31 +01:00
Zlatin Balevsky
3f95d2bf1d trust and distrust buttons 2019-07-09 21:15:08 +01:00
Zlatin Balevsky
1390983732 populate hits table 2019-07-09 21:05:49 +01:00
Zlatin Balevsky
ce660cefe9 deleting of rules 2019-07-09 20:50:07 +01:00
Zlatin Balevsky
72b81eb886 fix matching 2019-07-09 20:27:28 +01:00
Zlatin Balevsky
57d593a68a persist watched keywords and regexes 2019-07-09 20:11:29 +01:00
Zlatin Balevsky
39a81a3376 hook up rule creation 2019-07-09 19:53:40 +01:00
Zlatin Balevsky
fd0bf17c24 add ability to unregister event listeners 2019-07-09 19:53:08 +01:00
Zlatin Balevsky
ac12bff69b wip on content control panel ui 2019-07-09 19:20:06 +01:00
Zlatin Balevsky
feef773bac hook up content control panel to rest of UI 2019-07-09 17:55:36 +01:00
Zlatin Balevsky
239d8f12a7 wip on core side of content management 2019-07-09 17:13:09 +01:00
Zlatin Balevsky
8bbc61a7cb add settings for watched keywords and regexes 2019-07-09 16:50:51 +01:00
Zlatin Balevsky
7f31c4477f matchers for keywords 2019-07-09 11:47:55 +01:00
Zlatin Balevsky
6bad67c1bf Release 0.4.8 2019-07-08 18:30:19 +01:00
Zlatin Balevsky
c76e6dc99f Merge pull request #9 from zetok/backticks
Replace deprecated backticks with $() for command substitution
2019-07-08 08:24:37 +01:00
Zetok Zalbavar
acf9db0db3 Replace deprecated backticks with $() for command substitution
Although it's a Bash FAQ, the point also applies to POSIX-compatible
shells: https://mywiki.wooledge.org/BashFAQ/082
2019-07-08 06:29:33 +01:00
Zlatin Balevsky
69b4f0b547 Add trust/distrust action from monitor window. Thanks Aegon 2019-07-07 15:31:21 +01:00
Zlatin Balevsky
80e165b505 fix download size in renderer, thanks Aegon 2019-07-07 11:17:56 +01:00
Zlatin Balevsky
bcce55b873 fix integer overflow 2019-07-07 10:58:39 +01:00
Zlatin Balevsky
d5c92560db fix integer overflow 2019-07-07 10:56:14 +01:00
Zlatin Balevsky
f827c1c9bf Home directories for different OSes 2019-07-07 09:14:13 +01:00
Zlatin Balevsky
88c5f1a02d Add GPG key link 2019-07-07 09:04:52 +01:00
121 changed files with 26677 additions and 174 deletions

View File

@@ -4,14 +4,14 @@ MuWire is an easy to use file-sharing program which offers anonymity using [I2P
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
The current stable release - 0.4.6 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
The current stable release - 0.4.11 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
### Building
You need JRE 8 or newer. After installing that and setting up the appropriate paths, just type
```
./gradlew clean assemble
./gradlew clean assemble
```
If you want to run the unit tests, type
@@ -21,11 +21,23 @@ If you want to run the unit tests, type
Some of the UI tests will fail because they haven't been written yet :-/
If you want to build binary bundles for Windows and Mac that do not depend on Java or I2P, see the https://github.com/zlatinb/muwire-pkg project
### Running
After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar MuWire-x.y.z.jar` in a terminal or command prompt.
After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar MuWire-x.y.z.jar` in a terminal or command prompt.
If you have an I2P router running on the same machine that is all you need to do. If you use a custom I2CP host and port, create a file `$HOME/.MuWire/i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there.
If you have an I2P router running on the same machine that is all you need to do. If you use a custom I2CP host and port, create a file `i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there. On Windows that file should go into `%HOME%\AppData\Roaming\MuWire`, on Mac into `$HOME/Library/Application Support/MuWire` and on Linux `$HOME/.MuWire`
If you do not have an I2P router, pass the following switch to the Java process: `-DembeddedRouter=true`. This will launch MuWire's embedded router. Be aware that this causes startup to take a lot longer.
[Default I2CP port]\: `7654`
### GPG Fingerprint
```
471B 9FD4 5517 A5ED 101F C57D A728 3207 2D52 5E41
```
You can find the full key at https://keybase.io/zlatinb
[Default I2CP port]: https://geti2p.net/en/docs/ports

View File

@@ -12,10 +12,6 @@ This reduces query traffic by not sending last hop queries to peers that definit
This helps with scalability
##### Content Control Panel
To allow every user to not route queries for content they do not like. This is mostly GUI work, the backend part is simple
##### Web UI, REST Interface, etc.
Basically any non-gui non-cli user interface
@@ -29,3 +25,4 @@ To enable parsing of metadata from known file types and the user editing it or a
* Wrapper of some kind for in-place upgrades
* Download file sequentially
* Multiple-selection download, Ctrl-A
* Automatic adjustment of number of I2P tunnels

View File

@@ -2,7 +2,7 @@ subprojects {
apply plugin: 'groovy'
dependencies {
compile 'net.i2p:i2p:0.9.41'
compile 'net.i2p:i2p:0.9.42'
compile 'org.codehaus.groovy:groovy-all:2.4.15'
}

View File

@@ -35,7 +35,7 @@ class Cli {
Core core
try {
core = new Core(props, home, "0.4.7")
core = new Core(props, home, "0.4.12")
} catch (Exception bad) {
bad.printStackTrace(System.out)
println "Failed to initialize core, exiting"

View File

@@ -53,7 +53,7 @@ class CliDownloader {
Core core
try {
core = new Core(props, home, "0.4.7")
core = new Core(props, home, "0.4.12")
} catch (Exception bad) {
bad.printStackTrace(System.out)
println "Failed to initialize core, exiting"

View File

@@ -2,9 +2,9 @@ apply plugin : 'application'
mainClassName = 'com.muwire.core.Core'
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
dependencies {
compile 'net.i2p:router:0.9.41'
compile 'net.i2p.client:mstreaming:0.9.41'
compile 'net.i2p.client:streaming:0.9.41'
compile 'net.i2p:router:0.9.42'
compile 'net.i2p.client:mstreaming:0.9.42'
compile 'net.i2p.client:streaming:0.9.42'
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
testCompile 'junit:junit:4.12'

View File

@@ -48,6 +48,8 @@ import com.muwire.core.trust.TrustSubscriptionEvent
import com.muwire.core.update.UpdateClient
import com.muwire.core.upload.UploadManager
import com.muwire.core.util.MuWireLogManager
import com.muwire.core.content.ContentControlEvent
import com.muwire.core.content.ContentManager
import groovy.util.logging.Log
import net.i2p.I2PAppContext
@@ -90,6 +92,7 @@ public class Core {
private final DirectoryWatcher directoryWatcher
final FileManager fileManager
final UploadManager uploadManager
final ContentManager contentManager
private final Router router
@@ -217,7 +220,7 @@ public class Core {
eventBus.register(SourceDiscoveredEvent.class, meshManager)
log.info "initializing persistence service"
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 15000, fileManager)
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 60000, fileManager)
eventBus.register(UILoadedEvent.class, persisterService)
log.info("initializing host cache")
@@ -289,6 +292,11 @@ public class Core {
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
eventBus.register(UILoadedEvent.class, trustSubscriber)
eventBus.register(TrustSubscriptionEvent.class, trustSubscriber)
log.info("initializing content manager")
contentManager = new ContentManager()
eventBus.register(ContentControlEvent.class, contentManager)
eventBus.register(QueryEvent.class, contentManager)
}
public void startServices() {
@@ -353,7 +361,7 @@ public class Core {
}
}
Core core = new Core(props, home, "0.4.7")
Core core = new Core(props, home, "0.4.12")
core.startServices()
// ... at the end, sleep or execute script

View File

@@ -48,4 +48,9 @@ class EventBus {
}
currentHandlers.add handler
}
synchronized void unregister(Class<? extends Event> eventType, def handler) {
log.info("Unregistering $handler for type $eventType")
handlers[eventType]?.remove(handler)
}
}

View File

@@ -28,6 +28,8 @@ class MuWireSettings {
int meshExpiration
boolean embeddedRouter
int inBw, outBw
Set<String> watchedKeywords
Set<String> watchedRegexes
MuWireSettings() {
this(new Properties())
@@ -54,11 +56,9 @@ class MuWireSettings {
inBw = Integer.valueOf(props.getProperty("inBw","256"))
outBw = Integer.valueOf(props.getProperty("outBw","128"))
watchedDirectories = new HashSet<>()
if (props.containsKey("watchedDirectories")) {
String[] encoded = props.getProperty("watchedDirectories").split(",")
encoded.each { watchedDirectories << DataUtil.readi18nString(Base64.decode(it)) }
}
watchedDirectories = readEncodedSet(props, "watchedDirectories")
watchedKeywords = readEncodedSet(props, "watchedKeywords")
watchedRegexes = readEncodedSet(props, "watchedRegexes")
trustSubscriptions = new HashSet<>()
if (props.containsKey("trustSubscriptions")) {
@@ -66,6 +66,8 @@ class MuWireSettings {
trustSubscriptions.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
}
}
}
void write(OutputStream out) throws IOException {
@@ -89,12 +91,9 @@ class MuWireSettings {
props.setProperty("inBw", String.valueOf(inBw))
props.setProperty("outBw", String.valueOf(outBw))
if (!watchedDirectories.isEmpty()) {
String encoded = watchedDirectories.stream().
map({Base64.encode(DataUtil.encodei18nString(it))}).
collect(Collectors.joining(","))
props.setProperty("watchedDirectories", encoded)
}
writeEncodedSet(watchedDirectories, "watchedDirectories", props)
writeEncodedSet(watchedKeywords, "watchedKeywords", props)
writeEncodedSet(watchedRegexes, "watchedRegexes", props)
if (!trustSubscriptions.isEmpty()) {
String encoded = trustSubscriptions.stream().
@@ -105,6 +104,24 @@ class MuWireSettings {
props.store(out, "")
}
private static Set<String> readEncodedSet(Properties props, String property) {
Set<String> rv = new HashSet<>()
if (props.containsKey(property)) {
String[] encoded = props.getProperty(property).split(",")
encoded.each { rv << DataUtil.readi18nString(Base64.decode(it)) }
}
rv
}
private static void writeEncodedSet(Set<String> set, String property, Properties props) {
if (set.isEmpty())
return
String encoded = set.stream().
map({Base64.encode(DataUtil.encodei18nString(it))}).
collect(Collectors.joining(","))
props.setProperty(property, encoded)
}
boolean isLeaf() {
isLeaf

View File

@@ -0,0 +1,9 @@
package com.muwire.core.content
import com.muwire.core.Event
class ContentControlEvent extends Event {
String term
boolean regex
boolean add
}

View File

@@ -0,0 +1,30 @@
package com.muwire.core.content
import java.util.concurrent.ConcurrentHashMap
import com.muwire.core.search.QueryEvent
import net.i2p.util.ConcurrentHashSet
class ContentManager {
Set<Matcher> matchers = new ConcurrentHashSet()
void onContentControlEvent(ContentControlEvent e) {
Matcher m
if (e.regex)
m = new RegexMatcher(e.term)
else
m = new KeywordMatcher(e.term)
if (e.add)
matchers.add(m)
else
matchers.remove(m)
}
void onQueryEvent(QueryEvent e) {
if (e.searchEvent.searchTerms == null)
return
matchers.each { it.process(e) }
}
}

View File

@@ -0,0 +1,36 @@
package com.muwire.core.content
class KeywordMatcher extends Matcher {
private final String keyword
KeywordMatcher(String keyword) {
this.keyword = keyword
}
@Override
protected boolean match(List<String> searchTerms) {
boolean found = false
searchTerms.each {
if (keyword == it)
found = true
}
found
}
@Override
public String getTerm() {
keyword
}
@Override
public int hashCode() {
keyword.hashCode()
}
@Override
public boolean equals(Object o) {
if (!(o instanceof KeywordMatcher))
return false
KeywordMatcher other = (KeywordMatcher) o
keyword.equals(other.keyword)
}
}

View File

@@ -0,0 +1,9 @@
package com.muwire.core.content
import com.muwire.core.Persona
class Match {
Persona persona
String [] keywords
long timestamp
}

View File

@@ -0,0 +1,20 @@
package com.muwire.core.content
import com.muwire.core.search.QueryEvent
abstract class Matcher {
final List<Match> matches = Collections.synchronizedList(new ArrayList<>())
final Set<UUID> uuids = new HashSet<>()
protected abstract boolean match(List<String> searchTerms);
public abstract String getTerm();
public void process(QueryEvent qe) {
def terms = qe.searchEvent.searchTerms
if (match(terms) && uuids.add(qe.searchEvent.uuid)) {
long now = System.currentTimeMillis()
matches << new Match(persona : qe.originator, keywords : terms, timestamp : now)
}
}
}

View File

@@ -0,0 +1,35 @@
package com.muwire.core.content
import java.util.regex.Pattern
import java.util.stream.Collectors
class RegexMatcher extends Matcher {
private final Pattern pattern
RegexMatcher(String pattern) {
this.pattern = Pattern.compile(pattern)
}
@Override
protected boolean match(List<String> keywords) {
String combined = keywords.join(" ")
return pattern.matcher(combined).find()
}
@Override
public String getTerm() {
pattern.pattern()
}
@Override
public int hashCode() {
pattern.pattern().hashCode()
}
@Override
public boolean equals(Object o) {
if (!(o instanceof RegexMatcher))
return false
RegexMatcher other = (RegexMatcher) o
pattern.pattern() == other.pattern.pattern()
}
}

View File

@@ -84,7 +84,7 @@ class DownloadSession {
log.info("will download piece $piece from position $position steal $steal")
long pieceStart = piece * pieceSize
long pieceStart = piece * ((long)pieceSize)
long end = Math.min(fileLength, pieceStart + pieceSize) - 1
long start = pieceStart + position
String root = Base64.encode(infoHash.getRoot())

View File

@@ -326,7 +326,7 @@ public class Downloader {
}
eventBus.publish(
new FileDownloadedEvent(
downloadedFile : new DownloadedFile(file, getInfoHash(), pieceSizePow2, successfulDestinations),
downloadedFile : new DownloadedFile(file.getCanonicalFile(), getInfoHash(), pieceSizePow2, successfulDestinations),
downloader : Downloader.this))
}

View File

@@ -136,8 +136,8 @@ class PersisterService extends Service {
private def toJson(File f, SharedFile sf) {
def json = [:]
json.file = Base64.encode DataUtil.encodei18nString(f.getCanonicalFile().toString())
json.length = f.length()
json.file = Base64.encode DataUtil.encodei18nString(f.toString())
json.length = sf.getCachedLength()
InfoHash ih = sf.getInfoHash()
json.infoHash = Base64.encode ih.getRoot()
json.pieceSize = sf.getPieceSize()

View File

@@ -6,8 +6,12 @@ class CacheServers {
private static final int TO_GIVE = 3
private static Set<Destination> CACHES = [
// zlatinb
new Destination("Wddh2E6FyyXBF7SvUYHKdN-vjf3~N6uqQWNeBDTM0P33YjiQCOsyedrjmDZmWFrXUJfJLWnCb5bnKezfk4uDaMyj~uvDG~yvLVcFgcPWSUd7BfGgym-zqcG1q1DcM8vfun-US7YamBlmtC6MZ2j-~Igqzmgshita8aLPCfNAA6S6e2UMjjtG7QIXlxpMec75dkHdJlVWbzrk9z8Qgru3YIk0UztYgEwDNBbm9wInsbHhr3HtAfa02QcgRVqRN2PnQXuqUJs7R7~09FZPEviiIcUpkY3FeyLlX1sgQFBeGeA96blaPvZNGd6KnNdgfLgMebx5SSxC-N4KZMSMBz5cgonQF3~m2HHFRSI85zqZNG5X9bJN85t80ltiv1W1es8ZnQW4es11r7MrvJNXz5bmSH641yJIvS6qI8OJJNpFVBIQSXLD-96TayrLQPaYw~uNZ-eXaE6G5dYhiuN8xHsFI1QkdaUaVZnvDGfsRbpS5GtpUbBDbyLkdPurG0i7dN1wAAAA"),
new Destination("JC63wJNOqSJmymkj4~UJWywBTvDGikKMoYP0HX2Wz9c5l3otXSkwnxWAFL4cKr~Ygh3BNNi2t93vuLIiI1W8AsE42kR~PwRx~Y-WvIHXR6KUejRmOp-n8WidtjKg9k4aDy428uSOedqXDxys5mpoeQXwDsv1CoPTTwnmb1GWFy~oTGIsCguCl~aJWGnqiKarPO3GJQ~ev-NbvAQzUfC3HeP1e6pdI5CGGjExahTCID5UjpJw8GaDXWlGmYWWH303Xu4x-vAHQy1dJLsOBCn8dZravsn5BKJk~j0POUon45CCx-~NYtaPe0Itt9cMdD2ciC76Rep1D0X0sm1SjlSs8sZ52KmF3oaLZ6OzgI9QLMIyBUrfi41sK5I0qTuUVBAkvW1xr~L-20dYJ9TrbOaOb2-vDIfKaxVi6xQOuhgQDiSBhd3qv2m0xGu-BM9DQYfNA0FdMjnZmqjmji9RMavzQSsVFIbQGLbrLepiEFlb7TseCK5UtRp8TxnG7L4gbYevBQAEAAcAAA==")
// sNL
new Destination("JC63wJNOqSJmymkj4~UJWywBTvDGikKMoYP0HX2Wz9c5l3otXSkwnxWAFL4cKr~Ygh3BNNi2t93vuLIiI1W8AsE42kR~PwRx~Y-WvIHXR6KUejRmOp-n8WidtjKg9k4aDy428uSOedqXDxys5mpoeQXwDsv1CoPTTwnmb1GWFy~oTGIsCguCl~aJWGnqiKarPO3GJQ~ev-NbvAQzUfC3HeP1e6pdI5CGGjExahTCID5UjpJw8GaDXWlGmYWWH303Xu4x-vAHQy1dJLsOBCn8dZravsn5BKJk~j0POUon45CCx-~NYtaPe0Itt9cMdD2ciC76Rep1D0X0sm1SjlSs8sZ52KmF3oaLZ6OzgI9QLMIyBUrfi41sK5I0qTuUVBAkvW1xr~L-20dYJ9TrbOaOb2-vDIfKaxVi6xQOuhgQDiSBhd3qv2m0xGu-BM9DQYfNA0FdMjnZmqjmji9RMavzQSsVFIbQGLbrLepiEFlb7TseCK5UtRp8TxnG7L4gbYevBQAEAAcAAA=="),
// dark_trion
new Destination("Gec9L29FVcQvYDgpcYuEYdltJn06PPoOWAcAM8Af-gDm~ehlrJcwlLXXs0hidq~yP2A0X7QcDi6i6shAfuEofTchxGJl8LRNqj9lio7WnB7cIixXWL~uCkD7Np5LMX0~akNX34oOb9RcBYVT2U5rFGJmJ7OtBv~IBkGeLhsMrqaCjahd0jdBO~QJ-t82ZKZhh044d24~JEfF9zSJxdBoCdAcXzryGNy7sYtFVDFsPKJudAxSW-UsSQiGw2~k-TxyF0r-iAt1IdzfNu8Lu0WPqLdhDYJWcPldx2PR5uJorI~zo~z3I5RX3NwzarlbD4nEP5s65ahPSfVCEkzmaJUBgP8DvBqlFaX89K4nGRYc7jkEjJ8cX4L6YPXUpTPWcfKkW259WdQY3YFh6x7rzijrGZewpczOLCrt-bZRYgDrUibmZxKZmNhy~lQu4gYVVjkz1i4tL~DWlhIc4y0x2vItwkYLArPPi~ejTnt-~Lhb7oPMXRcWa3UrwGKpFvGZY4NXBQAEAAcAAA==")
]
static List<Destination> getCacheServers() {

View File

@@ -3,5 +3,5 @@ package com.muwire.core.update
import net.i2p.data.Destination
class UpdateServers {
static final Destination UPDATE_SERVER = new Destination("pSWieSRB3czCl3Zz4WpKp4Z8tjv-05zbogRDS7SEnKcSdWOupVwjzQ92GsgQh1VqgoSRk1F8dpZOnHxxz5HFy9D7ri0uFdkMyXdSKoB7IgkkvCfTAyEmeaPwSYnurF3Zk7u286E7YG2rZkQZgJ77tow7ZS0mxFB7Z0Ti-VkZ9~GeGePW~howwNm4iSQACZA0DyTpI8iv5j4I0itPCQRgaGziob~Vfvjk49nd8N4jtaDGo9cEcafikVzQ2OgBgYWL6LRbrrItwuGqsDvITUHWaElUYIDhRQYUq8gYiUA6rwAJputfhFU0J7lIxFR9vVY7YzRvcFckfr0DNI4VQVVlPnRPkUxQa--BlldMaCIppWugjgKLwqiSiHywKpSMlBWgY2z1ry4ueEBo1WEP-mEf88wRk4cFQBCKtctCQnIG2GsnATqTl-VGUAsuzeNWZiFSwXiTy~gQ094yWx-K06fFZUDt4CMiLZVhGlixiInD~34FCRC9LVMtFcqiFB2M-Ql2AAAA")
static final Destination UPDATE_SERVER = new Destination("VJYAiCPZHNLraWvLkeRLxRiT4PHAqNqRO1nH240r7u1noBw8Pa~-lJOhKR7CccPkEN8ejSi4H6XjqKYLC8BKLVLeOgnAbedUVx81MV7DETPDdPEGV4RVu6YDFri7-tJOeqauGHxtlXT44YWuR69xKrTG3u4~iTWgxKnlBDht9Q3aVpSPFD2KqEizfVxolqXI0zmAZ2xMi8jfl0oe4GbgHrD9hR2FYj6yKfdqcUgHVobY4kDdJt-u31QqwWdsQMEj8Y3tR2XcNaITEVPiAjoKgBrYwB4jddWPNaT4XdHz76d9p9Iqes7dhOKq3OKpk6kg-bfIKiEOiA1mY49fn5h8pNShTqV7QBhh4CE4EDT3Szl~WsLdrlHUKJufSi7erEMh3coF7HORpF1wah2Xw7q470t~b8dKGKi7N7xQsqhGruDm66PH9oE9Kt9WBVBq2zORdPRtRM61I7EnrwDlbOkL0y~XpvQ3JKUQKdBQ3QsOJt8CHlhHHXMMbvqhntR61RSDBQAEAAcAAA==")
}

View File

@@ -1,8 +1,20 @@
group = com.muwire
version = 0.4.7
version = 0.4.12
groovyVersion = 2.4.15
slf4jVersion = 1.7.25
spockVersion = 1.1-groovy-2.4
grailsVersion=4.0.0
gorm.version=7.0.2.RELEASE
sourceCompatibility=1.8
targetCompatibility=1.8
# plugin properties
author = zab@mail.i2p
signer = zab@mail.i2p
i2pVersion=0.9.41
keystorePassword=changeit
websiteURL=http://muwire.i2p
updateURLsu3=http://muwire.i2p/MuWire.su3
pack200=true

34
gradlew vendored
View File

@@ -11,21 +11,21 @@
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
ls=$(ls -ld "$PRG")
link=$(expr "$ls" : '.*-> \(.*\)$')
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
PRG=$(dirname "$PRG")"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
SAVED="$(pwd)"
cd "$(dirname "$PRG")/" >/dev/null
APP_HOME="$(pwd -P)"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=$(basename "$0")
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
@@ -49,7 +49,7 @@ cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
case "$(uname)" in
CYGWIN* )
cygwin=true
;;
@@ -90,7 +90,7 @@ fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
MAX_FD_LIMIT=$(ulimit -H -n)
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
@@ -111,12 +111,12 @@ fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
APP_HOME=$(cygpath --path --mixed "$APP_HOME")
CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
JAVACMD=$(cygpath --unix "$JAVACMD")
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
@@ -130,13 +130,13 @@ if $cygwin ; then
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
CHECK=$(echo "$arg"|egrep -c "$OURCYGPATTERN" -)
CHECK2=$(echo "$arg"|egrep -c "^-") ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg")
else
eval `echo args$i`="\"$arg\""
eval $(echo args$i)="\"$arg\""
fi
i=$((i+1))
done

View File

@@ -41,4 +41,9 @@ mvcGroups {
view = 'com.muwire.gui.TrustListView'
controller = 'com.muwire.gui.TrustListController'
}
'content-panel' {
model = 'com.muwire.gui.ContentPanelModel'
view = 'com.muwire.gui.ContentPanelView'
controller = 'com.muwire.gui.ContentPanelController'
}
}

View File

@@ -0,0 +1,95 @@
package com.muwire.gui
import griffon.core.artifact.GriffonController
import griffon.core.controller.ControllerAction
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import javax.annotation.Nonnull
import com.muwire.core.Core
import com.muwire.core.EventBus
import com.muwire.core.content.ContentControlEvent
import com.muwire.core.content.Match
import com.muwire.core.content.Matcher
import com.muwire.core.content.RegexMatcher
import com.muwire.core.trust.TrustEvent
import com.muwire.core.trust.TrustLevel
@ArtifactProviderFor(GriffonController)
class ContentPanelController {
@MVCMember @Nonnull
ContentPanelModel model
@MVCMember @Nonnull
ContentPanelView view
Core core
@ControllerAction
void addRule() {
def term = view.ruleTextField.text
if (model.regex)
core.muOptions.watchedRegexes.add(term)
else
core.muOptions.watchedKeywords.add(term)
saveMuWireSettings()
core.eventBus.publish(new ContentControlEvent(term : term, regex : model.regex, add:true))
}
@ControllerAction
void deleteRule() {
int rule = view.getSelectedRule()
if (rule < 0)
return
Matcher matcher = model.rules[rule]
String term = matcher.getTerm()
if (matcher instanceof RegexMatcher)
core.muOptions.watchedRegexes.remove(term)
else
core.muOptions.watchedKeywords.remove(term)
saveMuWireSettings()
core.eventBus.publish(new ContentControlEvent(term : term, regex : (matcher instanceof RegexMatcher), add: false))
}
@ControllerAction
void keyword() {
model.regex = false
}
@ControllerAction
void regex() {
model.regex = true
}
@ControllerAction
void refresh() {
model.refresh()
}
@ControllerAction
void trust() {
int selectedHit = view.getSelectedHit()
if (selectedHit < 0)
return
Match m = model.hits[selectedHit]
core.eventBus.publish(new TrustEvent(persona : m.persona, level : TrustLevel.TRUSTED))
}
@ControllerAction
void distrust() {
int selectedHit = view.getSelectedHit()
if (selectedHit < 0)
return
Match m = model.hits[selectedHit]
core.eventBus.publish(new TrustEvent(persona : m.persona, level : TrustLevel.DISTRUSTED))
}
void saveMuWireSettings() {
File f = new File(core.home, "MuWire.properties")
f.withOutputStream {
core.muOptions.write(it)
}
}
}

View File

@@ -17,6 +17,7 @@ import com.muwire.core.Constants
import com.muwire.core.Core
import com.muwire.core.Persona
import com.muwire.core.SharedFile
import com.muwire.core.download.Downloader
import com.muwire.core.download.DownloadStartedEvent
import com.muwire.core.download.UIDownloadCancelledEvent
import com.muwire.core.download.UIDownloadEvent
@@ -59,6 +60,7 @@ class MainFrameController {
Map<String, Object> params = new HashMap<>()
params["search-terms"] = search
params["uuid"] = uuid.toString()
params["core"] = core
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
model.results[uuid.toString()] = group
@@ -96,6 +98,7 @@ class MainFrameController {
Map<String, Object> params = new HashMap<>()
params["search-terms"] = tabTitle
params["uuid"] = uuid.toString()
params["core"] = core
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
model.results[uuid.toString()] = group
@@ -106,20 +109,6 @@ class MainFrameController {
originator : core.me))
}
private def selectedResult() {
def selected = builder.getVariable("result-tabs").getSelectedComponent()
def group = selected.getClientProperty("mvc-group")
def table = selected.getClientProperty("results-table")
int row = table.getSelectedRow()
if (row == -1)
return
def sortEvt = group.view.lastSortEvent
if (sortEvt != null) {
row = group.view.resultsTable.rowSorter.convertRowIndexToModel(row)
}
group.model.results[row]
}
private int selectedDownload() {
def downloadsTable = builder.getVariable("downloads-table")
def selected = downloadsTable.getSelectedRow()
@@ -130,39 +119,21 @@ class MainFrameController {
}
@ControllerAction
void download() {
def result = selectedResult()
if (result == null)
void trustPersonaFromSearch() {
int selected = builder.getVariable("searches-table").getSelectedRow()
if (selected < 0)
return
if (!model.canDownload(result.infohash))
return
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
def selected = builder.getVariable("result-tabs").getSelectedComponent()
def group = selected.getClientProperty("mvc-group")
def resultsBucket = group.model.hashBucket[result.infohash]
def sources = group.model.sourcesBucket[result.infohash]
core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, sources: sources, target : file))
Persona p = model.searches[selected].originator
core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.TRUSTED) )
}
@ControllerAction
void trust() {
def result = selectedResult()
if (result == null)
return // TODO disable button
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.TRUSTED))
}
@ControllerAction
void distrust() {
def result = selectedResult()
if (result == null)
return // TODO disable button
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.DISTRUSTED))
void distrustPersonaFromSearch() {
int selected = builder.getVariable("searches-table").getSelectedRow()
if (selected < 0)
return
Persona p = model.searches[selected].originator
core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED) )
}
@ControllerAction
@@ -187,6 +158,23 @@ class MainFrameController {
core.eventBus.publish(new UIDownloadPausedEvent())
}
@ControllerAction
void clear() {
def toRemove = []
model.downloads.each {
if (it.downloader.getCurrentState() == Downloader.DownloadState.CANCELLED) {
toRemove << it
} else if (it.downloader.getCurrentState() == Downloader.DownloadState.FINISHED) {
toRemove << it
}
}
toRemove.each {
model.downloads.remove(it)
}
model.clearButtonEnabled = false
}
private void markTrust(String tableName, TrustLevel level, def list) {
int row = view.getSelectedTrustTablesRow(tableName)
if (row < 0)

View File

@@ -6,6 +6,65 @@ import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import javax.annotation.Nonnull
import com.muwire.core.Core
import com.muwire.core.download.UIDownloadEvent
import com.muwire.core.trust.TrustEvent
import com.muwire.core.trust.TrustLevel
@ArtifactProviderFor(GriffonController)
class SearchTabController {
@MVCMember @Nonnull
SearchTabModel model
@MVCMember @Nonnull
SearchTabView view
Core core
private def selectedResult() {
int row = view.resultsTable.getSelectedRow()
if (row == -1)
return null
def sortEvt = view.lastSortEvent
if (sortEvt != null) {
row = view.resultsTable.rowSorter.convertRowIndexToModel(row)
}
model.results[row]
}
@ControllerAction
void download() {
def result = selectedResult()
if (result == null)
return
if (!mvcGroup.parentGroup.model.canDownload(result.infohash))
return
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
def resultsBucket = model.hashBucket[result.infohash]
def sources = model.sourcesBucket[result.infohash]
core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, sources: sources, target : file))
mvcGroup.parentGroup.view.showDownloadsWindow.call()
}
@ControllerAction
void trust() {
int row = view.selectedSenderRow()
if (row < 0)
return
def sender = model.senders[row]
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.TRUSTED))
}
@ControllerAction
void distrust() {
int row = view.selectedSenderRow()
if (row < 0)
return
def sender = model.senders[row]
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.DISTRUSTED))
}
}

View File

@@ -43,7 +43,7 @@ class Initialize extends AbstractLifecycleHandler {
application.context.put("muwire-home", home.getAbsolutePath())
System.getProperties().setProperty("awt.useSystemAAFontSettings", "true")
System.getProperties().setProperty("awt.useSystemAAFontSettings", "gasp")
def guiPropsFile = new File(home, "gui.properties")
UISettings uiSettings
@@ -86,6 +86,8 @@ class Initialize extends AbstractLifecycleHandler {
}
} else {
LookAndFeel chosen = lookAndFeel('system', 'gtk')
if (chosen == null)
chosen = lookAndFeel('metal')
uiSettings.lnf = chosen.getID()
log.info("ended up applying $chosen.name")
}

View File

@@ -0,0 +1,55 @@
package com.muwire.gui
import javax.annotation.Nonnull
import com.muwire.core.Core
import com.muwire.core.EventBus
import com.muwire.core.content.ContentControlEvent
import com.muwire.core.content.ContentManager
import griffon.core.artifact.GriffonModel
import griffon.inject.MVCMember
import griffon.transform.Observable
import griffon.metadata.ArtifactProviderFor
@ArtifactProviderFor(GriffonModel)
class ContentPanelModel {
@MVCMember @Nonnull
ContentPanelView view
Core core
private ContentManager contentManager
def rules = []
def hits = []
@Observable boolean regex
@Observable boolean deleteButtonEnabled
@Observable boolean trustButtonsEnabled
void mvcGroupInit(Map<String,String> args) {
contentManager = application.context.get("core").contentManager
rules.addAll(contentManager.matchers)
core.eventBus.register(ContentControlEvent.class, this)
}
void mvcGroupDestroy() {
core.eventBus.unregister(ContentControlEvent.class, this)
}
void refresh() {
rules.clear()
rules.addAll(contentManager.matchers)
hits.clear()
view.rulesTable.model.fireTableDataChanged()
view.hitsTable.model.fireTableDataChanged()
}
void onContentControlEvent(ContentControlEvent e) {
runInsideUIAsync {
refresh()
}
}
}

View File

@@ -17,6 +17,7 @@ import com.muwire.core.RouterDisconnectedEvent
import com.muwire.core.connection.ConnectionAttemptStatus
import com.muwire.core.connection.ConnectionEvent
import com.muwire.core.connection.DisconnectionEvent
import com.muwire.core.content.ContentControlEvent
import com.muwire.core.download.DownloadStartedEvent
import com.muwire.core.download.Downloader
import com.muwire.core.files.AllFilesLoadedEvent
@@ -75,11 +76,10 @@ class MainFrameModel {
@Observable String me
@Observable int loadedFiles
@Observable File hashingFile
@Observable boolean downloadActionEnabled
@Observable boolean trustButtonsEnabled
@Observable boolean cancelButtonEnabled
@Observable boolean retryButtonEnabled
@Observable boolean pauseButtonEnabled
@Observable boolean clearButtonEnabled
@Observable String resumeButtonText
@Observable boolean subscribeButtonEnabled
@Observable boolean markNeutralFromTrustedButtonEnabled
@@ -89,8 +89,14 @@ class MainFrameModel {
@Observable boolean reviewButtonEnabled
@Observable boolean updateButtonEnabled
@Observable boolean unsubscribeButtonEnabled
private final Set<InfoHash> infoHashes = new HashSet<>()
@Observable boolean searchesPaneButtonEnabled
@Observable boolean downloadsPaneButtonEnabled
@Observable boolean uploadsPaneButtonEnabled
@Observable boolean monitorPaneButtonEnabled
@Observable boolean trustPaneButtonEnabled
@Observable Downloader downloader
private final Set<InfoHash> downloadInfoHashes = new HashSet<>()
@@ -118,17 +124,26 @@ class MainFrameModel {
return
// remove cancelled or finished downloads
def toRemove = []
downloads.each {
if (uiSettings.clearCancelledDownloads &&
it.downloader.getCurrentState() == Downloader.DownloadState.CANCELLED)
toRemove << it
if (uiSettings.clearFinishedDownloads &&
it.downloader.getCurrentState() == Downloader.DownloadState.FINISHED)
toRemove << it
}
toRemove.each {
downloads.remove(it)
if (!clearButtonEnabled || uiSettings.clearCancelledDownloads || uiSettings.clearFinishedDownloads) {
def toRemove = []
downloads.each {
if (it.downloader.getCurrentState() == Downloader.DownloadState.CANCELLED) {
if (uiSettings.clearCancelledDownloads) {
toRemove << it
} else {
clearButtonEnabled = true
}
} else if (it.downloader.getCurrentState() == Downloader.DownloadState.FINISHED) {
if (uiSettings.clearFinishedDownloads) {
toRemove << it
} else {
clearButtonEnabled = true
}
}
}
toRemove.each {
downloads.remove(it)
}
}
builder.getVariable("uploads-table")?.model.fireTableDataChanged()
@@ -164,6 +179,13 @@ class MainFrameModel {
core.eventBus.register(UpdateDownloadedEvent.class, this)
core.eventBus.register(TrustSubscriptionUpdatedEvent.class, this)
core.muOptions.watchedKeywords.each {
core.eventBus.publish(new ContentControlEvent(term : it, regex: false, add: true))
}
core.muOptions.watchedRegexes.each {
core.eventBus.publish(new ContentControlEvent(term : it, regex: true, add: true))
}
timer.schedule({
if (core.shutdown.get())
return
@@ -192,6 +214,12 @@ class MainFrameModel {
distrusted.addAll(core.trustService.bad.values())
resumeButtonText = "Retry"
searchesPaneButtonEnabled = false
downloadsPaneButtonEnabled = true
uploadsPaneButtonEnabled = true
monitorPaneButtonEnabled = true
trustPaneButtonEnabled = true
}
})
@@ -280,9 +308,6 @@ class MainFrameModel {
}
if (e.error != null)
return // TODO do something
if (infoHashes.contains(e.sharedFile.infoHash))
return
infoHashes.add(e.sharedFile.infoHash)
runInsideUIAsync {
shared << e.sharedFile
loadedFiles = shared.size()
@@ -292,9 +317,6 @@ class MainFrameModel {
}
void onFileLoadedEvent(FileLoadedEvent e) {
if (infoHashes.contains(e.loadedFile.infoHash))
return
infoHashes.add(e.loadedFile.infoHash)
runInsideUIAsync {
shared << e.loadedFile
loadedFiles = shared.size()
@@ -304,9 +326,6 @@ class MainFrameModel {
}
void onFileUnsharedEvent(FileUnsharedEvent e) {
InfoHash infohash = e.unsharedFile.infoHash
if (!infoHashes.remove(infohash))
return
runInsideUIAsync {
shared.remove(e.unsharedFile)
loadedFiles = shared.size()
@@ -450,7 +469,6 @@ class MainFrameModel {
void onFileDownloadedEvent(FileDownloadedEvent e) {
if (!core.muOptions.shareDownloadedFiles)
return
infoHashes.add(e.downloadedFile.infoHash)
runInsideUIAsync {
shared << e.downloadedFile
JTable table = builder.getVariable("shared-files-table")

View File

@@ -5,6 +5,7 @@ import javax.inject.Inject
import javax.swing.JTable
import com.muwire.core.Core
import com.muwire.core.Persona
import com.muwire.core.search.UIResultEvent
import griffon.core.artifact.GriffonModel
@@ -17,14 +18,19 @@ import griffon.metadata.ArtifactProviderFor
class SearchTabModel {
@MVCMember @Nonnull
FactoryBuilderSupport builder
@Observable boolean downloadActionEnabled
@Observable boolean trustButtonsEnabled
Core core
UISettings uiSettings
String uuid
def senders = []
def results = []
def hashBucket = [:]
def sourcesBucket = [:]
def sendersBucket = new LinkedHashMap<>()
void mvcGroupInit(Map<String, String> args) {
core = mvcGroup.parentGroup.model.core
@@ -48,6 +54,15 @@ class SearchTabModel {
}
bucket << e
def senderBucket = sendersBucket.get(e.sender)
if (senderBucket == null) {
senderBucket = []
sendersBucket[e.sender] = senderBucket
senders.clear()
senders.addAll(sendersBucket.keySet())
}
senderBucket << e
Set sourceBucket = sourcesBucket.get(e.infohash)
if (sourceBucket == null) {
sourceBucket = new HashSet()
@@ -55,8 +70,7 @@ class SearchTabModel {
}
sourceBucket.addAll(e.sources)
results << e
JTable table = builder.getVariable("results-table")
JTable table = builder.getVariable("senders-table")
table.model.fireTableDataChanged()
}
}
@@ -72,6 +86,14 @@ class SearchTabModel {
bucket = []
hashBucket[it.infohash] = bucket
}
def senderBucket = sendersBucket.get(it.sender)
if (senderBucket == null) {
senderBucket = []
sendersBucket[it.sender] = senderBucket
senders.clear()
senders.addAll(sendersBucket.keySet())
}
Set sourceBucket = sourcesBucket.get(it.infohash)
if (sourceBucket == null) {
@@ -81,9 +103,9 @@ class SearchTabModel {
sourceBucket.addAll(it.sources)
bucket << it
results << it
senderBucket << it
}
JTable table = builder.getVariable("results-table")
JTable table = builder.getVariable("senders-table")
table.model.fireTableDataChanged()
}
}

View File

@@ -0,0 +1,154 @@
package com.muwire.gui
import griffon.core.artifact.GriffonView
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import javax.swing.JDialog
import javax.swing.JLabel
import javax.swing.ListSelectionModel
import javax.swing.SwingConstants
import javax.swing.table.DefaultTableCellRenderer
import com.muwire.core.content.Matcher
import com.muwire.core.content.RegexMatcher
import java.awt.BorderLayout
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import javax.annotation.Nonnull
@ArtifactProviderFor(GriffonView)
class ContentPanelView {
@MVCMember @Nonnull
FactoryBuilderSupport builder
@MVCMember @Nonnull
ContentPanelModel model
def dialog
def mainFrame
def mainPanel
def rulesTable
def ruleTextField
def lastRulesSortEvent
def hitsTable
def lastHitsSortEvent
void initUI() {
mainFrame = application.windowManager.findWindow("main-frame")
dialog = new JDialog(mainFrame, "Content Control Panel", true)
mainPanel = builder.panel {
gridLayout(rows:1, cols:2)
panel {
borderLayout()
panel (constraints : BorderLayout.NORTH) {
label(text : "Rules")
}
scrollPane (constraints : BorderLayout.CENTER) {
rulesTable = table(id : "rules-table", autoCreateRowSorter : true) {
tableModel(list : model.rules) {
closureColumn(header: "Term", type:String, read: {row -> row.getTerm()})
closureColumn(header: "Regex?", type:Boolean, read: {row -> row instanceof RegexMatcher})
closureColumn(header: "Hits", type:Integer, read : {row -> row.matches.size()})
}
}
}
panel (constraints : BorderLayout.SOUTH) {
borderLayout()
ruleTextField = textField(constraints: BorderLayout.CENTER, action: addRuleAction)
panel (constraints: BorderLayout.EAST) {
buttonGroup(id : "ruleType")
radioButton(text: "Keyword", selected : true, buttonGroup: ruleType, keywordAction)
radioButton(text: "Regex", selected : false, buttonGroup: ruleType, regexAction)
button(text : "Add Rule", addRuleAction)
button(text : "Delete Rule", enabled : bind {model.deleteButtonEnabled}, deleteRuleAction)
}
}
}
panel (border : etchedBorder()){
borderLayout()
panel (constraints : BorderLayout.NORTH) {
label(text : "Hits")
}
scrollPane(constraints : BorderLayout.CENTER) {
hitsTable = table(id : "hits-table", autoCreateRowSorter : true) {
tableModel(list : model.hits) {
closureColumn(header : "Searcher", type : String, read : {row -> row.persona.getHumanReadableName()})
closureColumn(header : "Keywords", type : String, read : {row -> row.keywords.join(" ")})
closureColumn(header : "Date", type : String, read : {row -> String.valueOf(new Date(row.timestamp))})
}
}
}
panel (constraints : BorderLayout.SOUTH) {
button(text : "Refresh", refreshAction)
button(text : "Trust", enabled : bind {model.trustButtonsEnabled}, trustAction)
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
}
}
}
}
int getSelectedRule() {
int selectedRow = rulesTable.getSelectedRow()
if (selectedRow < 0)
return -1
if (lastRulesSortEvent != null)
selectedRow = rulesTable.rowSorter.convertRowIndexToModel(selectedRow)
selectedRow
}
int getSelectedHit() {
int selectedRow = hitsTable.getSelectedRow()
if (selectedRow < 0)
return -1
if (lastHitsSortEvent != null)
selectedRow = hitsTable.rowSorter.convertRowIndexToModel(selectedRow)
selectedRow
}
void mvcGroupInit(Map<String,String> args) {
def centerRenderer = new DefaultTableCellRenderer()
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
rulesTable.setDefaultRenderer(Integer.class, centerRenderer)
rulesTable.rowSorter.addRowSorterListener({evt -> lastRulesSortEvent = evt})
rulesTable.rowSorter.setSortsOnUpdates(true)
def selectionModel = rulesTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
int selectedRow = getSelectedRule()
if (selectedRow < 0) {
model.deleteButtonEnabled = false
return
} else {
model.deleteButtonEnabled = true
model.hits.clear()
Matcher matcher = model.rules[selectedRow]
model.hits.addAll(matcher.matches)
hitsTable.model.fireTableDataChanged()
}
})
hitsTable.rowSorter.addRowSorterListener({evt -> lastHitsSortEvent = evt})
hitsTable.rowSorter.setSortsOnUpdates(true)
selectionModel = hitsTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
int selectedRow = getSelectedHit()
model.trustButtonsEnabled = selectedRow >= 0
})
dialog.getContentPane().add(mainPanel)
dialog.pack()
dialog.setLocationRelativeTo(mainFrame)
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
dialog.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) {
mvcGroup.destroy()
}
})
dialog.show()
}
}

View File

@@ -26,7 +26,6 @@ import com.muwire.core.MuWireSettings
import com.muwire.core.download.Downloader
import com.muwire.core.files.FileSharedEvent
import com.muwire.core.trust.RemoteTrustList
import java.awt.BorderLayout
import java.awt.CardLayout
import java.awt.FlowLayout
@@ -73,6 +72,11 @@ class MainFrameView {
menuBar {
menu (text : "Options") {
menuItem("Configuration", actionPerformed : {mvcGroup.createMVCGroup("Options")})
menuItem("Content Control", actionPerformed : {
def env = [:]
env["core"] = model.core
mvcGroup.createMVCGroup("content-panel", env)
})
}
menu (text : "Status") {
menuItem("MuWire", actionPerformed : {mvcGroup.createMVCGroup("mu-wire-status")})
@@ -85,11 +89,12 @@ class MainFrameView {
borderLayout()
panel (constraints: BorderLayout.WEST) {
gridLayout(rows:1, cols: 2)
button(text: "Searches", actionPerformed : showSearchWindow)
button(text: "Uploads", actionPerformed : showUploadsWindow)
button(text: "Searches", enabled : bind{model.searchesPaneButtonEnabled},actionPerformed : showSearchWindow)
button(text: "Downloads", enabled : bind{model.downloadsPaneButtonEnabled}, actionPerformed : showDownloadsWindow)
button(text: "Uploads", enabled : bind{model.uploadsPaneButtonEnabled}, actionPerformed : showUploadsWindow)
if (settings.showMonitor)
button(text: "Monitor", actionPerformed : showMonitorWindow)
button(text: "Trust", actionPerformed : showTrustWindow)
button(text: "Monitor", enabled: bind{model.monitorPaneButtonEnabled},actionPerformed : showMonitorWindow)
button(text: "Trust", enabled:bind{model.trustPaneButtonEnabled},actionPerformed : showTrustWindow)
}
panel(id: "top-panel", constraints: BorderLayout.CENTER) {
cardLayout()
@@ -113,18 +118,12 @@ class MainFrameView {
cardLayout()
panel (constraints : "search window") {
borderLayout()
splitPane( orientation : JSplitPane.VERTICAL_SPLIT, dividerLocation : 500,
continuousLayout : true, constraints : BorderLayout.CENTER) {
panel (constraints : JSplitPane.TOP) {
borderLayout()
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
panel(constraints : BorderLayout.SOUTH) {
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
button(text : "Trust", enabled: bind {model.trustButtonsEnabled }, trustAction)
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
}
}
panel (constraints : JSplitPane.BOTTOM) {
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
}
panel (constraints: "downloads window") {
gridLayout(rows : 1, cols : 1)
splitPane(orientation: JSplitPane.VERTICAL_SPLIT, continuousLayout : true, dividerLocation: 500 ) {
panel {
borderLayout()
scrollPane (constraints : BorderLayout.CENTER) {
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
@@ -132,7 +131,6 @@ class MainFrameView {
closureColumn(header: "Name", preferredWidth: 300, type: String, read : {row -> row.downloader.file.getName()})
closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()})
closureColumn(header: "Progress", preferredWidth: 70, type: Downloader, read: { row -> row.downloader })
closureColumn(header: "Sources", preferredWidth : 10, type: Integer, read : {row -> row.downloader.activeWorkers()})
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
DataHelper.formatSize2Decimal(row.downloader.speed(), false) + "B/sec"
})
@@ -141,8 +139,39 @@ class MainFrameView {
}
panel (constraints : BorderLayout.SOUTH) {
button(text: "Pause", enabled : bind {model.pauseButtonEnabled}, pauseAction)
button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction )
button(text: bind { model.resumeButtonText }, enabled : bind {model.retryButtonEnabled}, resumeAction)
button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction)
button(text: "Clear Done", enabled : bind {model.clearButtonEnabled}, clearAction)
}
}
panel {
borderLayout()
panel(constraints : BorderLayout.NORTH) {
label(text : "Download Details")
}
scrollPane(constraints : BorderLayout.CENTER) {
panel (id : "download-details-panel") {
cardLayout()
panel (constraints : "select-download") {
label(text : "Select a download to view details")
}
panel(constraints : "download-selected") {
gridBagLayout()
label(text : "Download Location:", constraints : gbc(gridx:0, gridy:0))
label(text : bind {model.downloader?.file?.getAbsolutePath()},
constraints: gbc(gridx:1, gridy:0, gridwidth: 2, insets : [0,0,0,20]))
label(text : "Piece Size", constraints : gbc(gridx: 0, gridy:1))
label(text : bind {model.downloader?.pieceSize}, constraints : gbc(gridx:1, gridy:1))
label(text : "Known Sources:", constraints : gbc(gridx:3, gridy: 0))
label(text : bind {model.downloader?.activeWorkers?.size()}, constraints : gbc(gridx:4, gridy:0, insets : [0,0,0,20]))
label(text : "Active Sources:", constraints : gbc(gridx:3, gridy:1))
label(text : bind {model.downloader?.activeWorkers()}, constraints : gbc(gridx:4, gridy:1, insets : [0,0,0,20]))
label(text : "Total Pieces:", constraints : gbc(gridx:5, gridy: 0))
label(text : bind {model.downloader?.nPieces}, constraints : gbc(gridx:6, gridy:0, insets : [0,0,0,20]))
label(text : "Done Pieces:", constraints: gbc(gridx:5, gridy: 1))
label(text : bind {model.downloader?.donePieces()}, constraints : gbc(gridx:6, gridy:1, insets : [0,0,0,20]))
}
}
}
}
}
@@ -367,16 +396,21 @@ class MainFrameView {
def selectionModel = downloadsTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
def downloadDetailsPanel = builder.getVariable("download-details-panel")
int selectedRow = selectedDownloaderRow()
if (selectedRow < 0) {
model.cancelButtonEnabled = false
model.retryButtonEnabled = false
model.pauseButtonEnabled = false
model.downloader = null
downloadDetailsPanel.getLayout().show(downloadDetailsPanel,"select-download")
return
}
def downloader = model.downloads[selectedRow]?.downloader
if (downloader == null)
if (downloader == null)
return
model.downloader = downloader
downloadDetailsPanel.getLayout().show(downloadDetailsPanel,"download-selected")
switch(downloader.getCurrentState()) {
case Downloader.DownloadState.CONNECTING :
case Downloader.DownloadState.DOWNLOADING :
@@ -456,9 +490,18 @@ class MainFrameView {
// searches table
def searchesTable = builder.getVariable("searches-table")
JPopupMenu searchTableMenu = new JPopupMenu()
JMenuItem copySearchToClipboard = new JMenuItem("Copy search to clipboard")
copySearchToClipboard.addActionListener({mvcGroup.view.copySearchToClipboard(searchesTable)})
JMenuItem trustSearcher = new JMenuItem("Trust searcher")
trustSearcher.addActionListener({mvcGroup.controller.trustPersonaFromSearch()})
JMenuItem distrustSearcher = new JMenuItem("Distrust searcher")
distrustSearcher.addActionListener({mvcGroup.controller.distrustPersonaFromSearch()})
searchTableMenu.add(copySearchToClipboard)
searchTableMenu.add(trustSearcher)
searchTableMenu.add(distrustSearcher)
searchesTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
@@ -677,21 +720,51 @@ class MainFrameView {
def showSearchWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel, "search window")
model.searchesPaneButtonEnabled = false
model.downloadsPaneButtonEnabled = true
model.uploadsPaneButtonEnabled = true
model.monitorPaneButtonEnabled = true
model.trustPaneButtonEnabled = true
}
def showDownloadsWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel, "downloads window")
model.searchesPaneButtonEnabled = true
model.downloadsPaneButtonEnabled = false
model.uploadsPaneButtonEnabled = true
model.monitorPaneButtonEnabled = true
model.trustPaneButtonEnabled = true
}
def showUploadsWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel, "uploads window")
model.searchesPaneButtonEnabled = true
model.downloadsPaneButtonEnabled = true
model.uploadsPaneButtonEnabled = false
model.monitorPaneButtonEnabled = true
model.trustPaneButtonEnabled = true
}
def showMonitorWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel,"monitor window")
model.searchesPaneButtonEnabled = true
model.downloadsPaneButtonEnabled = true
model.uploadsPaneButtonEnabled = true
model.monitorPaneButtonEnabled = false
model.trustPaneButtonEnabled = true
}
def showTrustWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel,"trust window")
model.searchesPaneButtonEnabled = true
model.downloadsPaneButtonEnabled = true
model.uploadsPaneButtonEnabled = true
model.monitorPaneButtonEnabled = true
model.trustPaneButtonEnabled = false
}
def shareFiles = {

View File

@@ -117,9 +117,9 @@ class OptionsView {
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2))
// label(text : "Show Monitor", constraints : gbc(gridx :0, gridy: 3))
// monitorCheckbox = checkBox(selected : bind {model.showMonitor}, constraints : gbc(gridx : 1, gridy: 3))
label(text : "Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:4))
label(text : "Automatically Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:4))
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads}, constraints : gbc(gridx : 1, gridy:4))
label(text : "Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:5))
label(text : "Automatically Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:5))
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads}, constraints : gbc(gridx : 1, gridy:5))
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:6))
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult}, constraints : gbc(gridx: 1, gridy : 6))

View File

@@ -0,0 +1,183 @@
package com.muwire.gui
import griffon.core.artifact.GriffonView
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import javax.swing.JDialog
import javax.swing.JPanel
import javax.swing.JTabbedPane
import javax.swing.SwingConstants
import com.muwire.core.Core
import java.awt.BorderLayout
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import javax.annotation.Nonnull
@ArtifactProviderFor(GriffonView)
class OptionsView {
@MVCMember @Nonnull
FactoryBuilderSupport builder
@MVCMember @Nonnull
OptionsModel model
def d
def p
def i
def u
def bandwidth
def trust
def retryField
def updateField
def autoDownloadUpdateCheckbox
def shareDownloadedCheckbox
def inboundLengthField
def inboundQuantityField
def outboundLengthField
def outboundQuantityField
def i2pUDPPortField
def i2pNTCPPortField
def lnfField
def monitorCheckbox
def fontField
def clearCancelledDownloadsCheckbox
def clearFinishedDownloadsCheckbox
def excludeLocalResultCheckbox
def showSearchHashesCheckbox
def inBwField
def outBwField
def allowUntrustedCheckbox
def allowTrustListsCheckbox
def trustListIntervalField
def buttonsPanel
def mainFrame
void initUI() {
mainFrame = application.windowManager.findWindow("main-frame")
d = new JDialog(mainFrame, "Options", true)
d.setResizable(false)
p = builder.panel {
gridBagLayout()
label(text : "Retry failed downloads every", constraints : gbc(gridx: 0, gridy: 0))
retryField = textField(text : bind { model.downloadRetryInterval }, columns : 2, constraints : gbc(gridx: 1, gridy: 0))
label(text : "minutes", constraints : gbc(gridx : 2, gridy: 0))
label(text : "Check for updates every", constraints : gbc(gridx : 0, gridy: 1))
updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 1))
label(text : "hours", constraints : gbc(gridx: 2, gridy : 1))
label(text : "Download updates automatically", constraints: gbc(gridx :0, gridy : 2))
autoDownloadUpdateCheckbox = checkBox(selected : bind {model.autoDownloadUpdate}, constraints : gbc(gridx:1, gridy : 2))
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:3))
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:3))
label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:4))
button(text : "Choose", constraints : gbc(gridx : 1, gridy:4), downloadLocationAction)
label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:5, gridwidth:2))
}
i = builder.panel {
gridBagLayout()
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
label(text : "Inbound Length", constraints : gbc(gridx:0, gridy:1))
inboundLengthField = textField(text : bind {model.inboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:1))
label(text : "Inbound Quantity", constraints : gbc(gridx:0, gridy:2))
inboundQuantityField = textField(text : bind {model.inboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:2))
label(text : "Outbound Length", constraints : gbc(gridx:0, gridy:3))
outboundLengthField = textField(text : bind {model.outboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:3))
label(text : "Outbound Quantity", constraints : gbc(gridx:0, gridy:4))
outboundQuantityField = textField(text : bind {model.outboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:4))
Core core = application.context.get("core")
if (core.router != null) {
label(text : "TCP Port", constraints : gbc(gridx :0, gridy: 5))
i2pNTCPPortField = textField(text : bind {model.i2pNTCPPort}, columns : 4, constraints : gbc(gridx:1, gridy:5))
label(text : "UDP Port", constraints : gbc(gridx :0, gridy: 6))
i2pUDPPortField = textField(text : bind {model.i2pUDPPort}, columns : 4, constraints : gbc(gridx:1, gridy:6))
}
}
u = builder.panel {
gridBagLayout()
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
label(text : "Look And Feel", constraints : gbc(gridx: 0, gridy:1))
lnfField = textField(text : bind {model.lnf}, columns : 4, constraints : gbc(gridx : 1, gridy : 1))
label(text : "Font", constraints : gbc(gridx: 0, gridy : 2))
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2))
// label(text : "Show Monitor", constraints : gbc(gridx :0, gridy: 3))
// monitorCheckbox = checkBox(selected : bind {model.showMonitor}, constraints : gbc(gridx : 1, gridy: 3))
label(text : "Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:4))
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads}, constraints : gbc(gridx : 1, gridy:4))
label(text : "Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:5))
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads}, constraints : gbc(gridx : 1, gridy:5))
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:6))
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult}, constraints : gbc(gridx: 1, gridy : 6))
// label(text : "Show Hash Searches In Monitor", constraints: gbc(gridx:0, gridy:7))
// showSearchHashesCheckbox = checkBox(selected : bind {model.showSearchHashes}, constraints : gbc(gridx: 1, gridy: 7))
}
bandwidth = builder.panel {
gridBagLayout()
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
label(text : "Inbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 1))
inBwField = textField(text : bind {model.inBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 1))
label(text : "Outbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 2))
outBwField = textField(text : bind {model.outBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 2))
}
trust = builder.panel {
gridBagLayout()
label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 0))
allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 0))
label(text : "Allow others to view my trust list", constraints : gbc(gridx: 0, gridy : 1))
allowTrustListsCheckbox = checkBox(selected : bind {model.trustLists}, constraints : gbc(gridx: 1, gridy : 1))
label(text : "Update trust lists every ", constraints : gbc(gridx:0, gridy:2))
trustListIntervalField = textField(text : bind {model.trustListInterval}, constraints:gbc(gridx:1, gridy:2))
label(text : "hours", constraints : gbc(gridx: 2, gridy:2))
}
buttonsPanel = builder.panel {
gridBagLayout()
button(text : "Save", constraints : gbc(gridx : 1, gridy: 2), saveAction)
button(text : "Cancel", constraints : gbc(gridx : 2, gridy: 2), cancelAction)
}
}
void mvcGroupInit(Map<String,String> args) {
def tabbedPane = new JTabbedPane()
tabbedPane.addTab("MuWire", p)
tabbedPane.addTab("I2P", i)
tabbedPane.addTab("GUI", u)
Core core = application.context.get("core")
if (core.router != null) {
tabbedPane.addTab("Bandwidth", bandwidth)
}
tabbedPane.addTab("Trust", trust)
JPanel panel = new JPanel()
panel.setLayout(new BorderLayout())
panel.add(tabbedPane, BorderLayout.CENTER)
panel.add(buttonsPanel, BorderLayout.SOUTH)
d.getContentPane().add(panel)
d.pack()
d.setLocationRelativeTo(mainFrame)
d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
d.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) {
mvcGroup.destroy()
}
})
d.show()
}
}

View File

@@ -11,11 +11,13 @@ import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JMenuItem
import javax.swing.JPopupMenu
import javax.swing.JSplitPane
import javax.swing.JTable
import javax.swing.ListSelectionModel
import javax.swing.SwingConstants
import javax.swing.table.DefaultTableCellRenderer
import com.muwire.core.Persona
import com.muwire.core.util.DataUtil
import java.awt.BorderLayout
@@ -37,23 +39,51 @@ class SearchTabView {
def pane
def parent
def searchTerms
def sendersTable
def lastSendersSortEvent
def resultsTable
def lastSortEvent
void initUI() {
builder.with {
def resultsTable
def pane = scrollPane {
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
tableModel(list: model.results) {
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
closureColumn(header: "Size", preferredWidth: 20, type: Long, read : {row -> row.size})
closureColumn(header: "Direct Sources", preferredWidth: 50, type : Integer, read : { row -> model.hashBucket[row.infohash].size()})
closureColumn(header: "Possible Sources", preferredWidth : 50, type : Integer, read : {row -> model.sourcesBucket[row.infohash].size()})
closureColumn(header: "Sender", preferredWidth: 170, type: String, read : {row -> row.sender.getHumanReadableName()})
closureColumn(header: "Trust", preferredWidth: 50, type: String, read : {row ->
model.core.trustService.getLevel(row.sender.destination).toString()
})
def sendersTable
def pane = panel {
gridLayout(rows :1, cols : 1)
splitPane(orientation: JSplitPane.VERTICAL_SPLIT, continuousLayout : true, dividerLocation: 300 ) {
panel {
borderLayout()
scrollPane (constraints : BorderLayout.CENTER) {
sendersTable = table(id : "senders-table", autoCreateRowSorter : true) {
tableModel(list : model.senders) {
closureColumn(header : "Sender", preferredWidth : 500, type: String, read : {row -> row.getHumanReadableName()})
closureColumn(header : "Results", preferredWidth : 20, type: Integer, read : {row -> model.sendersBucket[row].size()})
closureColumn(header : "Trust", preferredWidth : 50, type: String, read : { row ->
model.core.trustService.getLevel(row.destination).toString()
})
}
}
}
panel(constraints : BorderLayout.SOUTH) {
button(text : "Trust", enabled: bind {model.trustButtonsEnabled }, trustAction)
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
}
}
panel {
borderLayout()
scrollPane (constraints : BorderLayout.CENTER) {
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
tableModel(list: model.results) {
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
closureColumn(header: "Size", preferredWidth: 20, type: Long, read : {row -> row.size})
closureColumn(header: "Direct Sources", preferredWidth: 50, type : Integer, read : { row -> model.hashBucket[row.infohash].size()})
closureColumn(header: "Possible Sources", preferredWidth : 50, type : Integer, read : {row -> model.sourcesBucket[row.infohash].size()})
}
}
}
panel(constraints : BorderLayout.SOUTH) {
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
}
}
}
}
@@ -63,17 +93,19 @@ class SearchTabView {
this.pane.putClientProperty("results-table",resultsTable)
this.resultsTable = resultsTable
this.sendersTable = sendersTable
def selectionModel = resultsTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener( {
int row = resultsTable.getSelectedRow()
if (row < 0)
if (row < 0) {
model.downloadActionEnabled = false
return
}
if (lastSortEvent != null)
row = resultsTable.rowSorter.convertRowIndexToModel(row)
mvcGroup.parentGroup.model.trustButtonsEnabled = true
mvcGroup.parentGroup.model.downloadActionEnabled = mvcGroup.parentGroup.model.canDownload(model.results[row].infohash)
model.downloadActionEnabled = mvcGroup.parentGroup.model.canDownload(model.results[row].infohash)
})
}
}
@@ -98,12 +130,11 @@ class SearchTabView {
}
parent.setTabComponentAt(index, tabPanel)
mvcGroup.parentGroup.view.showSearchWindow.call()
def centerRenderer = new DefaultTableCellRenderer()
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
resultsTable.columnModel.getColumn(1).setCellRenderer(centerRenderer)
resultsTable.setDefaultRenderer(Integer.class,centerRenderer)
resultsTable.columnModel.getColumn(4).setCellRenderer(centerRenderer)
resultsTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
@@ -118,7 +149,7 @@ class SearchTabView {
if (e.button == MouseEvent.BUTTON3)
showPopupMenu(e)
else if (e.button == MouseEvent.BUTTON1 && e.clickCount == 2)
mvcGroup.parentGroup.controller.download()
mvcGroup.controller.download()
}
@Override
public void mouseReleased(MouseEvent e) {
@@ -126,21 +157,42 @@ class SearchTabView {
showPopupMenu(e)
}
})
// senders table
sendersTable.setDefaultRenderer(Integer.class, centerRenderer)
sendersTable.rowSorter.addRowSorterListener({evt -> lastSendersSortEvent = evt})
sendersTable.rowSorter.setSortsOnUpdates(true)
def selectionModel = sendersTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
int row = selectedSenderRow()
if (row < 0) {
model.trustButtonsEnabled = false
return
} else {
model.trustButtonsEnabled = true
model.results.clear()
Persona p = model.senders[row]
model.results.addAll(model.sendersBucket[p])
resultsTable.model.fireTableDataChanged()
}
})
}
def closeTab = {
int index = parent.indexOfTab(searchTerms)
parent.removeTabAt(index)
mvcGroup.parentGroup.model.trustButtonsEnabled = false
mvcGroup.parentGroup.model.downloadActionEnabled = false
model.trustButtonsEnabled = false
model.downloadActionEnabled = false
mvcGroup.destroy()
}
def showPopupMenu(MouseEvent e) {
JPopupMenu menu = new JPopupMenu()
if (mvcGroup.parentGroup.model.downloadActionEnabled) {
if (model.downloadActionEnabled) {
JMenuItem download = new JMenuItem("Download")
download.addActionListener({mvcGroup.parentGroup.controller.download()})
download.addActionListener({mvcGroup.controller.download()})
menu.add(download)
}
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
@@ -160,4 +212,13 @@ class SearchTabView {
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
clipboard.setContents(selection, null)
}
int selectedSenderRow() {
int row = sendersTable.getSelectedRow()
if (row < 0)
return -1
if (lastSendersSortEvent != null)
row = sendersTable.rowSorter.convertRowIndexToModel(row)
row
}
}

View File

@@ -0,0 +1,25 @@
package com.muwire.gui
import griffon.core.test.GriffonFestRule
import org.fest.swing.fixture.FrameFixture
import org.junit.Rule
import org.junit.Test
import static org.junit.Assert.fail
class ContentPanelIntegrationTest {
static {
System.setProperty('griffon.swing.edt.violations.check', 'true')
System.setProperty('griffon.swing.edt.hang.monitor', 'true')
}
@Rule
public final GriffonFestRule fest = new GriffonFestRule()
private FrameFixture window
@Test
void smokeTest() {
fail('Not implemented yet!')
}
}

View File

@@ -22,10 +22,9 @@ class DownloadProgressRenderer extends DefaultTableCellRenderer {
int done = d.donePieces()
int percent = -1
if (pieces != 0)
percent = (int)(done * 100.0 / pieces)
long size = d.pieceSize * pieces
String totalSize = DataHelper.formatSize2Decimal(size, false) + "B"
setText(String.format("%2d", percent) + "% of ${totalSize} ($done/$pieces pcs)".toString())
percent = (done * 100 / pieces)
String totalSize = DataHelper.formatSize2Decimal(d.length, false) + "B"
setText(String.format("%2d", percent) + "% of ${totalSize}".toString())
if (isSelected) {
setForeground(table.getSelectionForeground())

View File

@@ -0,0 +1,21 @@
package com.muwire.gui
import griffon.core.test.GriffonUnitRule
import griffon.core.test.TestFor
import org.junit.Rule
import org.junit.Test
import static org.junit.Assert.fail
@TestFor(ContentPanelController)
class ContentPanelControllerTest {
private ContentPanelController controller
@Rule
public final GriffonUnitRule griffon = new GriffonUnitRule()
@Test
void smokeTest() {
fail('Not yet implemented!')
}
}

123
plug/build.gradle Normal file
View File

@@ -0,0 +1,123 @@
buildscript {
repositories { mavenCentral() }
dependencies {
classpath fileTree("../i2pjars") { include '*.jar' }
classpath 'gnu.getopt:java-getopt:1.0.13'
}
}
apply plugin : 'java'
dependencies {
compile project(":webui")
}
def buildDir = new File("$buildDir")
def zipDir = new File(buildDir, "zip")
def libDir = new File(zipDir, "lib")
def consoleDir = new File(zipDir, "console")
def webAppDir = new File(consoleDir, "webapps")
def keystore = new File(System.getProperty("user.home")+"/.i2p-plugin-keys/plugin-su3-keystore.ks")
String libDirPath() {
def libDir = new File("$buildDir/zip/lib")
libDir.listFiles().stream().map({
"\$PLUGIN/lib/" + it.getName()
}).collect(java.util.stream.Collectors.joining(','))
}
task pluginConfig {
doLast {
def binding = [ "version" : project.version + "-b" + project.buildNumber,
"author" : project.author,
"signer" : project.signer,
"websiteURL" : project.websiteURL,
"updateURLsu3" : project.updateURLsu3,
"i2pVersion" : project.i2pVersion ]
def templateFile = new File("$projectDir/templates/plugin.config.template")
def engine = new groovy.text.SimpleTemplateEngine()
def output = engine.createTemplate(templateFile).make(binding)
zipDir.mkdirs()
def outputFile = new File(zipDir,"plugin.config")
outputFile.text = output
}
}
task pluginDir {
dependsOn ':webui:assemble'
doLast { task ->
libDir.mkdirs()
def webapp = project(":webui")
def i2pjars = task.project.fileTree("../i2pjars").getFiles()
webapp.configurations.runtime.each {
if (!i2pjars.contains(it)) {
def dest = new File(libDir, it.getName())
java.nio.file.Files.copy(it.toPath(), dest.toPath())
}
}
webAppDir.mkdirs()
def warFile = webapp.configurations.warArtifact.getAllArtifacts().file[0]
def dest = new File(webAppDir, "MuWire.war")
java.nio.file.Files.copy(warFile.toPath(), dest.toPath())
"zip -d ${dest.toString()} *.jar".execute()
}
}
task webappConfig {
doLast {
def cPath = "webapps.MuWire.classpath=" + libDirPath()
def webappConfig = new File(consoleDir, "webapps.config")
webappConfig.text = cPath
}
}
task pack {
doLast {
if (project.pack200 == "true") {
libDir.listFiles().stream().filter( { it.getName().endsWith(".jar") } ).
filter({it.length() > 512*1024}).forEach {
println "packing $it"
def name = it.toString()
println "pack200 --no-gzip ${name}.pack $name".execute().text
it.delete()
}
} else {
println "pack200 disabled"
}
}
}
task pluginZip(type: Zip) {
archiveFileName = "MuWire.zip"
destinationDirectory = buildDir
from zipDir
}
task sign {
doLast {
def password = project.keystorePassword
if (password == "") {
println "enter your keystore password:"
def reader = new BufferedReader(new InputStreamReader(System.in))
password = reader.readLine()
}
def su3args = []
def version = project.version + "-b" + project.buildNumber
su3args << 'sign' << '-t' << '6' << '-c' << 'PLUGIN' << '-f' << '0' << '-p' << password << \
"$buildDir/MuWire.zip".toString() << "$buildDir/MuWire.su3".toString() << \
keystore.getAbsoluteFile().toString() << version << project.signer
println "now enter private key password for signer $project.signer"
net.i2p.crypto.SU3File.main(su3args.toArray(new String[0]))
}
}
webappConfig.dependsOn pluginDir
pack.dependsOn webappConfig
pluginZip.dependsOn(webappConfig,pluginConfig,pack)
sign.dependsOn pluginZip
assemble.dependsOn sign

View File

@@ -0,0 +1,14 @@
name=MuWire
description=Easy Anonymous File-Sharing
license=GPLv3
version=${version}
author=${author}
signer=${signer}
websiteURL=${websiteURL}
updateURL.su3=${updateURLsu3}
min-i2p-version=${i2pVersion}
min-java-version=1.8
consoleLinkName=MuWire
consoleLinkTooltip=Anonymous File Sharing
consoleLinkURL=/MuWire
<% println "date="+System.currentTimeMillis() %>

View File

@@ -4,3 +4,5 @@ include 'update-server'
include 'core'
include 'gui'
include 'cli'
// include 'webui'
// include 'plug'

View File

@@ -9,6 +9,7 @@ import net.i2p.client.I2PSession
import net.i2p.client.I2PSessionMuxedListener
import net.i2p.client.datagram.I2PDatagramDissector
import net.i2p.client.datagram.I2PDatagramMaker
import net.i2p.crypto.SigType
@Log
@@ -28,7 +29,7 @@ class UpdateServer {
def session
if (!keyFile.exists()) {
def os = new FileOutputStream(keyFile);
myDest = i2pClient.createDestination(os)
myDest = i2pClient.createDestination(os, SigType.EdDSA_SHA512_Ed25519)
os.close()
log.info "No key.dat file was found, so creating a new destination."
log.info "This is the destination you want to give out for your new UpdateServer"

12
webui/.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
Thumbs.db
.DS_Store
.gradle
build/
out/
.idea
*.iml
*.ipr
*.iws
.project
.settings
.classpath

109
webui/build.gradle Normal file
View File

@@ -0,0 +1,109 @@
buildscript {
repositories {
maven { url "https://repo.grails.org/grails/core" }
}
dependencies {
classpath "org.grails:grails-gradle-plugin:$grailsVersion"
classpath "org.grails.plugins:hibernate5:7.0.0"
classpath "gradle.plugin.com.github.erdi.webdriver-binaries:webdriver-binaries-gradle-plugin:2.0"
classpath "com.bertramlabs.plugins:asset-pipeline-gradle:3.0.10"
}
}
version "0.1"
group "webui"
apply plugin:"eclipse"
apply plugin:"idea"
apply plugin:"war"
apply plugin:"org.grails.grails-web"
apply plugin:"com.github.erdi.webdriver-binaries"
apply plugin:"org.grails.grails-gsp"
apply plugin:"com.bertramlabs.asset-pipeline"
repositories {
maven { url "https://repo.grails.org/grails/core" }
}
configurations {
developmentOnly
runtimeClasspath {
extendsFrom developmentOnly
}
warArtifact
}
dependencies {
compile project(":core")
developmentOnly("org.springframework.boot:spring-boot-devtools")
compile "org.springframework.boot:spring-boot-starter-logging"
compile "org.springframework.boot:spring-boot-autoconfigure"
compile "org.grails:grails-core"
compile "org.springframework.boot:spring-boot-starter-actuator"
provided "org.springframework.boot:spring-boot-starter-tomcat"
compile "org.grails:grails-web-boot"
compile "org.grails:grails-logging"
compile "org.grails:grails-plugin-rest"
compile "org.grails:grails-plugin-databinding"
compile "org.grails:grails-plugin-i18n"
compile "org.grails:grails-plugin-services"
compile "org.grails:grails-plugin-url-mappings"
compile "org.grails:grails-plugin-interceptors"
compile "org.grails.plugins:cache"
compile "org.grails.plugins:async"
compile "org.grails.plugins:scaffolding"
compile "org.grails.plugins:events"
compile "org.grails.plugins:hibernate5"
compile "org.hibernate:hibernate-core:5.4.0.Final"
compile "org.grails.plugins:gsp"
compileOnly "io.micronaut:micronaut-inject-groovy"
console "org.grails:grails-console"
profile "org.grails.profiles:web"
runtime "org.glassfish.web:el-impl:2.1.2-b03"
runtime "com.h2database:h2"
runtime "org.apache.tomcat:tomcat-jdbc"
runtime "javax.xml.bind:jaxb-api:2.3.0"
runtime "com.bertramlabs.plugins:asset-pipeline-grails:3.0.10"
testCompile "org.grails:grails-gorm-testing-support"
testCompile "org.mockito:mockito-core"
testCompile "org.grails:grails-web-testing-support"
testCompile "org.grails.plugins:geb"
testCompile "org.seleniumhq.selenium:selenium-remote-driver:3.14.0"
testCompile "org.seleniumhq.selenium:selenium-api:3.14.0"
testCompile "org.seleniumhq.selenium:selenium-support:3.14.0"
testRuntime "org.seleniumhq.selenium:selenium-chrome-driver:3.14.0"
testRuntime "org.seleniumhq.selenium:selenium-firefox-driver:3.14.0"
}
bootRun {
jvmArgs(
'-Dspring.output.ansi.enabled=always',
'-noverify',
'-XX:TieredStopAtLevel=1',
'-Xmx1024m')
sourceResources sourceSets.main
String springProfilesActive = 'spring.profiles.active'
systemProperty springProfilesActive, System.getProperty(springProfilesActive)
}
webdriverBinaries {
chromedriver '2.45.0'
geckodriver '0.24.0'
}
tasks.withType(Test) {
systemProperty "geb.env", System.getProperty('geb.env')
systemProperty "geb.build.reportsDir", reporting.file("geb/integrationTest")
systemProperty "webdriver.chrome.driver", System.getProperty('webdriver.chrome.driver')
systemProperty "webdriver.gecko.driver", System.getProperty('webdriver.gecko.driver')
}
assets {
minifyJs = true
minifyCss = true
}
artifacts {
warArtifact war
}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="93.58px" height="93.58px" viewBox="0 0 93.58 93.58" enable-background="new 0 0 93.58 93.58" xml:space="preserve">
<g>
<g>
<circle fill="none" stroke="#FEB672" stroke-width="2.8347" stroke-miterlimit="10" cx="46.79" cy="46.789" r="45.374"/>
</g>
<g>
<path fill="#FEB672" d="M71.126,29.576c0,0.414-0.337,0.75-0.75,0.75h-3.25v3.25c0,0.415-0.337,0.751-0.751,0.751h-1.499
c-0.415,0-0.75-0.336-0.75-0.751v-3.25h-3.251c-0.414,0-0.749-0.336-0.749-0.75v-1.498c0-0.416,0.335-0.752,0.749-0.752h3.251
v-3.249c0-0.414,0.335-0.75,0.75-0.75h1.499c0.414,0,0.751,0.336,0.751,0.75v3.249h3.25c0.413,0,0.75,0.336,0.75,0.752V29.576z"/>
</g>
<path fill="#FEB672" d="M50.42,60.386c0.554,1.467,0.855,1.951,1.493,3.44c0.271,0.627,0.523,1.228,0.649,1.518
c0.049,0.117,0.036,0.248-0.033,0.355c-0.172,0.259-0.552,0.747-1.181,1.086c-1.098,0.594-3.409,0.809-4.555,0.812h-0.006
c-1.146-0.004-3.457-0.219-4.558-0.812c-0.627-0.339-1.006-0.827-1.177-1.086c-0.07-0.107-0.083-0.238-0.032-0.355
c0.123-0.29,0.376-0.891,0.646-1.518c0.64-1.489,0.941-1.974,1.495-3.44c0.485-1.294,0.729-3.175,0.745-4.593
c0.006-0.604-0.03-1.122-0.106-1.476c-0.121-0.56-0.501-1.412-0.907-2.042c-0.548-0.849-1.527-1.583-2.157-1.919
c-0.475-0.254-1.984-0.817-2.576-1.146c-0.755-0.416-1.739-1.067-2.399-1.584c-0.735-0.574-2.182-1.992-2.746-2.695
c-1.084-1.344-2.083-2.922-2.565-4.62c-0.601-2.106-0.576-3.009-0.657-3.688c-0.014-0.117,0.075-0.222,0.191-0.227
c0.73-0.025,3.854-0.093,16.809-0.081c12.953-0.012,16.076,0.056,16.806,0.081c0.118,0.005,0.206,0.109,0.191,0.227
c-0.08,0.68-0.057,1.582-0.654,3.688c-0.486,1.698-1.483,3.276-2.567,4.62c-0.564,0.703-2.011,2.121-2.746,2.695
c-0.661,0.517-1.646,1.168-2.399,1.584c-0.594,0.328-2.102,0.892-2.576,1.146c-0.63,0.336-1.608,1.07-2.158,1.919
c-0.405,0.63-0.785,1.482-0.904,2.042c-0.079,0.354-0.112,0.872-0.107,1.476C49.69,57.211,49.935,59.092,50.42,60.386z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="93.58px" height="93.58px" viewBox="0 0 93.58 93.58" enable-background="new 0 0 93.58 93.58" xml:space="preserve">
<g>
<g>
<circle fill="none" stroke="#FEB672" stroke-width="2.8347" stroke-miterlimit="10" cx="46.88" cy="46.792" r="45.374"/>
</g>
<path fill="#FEB672" d="M64.379,40.958v24.062c0,1.208-0.979,2.188-2.188,2.188H31.567c-1.208,0-2.188-0.979-2.188-2.188V28.562
c0-1.208,0.98-2.188,2.188-2.188h18.229v12.396c0,1.208,0.979,2.188,2.188,2.188H64.379z M55.629,44.604
c0-0.41-0.318-0.729-0.729-0.729H38.858c-0.41,0-0.729,0.319-0.729,0.729v1.458c0,0.41,0.319,0.729,0.729,0.729H54.9
c0.41,0,0.729-0.319,0.729-0.729V44.604z M55.629,50.438c0-0.41-0.318-0.729-0.729-0.729H38.858c-0.41,0-0.729,0.319-0.729,0.729
v1.458c0,0.41,0.319,0.729,0.729,0.729H54.9c0.41,0,0.729-0.319,0.729-0.729V50.438z M55.629,56.271
c0-0.41-0.318-0.729-0.729-0.729H38.858c-0.41,0-0.729,0.319-0.729,0.729v1.458c0,0.41,0.319,0.729,0.729,0.729H54.9
c0.41,0,0.729-0.319,0.729-0.729V56.271z M63.468,38.042H52.713V27.287c0.318,0.205,0.592,0.41,0.82,0.638l9.297,9.297
C63.059,37.449,63.264,37.723,63.468,38.042z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="500">
<desc iVinci="yes" version="4.5" gridStep="20" showGrid="no" snapToGrid="no" codePlatform="0"/>
<g id="Layer1" opacity="1">
<g id="Shape1">
<desc shapeID="1" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(-74.3391,-50.75,148.678,101.5)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(4.79624,0,0,4.79624,500,250)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
<path id="shapePath1" d="M527.264,491.011 C544.051,488.613 563.236,483.817 572.829,479.021 C582.421,474.224 589.615,467.03 589.615,462.234 C589.615,462.234 587.217,457.438 584.819,452.641 C580.023,445.447 575.227,435.854 563.236,409.475 C558.44,397.484 547.589,366.072 544.051,351.92 C540.386,330.773 540.051,308.254 544.051,287.171 C547.531,274.839 552.314,262.919 560.838,253.597 C570.402,240.945 581.622,228.467 596.81,222.422 C644.094,203.599 699.929,162.469 728.707,116.904 C738.299,100.117 742.876,92.923 746.372,83.3305 C755.023,59.5988 762.66,34.3876 762.28,8.98871 L762.28,6.59059 L498.487,6.59059 L232.295,6.59059 L232.295,11.3868 C231.901,74.2274 269.048,130.868 313.831,172.061 C337.813,193.644 366.59,210.431 400.164,222.422 C412.154,227.218 416.951,229.616 426.543,239.208 C438.534,253.597 448.126,270.384 452.923,289.569 C455.827,317.286 453.654,346.577 445.728,373.503 L440.932,387.892 C438.534,397.484 431.339,411.873 419.349,435.854 C407.358,459.836 407.358,462.234 407.358,464.632 C412.154,479.021 440.932,488.613 484.098,493.409 C493.691,493.409 508.079,493.409 527.264,491.011 M325.822,409.475 C342.609,407.077 356.998,402.281 361.794,395.086 L361.794,392.688 L359.396,385.494 C342.609,354.318 333.016,327.939 333.016,301.56 C333.016,287.171 335.415,279.977 340.211,267.986 C347.405,255.995 349.803,252.125 361.794,247.329 C366.59,244.876 372.313,243.95 374.711,242.478 C380.979,240.625 388.173,236.81 388.173,236.81 C388.173,236.81 383.868,235.884 379.016,233.486 C364.628,228.69 359.396,224.82 347.405,217.625 C309.035,196.042 285.054,174.459 261.073,143.284 C253.878,131.293 250.156,125.996 246.684,121.163 L244.286,116.904 C241.888,114.506 145.963,114.506 143.565,116.904 C141.939,150.478 158.03,180.057 179.536,205.635 C204.661,235.514 225.101,244.005 244.286,248.801 C261.073,253.597 263.471,255.995 270.665,265.588 C275.462,277.578 277.86,284.773 277.86,299.161 C280.258,320.745 273.063,342.328 258.675,373.503 C253.878,383.096 249.082,392.688 249.082,392.688 C249.082,395.086 253.878,399.883 258.675,402.281 C270.665,409.475 304.239,414.271 325.822,409.475 M716.716,409.475 C735.901,407.077 747.892,402.281 750.29,395.086 C750.29,392.688 750.29,390.29 743.095,375.901 C728.008,346.118 717.597,310.72 726.308,277.578 C731.287,264.162 737.689,250.182 752.688,247.852 C776.669,240.658 795.854,229.616 819.835,205.635 C834.224,191.246 847.61,166.971 851.369,152.876 C854.382,141.577 858.172,128.066 855.807,116.904 C853.409,114.506 755.086,114.506 752.688,116.904 C752.688,116.904 750.29,119.302 747.892,121.7 C745.493,128.895 735.901,143.284 728.707,150.478 C719.114,162.469 690.337,191.246 680.744,198.44 C663.057,216.559 629.114,228.768 611.199,236.81 C613.597,239.208 625.587,246.403 635.18,248.801 C654.365,255.995 654.365,255.995 661.559,267.986 C666.355,279.977 668.754,287.171 668.754,301.56 C670.08,334.844 653.109,365.67 639.976,392.688 C657.022,411.883 692.824,411.394 716.716,409.475 Z" style="stroke:none;fill-rule:evenodd;fill:#ffffff;fill-opacity:1;"/>
</g>
<g id="Shape2">
<desc shapeID="2" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(-3.75,-28,7.5,56)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(1,0,0,1,417.25,99.5)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
<path id="shapePath2" d="M413.5,127.5 C414,126.5 416,123 416.5,122.5 C416,123 414,126.5 413.5,127.5 M421,71.5 " style="stroke:none;fill-rule:evenodd;fill:#669020;fill-opacity:1;"/>
</g>
<g id="Shape3">
<desc shapeID="3" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(0,0,0,0)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(1,0,0,1,0,0)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
<path id="shapePath3" d="M0,0 Z" style="stroke:none;fill-rule:evenodd;fill:#4c4c4c;fill-opacity:1;"/>
</g>
<g id="Shape4">
<desc shapeID="4" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(0,0,0,0)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(1,0,0,1,0,0)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
<path id="shapePath4" d="M0,0 Z" style="stroke:none;fill-rule:evenodd;fill:#000000;fill-opacity:1;"/>
</g>
<g id="Shape5">
<desc shapeID="5" type="0" basicInfo-basicType="0" basicInfo-roundedRectRadius="12" basicInfo-polygonSides="6" basicInfo-starPoints="5" bounding="rect(-84.6928,-47.6497,169.386,95.2993)" text="" font-familyName="" font-pixelSize="20" font-bold="0" font-underline="0" font-alignment="1" strokeStyle="0" markerStart="0" markerEnd="0" shadowEnabled="0" shadowOffsetX="0" shadowOffsetY="2" shadowBlur="4" shadowOpacity="160" blurEnabled="0" blurRadius="4" transform="matrix(1,0,0,1,90.9499,90.9738)" pers-center="0,0" pers-size="0,0" pers-start="0,0" pers-end="0,0" locked="0" mesh="" flag=""/>
<path id="shapePath5" d="M0,0 Z" style="stroke:none;fill-rule:evenodd;fill:#0d0d0d;fill-opacity:1;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="45px" height="45px" viewBox="0 0 45 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.1 (67048) - http://www.bohemiancoding.com/sketch -->
<title>slack_orange</title>
<desc>Created with Sketch.</desc>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="slack_orange">
<path d="M22.502,0 C10.073,0 0,10.073 0,22.499 C0,34.927 10.073,45 22.502,45 C34.927,45 45,34.927 45,22.499 C45,10.073 34.927,0 22.502,0 Z" id="Shape" fill="#FEB672"></path>
<g id="Slack_Mark_Monochrome_White" transform="translate(11.000000, 11.000000)" fill="#FFFFFF">
<rect id="Rectangle-path" transform="translate(11.554441, 11.462704) rotate(-18.518296) translate(-11.554441, -11.462704) " x="9.94812935" y="9.91115274" width="3.21262291" height="3.10310168"></rect>
<g id="Group">
<rect id="Rectangle-path" transform="translate(11.554441, 11.462704) rotate(-18.518296) translate(-11.554441, -11.462704) " x="9.94812935" y="9.91115274" width="3.21262291" height="3.10310168"></rect>
<path d="M22.0142857,8.30555556 C19.6595238,0.456349206 16.2642857,-1.36904762 8.41507937,0.985714286 C0.565873016,3.34047619 -1.25952381,6.73571429 1.0952381,14.5849206 C3.45,22.434127 6.8452381,24.2595238 14.6944444,21.9047619 C22.5436508,19.55 24.3690476,16.1547619 22.0142857,8.30555556 Z M18.0531746,13.3984127 L16.5746032,13.8912698 L17.0857143,15.4246032 C17.2865079,16.0452381 16.9579365,16.7206349 16.3373016,16.9214286 C16.2095238,16.9579365 16.0634921,16.9944444 15.9357143,16.9761905 C15.4611111,16.9579365 15.0047619,16.647619 14.8404762,16.1730159 L14.3293651,14.6396825 L11.2809524,15.6619048 L11.7920635,17.1952381 C11.9928571,17.815873 11.6642857,18.4912698 11.0436508,18.6920635 C10.915873,18.7285714 10.7698413,18.7650794 10.6420635,18.7468254 C10.1674603,18.7285714 9.71111111,18.418254 9.5468254,17.9436508 L9.03571429,16.4103175 L7.55714286,16.9031746 C7.42936508,16.9396825 7.28333333,16.9761905 7.15555556,16.9579365 C6.68095238,16.9396825 6.22460317,16.6293651 6.06031746,16.1547619 C5.85952381,15.534127 6.18809524,14.8587302 6.80873016,14.6579365 L8.28730159,14.1650794 L7.3015873,11.2261905 L5.82301587,11.7190476 C5.6952381,11.7555556 5.54920635,11.7920635 5.42142857,11.7738095 C4.9468254,11.7555556 4.49047619,11.4452381 4.32619048,10.9706349 C4.12539683,10.35 4.45396825,9.67460317 5.07460317,9.47380952 L6.5531746,8.98095238 L6.04206349,7.44761905 C5.84126984,6.82698413 6.16984127,6.1515873 6.79047619,5.95079365 C7.41111111,5.75 8.08650794,6.07857143 8.28730159,6.69920635 L8.7984127,8.23253968 L11.8468254,7.21031746 L11.3357143,5.67698413 C11.1349206,5.05634921 11.4634921,4.38095238 12.084127,4.18015873 C12.7047619,3.97936508 13.3801587,4.30793651 13.5809524,4.92857143 L14.0920635,6.46190476 L15.5706349,5.96904762 C16.1912698,5.76825397 16.8666667,6.0968254 17.0674603,6.71746032 C17.268254,7.33809524 16.9396825,8.01349206 16.3190476,8.21428571 L14.8404762,8.70714286 L15.8261905,11.6460317 L17.3047619,11.1531746 C17.9253968,10.952381 18.6007937,11.2809524 18.8015873,11.9015873 C19.002381,12.5222222 18.6738095,13.197619 18.0531746,13.3984127 Z" id="Shape" fill-rule="nonzero"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,11 @@
// This is a manifest file that'll be compiled into application.js.
//
// Any JavaScript file within this directory can be referenced here using a relative path.
//
// You're free to add application-wide JavaScript to this file, but it's generally better
// to create separate JavaScript files as needed.
//
//= require jquery-3.3.1.min
//= require bootstrap
//= require popper.min
//= require_self

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,15 @@
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS file within this directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the top of the
* compiled file, but it's generally better to create a new file per style scope.
*
*= require bootstrap
*= require grails
*= require main
*= require mobile
*= require_self
*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,331 @@
/*!
* Bootstrap Reboot v4.1.3 (https://getbootstrap.com/)
* Copyright 2011-2018 The Bootstrap Authors
* Copyright 2011-2018 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-ms-overflow-style: scrollbar;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
@-ms-viewport {
width: device-width;
}
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus {
outline: 0 !important;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0.5rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-original-title] {
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: .5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
dfn {
font-style: italic;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -.25em;
}
sup {
top: -.5em;
}
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
-webkit-text-decoration-skip: objects;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
a:not([href]):not([tabindex]) {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):focus {
outline: 0;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
-ms-overflow-style: scrollbar;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
border-style: none;
}
svg {
overflow: hidden;
vertical-align: middle;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
button,
html [type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
-webkit-appearance: listbox;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
cursor: pointer;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,109 @@
h1, h2 {
margin: 10px 25px 5px;
}
h2 {
font-size: 1.1em;
}
.filename {
font-style: italic;
}
.exceptionMessage {
margin: 10px;
border: 1px solid #000;
padding: 5px;
background-color: #E9E9E9;
}
.stack,
.snippet {
margin: 0 25px 10px;
}
.stack,
.snippet {
border: 1px solid #ccc;
-mox-box-shadow: 0 0 2px rgba(0,0,0,0.2);
-webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2);
box-shadow: 0 0 2px rgba(0,0,0,0.2);
}
/* error details */
.error-details {
border-top: 1px solid #FFAAAA;
-mox-box-shadow: 0 0 2px rgba(0,0,0,0.2);
-webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2);
box-shadow: 0 0 2px rgba(0,0,0,0.2);
border-bottom: 1px solid #FFAAAA;
-mox-box-shadow: 0 0 2px rgba(0,0,0,0.2);
-webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2);
box-shadow: 0 0 2px rgba(0,0,0,0.2);
background-color:#FFF3F3;
line-height: 1.5;
overflow: hidden;
padding: 5px;
padding-left:25px;
}
.error-details dt {
clear: left;
float: left;
font-weight: bold;
margin-right: 5px;
}
.error-details dt:after {
content: ":";
}
.error-details dd {
display: block;
}
/* stack trace */
.stack {
padding: 5px;
overflow: auto;
height: 150px;
}
/* code snippet */
.snippet {
background-color: #fff;
font-family: monospace;
}
.snippet .line {
display: block;
}
.snippet .lineNumber {
background-color: #ddd;
color: #999;
display: inline-block;
margin-right: 5px;
padding: 0 3px;
text-align: right;
width: 3em;
}
.snippet .error {
background-color: #fff3f3;
font-weight: bold;
}
.snippet .error .lineNumber {
background-color: #faa;
color: #333;
font-weight: bold;
}
.snippet .line:first-child .lineNumber {
padding-top: 5px;
}
.snippet .line:last-child .lineNumber {
padding-bottom: 5px;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,594 @@
/* FONT STACK */
body,
input, select, textarea {
font-family: "Open Sans", "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
}
h1, h2, h3, h4, h5, h6 {
line-height: 1.1;
}
/* BASE LAYOUT */
html {
background-color: #ddd;
background-image: -moz-linear-gradient(center top, #aaa, #ddd);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #aaa), color-stop(1, #ddd));
background-image: linear-gradient(to bottom, #aaa, #ddd);
filter: progid:DXImageTransform.Microsoft.gradient(startColorStr = '#aaaaaa', EndColorStr = '#dddddd');
background-repeat: no-repeat;
height: 100%;
/* change the box model to exclude the padding from the calculation of 100% height (IE8+) */
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html.no-cssgradients {
background-color: #aaa;
}
html * {
margin: 0;
}
body {
background-color: #F5F5F5;
color: #333333;
overflow-x: hidden; /* prevents box-shadow causing a horizontal scrollbar in firefox when viewport < 960px wide */
-moz-box-shadow: 0 0 0.3em #424649;
-webkit-box-shadow: 0 0 0.3em #424649;
box-shadow: 0 0 0.3em #424649;
}
#grailsLogo {
background-color: #feb672;
}
a:hover, a:active {
outline: none; /* prevents outline in webkit on active links but retains it for tab focus */
}
h1, h2, h3 {
font-weight: normal;
font-size: 1.25em;
margin: 0.8em 0 0.3em 0;
}
ul {
padding: 0;
}
img {
border: 0;
}
/* GENERAL */
#grailsLogo a {
display: inline-block;
margin: 1em;
}
.content {
}
.content h1 {
border-bottom: 1px solid #CCCCCC;
margin: 0.8em 1em 0.3em;
padding: 0 0.25em;
}
.scaffold-list h1 {
border: none;
}
.footer img {
height: 80px;
margin-right: 25px;
margin-bottom: 15px;
clear: bottom;
}
.footer strong a {
color: white;
text-decoration: none;
font-size: 1.1rem;
}
.footer {
background: #424649;
color: #ffffff;
clear: both;
font-size: 1em;
margin-top: 1.5em;
padding: 1em;
padding-bottom: 2em;
min-height: 1em;
}
.footer a {
color: #feb672;
}
.spinner {
background: url(../images/spinner.gif) 50% 50% no-repeat transparent;
height: 16px;
width: 16px;
padding: 0.5em;
position: absolute;
right: 0;
top: 0;
text-indent: -9999px;
}
/* NAVIGATION MENU */
.nav {
zoom: 1;
}
.nav ul {
overflow: hidden;
padding-left: 0;
zoom: 1;
}
.nav li {
display: block;
float: left;
list-style-type: none;
margin-right: 0.5em;
padding: 0;
}
.nav a {
color: #666666;
display: block;
padding: 0.25em 0.7em;
text-decoration: none;
-moz-border-radius: 0.3em;
-webkit-border-radius: 0.3em;
border-radius: 0.3em;
}
.nav li.dropdown-item a {
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
.nav a:active, .nav a:visited {
color: #666666;
}
.nav a:focus, .nav a:hover {
background-color: #999999;
color: #ffffff;
outline: none;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
}
.no-borderradius .nav a:focus, .no-borderradius .nav a:hover {
background-color: transparent;
color: #444444;
text-decoration: underline;
}
.nav a.home, .nav a.list, .nav a.create {
background-position: 0.7em center;
background-repeat: no-repeat;
text-indent: 25px;
}
.nav a.home {
background-image: url(../images/skin/house.png);
}
.nav a.list {
background-image: url(../images/skin/database_table.png);
}
.nav a.create {
background-image: url(../images/skin/database_add.png);
}
.nav li.dropdown.show ul.dropdown-menu {
background-color: #424649;
}
/* CREATE/EDIT FORMS AND SHOW PAGES */
fieldset,
.property-list {
margin: 0.6em 1.25em 0 1.25em;
padding: 0.3em 1.8em 1.25em;
position: relative;
zoom: 1;
border: none;
}
.property-list .fieldcontain {
list-style: none;
overflow: hidden;
zoom: 1;
}
.fieldcontain {
margin-top: 1em;
}
.fieldcontain label,
.fieldcontain .property-label {
color: #666666;
text-align: right;
width: 25%;
}
.fieldcontain .property-label {
float: left;
}
.fieldcontain .property-value {
display: block;
margin-left: 27%;
}
label {
cursor: pointer;
display: inline-block;
margin: 0 0.25em 0 0;
}
input, select, textarea {
background-color: #fcfcfc;
border: 1px solid #cccccc;
font-size: 1em;
padding: 0.2em 0.4em;
}
select {
padding: 0.2em 0.2em 0.2em 0;
}
select[multiple] {
vertical-align: top;
}
textarea {
width: 250px;
height: 150px;
overflow: auto; /* IE always renders vertical scrollbar without this */
vertical-align: top;
}
input[type=checkbox], input[type=radio] {
background-color: transparent;
border: 0;
padding: 0;
}
input:focus, select:focus, textarea:focus {
background-color: #ffffff;
border: 1px solid #eeeeee;
outline: 0;
-moz-box-shadow: 0 0 0.5em #ffffff;
-webkit-box-shadow: 0 0 0.5em #ffffff;
box-shadow: 0 0 0.5em #ffffff;
}
.required-indicator {
color: #cc0000;
display: inline-block;
font-weight: bold;
margin-left: 0.3em;
position: relative;
top: 0.1em;
}
ul.one-to-many {
display: inline-block;
list-style-position: inside;
vertical-align: top;
}
ul.one-to-many li.add {
list-style-type: none;
}
/* EMBEDDED PROPERTIES */
fieldset.embedded {
background-color: transparent;
border: 1px solid #CCCCCC;
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
fieldset.embedded legend {
margin: 0 1em;
}
/* MESSAGES AND ERRORS */
.errors,
.message {
font-size: 0.8em;
line-height: 2;
margin: 1em 2em;
padding: 0.25em;
}
.message {
background: #f3f3ff;
border: 1px solid #b2d1ff;
color: #006dba;
-moz-box-shadow: 0 0 0.25em #b2d1ff;
-webkit-box-shadow: 0 0 0.25em #b2d1ff;
box-shadow: 0 0 0.25em #b2d1ff;
}
.errors {
background: #fff3f3;
border: 1px solid #ffaaaa;
color: #cc0000;
-moz-box-shadow: 0 0 0.25em #ff8888;
-webkit-box-shadow: 0 0 0.25em #ff8888;
box-shadow: 0 0 0.25em #ff8888;
}
.errors ul,
.message {
padding: 0;
}
.errors li {
list-style: none;
background: transparent url(../images/skin/exclamation.png) 0.5em 50% no-repeat;
text-indent: 2.2em;
}
.message {
background: transparent url(../images/skin/information.png) 0.5em 50% no-repeat;
text-indent: 2.2em;
}
/* form fields with errors */
.error input, .error select, .error textarea {
background: #fff3f3;
border-color: #ffaaaa;
color: #cc0000;
}
.error input:focus, .error select:focus, .error textarea:focus {
-moz-box-shadow: 0 0 0.5em #ffaaaa;
-webkit-box-shadow: 0 0 0.5em #ffaaaa;
box-shadow: 0 0 0.5em #ffaaaa;
}
/* same effects for browsers that support HTML5 client-side validation (these have to be specified separately or IE will ignore the entire rule) */
input:invalid, select:invalid, textarea:invalid {
background: #fff3f3;
border-color: #ffaaaa;
color: #cc0000;
}
input:invalid:focus, select:invalid:focus, textarea:invalid:focus {
-moz-box-shadow: 0 0 0.5em #ffaaaa;
-webkit-box-shadow: 0 0 0.5em #ffaaaa;
box-shadow: 0 0 0.5em #ffaaaa;
}
/* TABLES */
table {
border-top: 1px solid #DFDFDF;
border-collapse: collapse;
width: 100%;
margin-bottom: 1em;
}
tr {
border: 0;
}
tr>td:first-child, tr>th:first-child {
padding-left: 1.25em;
}
tr>td:last-child, tr>th:last-child {
padding-right: 1.25em;
}
td, th {
line-height: 1.5em;
padding: 0.5em 0.6em;
text-align: left;
vertical-align: top;
}
th {
background-color: #efefef;
background-image: -moz-linear-gradient(top, #ffffff, #eaeaea);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #ffffff), color-stop(1, #eaeaea));
filter: progid:DXImageTransform.Microsoft.gradient(startColorStr = '#ffffff', EndColorStr = '#eaeaea');
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr='#ffffff', EndColorStr='#eaeaea')";
color: #666666;
font-weight: bold;
line-height: 1.7em;
padding: 0.2em 0.6em;
}
thead th {
white-space: nowrap;
}
th a {
display: block;
text-decoration: none;
}
th a:link, th a:visited {
color: #666666;
}
th a:hover, th a:focus {
color: #333333;
}
th.sortable a {
background-position: right;
background-repeat: no-repeat;
padding-right: 1.1em;
}
th.asc a {
background-image: url(../images/skin/sorted_asc.gif);
}
th.desc a {
background-image: url(../images/skin/sorted_desc.gif);
}
.odd {
background: #f7f7f7;
}
.even {
background: #ffffff;
}
th:hover, tr:hover {
background: #f5f5f5;
}
/* PAGINATION */
.pagination {
border-top: 0;
margin: 0.8em 1em 0.3em;
padding: 0.3em 0.2em;
text-align: center;
-moz-box-shadow: 0 0 3px 1px #AAAAAA;
-webkit-box-shadow: 0 0 3px 1px #AAAAAA;
box-shadow: 0 0 3px 1px #AAAAAA;
background-color: #EFEFEF;
}
.pagination a,
.pagination .currentStep {
color: #666666;
display: inline-block;
margin: 0 0.1em;
padding: 0.25em 0.7em;
text-decoration: none;
-moz-border-radius: 0.3em;
-webkit-border-radius: 0.3em;
border-radius: 0.3em;
}
.pagination a:hover, .pagination a:focus,
.pagination .currentStep {
background-color: #999999;
color: #ffffff;
outline: none;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
}
.no-borderradius .pagination a:hover, .no-borderradius .pagination a:focus,
.no-borderradius .pagination .currentStep {
background-color: transparent;
color: #444444;
text-decoration: underline;
}
/* ACTION BUTTONS */
.buttons {
background-color: #efefef;
overflow: hidden;
padding: 0.3em;
-moz-box-shadow: 0 0 3px 1px #aaaaaa;
-webkit-box-shadow: 0 0 3px 1px #aaaaaa;
box-shadow: 0 0 3px 1px #aaaaaa;
margin: 0.1em 0 0 0;
border: none;
}
.buttons input,
.buttons a {
background-color: transparent;
border: 0;
color: #666666;
cursor: pointer;
display: inline-block;
margin: 0 0.25em 0;
overflow: visible;
padding: 0.25em 0.7em;
text-decoration: none;
-moz-border-radius: 0.3em;
-webkit-border-radius: 0.3em;
border-radius: 0.3em;
}
.buttons input:hover, .buttons input:focus,
.buttons a:hover, .buttons a:focus {
background-color: #999999;
color: #ffffff;
outline: none;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
.no-borderradius .buttons input:hover, .no-borderradius .buttons input:focus,
.no-borderradius .buttons a:hover, .no-borderradius .buttons a:focus {
background-color: transparent;
color: #444444;
text-decoration: underline;
}
.buttons .delete, .buttons .edit, .buttons .save {
background-position: 0.7em center;
background-repeat: no-repeat;
text-indent: 25px;
}
.buttons .delete {
background-image: url(../images/skin/database_delete.png);
}
.buttons .edit {
background-image: url(../images/skin/database_edit.png);
}
.buttons .save {
background-image: url(../images/skin/database_save.png);
}
a.skip {
position: absolute;
left: -9999px;
}
.grails-logo-container {
background: #7c7c7c no-repeat 50% 30%;
margin-bottom: 20px;
color: white;
height:300px;
text-align:center;
}
img.grails-logo {
height:340px;
margin-top:-10px;
}

View File

@@ -0,0 +1,82 @@
/* Styles for mobile devices */
@media screen and (max-width: 480px) {
.nav {
padding: 0.5em;
}
.nav li {
margin: 0 0.5em 0 0;
padding: 0.25em;
}
/* Hide individual steps in pagination, just have next & previous */
.pagination .step, .pagination .currentStep {
display: none;
}
.pagination .prevLink {
float: left;
}
.pagination .nextLink {
float: right;
}
/* pagination needs to wrap around floated buttons */
.pagination {
overflow: hidden;
}
/* slightly smaller margin around content body */
fieldset,
.property-list {
padding: 0.3em 1em 1em;
}
input, textarea {
width: 100%;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
select, input[type=checkbox], input[type=radio], input[type=submit], input[type=button], input[type=reset] {
width: auto;
}
/* hide all but the first column of list tables */
.scaffold-list td:not(:first-child),
.scaffold-list th:not(:first-child) {
display: none;
}
.scaffold-list thead th {
text-align: center;
}
/* stack form elements */
.fieldcontain {
margin-top: 0.6em;
}
.fieldcontain label,
.fieldcontain .property-label,
.fieldcontain .property-value {
display: block;
float: none;
margin: 0 0 0.25em 0;
text-align: left;
width: auto;
}
.errors ul,
.message p {
margin: 0.5em;
}
.error ul {
margin-left: 0;
}
}

View File

@@ -0,0 +1,137 @@
---
grails:
profile: web
codegen:
defaultPackage: webui
gorm:
reactor:
# Whether to translate GORM events into Reactor events
# Disabled by default for performance reasons
events: false
info:
app:
name: '@info.app.name@'
version: '@info.app.version@'
grailsVersion: '@info.app.grailsVersion@'
spring:
jmx:
unique-names: true
main:
banner-mode: "off"
groovy:
template:
check-template-location: false
devtools:
restart:
additional-exclude:
- '*.gsp'
- '**/*.gsp'
- '*.gson'
- '**/*.gson'
- 'logback.groovy'
- '*.properties'
management:
endpoints:
enabled-by-default: false
---
grails:
mime:
disable:
accept:
header:
userAgents:
- Gecko
- WebKit
- Presto
- Trident
types:
all: '*/*'
atom: application/atom+xml
css: text/css
csv: text/csv
form: application/x-www-form-urlencoded
html:
- text/html
- application/xhtml+xml
js: text/javascript
json:
- application/json
- text/json
multipartForm: multipart/form-data
pdf: application/pdf
rss: application/rss+xml
text: text/plain
hal:
- application/hal+json
- application/hal+xml
xml:
- text/xml
- application/xml
urlmapping:
cache:
maxsize: 1000
controllers:
defaultScope: singleton
converters:
encoding: UTF-8
views:
default:
codec: html
gsp:
encoding: UTF-8
htmlcodec: xml
codecs:
expression: html
scriptlet: html
taglib: none
staticparts: none
management:
endpoints:
jmx:
unique-names: true
---
hibernate:
cache:
queries: false
use_second_level_cache: false
use_query_cache: false
dataSource:
pooled: true
jmxExport: true
driverClassName: org.h2.Driver
username: sa
password: ''
environments:
development:
dataSource:
dbCreate: create-drop
url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
test:
dataSource:
dbCreate: update
url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
production:
dataSource:
dbCreate: none
url: jdbc:h2:./prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
properties:
jmxEnabled: true
initialSize: 5
maxActive: 50
minIdle: 5
maxIdle: 25
maxWait: 10000
maxAge: 600000
timeBetweenEvictionRunsMillis: 5000
minEvictableIdleTimeMillis: 60000
validationQuery: SELECT 1
validationQueryTimeout: 3
validationInterval: 15000
testOnBorrow: true
testWhileIdle: true
testOnReturn: false
jdbcInterceptors: ConnectionState
defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED

View File

@@ -0,0 +1,37 @@
import grails.util.BuildSettings
import grails.util.Environment
import org.springframework.boot.logging.logback.ColorConverter
import org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter
import java.nio.charset.StandardCharsets
conversionRule 'clr', ColorConverter
conversionRule 'wex', WhitespaceThrowableProxyConverter
// See http://logback.qos.ch/manual/groovy.html for details on configuration
appender('STDOUT', ConsoleAppender) {
encoder(PatternLayoutEncoder) {
charset = StandardCharsets.UTF_8
pattern =
'%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} ' + // Date
'%clr(%5p) ' + // Log level
'%clr(---){faint} %clr([%15.15t]){faint} ' + // Thread
'%clr(%-40.40logger{39}){cyan} %clr(:){faint} ' + // Logger
'%m%n%wex' // Message
}
}
def targetDir = BuildSettings.TARGET_DIR
if (Environment.isDevelopmentMode() && targetDir != null) {
appender("FULL_STACKTRACE", FileAppender) {
file = "${targetDir}/stacktrace.log"
append = true
encoder(PatternLayoutEncoder) {
charset = StandardCharsets.UTF_8
pattern = "%level %logger - %msg%n"
}
}
logger("StackTrace", ERROR, ['FULL_STACKTRACE'], false)
}
root(ERROR, ['STDOUT'])

View File

@@ -0,0 +1,3 @@
// Place your Spring DSL code here
beans = {
}

View File

@@ -0,0 +1,8 @@
package webui
class HelloController {
def index() {
render "Hello from MuWire"
}
}

View File

@@ -0,0 +1,16 @@
package webui
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
"/"(view:"/index")
"500"(view:'/error')
"404"(view:'/notFound')
}
}

View File

@@ -0,0 +1,56 @@
default.doesnt.match.message=Property [{0}] of class [{1}] with value [{2}] does not match the required pattern [{3}]
default.invalid.url.message=Property [{0}] of class [{1}] with value [{2}] is not a valid URL
default.invalid.creditCard.message=Property [{0}] of class [{1}] with value [{2}] is not a valid credit card number
default.invalid.email.message=Property [{0}] of class [{1}] with value [{2}] is not a valid e-mail address
default.invalid.range.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid range from [{3}] to [{4}]
default.invalid.size.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid size range from [{3}] to [{4}]
default.invalid.max.message=Property [{0}] of class [{1}] with value [{2}] exceeds maximum value [{3}]
default.invalid.min.message=Property [{0}] of class [{1}] with value [{2}] is less than minimum value [{3}]
default.invalid.max.size.message=Property [{0}] of class [{1}] with value [{2}] exceeds the maximum size of [{3}]
default.invalid.min.size.message=Property [{0}] of class [{1}] with value [{2}] is less than the minimum size of [{3}]
default.invalid.validator.message=Property [{0}] of class [{1}] with value [{2}] does not pass custom validation
default.not.inlist.message=Property [{0}] of class [{1}] with value [{2}] is not contained within the list [{3}]
default.blank.message=Property [{0}] of class [{1}] cannot be blank
default.not.equal.message=Property [{0}] of class [{1}] with value [{2}] cannot equal [{3}]
default.null.message=Property [{0}] of class [{1}] cannot be null
default.not.unique.message=Property [{0}] of class [{1}] with value [{2}] must be unique
default.paginate.prev=Previous
default.paginate.next=Next
default.boolean.true=True
default.boolean.false=False
default.date.format=yyyy-MM-dd HH:mm:ss z
default.number.format=0
default.created.message={0} {1} created
default.updated.message={0} {1} updated
default.deleted.message={0} {1} deleted
default.not.deleted.message={0} {1} could not be deleted
default.not.found.message={0} not found with id {1}
default.optimistic.locking.failure=Another user has updated this {0} while you were editing
default.home.label=Home
default.list.label={0} List
default.add.label=Add {0}
default.new.label=New {0}
default.create.label=Create {0}
default.show.label=Show {0}
default.edit.label=Edit {0}
default.button.create.label=Create
default.button.edit.label=Edit
default.button.update.label=Update
default.button.delete.label=Delete
default.button.delete.confirm.message=Are you sure?
# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
typeMismatch.java.net.URL=Property {0} must be a valid URL
typeMismatch.java.net.URI=Property {0} must be a valid URI
typeMismatch.java.util.Date=Property {0} must be a valid Date
typeMismatch.java.lang.Double=Property {0} must be a valid number
typeMismatch.java.lang.Integer=Property {0} must be a valid number
typeMismatch.java.lang.Long=Property {0} must be a valid number
typeMismatch.java.lang.Short=Property {0} must be a valid number
typeMismatch.java.math.BigDecimal=Property {0} must be a valid number
typeMismatch.java.math.BigInteger=Property {0} must be a valid number
typeMismatch=Property {0} is type-mismatched

View File

@@ -0,0 +1,55 @@
default.doesnt.match.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] neodpovídá požadovanému vzoru [{3}]
default.invalid.url.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není validní URL
default.invalid.creditCard.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není validní číslo kreditní karty
default.invalid.email.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není validní emailová adresa
default.invalid.range.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není v povoleném rozmezí od [{3}] do [{4}]
default.invalid.size.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není v povoleném rozmezí od [{3}] do [{4}]
default.invalid.max.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] překračuje maximální povolenou hodnotu [{3}]
default.invalid.min.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] je menší než minimální povolená hodnota [{3}]
default.invalid.max.size.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] překračuje maximální velikost [{3}]
default.invalid.min.size.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] je menší než minimální velikost [{3}]
default.invalid.validator.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] neprošla validací
default.not.inlist.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] není obsažena v seznamu [{3}]
default.blank.message=Položka [{0}] třídy [{1}] nemůže být prázdná
default.not.equal.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] nemůže být stejná jako [{3}]
default.null.message=Položka [{0}] třídy [{1}] nemůže být prázdná
default.not.unique.message=Položka [{0}] třídy [{1}] o hodnotě [{2}] musí být unikátní
default.paginate.prev=Předcházející
default.paginate.next=Následující
default.boolean.true=Pravda
default.boolean.false=Nepravda
default.date.format=dd. MM. yyyy HH:mm:ss z
default.number.format=0
default.created.message={0} {1} vytvořeno
default.updated.message={0} {1} aktualizováno
default.deleted.message={0} {1} smazáno
default.not.deleted.message={0} {1} nelze smazat
default.not.found.message={0} nenalezen s id {1}
default.optimistic.locking.failure=Jiný uživatel aktualizoval záznam {0}, právě když byl vámi editován
default.home.label=Domů
default.list.label={0} Seznam
default.add.label=Přidat {0}
default.new.label=Nový {0}
default.create.label=Vytvořit {0}
default.show.label=Ukázat {0}
default.edit.label=Editovat {0}
default.button.create.label=Vytvoř
default.button.edit.label=Edituj
default.button.update.label=Aktualizuj
default.button.delete.label=Smaž
default.button.delete.confirm.message=Jste si jistý?
# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
typeMismatch.java.net.URL=Položka {0} musí být validní URL
typeMismatch.java.net.URI=Položka {0} musí být validní URI
typeMismatch.java.util.Date=Položka {0} musí být validní datum
typeMismatch.java.lang.Double=Položka {0} musí být validní desetinné číslo
typeMismatch.java.lang.Integer=Položka {0} musí být validní číslo
typeMismatch.java.lang.Long=Položka {0} musí být validní číslo
typeMismatch.java.lang.Short=Položka {0} musí být validní číslo
typeMismatch.java.math.BigDecimal=Položka {0} musí být validní číslo
typeMismatch.java.math.BigInteger=Položka {0} musí být validní číslo

View File

@@ -0,0 +1,56 @@
default.doesnt.match.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overholder ikke mønsteret [{3}]
default.invalid.url.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke en gyldig URL
default.invalid.creditCard.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke et gyldigt kreditkortnummer
default.invalid.email.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er ikke en gyldig e-mail adresse
default.invalid.range.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] ligger ikke inden for intervallet fra [{3}] til [{4}]
default.invalid.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] ligger ikke inden for størrelsen fra [{3}] til [{4}]
default.invalid.max.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overstiger den maksimale værdi [{3}]
default.invalid.min.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er under den minimale værdi [{3}]
default.invalid.max.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overstiger den maksimale størrelse på [{3}]
default.invalid.min.size.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] er under den minimale størrelse på [{3}]
default.invalid.validator.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] overholder ikke den brugerdefinerede validering
default.not.inlist.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] findes ikke i listen [{3}]
default.blank.message=Feltet [{0}] i klassen [{1}] kan ikke være tom
default.not.equal.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] må ikke være [{3}]
default.null.message=Feltet [{0}] i klassen [{1}] kan ikke være null
default.not.unique.message=Feltet [{0}] i klassen [{1}] som har værdien [{2}] skal være unik
default.paginate.prev=Forrige
default.paginate.next=Næste
default.boolean.true=Sand
default.boolean.false=Falsk
default.date.format=yyyy-MM-dd HH:mm:ss z
default.number.format=0
default.created.message={0} {1} oprettet
default.updated.message={0} {1} opdateret
default.deleted.message={0} {1} slettet
default.not.deleted.message={0} {1} kunne ikke slettes
default.not.found.message={0} med id {1} er ikke fundet
default.optimistic.locking.failure=En anden bruger har opdateret denne {0} imens du har lavet rettelser
default.home.label=Hjem
default.list.label={0} Liste
default.add.label=Tilføj {0}
default.new.label=Ny {0}
default.create.label=Opret {0}
default.show.label=Vis {0}
default.edit.label=Ret {0}
default.button.create.label=Opret
default.button.edit.label=Ret
default.button.update.label=Opdater
default.button.delete.label=Slet
default.button.delete.confirm.message=Er du sikker?
# Databindingsfejl. Brug "typeMismatch.$className.$propertyName for at passe til en given klasse (f.eks typeMismatch.Book.author)
typeMismatch.java.net.URL=Feltet {0} skal være en valid URL
typeMismatch.java.net.URI=Feltet {0} skal være en valid URI
typeMismatch.java.util.Date=Feltet {0} skal være en valid Dato
typeMismatch.java.lang.Double=Feltet {0} skal være et valid tal
typeMismatch.java.lang.Integer=Feltet {0} skal være et valid tal
typeMismatch.java.lang.Long=Feltet {0} skal være et valid tal
typeMismatch.java.lang.Short=Feltet {0} skal være et valid tal
typeMismatch.java.math.BigDecimal=Feltet {0} skal være et valid tal
typeMismatch.java.math.BigInteger=Feltet {0} skal være et valid tal

View File

@@ -0,0 +1,55 @@
default.doesnt.match.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] entspricht nicht dem vorgegebenen Muster [{3}]
default.invalid.url.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige URL
default.invalid.creditCard.message=Das Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige Kreditkartennummer
default.invalid.email.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist keine gültige E-Mail Adresse
default.invalid.range.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht im Wertebereich von [{3}] bis [{4}]
default.invalid.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht im Wertebereich von [{3}] bis [{4}]
default.invalid.max.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist größer als der Höchstwert von [{3}]
default.invalid.min.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist kleiner als der Mindestwert von [{3}]
default.invalid.max.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] übersteigt den Höchstwert von [{3}]
default.invalid.min.size.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] unterschreitet den Mindestwert von [{3}]
default.invalid.validator.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist ungültig
default.not.inlist.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] ist nicht in der Liste [{3}] enthalten.
default.blank.message=Die Eigenschaft [{0}] des Typs [{1}] darf nicht leer sein
default.not.equal.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] darf nicht gleich [{3}] sein
default.null.message=Die Eigenschaft [{0}] des Typs [{1}] darf nicht null sein
default.not.unique.message=Die Eigenschaft [{0}] des Typs [{1}] mit dem Wert [{2}] darf nur einmal vorkommen
default.paginate.prev=Vorherige
default.paginate.next=Nächste
default.boolean.true=Wahr
default.boolean.false=Falsch
default.date.format=dd.MM.yyyy HH:mm:ss z
default.number.format=0
default.created.message={0} {1} wurde angelegt
default.updated.message={0} {1} wurde geändert
default.deleted.message={0} {1} wurde gelöscht
default.not.deleted.message={0} {1} konnte nicht gelöscht werden
default.not.found.message={0} mit der id {1} wurde nicht gefunden
default.optimistic.locking.failure=Ein anderer Benutzer hat das {0} Object geändert während Sie es bearbeitet haben
default.home.label=Home
default.list.label={0} Liste
default.add.label={0} hinzufügen
default.new.label={0} anlegen
default.create.label={0} anlegen
default.show.label={0} anzeigen
default.edit.label={0} bearbeiten
default.button.create.label=Anlegen
default.button.edit.label=Bearbeiten
default.button.update.label=Aktualisieren
default.button.delete.label=Löschen
default.button.delete.confirm.message=Sind Sie sicher?
# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
typeMismatch.java.net.URL=Die Eigenschaft {0} muss eine gültige URL sein
typeMismatch.java.net.URI=Die Eigenschaft {0} muss eine gültige URI sein
typeMismatch.java.util.Date=Die Eigenschaft {0} muss ein gültiges Datum sein
typeMismatch.java.lang.Double=Die Eigenschaft {0} muss eine gültige Zahl sein
typeMismatch.java.lang.Integer=Die Eigenschaft {0} muss eine gültige Zahl sein
typeMismatch.java.lang.Long=Die Eigenschaft {0} muss eine gültige Zahl sein
typeMismatch.java.lang.Short=Die Eigenschaft {0} muss eine gültige Zahl sein
typeMismatch.java.math.BigDecimal=Die Eigenschaft {0} muss eine gültige Zahl sein
typeMismatch.java.math.BigInteger=Die Eigenschaft {0} muss eine gültige Zahl sein

View File

@@ -0,0 +1,55 @@
default.doesnt.match.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no corresponde al patrón [{3}]
default.invalid.url.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es una URL válida
default.invalid.creditCard.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es un número de tarjeta de crédito válida
default.invalid.email.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es una dirección de correo electrónico válida
default.invalid.range.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no entra en el rango válido de [{3}] a [{4}]
default.invalid.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no entra en el tamaño válido de [{3}] a [{4}]
default.invalid.max.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] excede el valor máximo [{3}]
default.invalid.min.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] es menos que el valor mínimo [{3}]
default.invalid.max.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] excede el tamaño máximo de [{3}]
default.invalid.min.size.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] es menor que el tamaño mínimo de [{3}]
default.invalid.validator.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no es válido
default.not.inlist.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no esta contenido dentro de la lista [{3}]
default.blank.message=La propiedad [{0}] de la clase [{1}] no puede ser vacía
default.not.equal.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] no puede igualar a [{3}]
default.null.message=La propiedad [{0}] de la clase [{1}] no puede ser nulo
default.not.unique.message=La propiedad [{0}] de la clase [{1}] con valor [{2}] debe ser única
default.paginate.prev=Anterior
default.paginate.next=Siguiente
default.boolean.true=Verdadero
default.boolean.false=Falso
default.date.format=yyyy-MM-dd HH:mm:ss z
default.number.format=0
default.created.message={0} {1} creado
default.updated.message={0} {1} actualizado
default.deleted.message={0} {1} eliminado
default.not.deleted.message={0} {1} no puede eliminarse
default.not.found.message=No se encuentra {0} con id {1}
default.optimistic.locking.failure=Mientras usted editaba, otro usuario ha actualizado su {0}
default.home.label=Principal
default.list.label={0} Lista
default.add.label=Agregar {0}
default.new.label=Nuevo {0}
default.create.label=Crear {0}
default.show.label=Mostrar {0}
default.edit.label=Editar {0}
default.button.create.label=Crear
default.button.edit.label=Editar
default.button.update.label=Actualizar
default.button.delete.label=Eliminar
default.button.delete.confirm.message=¿Está usted seguro?
# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
typeMismatch.java.net.URL=La propiedad {0} debe ser una URL válida
typeMismatch.java.net.URI=La propiedad {0} debe ser una URI válida
typeMismatch.java.util.Date=La propiedad {0} debe ser una fecha válida
typeMismatch.java.lang.Double=La propiedad {0} debe ser un número válido
typeMismatch.java.lang.Integer=La propiedad {0} debe ser un número válido
typeMismatch.java.lang.Long=La propiedad {0} debe ser un número válido
typeMismatch.java.lang.Short=La propiedad {0} debe ser un número válido
typeMismatch.java.math.BigDecimal=La propiedad {0} debe ser un número válido
typeMismatch.java.math.BigInteger=La propiedad {0} debe ser un número válido

View File

@@ -0,0 +1,19 @@
default.doesnt.match.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne correspond pas au pattern [{3}]
default.invalid.url.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas une URL valide
default.invalid.creditCard.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas un numéro de carte de crédit valide
default.invalid.email.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas une adresse e-mail valide
default.invalid.range.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas contenue dans l'intervalle [{3}] à [{4}]
default.invalid.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas contenue dans l'intervalle [{3}] à [{4}]
default.invalid.max.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est supérieure à la valeur maximum [{3}]
default.invalid.min.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est inférieure à la valeur minimum [{3}]
default.invalid.max.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est supérieure à la valeur maximum [{3}]
default.invalid.min.size.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] est inférieure à la valeur minimum [{3}]
default.invalid.validator.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] n'est pas valide
default.not.inlist.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne fait pas partie de la liste [{3}]
default.blank.message=La propriété [{0}] de la classe [{1}] ne peut pas être vide
default.not.equal.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] ne peut pas être égale à [{3}]
default.null.message=La propriété [{0}] de la classe [{1}] ne peut pas être nulle
default.not.unique.message=La propriété [{0}] de la classe [{1}] avec la valeur [{2}] doit être unique
default.paginate.prev=Précédent
default.paginate.next=Suivant

View File

@@ -0,0 +1,55 @@
default.doesnt.match.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non corrisponde al pattern [{3}]
default.invalid.url.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un URL valido
default.invalid.creditCard.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un numero di carta di credito valido
default.invalid.email.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è un indirizzo email valido
default.invalid.range.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non rientra nell'intervallo valido da [{3}] a [{4}]
default.invalid.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non rientra nell'intervallo di dimensioni valide da [{3}] a [{4}]
default.invalid.max.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è maggiore di [{3}]
default.invalid.min.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è minore di [{3}]
default.invalid.max.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è maggiore di [{3}]
default.invalid.min.size.message=La proprietà [{0}] della classe [{1}] con valore [{2}] è minore di [{3}]
default.invalid.validator.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è valida
default.not.inlist.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non è contenuta nella lista [{3}]
default.blank.message=La proprietà [{0}] della classe [{1}] non può essere vuota
default.not.equal.message=La proprietà [{0}] della classe [{1}] con valore [{2}] non può essere uguale a [{3}]
default.null.message=La proprietà [{0}] della classe [{1}] non può essere null
default.not.unique.message=La proprietà [{0}] della classe [{1}] con valore [{2}] deve essere unica
default.paginate.prev=Precedente
default.paginate.next=Successivo
default.boolean.true=Vero
default.boolean.false=Falso
default.date.format=dd/MM/yyyy HH:mm:ss z
default.number.format=0
default.created.message={0} {1} creato
default.updated.message={0} {1} aggiornato
default.deleted.message={0} {1} eliminato
default.not.deleted.message={0} {1} non può essere eliminato
default.not.found.message={0} non trovato con id {1}
default.optimistic.locking.failure=Un altro utente ha aggiornato questo {0} mentre si era in modifica
default.home.label=Home
default.list.label={0} Elenco
default.add.label=Aggiungi {0}
default.new.label=Nuovo {0}
default.create.label=Crea {0}
default.show.label=Mostra {0}
default.edit.label=Modifica {0}
default.button.create.label=Crea
default.button.edit.label=Modifica
default.button.update.label=Aggiorna
default.button.delete.label=Elimina
default.button.delete.confirm.message=Si è sicuri?
# Data binding errors. Usa "typeMismatch.$className.$propertyName per la personalizzazione (es typeMismatch.Book.author)
typeMismatch.java.net.URL=La proprietà {0} deve essere un URL valido
typeMismatch.java.net.URI=La proprietà {0} deve essere un URI valido
typeMismatch.java.util.Date=La proprietà {0} deve essere una data valida
typeMismatch.java.lang.Double=La proprietà {0} deve essere un numero valido
typeMismatch.java.lang.Integer=La proprietà {0} deve essere un numero valido
typeMismatch.java.lang.Long=La proprietà {0} deve essere un numero valido
typeMismatch.java.lang.Short=La proprietà {0} deve essere un numero valido
typeMismatch.java.math.BigDecimal=La proprietà {0} deve essere un numero valido
typeMismatch.java.math.BigInteger=La proprietà {0} deve essere un numero valido

View File

@@ -0,0 +1,55 @@
default.doesnt.match.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]パターンと一致していません。
default.invalid.url.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、有効なURLではありません。
default.invalid.creditCard.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、有効なクレジットカード番号ではありません。
default.invalid.email.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、有効なメールアドレスではありません。
default.invalid.range.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]から[{4}]範囲内を指定してください。
default.invalid.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]から[{4}]以内を指定してください。
default.invalid.max.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最大値[{3}]より大きいです。
default.invalid.min.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最小値[{3}]より小さいです。
default.invalid.max.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最大値[{3}]より大きいです。
default.invalid.min.size.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、最小値[{3}]より小さいです。
default.invalid.validator.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、カスタムバリデーションを通過できません。
default.not.inlist.message=クラス[{1}]プロパティ[{0}]の値[{2}]は、[{3}]リスト内に存在しません。
default.blank.message=[{1}]クラスのプロパティ[{0}]の空白は許可されません。
default.not.equal.message=クラス[{1}]プロパティ[{0}]の値[{2}]に[{3}]は許可されません。
default.null.message=[{1}]クラスのプロパティ[{0}]にnullは許可されません。
default.not.unique.message=クラス[{1}]プロパティ[{0}]の値[{2}]は既に使用されています。
default.paginate.prev=戻る
default.paginate.next=次へ
default.boolean.true=はい
default.boolean.false=いいえ
default.date.format=yyyy/MM/dd HH:mm:ss z
default.number.format=0
default.created.message={0}(id:{1})を作成しました。
default.updated.message={0}(id:{1})を更新しました。
default.deleted.message={0}(id:{1})を削除しました。
default.not.deleted.message={0}(id:{1})は削除できませんでした。
default.not.found.message={0}(id:{1})は見つかりませんでした。
default.optimistic.locking.failure=この{0}は編集中に他のユーザによって先に更新されています。
default.home.label=ホーム
default.list.label={0}リスト
default.add.label={0}を追加
default.new.label={0}を新規作成
default.create.label={0}を作成
default.show.label={0}詳細
default.edit.label={0}を編集
default.button.create.label=作成
default.button.edit.label=編集
default.button.update.label=更新
default.button.delete.label=削除
default.button.delete.confirm.message=本当に削除してよろしいですか?
# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
typeMismatch.java.net.URL={0}は有効なURLでなければなりません。
typeMismatch.java.net.URI={0}は有効なURIでなければなりません。
typeMismatch.java.util.Date={0}は有効な日付でなければなりません。
typeMismatch.java.lang.Double={0}は有効な数値でなければなりません。
typeMismatch.java.lang.Integer={0}は有効な数値でなければなりません。
typeMismatch.java.lang.Long={0}は有効な数値でなければなりません。
typeMismatch.java.lang.Short={0}は有効な数値でなければなりません。
typeMismatch.java.math.BigDecimal={0}は有効な数値でなければなりません。
typeMismatch.java.math.BigInteger={0}は有効な数値でなければなりません。

Some files were not shown because too many files have changed in this diff Show More